Browse Source

Merge branch 'AssimpWIP' of https://github.com/Areloch/Torque3D into development

Areloch 6 years ago
parent
commit
951594259f
100 changed files with 46881 additions and 0 deletions
  1. 8 0
      Engine/lib/assimp/code/.editorconfig
  2. 873 0
      Engine/lib/assimp/code/3DSConverter.cpp
  3. 580 0
      Engine/lib/assimp/code/3DSExporter.cpp
  4. 98 0
      Engine/lib/assimp/code/3DSExporter.h
  5. 652 0
      Engine/lib/assimp/code/3DSHelper.h
  6. 1432 0
      Engine/lib/assimp/code/3DSLoader.cpp
  7. 284 0
      Engine/lib/assimp/code/3DSLoader.h
  8. 104 0
      Engine/lib/assimp/code/3MFXmlTags.h
  9. 910 0
      Engine/lib/assimp/code/ACLoader.cpp
  10. 276 0
      Engine/lib/assimp/code/ACLoader.h
  11. 705 0
      Engine/lib/assimp/code/AMFImporter.cpp
  12. 432 0
      Engine/lib/assimp/code/AMFImporter.hpp
  13. 357 0
      Engine/lib/assimp/code/AMFImporter_Geometry.cpp
  14. 166 0
      Engine/lib/assimp/code/AMFImporter_Macro.hpp
  15. 329 0
      Engine/lib/assimp/code/AMFImporter_Material.cpp
  16. 340 0
      Engine/lib/assimp/code/AMFImporter_Node.hpp
  17. 978 0
      Engine/lib/assimp/code/AMFImporter_Postprocess.cpp
  18. 1328 0
      Engine/lib/assimp/code/ASELoader.cpp
  19. 208 0
      Engine/lib/assimp/code/ASELoader.h
  20. 2160 0
      Engine/lib/assimp/code/ASEParser.cpp
  21. 693 0
      Engine/lib/assimp/code/ASEParser.h
  22. 845 0
      Engine/lib/assimp/code/AssbinExporter.cpp
  23. 58 0
      Engine/lib/assimp/code/AssbinExporter.h
  24. 733 0
      Engine/lib/assimp/code/AssbinLoader.cpp
  25. 106 0
      Engine/lib/assimp/code/AssbinLoader.h
  26. 695 0
      Engine/lib/assimp/code/Assimp.cpp
  27. 156 0
      Engine/lib/assimp/code/AssimpCExport.cpp
  28. 655 0
      Engine/lib/assimp/code/AssxmlExporter.cpp
  29. 51 0
      Engine/lib/assimp/code/AssxmlExporter.h
  30. 738 0
      Engine/lib/assimp/code/B3DImporter.cpp
  31. 134 0
      Engine/lib/assimp/code/B3DImporter.h
  32. 578 0
      Engine/lib/assimp/code/BVHLoader.cpp
  33. 176 0
      Engine/lib/assimp/code/BVHLoader.h
  34. 616 0
      Engine/lib/assimp/code/BaseImporter.cpp
  35. 361 0
      Engine/lib/assimp/code/BaseImporter.h
  36. 107 0
      Engine/lib/assimp/code/BaseProcess.cpp
  37. 290 0
      Engine/lib/assimp/code/BaseProcess.h
  38. 155 0
      Engine/lib/assimp/code/Bitmap.cpp
  39. 123 0
      Engine/lib/assimp/code/Bitmap.h
  40. 206 0
      Engine/lib/assimp/code/BlenderBMesh.cpp
  41. 94 0
      Engine/lib/assimp/code/BlenderBMesh.h
  42. 189 0
      Engine/lib/assimp/code/BlenderCustomData.cpp
  43. 89 0
      Engine/lib/assimp/code/BlenderCustomData.h
  44. 375 0
      Engine/lib/assimp/code/BlenderDNA.cpp
  45. 844 0
      Engine/lib/assimp/code/BlenderDNA.h
  46. 856 0
      Engine/lib/assimp/code/BlenderDNA.inl
  47. 206 0
      Engine/lib/assimp/code/BlenderIntermediate.h
  48. 1370 0
      Engine/lib/assimp/code/BlenderLoader.cpp
  49. 240 0
      Engine/lib/assimp/code/BlenderLoader.h
  50. 299 0
      Engine/lib/assimp/code/BlenderModifier.cpp
  51. 155 0
      Engine/lib/assimp/code/BlenderModifier.h
  52. 872 0
      Engine/lib/assimp/code/BlenderScene.cpp
  53. 979 0
      Engine/lib/assimp/code/BlenderScene.h
  54. 266 0
      Engine/lib/assimp/code/BlenderSceneGen.h
  55. 526 0
      Engine/lib/assimp/code/BlenderTessellator.cpp
  56. 210 0
      Engine/lib/assimp/code/BlenderTessellator.h
  57. 337 0
      Engine/lib/assimp/code/BlobIOSystem.h
  58. 287 0
      Engine/lib/assimp/code/ByteSwapper.h
  59. 650 0
      Engine/lib/assimp/code/C4DImporter.cpp
  60. 123 0
      Engine/lib/assimp/code/C4DImporter.h
  61. 136 0
      Engine/lib/assimp/code/CInterfaceIOWrapper.cpp
  62. 99 0
      Engine/lib/assimp/code/CInterfaceIOWrapper.h
  63. 1067 0
      Engine/lib/assimp/code/CMakeLists.txt
  64. 1248 0
      Engine/lib/assimp/code/COBLoader.cpp
  65. 156 0
      Engine/lib/assimp/code/COBLoader.h
  66. 277 0
      Engine/lib/assimp/code/COBScene.h
  67. 308 0
      Engine/lib/assimp/code/CSMLoader.cpp
  68. 93 0
      Engine/lib/assimp/code/CSMLoader.h
  69. 319 0
      Engine/lib/assimp/code/CalcTangentsProcess.cpp
  70. 117 0
      Engine/lib/assimp/code/CalcTangentsProcess.h
  71. 1640 0
      Engine/lib/assimp/code/ColladaExporter.cpp
  72. 223 0
      Engine/lib/assimp/code/ColladaExporter.h
  73. 705 0
      Engine/lib/assimp/code/ColladaHelper.h
  74. 1954 0
      Engine/lib/assimp/code/ColladaLoader.cpp
  75. 259 0
      Engine/lib/assimp/code/ColladaLoader.h
  76. 3215 0
      Engine/lib/assimp/code/ColladaParser.cpp
  77. 371 0
      Engine/lib/assimp/code/ColladaParser.h
  78. 506 0
      Engine/lib/assimp/code/ComputeUVMappingProcess.cpp
  79. 148 0
      Engine/lib/assimp/code/ComputeUVMappingProcess.h
  80. 414 0
      Engine/lib/assimp/code/ConvertToLHProcess.cpp
  81. 170 0
      Engine/lib/assimp/code/ConvertToLHProcess.h
  82. 92 0
      Engine/lib/assimp/code/CreateAnimMesh.cpp
  83. 57 0
      Engine/lib/assimp/code/CreateAnimMesh.h
  84. 398 0
      Engine/lib/assimp/code/D3MFExporter.cpp
  85. 106 0
      Engine/lib/assimp/code/D3MFExporter.h
  86. 483 0
      Engine/lib/assimp/code/D3MFImporter.cpp
  87. 65 0
      Engine/lib/assimp/code/D3MFImporter.h
  88. 531 0
      Engine/lib/assimp/code/D3MFOpcPackage.cpp
  89. 84 0
      Engine/lib/assimp/code/D3MFOpcPackage.h
  90. 222 0
      Engine/lib/assimp/code/DXFHelper.h
  91. 918 0
      Engine/lib/assimp/code/DXFLoader.cpp
  92. 149 0
      Engine/lib/assimp/code/DXFLoader.h
  93. 465 0
      Engine/lib/assimp/code/DeboneProcess.cpp
  94. 134 0
      Engine/lib/assimp/code/DeboneProcess.h
  95. 154 0
      Engine/lib/assimp/code/DefaultIOStream.cpp
  96. 257 0
      Engine/lib/assimp/code/DefaultIOSystem.cpp
  97. 418 0
      Engine/lib/assimp/code/DefaultLogger.cpp
  98. 65 0
      Engine/lib/assimp/code/DefaultProgressHandler.h
  99. 109 0
      Engine/lib/assimp/code/DropFaceNormalsProcess.cpp
  100. 86 0
      Engine/lib/assimp/code/DropFaceNormalsProcess.h

+ 8 - 0
Engine/lib/assimp/code/.editorconfig

@@ -0,0 +1,8 @@
+# See <http://EditorConfig.org> for details
+
+[*.{h,hpp,c,cpp}]
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+indent_size = 4
+indent_style = space

+ 873 - 0
Engine/lib/assimp/code/3DSConverter.cpp

@@ -0,0 +1,873 @@
+/*
+---------------------------------------------------------------------------
+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 3ds importer class */
+
+
+#ifndef ASSIMP_BUILD_NO_3DS_IMPORTER
+
+// internal headers
+#include "3DSLoader.h"
+#include "TargetAnimation.h"
+#include <assimp/scene.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/StringComparison.h>
+#include <memory>
+#include <cctype>
+
+using namespace Assimp;
+
+static const unsigned int NotSet = 0xcdcdcdcd;
+
+// ------------------------------------------------------------------------------------------------
+// Setup final material indices, generae a default material if necessary
+void Discreet3DSImporter::ReplaceDefaultMaterial()
+{
+    // Try to find an existing material that matches the
+    // typical default material setting:
+    // - no textures
+    // - diffuse color (in grey!)
+    // NOTE: This is here to workaround the fact that some
+    // exporters are writing a default material, too.
+    unsigned int idx( NotSet );
+    for (unsigned int i = 0; i < mScene->mMaterials.size();++i)
+    {
+        std::string &s = mScene->mMaterials[i].mName;
+        for ( std::string::iterator it = s.begin(); it != s.end(); ++it ) {
+            *it = static_cast< char >( ::tolower( *it ) );
+        }
+
+        if (std::string::npos == s.find("default"))continue;
+
+        if (mScene->mMaterials[i].mDiffuse.r !=
+            mScene->mMaterials[i].mDiffuse.g ||
+            mScene->mMaterials[i].mDiffuse.r !=
+            mScene->mMaterials[i].mDiffuse.b)continue;
+
+        if (mScene->mMaterials[i].sTexDiffuse.mMapName.length()   != 0  ||
+            mScene->mMaterials[i].sTexBump.mMapName.length()      != 0  ||
+            mScene->mMaterials[i].sTexOpacity.mMapName.length()   != 0  ||
+            mScene->mMaterials[i].sTexEmissive.mMapName.length()  != 0  ||
+            mScene->mMaterials[i].sTexSpecular.mMapName.length()  != 0  ||
+            mScene->mMaterials[i].sTexShininess.mMapName.length() != 0 )
+        {
+            continue;
+        }
+        idx = i;
+    }
+    if ( NotSet == idx ) {
+        idx = ( unsigned int )mScene->mMaterials.size();
+    }
+
+    // now iterate through all meshes and through all faces and
+    // find all faces that are using the default material
+    unsigned int cnt = 0;
+    for (std::vector<D3DS::Mesh>::iterator
+        i =  mScene->mMeshes.begin();
+        i != mScene->mMeshes.end();++i)
+    {
+        for (std::vector<unsigned int>::iterator
+            a =  (*i).mFaceMaterials.begin();
+            a != (*i).mFaceMaterials.end();++a)
+        {
+            // NOTE: The additional check seems to be necessary,
+            // some exporters seem to generate invalid data here
+            if (0xcdcdcdcd == (*a))
+            {
+                (*a) = idx;
+                ++cnt;
+            }
+            else if ( (*a) >= mScene->mMaterials.size())
+            {
+                (*a) = idx;
+                ASSIMP_LOG_WARN("Material index overflow in 3DS file. Using default material");
+                ++cnt;
+            }
+        }
+    }
+    if (cnt && idx == mScene->mMaterials.size())
+    {
+        // We need to create our own default material
+        D3DS::Material sMat("%%%DEFAULT");
+        sMat.mDiffuse = aiColor3D(0.3f,0.3f,0.3f);
+        mScene->mMaterials.push_back(sMat);
+
+        ASSIMP_LOG_INFO("3DS: Generating default material");
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Check whether all indices are valid. Otherwise we'd crash before the validation step is reached
+void Discreet3DSImporter::CheckIndices(D3DS::Mesh& sMesh)
+{
+    for (std::vector< D3DS::Face >::iterator i =  sMesh.mFaces.begin(); i != sMesh.mFaces.end();++i)
+    {
+        // check whether all indices are in range
+        for (unsigned int a = 0; a < 3;++a)
+        {
+            if ((*i).mIndices[a] >= sMesh.mPositions.size())
+            {
+                ASSIMP_LOG_WARN("3DS: Vertex index overflow)");
+                (*i).mIndices[a] = (uint32_t)sMesh.mPositions.size()-1;
+            }
+            if ( !sMesh.mTexCoords.empty() && (*i).mIndices[a] >= sMesh.mTexCoords.size())
+            {
+                ASSIMP_LOG_WARN("3DS: Texture coordinate index overflow)");
+                (*i).mIndices[a] = (uint32_t)sMesh.mTexCoords.size()-1;
+            }
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Generate out unique verbose format representation
+void Discreet3DSImporter::MakeUnique(D3DS::Mesh& sMesh)
+{
+    // TODO: really necessary? I don't think. Just a waste of memory and time
+    // to do it now in a separate buffer.
+
+    // Allocate output storage
+    std::vector<aiVector3D> vNew  (sMesh.mFaces.size() * 3);
+    std::vector<aiVector3D> vNew2;
+    if (sMesh.mTexCoords.size())
+        vNew2.resize(sMesh.mFaces.size() * 3);
+
+    for (unsigned int i = 0, base = 0; i < sMesh.mFaces.size();++i)
+    {
+        D3DS::Face& face = sMesh.mFaces[i];
+
+        // Positions
+        for (unsigned int a = 0; a < 3;++a,++base)
+        {
+            vNew[base] = sMesh.mPositions[face.mIndices[a]];
+            if (sMesh.mTexCoords.size())
+                vNew2[base] = sMesh.mTexCoords[face.mIndices[a]];
+
+            face.mIndices[a] = base;
+        }
+    }
+    sMesh.mPositions = vNew;
+    sMesh.mTexCoords = vNew2;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Convert a 3DS texture to texture keys in an aiMaterial
+void CopyTexture(aiMaterial& mat, D3DS::Texture& texture, aiTextureType type)
+{
+    // Setup the texture name
+    aiString tex;
+    tex.Set( texture.mMapName);
+    mat.AddProperty( &tex, AI_MATKEY_TEXTURE(type,0));
+
+    // Setup the texture blend factor
+    if (is_not_qnan(texture.mTextureBlend))
+        mat.AddProperty<ai_real>( &texture.mTextureBlend, 1, AI_MATKEY_TEXBLEND(type,0));
+
+    // Setup the texture mapping mode
+    int mapMode = static_cast<int>(texture.mMapMode);
+    mat.AddProperty<int>(&mapMode,1,AI_MATKEY_MAPPINGMODE_U(type,0));
+    mat.AddProperty<int>(&mapMode,1,AI_MATKEY_MAPPINGMODE_V(type,0));
+
+    // Mirroring - double the scaling values
+    // FIXME: this is not really correct ...
+    if (texture.mMapMode == aiTextureMapMode_Mirror)
+    {
+        texture.mScaleU *= 2.0;
+        texture.mScaleV *= 2.0;
+        texture.mOffsetU /= 2.0;
+        texture.mOffsetV /= 2.0;
+    }
+
+    // Setup texture UV transformations
+    mat.AddProperty<ai_real>(&texture.mOffsetU,5,AI_MATKEY_UVTRANSFORM(type,0));
+}
+
+// ------------------------------------------------------------------------------------------------
+// Convert a 3DS material to an aiMaterial
+void Discreet3DSImporter::ConvertMaterial(D3DS::Material& oldMat,
+    aiMaterial& mat)
+{
+    // NOTE: Pass the background image to the viewer by bypassing the
+    // material system. This is an evil hack, never do it again!
+    if (0 != mBackgroundImage.length() && bHasBG)
+    {
+        aiString tex;
+        tex.Set( mBackgroundImage);
+        mat.AddProperty( &tex, AI_MATKEY_GLOBAL_BACKGROUND_IMAGE);
+
+        // Be sure this is only done for the first material
+        mBackgroundImage = std::string("");
+    }
+
+    // At first add the base ambient color of the scene to the material
+    oldMat.mAmbient.r += mClrAmbient.r;
+    oldMat.mAmbient.g += mClrAmbient.g;
+    oldMat.mAmbient.b += mClrAmbient.b;
+
+    aiString name;
+    name.Set( oldMat.mName);
+    mat.AddProperty( &name, AI_MATKEY_NAME);
+
+    // Material colors
+    mat.AddProperty( &oldMat.mAmbient, 1, AI_MATKEY_COLOR_AMBIENT);
+    mat.AddProperty( &oldMat.mDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE);
+    mat.AddProperty( &oldMat.mSpecular, 1, AI_MATKEY_COLOR_SPECULAR);
+    mat.AddProperty( &oldMat.mEmissive, 1, AI_MATKEY_COLOR_EMISSIVE);
+
+    // Phong shininess and shininess strength
+    if (D3DS::Discreet3DS::Phong == oldMat.mShading ||
+        D3DS::Discreet3DS::Metal == oldMat.mShading)
+    {
+        if (!oldMat.mSpecularExponent || !oldMat.mShininessStrength)
+        {
+            oldMat.mShading = D3DS::Discreet3DS::Gouraud;
+        }
+        else
+        {
+            mat.AddProperty( &oldMat.mSpecularExponent, 1, AI_MATKEY_SHININESS);
+            mat.AddProperty( &oldMat.mShininessStrength, 1, AI_MATKEY_SHININESS_STRENGTH);
+        }
+    }
+
+    // Opacity
+    mat.AddProperty<ai_real>( &oldMat.mTransparency,1,AI_MATKEY_OPACITY);
+
+    // Bump height scaling
+    mat.AddProperty<ai_real>( &oldMat.mBumpHeight,1,AI_MATKEY_BUMPSCALING);
+
+    // Two sided rendering?
+    if (oldMat.mTwoSided)
+    {
+        int i = 1;
+        mat.AddProperty<int>(&i,1,AI_MATKEY_TWOSIDED);
+    }
+
+    // Shading mode
+    aiShadingMode eShading = aiShadingMode_NoShading;
+    switch (oldMat.mShading)
+    {
+        case D3DS::Discreet3DS::Flat:
+            eShading = aiShadingMode_Flat; break;
+
+        // I don't know what "Wire" shading should be,
+        // assume it is simple lambertian diffuse shading
+        case D3DS::Discreet3DS::Wire:
+            {
+                // Set the wireframe flag
+                unsigned int iWire = 1;
+                mat.AddProperty<int>( (int*)&iWire,1,AI_MATKEY_ENABLE_WIREFRAME);
+            }
+
+        case D3DS::Discreet3DS::Gouraud:
+            eShading = aiShadingMode_Gouraud; break;
+
+        // assume cook-torrance shading for metals.
+        case D3DS::Discreet3DS::Phong :
+            eShading = aiShadingMode_Phong; break;
+
+        case D3DS::Discreet3DS::Metal :
+            eShading = aiShadingMode_CookTorrance; break;
+
+            // FIX to workaround a warning with GCC 4 who complained
+            // about a missing case Blinn: here - Blinn isn't a valid
+            // value in the 3DS Loader, it is just needed for ASE
+        case D3DS::Discreet3DS::Blinn :
+            eShading = aiShadingMode_Blinn; break;
+    }
+    int eShading_ = static_cast<int>(eShading);
+    mat.AddProperty<int>(&eShading_, 1, AI_MATKEY_SHADING_MODEL);
+
+    // DIFFUSE texture
+    if( oldMat.sTexDiffuse.mMapName.length() > 0)
+        CopyTexture(mat,oldMat.sTexDiffuse, aiTextureType_DIFFUSE);
+
+    // SPECULAR texture
+    if( oldMat.sTexSpecular.mMapName.length() > 0)
+        CopyTexture(mat,oldMat.sTexSpecular, aiTextureType_SPECULAR);
+
+    // OPACITY texture
+    if( oldMat.sTexOpacity.mMapName.length() > 0)
+        CopyTexture(mat,oldMat.sTexOpacity, aiTextureType_OPACITY);
+
+    // EMISSIVE texture
+    if( oldMat.sTexEmissive.mMapName.length() > 0)
+        CopyTexture(mat,oldMat.sTexEmissive, aiTextureType_EMISSIVE);
+
+    // BUMP texture
+    if( oldMat.sTexBump.mMapName.length() > 0)
+        CopyTexture(mat,oldMat.sTexBump, aiTextureType_HEIGHT);
+
+    // SHININESS texture
+    if( oldMat.sTexShininess.mMapName.length() > 0)
+        CopyTexture(mat,oldMat.sTexShininess, aiTextureType_SHININESS);
+
+    // REFLECTION texture
+    if( oldMat.sTexReflective.mMapName.length() > 0)
+        CopyTexture(mat,oldMat.sTexReflective, aiTextureType_REFLECTION);
+
+    // Store the name of the material itself, too
+    if( oldMat.mName.length())  {
+        aiString tex;
+        tex.Set( oldMat.mName);
+        mat.AddProperty( &tex, AI_MATKEY_NAME);
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Split meshes by their materials and generate output aiMesh'es
+void Discreet3DSImporter::ConvertMeshes(aiScene* pcOut)
+{
+    std::vector<aiMesh*> avOutMeshes;
+    avOutMeshes.reserve(mScene->mMeshes.size() * 2);
+
+    unsigned int iFaceCnt = 0,num = 0;
+    aiString name;
+
+    // we need to split all meshes by their materials
+    for (std::vector<D3DS::Mesh>::iterator i =  mScene->mMeshes.begin(); i != mScene->mMeshes.end();++i)    {
+        std::unique_ptr< std::vector<unsigned int>[] > aiSplit(new std::vector<unsigned int>[mScene->mMaterials.size()]);
+
+        name.length = ASSIMP_itoa10(name.data,num++);
+
+        unsigned int iNum = 0;
+        for (std::vector<unsigned int>::const_iterator a =  (*i).mFaceMaterials.begin();
+            a != (*i).mFaceMaterials.end();++a,++iNum)
+        {
+            aiSplit[*a].push_back(iNum);
+        }
+        // now generate submeshes
+        for (unsigned int p = 0; p < mScene->mMaterials.size();++p)
+        {
+            if (aiSplit[p].empty()) {
+                continue;
+            }
+            aiMesh* meshOut = new aiMesh();
+            meshOut->mName = name;
+            meshOut->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
+
+            // be sure to setup the correct material index
+            meshOut->mMaterialIndex = p;
+
+            // use the color data as temporary storage
+            meshOut->mColors[0] = (aiColor4D*)(&*i);
+            avOutMeshes.push_back(meshOut);
+
+            // convert vertices
+            meshOut->mNumFaces = (unsigned int)aiSplit[p].size();
+            meshOut->mNumVertices = meshOut->mNumFaces*3;
+
+            // allocate enough storage for faces
+            meshOut->mFaces = new aiFace[meshOut->mNumFaces];
+            iFaceCnt += meshOut->mNumFaces;
+
+            meshOut->mVertices = new aiVector3D[meshOut->mNumVertices];
+            meshOut->mNormals  = new aiVector3D[meshOut->mNumVertices];
+            if ((*i).mTexCoords.size())
+            {
+                meshOut->mTextureCoords[0] = new aiVector3D[meshOut->mNumVertices];
+            }
+            for (unsigned int q = 0, base = 0; q < aiSplit[p].size();++q)
+            {
+                unsigned int index = aiSplit[p][q];
+                aiFace& face = meshOut->mFaces[q];
+
+                face.mIndices = new unsigned int[3];
+                face.mNumIndices = 3;
+
+                for (unsigned int a = 0; a < 3;++a,++base)
+                {
+                    unsigned int idx = (*i).mFaces[index].mIndices[a];
+                    meshOut->mVertices[base]  = (*i).mPositions[idx];
+                    meshOut->mNormals [base]  = (*i).mNormals[idx];
+
+                    if ((*i).mTexCoords.size())
+                        meshOut->mTextureCoords[0][base] = (*i).mTexCoords[idx];
+
+                    face.mIndices[a] = base;
+                }
+            }
+        }
+    }
+
+    // Copy them to the output array
+    pcOut->mNumMeshes = (unsigned int)avOutMeshes.size();
+    pcOut->mMeshes = new aiMesh*[pcOut->mNumMeshes]();
+    for (unsigned int a = 0; a < pcOut->mNumMeshes;++a) {
+        pcOut->mMeshes[a] = avOutMeshes[a];
+    }
+
+    // We should have at least one face here
+    if (!iFaceCnt) {
+        throw DeadlyImportError("No faces loaded. The mesh is empty");
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Add a node to the scenegraph and setup its final transformation
+void Discreet3DSImporter::AddNodeToGraph(aiScene* pcSOut,aiNode* pcOut,
+    D3DS::Node* pcIn, aiMatrix4x4& /*absTrafo*/)
+{
+    std::vector<unsigned int> iArray;
+    iArray.reserve(3);
+
+    aiMatrix4x4 abs;
+
+    // Find all meshes with the same name as the node
+    for (unsigned int a = 0; a < pcSOut->mNumMeshes;++a)
+    {
+        const D3DS::Mesh* pcMesh = (const D3DS::Mesh*)pcSOut->mMeshes[a]->mColors[0];
+        ai_assert(NULL != pcMesh);
+
+        if (pcIn->mName == pcMesh->mName)
+            iArray.push_back(a);
+    }
+    if (!iArray.empty())
+    {
+        // The matrix should be identical for all meshes with the
+        // same name. It HAS to be identical for all meshes .....
+        D3DS::Mesh* imesh = ((D3DS::Mesh*)pcSOut->mMeshes[iArray[0]]->mColors[0]);
+
+        // Compute the inverse of the transformation matrix to move the
+        // vertices back to their relative and local space
+        aiMatrix4x4 mInv = imesh->mMat, mInvTransposed = imesh->mMat;
+        mInv.Inverse();mInvTransposed.Transpose();
+        aiVector3D pivot = pcIn->vPivot;
+
+        pcOut->mNumMeshes = (unsigned int)iArray.size();
+        pcOut->mMeshes = new unsigned int[iArray.size()];
+        for (unsigned int i = 0;i < iArray.size();++i)  {
+            const unsigned int iIndex = iArray[i];
+            aiMesh* const mesh = pcSOut->mMeshes[iIndex];
+
+            if (mesh->mColors[1] == NULL)
+            {
+                // Transform the vertices back into their local space
+                // fixme: consider computing normals after this, so we don't need to transform them
+                const aiVector3D* const pvEnd = mesh->mVertices + mesh->mNumVertices;
+                aiVector3D* pvCurrent = mesh->mVertices, *t2 = mesh->mNormals;
+
+                for (; pvCurrent != pvEnd; ++pvCurrent, ++t2) {
+                    *pvCurrent = mInv * (*pvCurrent);
+                    *t2 = mInvTransposed * (*t2);
+                }
+
+                // Handle negative transformation matrix determinant -> invert vertex x
+                if (imesh->mMat.Determinant() < 0.0f)
+                {
+                    /* we *must* have normals */
+                    for (pvCurrent = mesh->mVertices, t2 = mesh->mNormals; pvCurrent != pvEnd; ++pvCurrent, ++t2) {
+                        pvCurrent->x *= -1.f;
+                        t2->x *= -1.f;
+                    }
+                    ASSIMP_LOG_INFO("3DS: Flipping mesh X-Axis");
+                }
+
+                // Handle pivot point
+                if (pivot.x || pivot.y || pivot.z)
+                {
+                    for (pvCurrent = mesh->mVertices; pvCurrent != pvEnd; ++pvCurrent)  {
+                        *pvCurrent -= pivot;
+                    }
+                }
+
+                mesh->mColors[1] = (aiColor4D*)1;
+            }
+            else
+                mesh->mColors[1] = (aiColor4D*)1;
+
+            // Setup the mesh index
+            pcOut->mMeshes[i] = iIndex;
+        }
+    }
+
+    // Setup the name of the node
+    // First instance keeps its name otherwise something might break, all others will be postfixed with their instance number
+    if (pcIn->mInstanceNumber > 1)
+    {
+        char tmp[12];
+        ASSIMP_itoa10(tmp, pcIn->mInstanceNumber);
+        std::string tempStr = pcIn->mName + "_inst_";
+        tempStr += tmp;
+        pcOut->mName.Set(tempStr);
+    }
+    else
+        pcOut->mName.Set(pcIn->mName);
+
+    // Now build the transformation matrix of the node
+    // ROTATION
+    if (pcIn->aRotationKeys.size()){
+
+        // FIX to get to Assimp's quaternion conventions
+        for (std::vector<aiQuatKey>::iterator it = pcIn->aRotationKeys.begin(); it != pcIn->aRotationKeys.end(); ++it) {
+            (*it).mValue.w *= -1.f;
+        }
+
+        pcOut->mTransformation = aiMatrix4x4( pcIn->aRotationKeys[0].mValue.GetMatrix() );
+    }
+    else if (pcIn->aCameraRollKeys.size())
+    {
+        aiMatrix4x4::RotationZ(AI_DEG_TO_RAD(- pcIn->aCameraRollKeys[0].mValue),
+            pcOut->mTransformation);
+    }
+
+    // SCALING
+    aiMatrix4x4& m = pcOut->mTransformation;
+    if (pcIn->aScalingKeys.size())
+    {
+        const aiVector3D& v = pcIn->aScalingKeys[0].mValue;
+        m.a1 *= v.x; m.b1 *= v.x; m.c1 *= v.x;
+        m.a2 *= v.y; m.b2 *= v.y; m.c2 *= v.y;
+        m.a3 *= v.z; m.b3 *= v.z; m.c3 *= v.z;
+    }
+
+    // TRANSLATION
+    if (pcIn->aPositionKeys.size())
+    {
+        const aiVector3D& v = pcIn->aPositionKeys[0].mValue;
+        m.a4 += v.x;
+        m.b4 += v.y;
+        m.c4 += v.z;
+    }
+
+    // Generate animation channels for the node
+    if (pcIn->aPositionKeys.size()  > 1  || pcIn->aRotationKeys.size()   > 1 ||
+        pcIn->aScalingKeys.size()   > 1  || pcIn->aCameraRollKeys.size() > 1 ||
+        pcIn->aTargetPositionKeys.size() > 1)
+    {
+        aiAnimation* anim = pcSOut->mAnimations[0];
+        ai_assert(nullptr != anim);
+
+        if (pcIn->aCameraRollKeys.size() > 1)
+        {
+            ASSIMP_LOG_DEBUG("3DS: Converting camera roll track ...");
+
+            // Camera roll keys - in fact they're just rotations
+            // around the camera's z axis. The angles are given
+            // in degrees (and they're clockwise).
+            pcIn->aRotationKeys.resize(pcIn->aCameraRollKeys.size());
+            for (unsigned int i = 0; i < pcIn->aCameraRollKeys.size();++i)
+            {
+                aiQuatKey&  q = pcIn->aRotationKeys[i];
+                aiFloatKey& f = pcIn->aCameraRollKeys[i];
+
+                q.mTime  = f.mTime;
+
+                // FIX to get to Assimp quaternion conventions
+                q.mValue = aiQuaternion(0.f,0.f,AI_DEG_TO_RAD( /*-*/ f.mValue));
+            }
+        }
+#if 0
+        if (pcIn->aTargetPositionKeys.size() > 1)
+        {
+            ASSIMP_LOG_DEBUG("3DS: Converting target track ...");
+
+            // Camera or spot light - need to convert the separate
+            // target position channel to our representation
+            TargetAnimationHelper helper;
+
+            if (pcIn->aPositionKeys.empty())
+            {
+                // We can just pass zero here ...
+                helper.SetFixedMainAnimationChannel(aiVector3D());
+            }
+            else  helper.SetMainAnimationChannel(&pcIn->aPositionKeys);
+            helper.SetTargetAnimationChannel(&pcIn->aTargetPositionKeys);
+
+            // Do the conversion
+            std::vector<aiVectorKey> distanceTrack;
+            helper.Process(&distanceTrack);
+
+            // Now add a new node as child, name it <ourName>.Target
+            // and assign the distance track to it. This is that the
+            // information where the target is and how it moves is
+            // not lost
+            D3DS::Node* nd = new D3DS::Node();
+            pcIn->push_back(nd);
+
+            nd->mName = pcIn->mName + ".Target";
+
+            aiNodeAnim* nda = anim->mChannels[anim->mNumChannels++] = new aiNodeAnim();
+            nda->mNodeName.Set(nd->mName);
+
+            nda->mNumPositionKeys = (unsigned int)distanceTrack.size();
+            nda->mPositionKeys = new aiVectorKey[nda->mNumPositionKeys];
+            ::memcpy(nda->mPositionKeys,&distanceTrack[0],
+                sizeof(aiVectorKey)*nda->mNumPositionKeys);
+        }
+#endif
+
+        // Cameras or lights define their transformation in their parent node and in the
+        // corresponding light or camera chunks. However, we read and process the latter
+        // to to be able to return valid cameras/lights even if no scenegraph is given.
+        for (unsigned int n = 0; n < pcSOut->mNumCameras;++n)   {
+            if (pcSOut->mCameras[n]->mName == pcOut->mName) {
+                pcSOut->mCameras[n]->mLookAt = aiVector3D(0.f,0.f,1.f);
+            }
+        }
+        for (unsigned int n = 0; n < pcSOut->mNumLights;++n)    {
+            if (pcSOut->mLights[n]->mName == pcOut->mName) {
+                pcSOut->mLights[n]->mDirection = aiVector3D(0.f,0.f,1.f);
+            }
+        }
+
+        // Allocate a new node anim and setup its name
+        aiNodeAnim* nda = anim->mChannels[anim->mNumChannels++] = new aiNodeAnim();
+        nda->mNodeName.Set(pcIn->mName);
+
+        // POSITION keys
+        if (pcIn->aPositionKeys.size()  > 0)
+        {
+            nda->mNumPositionKeys = (unsigned int)pcIn->aPositionKeys.size();
+            nda->mPositionKeys = new aiVectorKey[nda->mNumPositionKeys];
+            ::memcpy(nda->mPositionKeys,&pcIn->aPositionKeys[0],
+                sizeof(aiVectorKey)*nda->mNumPositionKeys);
+        }
+
+        // ROTATION keys
+        if (pcIn->aRotationKeys.size()  > 0)
+        {
+            nda->mNumRotationKeys = (unsigned int)pcIn->aRotationKeys.size();
+            nda->mRotationKeys = new aiQuatKey[nda->mNumRotationKeys];
+
+            // Rotations are quaternion offsets
+            aiQuaternion abs1;
+            for (unsigned int n = 0; n < nda->mNumRotationKeys;++n)
+            {
+                const aiQuatKey& q = pcIn->aRotationKeys[n];
+
+                abs1 = (n ? abs1 * q.mValue : q.mValue);
+                nda->mRotationKeys[n].mTime  = q.mTime;
+                nda->mRotationKeys[n].mValue = abs1.Normalize();
+            }
+        }
+
+        // SCALING keys
+        if (pcIn->aScalingKeys.size()  > 0)
+        {
+            nda->mNumScalingKeys = (unsigned int)pcIn->aScalingKeys.size();
+            nda->mScalingKeys = new aiVectorKey[nda->mNumScalingKeys];
+            ::memcpy(nda->mScalingKeys,&pcIn->aScalingKeys[0],
+                sizeof(aiVectorKey)*nda->mNumScalingKeys);
+        }
+    }
+
+    // Allocate storage for children
+    pcOut->mNumChildren = (unsigned int)pcIn->mChildren.size();
+    pcOut->mChildren = new aiNode*[pcIn->mChildren.size()];
+
+    // Recursively process all children
+    const unsigned int size = static_cast<unsigned int>(pcIn->mChildren.size());
+    for (unsigned int i = 0; i < size;++i)
+    {
+        pcOut->mChildren[i] = new aiNode();
+        pcOut->mChildren[i]->mParent = pcOut;
+        AddNodeToGraph(pcSOut,pcOut->mChildren[i],pcIn->mChildren[i],abs);
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Find out how many node animation channels we'll have finally
+void CountTracks(D3DS::Node* node, unsigned int& cnt)
+{
+    //////////////////////////////////////////////////////////////////////////////
+    // We will never generate more than one channel for a node, so
+    // this is rather easy here.
+
+    if (node->aPositionKeys.size()  > 1  || node->aRotationKeys.size()   > 1   ||
+        node->aScalingKeys.size()   > 1  || node->aCameraRollKeys.size() > 1 ||
+        node->aTargetPositionKeys.size()  > 1)
+    {
+        ++cnt;
+
+        // account for the additional channel for the camera/spotlight target position
+        if (node->aTargetPositionKeys.size()  > 1)++cnt;
+    }
+
+    // Recursively process all children
+    for (unsigned int i = 0; i < node->mChildren.size();++i)
+        CountTracks(node->mChildren[i],cnt);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Generate the output node graph
+void Discreet3DSImporter::GenerateNodeGraph(aiScene* pcOut)
+{
+    pcOut->mRootNode = new aiNode();
+    if (0 == mRootNode->mChildren.size())
+    {
+        //////////////////////////////////////////////////////////////////////////////
+        // It seems the file is so messed up that it has not even a hierarchy.
+        // generate a flat hiearachy which looks like this:
+        //
+        //                ROOT_NODE
+        //                   |
+        //   ----------------------------------------
+        //   |       |       |            |         |
+        // MESH_0  MESH_1  MESH_2  ...  MESH_N    CAMERA_0 ....
+        //
+        ASSIMP_LOG_WARN("No hierarchy information has been found in the file. ");
+
+        pcOut->mRootNode->mNumChildren = pcOut->mNumMeshes +
+            static_cast<unsigned int>(mScene->mCameras.size() + mScene->mLights.size());
+
+        pcOut->mRootNode->mChildren = new aiNode* [ pcOut->mRootNode->mNumChildren ];
+        pcOut->mRootNode->mName.Set("<3DSDummyRoot>");
+
+        // Build dummy nodes for all meshes
+        unsigned int a = 0;
+        for (unsigned int i = 0; i < pcOut->mNumMeshes;++i,++a)
+        {
+            aiNode* pcNode = pcOut->mRootNode->mChildren[a] = new aiNode();
+            pcNode->mParent = pcOut->mRootNode;
+            pcNode->mMeshes = new unsigned int[1];
+            pcNode->mMeshes[0] = i;
+            pcNode->mNumMeshes = 1;
+
+            // Build a name for the node
+            pcNode->mName.length = ai_snprintf(pcNode->mName.data, MAXLEN, "3DSMesh_%u",i);
+        }
+
+        // Build dummy nodes for all cameras
+        for (unsigned int i = 0; i < (unsigned int )mScene->mCameras.size();++i,++a)
+        {
+            aiNode* pcNode = pcOut->mRootNode->mChildren[a] = new aiNode();
+            pcNode->mParent = pcOut->mRootNode;
+
+            // Build a name for the node
+            pcNode->mName = mScene->mCameras[i]->mName;
+        }
+
+        // Build dummy nodes for all lights
+        for (unsigned int i = 0; i < (unsigned int )mScene->mLights.size();++i,++a)
+        {
+            aiNode* pcNode = pcOut->mRootNode->mChildren[a] = new aiNode();
+            pcNode->mParent = pcOut->mRootNode;
+
+            // Build a name for the node
+            pcNode->mName = mScene->mLights[i]->mName;
+        }
+    }
+    else
+    {
+        // First of all: find out how many scaling, rotation and translation
+        // animation tracks we'll have afterwards
+        unsigned int numChannel = 0;
+        CountTracks(mRootNode,numChannel);
+
+        if (numChannel)
+        {
+            // Allocate a primary animation channel
+            pcOut->mNumAnimations = 1;
+            pcOut->mAnimations    = new aiAnimation*[1];
+            aiAnimation* anim     = pcOut->mAnimations[0] = new aiAnimation();
+
+            anim->mName.Set("3DSMasterAnim");
+
+            // Allocate enough storage for all node animation channels,
+            // but don't set the mNumChannels member - we'll use it to
+            // index into the array
+            anim->mChannels = new aiNodeAnim*[numChannel];
+        }
+
+        aiMatrix4x4 m;
+        AddNodeToGraph(pcOut,  pcOut->mRootNode, mRootNode,m);
+    }
+
+    // We used the first and second vertex color set to store some temporary values so we need to cleanup here
+    for (unsigned int a = 0; a < pcOut->mNumMeshes; ++a)
+    {
+        pcOut->mMeshes[a]->mColors[0] = NULL;
+        pcOut->mMeshes[a]->mColors[1] = NULL;
+    }
+
+    pcOut->mRootNode->mTransformation = aiMatrix4x4(
+        1.f,0.f,0.f,0.f,
+        0.f,0.f,1.f,0.f,
+        0.f,-1.f,0.f,0.f,
+        0.f,0.f,0.f,1.f) * pcOut->mRootNode->mTransformation;
+
+    // If the root node is unnamed name it "<3DSRoot>"
+    if (::strstr( pcOut->mRootNode->mName.data, "UNNAMED" ) ||
+        (pcOut->mRootNode->mName.data[0] == '$' && pcOut->mRootNode->mName.data[1] == '$') )
+    {
+        pcOut->mRootNode->mName.Set("<3DSRoot>");
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Convert all meshes in the scene and generate the final output scene.
+void Discreet3DSImporter::ConvertScene(aiScene* pcOut)
+{
+    // Allocate enough storage for all output materials
+    pcOut->mNumMaterials = (unsigned int)mScene->mMaterials.size();
+    pcOut->mMaterials    = new aiMaterial*[pcOut->mNumMaterials];
+
+    //  ... and convert the 3DS materials to aiMaterial's
+    for (unsigned int i = 0; i < pcOut->mNumMaterials;++i)
+    {
+        aiMaterial* pcNew = new aiMaterial();
+        ConvertMaterial(mScene->mMaterials[i],*pcNew);
+        pcOut->mMaterials[i] = pcNew;
+    }
+
+    // Generate the output mesh list
+    ConvertMeshes(pcOut);
+
+    // Now copy all light sources to the output scene
+    pcOut->mNumLights = (unsigned int)mScene->mLights.size();
+    if (pcOut->mNumLights)
+    {
+        pcOut->mLights = new aiLight*[pcOut->mNumLights];
+        ::memcpy(pcOut->mLights,&mScene->mLights[0],sizeof(void*)*pcOut->mNumLights);
+    }
+
+    // Now copy all cameras to the output scene
+    pcOut->mNumCameras = (unsigned int)mScene->mCameras.size();
+    if (pcOut->mNumCameras)
+    {
+        pcOut->mCameras = new aiCamera*[pcOut->mNumCameras];
+        ::memcpy(pcOut->mCameras,&mScene->mCameras[0],sizeof(void*)*pcOut->mNumCameras);
+    }
+}
+
+#endif // !! ASSIMP_BUILD_NO_3DS_IMPORTER

+ 580 - 0
Engine/lib/assimp/code/3DSExporter.cpp

@@ -0,0 +1,580 @@
+/*
+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_3DS_EXPORTER
+
+#include "3DSExporter.h"
+#include "3DSLoader.h"
+#include "3DSHelper.h"
+#include <assimp/SceneCombiner.h>
+#include "SplitLargeMeshes.h"
+#include <assimp/StringComparison.h>
+#include <assimp/IOSystem.hpp>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/Exporter.hpp>
+#include <memory>
+
+using namespace Assimp;
+namespace Assimp    {
+using namespace D3DS;
+
+namespace {
+
+    //////////////////////////////////////////////////////////////////////////////////////
+    // Scope utility to write a 3DS file chunk.
+    //
+    // Upon construction, the chunk header is written with the chunk type (flags)
+    // filled out, but the chunk size left empty. Upon destruction, the correct chunk
+    // size based on the then-position of the output stream cursor is filled in.
+    class ChunkWriter {
+        enum {
+              CHUNK_SIZE_NOT_SET = 0xdeadbeef
+            , SIZE_OFFSET        = 2
+        };
+    public:
+
+        ChunkWriter(StreamWriterLE& writer, uint16_t chunk_type)
+            : writer(writer)
+        {
+            chunk_start_pos = writer.GetCurrentPos();
+            writer.PutU2(chunk_type);
+            writer.PutU4(CHUNK_SIZE_NOT_SET);
+        }
+
+        ~ChunkWriter() {
+            std::size_t head_pos = writer.GetCurrentPos();
+
+            ai_assert(head_pos > chunk_start_pos);
+            const std::size_t chunk_size = head_pos - chunk_start_pos;
+
+            writer.SetCurrentPos(chunk_start_pos + SIZE_OFFSET);
+            writer.PutU4(static_cast<uint32_t>(chunk_size));
+            writer.SetCurrentPos(head_pos);
+        }
+
+    private:
+        StreamWriterLE& writer;
+        std::size_t chunk_start_pos;
+    };
+
+
+    // Return an unique name for a given |mesh| attached to |node| that
+    // preserves the mesh's given name if it has one. |index| is the index
+    // of the mesh in |aiScene::mMeshes|.
+    std::string GetMeshName(const aiMesh& mesh, unsigned int index, const aiNode& node) {
+        static const std::string underscore = "_";
+        char postfix[10] = {0};
+        ASSIMP_itoa10(postfix, index);
+
+        std::string result = node.mName.C_Str();
+        if (mesh.mName.length > 0) {
+            result += underscore + mesh.mName.C_Str();
+        }
+        return result + underscore + postfix;
+    }
+
+    // Return an unique name for a given |mat| with original position |index|
+    // in |aiScene::mMaterials|. The name preserves the original material
+    // name if possible.
+    std::string GetMaterialName(const aiMaterial& mat, unsigned int index) {
+        static const std::string underscore = "_";
+        char postfix[10] = {0};
+        ASSIMP_itoa10(postfix, index);
+
+        aiString mat_name;
+        if (AI_SUCCESS == mat.Get(AI_MATKEY_NAME, mat_name)) {
+            return mat_name.C_Str() + underscore + postfix;
+        }
+
+        return "Material" + underscore + postfix;
+    }
+
+    // Collect world transformations for each node
+    void CollectTrafos(const aiNode* node, std::map<const aiNode*, aiMatrix4x4>& trafos) {
+        const aiMatrix4x4& parent = node->mParent ? trafos[node->mParent] : aiMatrix4x4();
+        trafos[node] = parent * node->mTransformation;
+        for (unsigned int i = 0; i < node->mNumChildren; ++i) {
+            CollectTrafos(node->mChildren[i], trafos);
+        }
+    }
+
+    // Generate a flat list of the meshes (by index) assigned to each node
+    void CollectMeshes(const aiNode* node, std::multimap<const aiNode*, unsigned int>& meshes) {
+        for (unsigned int i = 0; i < node->mNumMeshes; ++i) {
+            meshes.insert(std::make_pair(node, node->mMeshes[i]));
+        }
+        for (unsigned int i = 0; i < node->mNumChildren; ++i) {
+            CollectMeshes(node->mChildren[i], meshes);
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Worker function for exporting a scene to 3DS. Prototyped and registered in Exporter.cpp
+void ExportScene3DS(const char* pFile, IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/)
+{
+    std::shared_ptr<IOStream> outfile (pIOSystem->Open(pFile, "wb"));
+    if(!outfile) {
+        throw DeadlyExportError("Could not open output .3ds file: " + std::string(pFile));
+    }
+
+    // TODO: This extra copy should be avoided and all of this made a preprocess
+    // requirement of the 3DS exporter.
+    //
+    // 3DS meshes can be max 0xffff (16 Bit) vertices and faces, respectively.
+    // SplitLargeMeshes can do this, but it requires the correct limit to be set
+    // which is not possible with the current way of specifying preprocess steps
+    // in |Exporter::ExportFormatEntry|.
+    aiScene* scenecopy_tmp;
+    SceneCombiner::CopyScene(&scenecopy_tmp,pScene);
+    std::unique_ptr<aiScene> scenecopy(scenecopy_tmp);
+
+    SplitLargeMeshesProcess_Triangle tri_splitter;
+    tri_splitter.SetLimit(0xffff);
+    tri_splitter.Execute(scenecopy.get());
+
+    SplitLargeMeshesProcess_Vertex vert_splitter;
+    vert_splitter.SetLimit(0xffff);
+    vert_splitter.Execute(scenecopy.get());
+
+    // Invoke the actual exporter
+    Discreet3DSExporter exporter(outfile, scenecopy.get());
+}
+
+} // end of namespace Assimp
+
+// ------------------------------------------------------------------------------------------------
+Discreet3DSExporter:: Discreet3DSExporter(std::shared_ptr<IOStream> &outfile, const aiScene* scene)
+: scene(scene)
+, writer(outfile)
+{
+    CollectTrafos(scene->mRootNode, trafos);
+    CollectMeshes(scene->mRootNode, meshes);
+
+    ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAIN);
+
+    {
+        ChunkWriter chunk(writer, Discreet3DS::CHUNK_OBJMESH);
+        WriteMaterials();
+        WriteMeshes();
+
+        {
+            ChunkWriter chunk(writer, Discreet3DS::CHUNK_MASTER_SCALE);
+            writer.PutF4(1.0f);
+        }
+    }
+
+    {
+        ChunkWriter chunk(writer, Discreet3DS::CHUNK_KEYFRAMER);
+        WriteHierarchy(*scene->mRootNode, -1, -1);
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+Discreet3DSExporter::~Discreet3DSExporter() {
+    // empty
+}
+
+
+// ------------------------------------------------------------------------------------------------
+int Discreet3DSExporter::WriteHierarchy(const aiNode& node, int seq, int sibling_level)
+{
+    // 3DS scene hierarchy is serialized as in http://www.martinreddy.net/gfx/3d/3DS.spec
+    {
+        ChunkWriter chunk(writer, Discreet3DS::CHUNK_TRACKINFO);
+        {
+            ChunkWriter chunk(writer, Discreet3DS::CHUNK_TRACKOBJNAME);
+
+            // Assimp node names are unique and distinct from all mesh-node
+            // names we generate; thus we can use them as-is
+            WriteString(node.mName);
+
+            // Two unknown int16 values - it is even unclear if 0 is a safe value
+            // but luckily importers do not know better either.
+            writer.PutI4(0);
+
+            int16_t hierarchy_pos = static_cast<int16_t>(seq);
+            if (sibling_level != -1) {
+                hierarchy_pos = sibling_level;
+            }
+
+            // Write the hierarchy position
+            writer.PutI2(hierarchy_pos);
+        }
+    }
+
+    // TODO: write transformation chunks
+
+    ++seq;
+    sibling_level = seq;
+
+    // Write all children
+    for (unsigned int i = 0; i < node.mNumChildren; ++i) {
+        seq = WriteHierarchy(*node.mChildren[i], seq, i == 0 ? -1 : sibling_level);
+    }
+
+    // Write all meshes as separate nodes to be able to reference the meshes by name
+    for (unsigned int i = 0; i < node.mNumMeshes; ++i) {
+        const bool first_child = node.mNumChildren == 0 && i == 0;
+
+        const unsigned int mesh_idx = node.mMeshes[i];
+        const aiMesh& mesh = *scene->mMeshes[mesh_idx];
+
+        ChunkWriter chunk(writer, Discreet3DS::CHUNK_TRACKINFO);
+        {
+            ChunkWriter chunk(writer, Discreet3DS::CHUNK_TRACKOBJNAME);
+            WriteString(GetMeshName(mesh, mesh_idx, node));
+
+            writer.PutI4(0);
+            writer.PutI2(static_cast<int16_t>(first_child ? seq : sibling_level));
+            ++seq;
+        }
+    }
+    return seq;
+}
+
+// ------------------------------------------------------------------------------------------------
+void Discreet3DSExporter::WriteMaterials()
+{
+    for (unsigned int i = 0; i < scene->mNumMaterials; ++i) {
+        ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_MATERIAL);
+        const aiMaterial& mat = *scene->mMaterials[i];
+
+        {
+            ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_MATNAME);
+            const std::string& name = GetMaterialName(mat, i);
+            WriteString(name);
+        }
+
+        aiColor3D color;
+        if (mat.Get(AI_MATKEY_COLOR_DIFFUSE, color) == AI_SUCCESS) {
+            ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_DIFFUSE);
+            WriteColor(color);
+        }
+
+        if (mat.Get(AI_MATKEY_COLOR_SPECULAR, color) == AI_SUCCESS) {
+            ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_SPECULAR);
+            WriteColor(color);
+        }
+
+        if (mat.Get(AI_MATKEY_COLOR_AMBIENT, color) == AI_SUCCESS) {
+            ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_AMBIENT);
+            WriteColor(color);
+        }
+
+        if (mat.Get(AI_MATKEY_COLOR_EMISSIVE, color) == AI_SUCCESS) {
+            ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_SELF_ILLUM);
+            WriteColor(color);
+        }
+
+        aiShadingMode shading_mode = aiShadingMode_Flat;
+        if (mat.Get(AI_MATKEY_SHADING_MODEL, shading_mode) == AI_SUCCESS) {
+            ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_SHADING);
+
+            Discreet3DS::shadetype3ds shading_mode_out;
+            switch(shading_mode) {
+            case aiShadingMode_Flat:
+            case aiShadingMode_NoShading:
+                shading_mode_out = Discreet3DS::Flat;
+                break;
+
+            case aiShadingMode_Gouraud:
+            case aiShadingMode_Toon:
+            case aiShadingMode_OrenNayar:
+            case aiShadingMode_Minnaert:
+                shading_mode_out = Discreet3DS::Gouraud;
+                break;
+
+            case aiShadingMode_Phong:
+            case aiShadingMode_Blinn:
+            case aiShadingMode_CookTorrance:
+            case aiShadingMode_Fresnel:
+                shading_mode_out = Discreet3DS::Phong;
+                break;
+
+            default:
+                shading_mode_out = Discreet3DS::Flat;
+                ai_assert(false);
+            };
+            writer.PutU2(static_cast<uint16_t>(shading_mode_out));
+        }
+
+
+        float f;
+        if (mat.Get(AI_MATKEY_SHININESS, f) == AI_SUCCESS) {
+            ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_SHININESS);
+            WritePercentChunk(f);
+        }
+
+        if (mat.Get(AI_MATKEY_SHININESS_STRENGTH, f) == AI_SUCCESS) {
+            ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_SHININESS_PERCENT);
+            WritePercentChunk(f);
+        }
+
+        int twosided;
+        if (mat.Get(AI_MATKEY_TWOSIDED, twosided) == AI_SUCCESS && twosided != 0) {
+            ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_TWO_SIDE);
+            writer.PutI2(1);
+        }
+
+        WriteTexture(mat, aiTextureType_DIFFUSE, Discreet3DS::CHUNK_MAT_TEXTURE);
+        WriteTexture(mat, aiTextureType_HEIGHT, Discreet3DS::CHUNK_MAT_BUMPMAP);
+        WriteTexture(mat, aiTextureType_OPACITY, Discreet3DS::CHUNK_MAT_OPACMAP);
+        WriteTexture(mat, aiTextureType_SHININESS, Discreet3DS::CHUNK_MAT_MAT_SHINMAP);
+        WriteTexture(mat, aiTextureType_SPECULAR, Discreet3DS::CHUNK_MAT_SPECMAP);
+        WriteTexture(mat, aiTextureType_EMISSIVE, Discreet3DS::CHUNK_MAT_SELFIMAP);
+        WriteTexture(mat, aiTextureType_REFLECTION, Discreet3DS::CHUNK_MAT_REFLMAP);
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void Discreet3DSExporter::WriteTexture(const aiMaterial& mat, aiTextureType type, uint16_t chunk_flags)
+{
+    aiString path;
+    aiTextureMapMode map_mode[2] = {
+        aiTextureMapMode_Wrap, aiTextureMapMode_Wrap
+    };
+    ai_real blend = 1.0;
+    if (mat.GetTexture(type, 0, &path, NULL, NULL, &blend, NULL, map_mode) != AI_SUCCESS || !path.length) {
+        return;
+    }
+
+    // TODO: handle embedded textures properly
+    if (path.data[0] == '*') {
+        ASSIMP_LOG_ERROR("Ignoring embedded texture for export: " + std::string(path.C_Str()));
+        return;
+    }
+
+    ChunkWriter chunk(writer, chunk_flags);
+    {
+        ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAPFILE);
+        WriteString(path);
+    }
+
+    WritePercentChunk(blend);
+
+    {
+        ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_MAP_TILING);
+        uint16_t val = 0; // WRAP
+        if (map_mode[0] == aiTextureMapMode_Mirror) {
+            val = 0x2;
+        }
+        else if (map_mode[0] == aiTextureMapMode_Decal) {
+            val = 0x10;
+        }
+        writer.PutU2(val);
+    }
+    // TODO: export texture transformation (i.e. UV offset, scale, rotation)
+}
+
+// ------------------------------------------------------------------------------------------------
+void Discreet3DSExporter::WriteMeshes()
+{
+    // NOTE: 3DS allows for instances. However:
+    //   i)  not all importers support reading them
+    //   ii) instances are not as flexible as they are in assimp, in particular,
+    //        nodes can carry (and instance) only one mesh.
+    //
+    // This exporter currently deep clones all instanced meshes, i.e. for each mesh
+    // attached to a node a full TRIMESH chunk is written to the file.
+    //
+    // Furthermore, the TRIMESH is transformed into world space so that it will
+    // appear correctly if importers don't read the scene hierarchy at all.
+    for (MeshesByNodeMap::const_iterator it = meshes.begin(); it != meshes.end(); ++it) {
+        const aiNode& node = *(*it).first;
+        const unsigned int mesh_idx = (*it).second;
+
+        const aiMesh& mesh = *scene->mMeshes[mesh_idx];
+
+        // This should not happen if the SLM step is correctly executed
+        // before the scene is handed to the exporter
+        ai_assert(mesh.mNumVertices <= 0xffff);
+        ai_assert(mesh.mNumFaces <= 0xffff);
+
+        const aiMatrix4x4& trafo = trafos[&node];
+
+        ChunkWriter chunk(writer, Discreet3DS::CHUNK_OBJBLOCK);
+
+        // Mesh name is tied to the node it is attached to so it can later be referenced
+        const std::string& name = GetMeshName(mesh, mesh_idx, node);
+        WriteString(name);
+
+
+        // TRIMESH chunk
+        ChunkWriter chunk2(writer, Discreet3DS::CHUNK_TRIMESH);
+
+        // Vertices in world space
+        {
+            ChunkWriter chunk(writer, Discreet3DS::CHUNK_VERTLIST);
+
+            const uint16_t count = static_cast<uint16_t>(mesh.mNumVertices);
+            writer.PutU2(count);
+            for (unsigned int i = 0; i < mesh.mNumVertices; ++i) {
+                const aiVector3D& v = trafo * mesh.mVertices[i];
+                writer.PutF4(v.x);
+                writer.PutF4(v.y);
+                writer.PutF4(v.z);
+            }
+        }
+
+        // UV coordinates
+        if (mesh.HasTextureCoords(0)) {
+            ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAPLIST);
+            const uint16_t count = static_cast<uint16_t>(mesh.mNumVertices);
+            writer.PutU2(count);
+
+            for (unsigned int i = 0; i < mesh.mNumVertices; ++i) {
+                const aiVector3D& v = mesh.mTextureCoords[0][i];
+                writer.PutF4(v.x);
+                writer.PutF4(v.y);
+            }
+        }
+
+        // Faces (indices)
+        {
+            ChunkWriter chunk(writer, Discreet3DS::CHUNK_FACELIST);
+
+            ai_assert(mesh.mNumFaces <= 0xffff);
+
+            // Count triangles, discard lines and points
+            uint16_t count = 0;
+            for (unsigned int i = 0; i < mesh.mNumFaces; ++i) {
+                const aiFace& f = mesh.mFaces[i];
+                if (f.mNumIndices < 3) {
+                    continue;
+                }
+                // TRIANGULATE step is a pre-requisite so we should not see polys here
+                ai_assert(f.mNumIndices == 3);
+                ++count;
+            }
+
+            writer.PutU2(count);
+            for (unsigned int i = 0; i < mesh.mNumFaces; ++i) {
+                const aiFace& f = mesh.mFaces[i];
+                if (f.mNumIndices < 3) {
+                    continue;
+                }
+
+                for (unsigned int j = 0; j < 3; ++j) {
+                    ai_assert(f.mIndices[j] <= 0xffff);
+                    writer.PutI2(static_cast<uint16_t>(f.mIndices[j]));
+                }
+
+                // Edge visibility flag
+                writer.PutI2(0x0);
+            }
+
+            // TODO: write smoothing groups (CHUNK_SMOOLIST)
+
+            WriteFaceMaterialChunk(mesh);
+        }
+
+        // Transformation matrix by which the mesh vertices have been pre-transformed with.
+        {
+            ChunkWriter chunk(writer, Discreet3DS::CHUNK_TRMATRIX);
+            for (unsigned int r = 0; r < 4; ++r) {
+                for (unsigned int c = 0; c < 3; ++c) {
+                    writer.PutF4(trafo[r][c]);
+                }
+            }
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void Discreet3DSExporter::WriteFaceMaterialChunk(const aiMesh& mesh)
+{
+    ChunkWriter chunk(writer, Discreet3DS::CHUNK_FACEMAT);
+    const std::string& name = GetMaterialName(*scene->mMaterials[mesh.mMaterialIndex], mesh.mMaterialIndex);
+    WriteString(name);
+
+    // Because assimp splits meshes by material, only a single
+    // FACEMAT chunk needs to be written
+    ai_assert(mesh.mNumFaces <= 0xffff);
+    const uint16_t count = static_cast<uint16_t>(mesh.mNumFaces);
+    writer.PutU2(count);
+
+    for (unsigned int i = 0; i < mesh.mNumFaces; ++i) {
+        writer.PutU2(static_cast<uint16_t>(i));
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void Discreet3DSExporter::WriteString(const std::string& s) {
+    for (std::string::const_iterator it = s.begin(); it != s.end(); ++it) {
+        writer.PutI1(*it);
+    }
+    writer.PutI1('\0');
+}
+
+// ------------------------------------------------------------------------------------------------
+void Discreet3DSExporter::WriteString(const aiString& s) {
+    for (std::size_t i = 0; i < s.length; ++i) {
+        writer.PutI1(s.data[i]);
+    }
+    writer.PutI1('\0');
+}
+
+// ------------------------------------------------------------------------------------------------
+void Discreet3DSExporter::WriteColor(const aiColor3D& color) {
+    ChunkWriter chunk(writer, Discreet3DS::CHUNK_RGBF);
+    writer.PutF4(color.r);
+    writer.PutF4(color.g);
+    writer.PutF4(color.b);
+}
+
+// ------------------------------------------------------------------------------------------------
+void Discreet3DSExporter::WritePercentChunk(float f) {
+    ChunkWriter chunk(writer, Discreet3DS::CHUNK_PERCENTF);
+    writer.PutF4(f);
+}
+
+// ------------------------------------------------------------------------------------------------
+void Discreet3DSExporter::WritePercentChunk(double f) {
+    ChunkWriter chunk(writer, Discreet3DS::CHUNK_PERCENTD);
+    writer.PutF8(f);
+}
+
+
+#endif // ASSIMP_BUILD_NO_3DS_EXPORTER
+#endif // ASSIMP_BUILD_NO_EXPORT

+ 98 - 0
Engine/lib/assimp/code/3DSExporter.h

@@ -0,0 +1,98 @@
+/*
+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 3DSExporter.h
+ * 3DS Exporter Main Header
+ */
+#ifndef AI_3DSEXPORTER_H_INC
+#define AI_3DSEXPORTER_H_INC
+
+#include <map>
+#include <memory>
+
+#include <assimp/StreamWriter.h>
+#include <assimp/material.h>
+
+struct aiScene;
+struct aiNode;
+struct aiMaterial;
+struct aiMesh;
+
+namespace Assimp
+{
+
+// ------------------------------------------------------------------------------------------------
+/**
+ *  @brief  Helper class to export a given scene to a 3DS file.
+ */
+// ------------------------------------------------------------------------------------------------
+class Discreet3DSExporter {
+public:
+    Discreet3DSExporter(std::shared_ptr<IOStream> &outfile, const aiScene* pScene);
+    ~Discreet3DSExporter();
+
+private:
+    void WriteMeshes();
+    void WriteMaterials();
+    void WriteTexture(const aiMaterial& mat, aiTextureType type, uint16_t chunk_flags);
+    void WriteFaceMaterialChunk(const aiMesh& mesh);
+    int WriteHierarchy(const aiNode& node, int level, int sibling_level);
+    void WriteString(const std::string& s);
+    void WriteString(const aiString& s);
+    void WriteColor(const aiColor3D& color);
+    void WritePercentChunk(float f);
+    void WritePercentChunk(double f);
+
+private:
+    const aiScene* const scene;
+    StreamWriterLE writer;
+
+    std::map<const aiNode*, aiMatrix4x4> trafos;
+
+    typedef std::multimap<const aiNode*, unsigned int> MeshesByNodeMap;
+    MeshesByNodeMap meshes;
+
+};
+
+} // Namespace Assimp
+
+#endif // AI_3DSEXPORTER_H_INC

+ 652 - 0
Engine/lib/assimp/code/3DSHelper.h

@@ -0,0 +1,652 @@
+/*
+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 helper data structures for the import of 3DS files */
+
+#ifndef AI_3DSFILEHELPER_H_INC
+#define AI_3DSFILEHELPER_H_INC
+
+#include <assimp/SpatialSort.h>
+#include <assimp/SmoothingGroups.h>
+#include <assimp/StringUtils.h>
+#include <assimp/qnan.h>
+#include <assimp/material.h>
+#include <assimp/camera.h>
+#include <assimp/light.h>
+#include <assimp/anim.h>
+#include <stdio.h> //sprintf
+
+namespace Assimp    {
+namespace D3DS  {
+
+#include <assimp/Compiler/pushpack1.h>
+
+// ---------------------------------------------------------------------------
+/** Discreet3DS class: Helper class for loading 3ds files. Defines chunks
+*  and data structures.
+*/
+class Discreet3DS {
+private:
+    Discreet3DS() AI_NO_EXCEPT {
+        // empty
+    }
+
+    ~Discreet3DS() {
+        // empty
+    }
+
+public:
+    //! data structure for a single chunk in a .3ds file
+    struct Chunk {
+        uint16_t    Flag;
+        uint32_t    Size;
+    } PACK_STRUCT;
+
+
+    //! Used for shading field in material3ds structure
+    //! From AutoDesk 3ds SDK
+    typedef enum
+    {
+        // translated to gouraud shading with wireframe active
+        Wire = 0x0,
+
+        // if this material is set, no vertex normals will
+        // be calculated for the model. Face normals + gouraud
+        Flat = 0x1,
+
+        // standard gouraud shading
+        Gouraud = 0x2,
+
+        // phong shading
+        Phong = 0x3,
+
+        // cooktorrance or anistropic phong shading ...
+        // the exact meaning is unknown, if you know it
+        // feel free to tell me ;-)
+        Metal = 0x4,
+
+        // required by the ASE loader
+        Blinn = 0x5
+    } shadetype3ds;
+
+    // Flags for animated keys
+    enum
+    {
+        KEY_USE_TENS         = 0x1,
+        KEY_USE_CONT         = 0x2,
+        KEY_USE_BIAS         = 0x4,
+        KEY_USE_EASE_TO      = 0x8,
+        KEY_USE_EASE_FROM    = 0x10
+    } ;
+
+    enum
+    {
+
+        // ********************************************************************
+        // Basic chunks which can be found everywhere in the file
+        CHUNK_VERSION   = 0x0002,
+        CHUNK_RGBF      = 0x0010,       // float4 R; float4 G; float4 B
+        CHUNK_RGBB      = 0x0011,       // int1 R; int1 G; int B
+
+        // Linear color values (gamma = 2.2?)
+        CHUNK_LINRGBF      = 0x0013,    // float4 R; float4 G; float4 B
+        CHUNK_LINRGBB      = 0x0012,    // int1 R; int1 G; int B
+
+        CHUNK_PERCENTW  = 0x0030,       // int2   percentage
+        CHUNK_PERCENTF  = 0x0031,       // float4  percentage
+        CHUNK_PERCENTD  = 0x0032,       // float8  percentage
+        // ********************************************************************
+
+        // Prj master chunk
+        CHUNK_PRJ       = 0xC23D,
+
+        // MDLI master chunk
+        CHUNK_MLI       = 0x3DAA,
+
+        // Primary main chunk of the .3ds file
+        CHUNK_MAIN      = 0x4D4D,
+
+        // Mesh main chunk
+        CHUNK_OBJMESH   = 0x3D3D,
+
+        // Specifies the background color of the .3ds file
+        // This is passed through the material system for
+        // viewing purposes.
+        CHUNK_BKGCOLOR  = 0x1200,
+
+        // Specifies the ambient base color of the scene.
+        // This is added to all materials in the file
+        CHUNK_AMBCOLOR  = 0x2100,
+
+        // Specifies the background image for the whole scene
+        // This value is passed through the material system
+        // to the viewer
+        CHUNK_BIT_MAP   = 0x1100,
+        CHUNK_BIT_MAP_EXISTS  = 0x1101,
+
+        // ********************************************************************
+        // Viewport related stuff. Ignored
+        CHUNK_DEFAULT_VIEW = 0x3000,
+        CHUNK_VIEW_TOP = 0x3010,
+        CHUNK_VIEW_BOTTOM = 0x3020,
+        CHUNK_VIEW_LEFT = 0x3030,
+        CHUNK_VIEW_RIGHT = 0x3040,
+        CHUNK_VIEW_FRONT = 0x3050,
+        CHUNK_VIEW_BACK = 0x3060,
+        CHUNK_VIEW_USER = 0x3070,
+        CHUNK_VIEW_CAMERA = 0x3080,
+        // ********************************************************************
+
+        // Mesh chunks
+        CHUNK_OBJBLOCK  = 0x4000,
+        CHUNK_TRIMESH   = 0x4100,
+        CHUNK_VERTLIST  = 0x4110,
+        CHUNK_VERTFLAGS = 0x4111,
+        CHUNK_FACELIST  = 0x4120,
+        CHUNK_FACEMAT   = 0x4130,
+        CHUNK_MAPLIST   = 0x4140,
+        CHUNK_SMOOLIST  = 0x4150,
+        CHUNK_TRMATRIX  = 0x4160,
+        CHUNK_MESHCOLOR = 0x4165,
+        CHUNK_TXTINFO   = 0x4170,
+        CHUNK_LIGHT     = 0x4600,
+        CHUNK_CAMERA    = 0x4700,
+        CHUNK_HIERARCHY = 0x4F00,
+
+        // Specifies the global scaling factor. This is applied
+        // to the root node's transformation matrix
+        CHUNK_MASTER_SCALE    = 0x0100,
+
+        // ********************************************************************
+        // Material chunks
+        CHUNK_MAT_MATERIAL  = 0xAFFF,
+
+            // asciiz containing the name of the material
+            CHUNK_MAT_MATNAME   = 0xA000,
+            CHUNK_MAT_AMBIENT   = 0xA010, // followed by color chunk
+            CHUNK_MAT_DIFFUSE   = 0xA020, // followed by color chunk
+            CHUNK_MAT_SPECULAR  = 0xA030, // followed by color chunk
+
+            // Specifies the shininess of the material
+            // followed by percentage chunk
+            CHUNK_MAT_SHININESS  = 0xA040,
+            CHUNK_MAT_SHININESS_PERCENT  = 0xA041 ,
+
+            // Specifies the shading mode to be used
+            // followed by a short
+            CHUNK_MAT_SHADING  = 0xA100,
+
+            // NOTE: Emissive color (self illumination) seems not
+            // to be a color but a single value, type is unknown.
+            // Make the parser accept both of them.
+            // followed by percentage chunk (?)
+            CHUNK_MAT_SELF_ILLUM = 0xA080,
+
+            // Always followed by percentage chunk  (?)
+            CHUNK_MAT_SELF_ILPCT = 0xA084,
+
+            // Always followed by percentage chunk
+            CHUNK_MAT_TRANSPARENCY = 0xA050,
+
+            // Diffuse texture channel 0
+            CHUNK_MAT_TEXTURE   = 0xA200,
+
+            // Contains opacity information for each texel
+            CHUNK_MAT_OPACMAP = 0xA210,
+
+            // Contains a reflection map to be used to reflect
+            // the environment. This is partially supported.
+            CHUNK_MAT_REFLMAP = 0xA220,
+
+            // Self Illumination map (emissive colors)
+            CHUNK_MAT_SELFIMAP = 0xA33d,
+
+            // Bumpmap. Not specified whether it is a heightmap
+            // or a normal map. Assme it is a heightmap since
+            // artist normally prefer this format.
+            CHUNK_MAT_BUMPMAP = 0xA230,
+
+            // Specular map. Seems to influence the specular color
+            CHUNK_MAT_SPECMAP = 0xA204,
+
+            // Holds shininess data.
+            CHUNK_MAT_MAT_SHINMAP = 0xA33C,
+
+            // Scaling in U/V direction.
+            // (need to gen separate UV coordinate set
+            // and do this by hand)
+            CHUNK_MAT_MAP_USCALE      = 0xA354,
+            CHUNK_MAT_MAP_VSCALE      = 0xA356,
+
+            // Translation in U/V direction.
+            // (need to gen separate UV coordinate set
+            // and do this by hand)
+            CHUNK_MAT_MAP_UOFFSET     = 0xA358,
+            CHUNK_MAT_MAP_VOFFSET     = 0xA35a,
+
+            // UV-coordinates rotation around the z-axis
+            // Assumed to be in radians.
+            CHUNK_MAT_MAP_ANG = 0xA35C,
+
+            // Tiling flags for 3DS files
+            CHUNK_MAT_MAP_TILING = 0xa351,
+
+            // Specifies the file name of a texture
+            CHUNK_MAPFILE   = 0xA300,
+
+            // Specifies whether a materail requires two-sided rendering
+            CHUNK_MAT_TWO_SIDE = 0xA081,
+        // ********************************************************************
+
+        // Main keyframer chunk. Contains translation/rotation/scaling data
+        CHUNK_KEYFRAMER     = 0xB000,
+
+        // Supported sub chunks
+        CHUNK_TRACKINFO     = 0xB002,
+        CHUNK_TRACKOBJNAME  = 0xB010,
+        CHUNK_TRACKDUMMYOBJNAME  = 0xB011,
+        CHUNK_TRACKPIVOT    = 0xB013,
+        CHUNK_TRACKPOS      = 0xB020,
+        CHUNK_TRACKROTATE   = 0xB021,
+        CHUNK_TRACKSCALE    = 0xB022,
+
+        // ********************************************************************
+        // Keyframes for various other stuff in the file
+        // Partially ignored
+        CHUNK_AMBIENTKEY    = 0xB001,
+        CHUNK_TRACKMORPH    = 0xB026,
+        CHUNK_TRACKHIDE     = 0xB029,
+        CHUNK_OBJNUMBER     = 0xB030,
+        CHUNK_TRACKCAMERA   = 0xB003,
+        CHUNK_TRACKFOV      = 0xB023,
+        CHUNK_TRACKROLL     = 0xB024,
+        CHUNK_TRACKCAMTGT   = 0xB004,
+        CHUNK_TRACKLIGHT    = 0xB005,
+        CHUNK_TRACKLIGTGT   = 0xB006,
+        CHUNK_TRACKSPOTL    = 0xB007,
+        CHUNK_FRAMES        = 0xB008,
+        // ********************************************************************
+
+        // light sub-chunks
+        CHUNK_DL_OFF                 = 0x4620,
+        CHUNK_DL_OUTER_RANGE         = 0x465A,
+        CHUNK_DL_INNER_RANGE         = 0x4659,
+        CHUNK_DL_MULTIPLIER          = 0x465B,
+        CHUNK_DL_EXCLUDE             = 0x4654,
+        CHUNK_DL_ATTENUATE           = 0x4625,
+        CHUNK_DL_SPOTLIGHT           = 0x4610,
+
+        // camera sub-chunks
+        CHUNK_CAM_RANGES             = 0x4720
+    };
+};
+
+// ---------------------------------------------------------------------------
+/** Helper structure representing a 3ds mesh face */
+struct Face : public FaceWithSmoothingGroup
+{
+};
+
+// ---------------------------------------------------------------------------
+/** Helper structure representing a texture */
+struct Texture {
+    //! Default constructor
+    Texture() AI_NO_EXCEPT
+    : mOffsetU  (0.0)
+    , mOffsetV  (0.0)
+    , mScaleU   (1.0)
+    , mScaleV   (1.0)
+    , mRotation (0.0)
+    , mMapMode  (aiTextureMapMode_Wrap)
+    , bPrivate()
+    , iUVSrc    (0) {
+        mTextureBlend = get_qnan();
+    }
+
+    //! Specifies the blend factor for the texture
+    ai_real mTextureBlend;
+
+    //! Specifies the filename of the texture
+    std::string mMapName;
+
+    //! Specifies texture coordinate offsets/scaling/rotations
+    ai_real mOffsetU;
+    ai_real mOffsetV;
+    ai_real mScaleU;
+    ai_real mScaleV;
+    ai_real mRotation;
+
+    //! Specifies the mapping mode to be used for the texture
+    aiTextureMapMode mMapMode;
+
+    //! Used internally
+    bool bPrivate;
+    int iUVSrc;
+};
+
+#include <assimp/Compiler/poppack1.h>
+
+// ---------------------------------------------------------------------------
+/** Helper structure representing a 3ds material */
+struct Material
+{
+    //! Default constructor has been deleted
+    Material() = delete;
+
+
+    //! Constructor with explicit name
+    explicit Material(const std::string &name)
+    : mName(name)
+    , mDiffuse            ( ai_real( 0.6 ), ai_real( 0.6 ), ai_real( 0.6 ) ) // FIX ... we won't want object to be black
+    , mSpecularExponent   ( ai_real( 0.0 ) )
+    , mShininessStrength  ( ai_real( 1.0 ) )
+    , mShading(Discreet3DS::Gouraud)
+    , mTransparency       ( ai_real( 1.0 ) )
+    , mBumpHeight         ( ai_real( 1.0 ) )
+    , mTwoSided           (false)
+    {
+    }
+
+
+    Material(const Material &other)            = default;
+    Material &operator=(const Material &other) = default;
+
+
+    //! Move constructor. This is explicitly written because MSVC doesn't support defaulting it
+    Material(Material &&other) AI_NO_EXCEPT
+    : mName(std::move(other.mName))
+    , mDiffuse(std::move(other.mDiffuse))
+    , mSpecularExponent(std::move(other.mSpecularExponent))
+    , mShininessStrength(std::move(other.mShininessStrength))
+    , mSpecular(std::move(other.mSpecular))
+    , mAmbient(std::move(other.mAmbient))
+    , mShading(std::move(other.mShading))
+    , mTransparency(std::move(other.mTransparency))
+    , sTexDiffuse(std::move(other.sTexDiffuse))
+    , sTexOpacity(std::move(other.sTexOpacity))
+    , sTexSpecular(std::move(other.sTexSpecular))
+    , sTexReflective(std::move(other.sTexReflective))
+    , sTexBump(std::move(other.sTexBump))
+    , sTexEmissive(std::move(other.sTexEmissive))
+    , sTexShininess(std::move(other.sTexShininess))
+    , mBumpHeight(std::move(other.mBumpHeight))
+    , mEmissive(std::move(other.mEmissive))
+    , sTexAmbient(std::move(other.sTexAmbient))
+    , mTwoSided(std::move(other.mTwoSided))
+    {
+    }
+
+
+    Material &operator=(Material &&other) AI_NO_EXCEPT {
+        if (this == &other) {
+            return *this;
+        }
+
+        mName = std::move(other.mName);
+        mDiffuse = std::move(other.mDiffuse);
+        mSpecularExponent = std::move(other.mSpecularExponent);
+        mShininessStrength = std::move(other.mShininessStrength),
+        mSpecular = std::move(other.mSpecular);
+        mAmbient = std::move(other.mAmbient);
+        mShading = std::move(other.mShading);
+        mTransparency = std::move(other.mTransparency);
+        sTexDiffuse = std::move(other.sTexDiffuse);
+        sTexOpacity = std::move(other.sTexOpacity);
+        sTexSpecular = std::move(other.sTexSpecular);
+        sTexReflective = std::move(other.sTexReflective);
+        sTexBump = std::move(other.sTexBump);
+        sTexEmissive = std::move(other.sTexEmissive);
+        sTexShininess = std::move(other.sTexShininess);
+        mBumpHeight = std::move(other.mBumpHeight);
+        mEmissive = std::move(other.mEmissive);
+        sTexAmbient = std::move(other.sTexAmbient);
+        mTwoSided = std::move(other.mTwoSided);
+
+        return *this;
+    }
+
+
+    virtual ~Material() {}
+
+
+    //! Name of the material
+    std::string mName;
+    //! Diffuse color of the material
+    aiColor3D mDiffuse;
+    //! Specular exponent
+    ai_real mSpecularExponent;
+    //! Shininess strength, in percent
+    ai_real mShininessStrength;
+    //! Specular color of the material
+    aiColor3D mSpecular;
+    //! Ambient color of the material
+    aiColor3D mAmbient;
+    //! Shading type to be used
+    Discreet3DS::shadetype3ds mShading;
+    //! Opacity of the material
+    ai_real mTransparency;
+    //! Diffuse texture channel
+    Texture sTexDiffuse;
+    //! Opacity texture channel
+    Texture sTexOpacity;
+    //! Specular texture channel
+    Texture sTexSpecular;
+    //! Reflective texture channel
+    Texture sTexReflective;
+    //! Bump texture channel
+    Texture sTexBump;
+    //! Emissive texture channel
+    Texture sTexEmissive;
+    //! Shininess texture channel
+    Texture sTexShininess;
+    //! Scaling factor for the bump values
+    ai_real mBumpHeight;
+    //! Emissive color
+    aiColor3D mEmissive;
+    //! Ambient texture channel
+    //! (used by the ASE format)
+    Texture sTexAmbient;
+    //! True if the material must be rendered from two sides
+    bool mTwoSided;
+};
+
+// ---------------------------------------------------------------------------
+/** Helper structure to represent a 3ds file mesh */
+struct Mesh : public MeshWithSmoothingGroups<D3DS::Face>
+{
+    //! Default constructor has been deleted
+    Mesh() = delete;
+
+    //! Constructor with explicit name
+    explicit Mesh(const std::string &name)
+    : mName(name)
+    {
+    }
+
+
+    //! Name of the mesh
+    std::string mName;
+
+    //! Texture coordinates
+    std::vector<aiVector3D> mTexCoords;
+
+    //! Face materials
+    std::vector<unsigned int> mFaceMaterials;
+
+    //! Local transformation matrix
+    aiMatrix4x4 mMat;
+};
+
+// ---------------------------------------------------------------------------
+/** Float key - quite similar to aiVectorKey and aiQuatKey. Both are in the
+    C-API, so it would be difficult to make them a template. */
+struct aiFloatKey
+{
+    double mTime;      ///< The time of this key
+    ai_real mValue;   ///< The value of this key
+
+#ifdef __cplusplus
+
+    // time is not compared
+    bool operator == (const aiFloatKey& o) const
+        {return o.mValue == this->mValue;}
+
+    bool operator != (const aiFloatKey& o) const
+        {return o.mValue != this->mValue;}
+
+    // Only time is compared. This operator is defined
+    // for use with std::sort
+    bool operator < (const aiFloatKey& o) const
+        {return mTime < o.mTime;}
+
+    bool operator > (const aiFloatKey& o) const
+        {return mTime > o.mTime;}
+
+#endif
+};
+
+// ---------------------------------------------------------------------------
+/** Helper structure to represent a 3ds file node */
+struct Node
+{
+    Node() = delete;
+
+    explicit Node(const std::string &name)
+    : mParent(NULL)
+    , mName(name)
+    , mInstanceNumber(0)
+    , mHierarchyPos       (0)
+    , mHierarchyIndex     (0)
+    , mInstanceCount      (1)
+    {
+        aRotationKeys.reserve (20);
+        aPositionKeys.reserve (20);
+        aScalingKeys.reserve  (20);
+    }
+
+
+    ~Node()
+    {
+        for (unsigned int i = 0; i < mChildren.size();++i)
+            delete mChildren[i];
+    }
+
+    //! Pointer to the parent node
+    Node* mParent;
+
+    //! Holds all child nodes
+    std::vector<Node*> mChildren;
+
+    //! Name of the node
+    std::string mName;
+
+    //! InstanceNumber of the node
+    int32_t mInstanceNumber;
+
+    //! Dummy nodes: real name to be combined with the $$$DUMMY
+    std::string mDummyName;
+
+    //! Position of the node in the hierarchy (tree depth)
+    int16_t mHierarchyPos;
+
+    //! Index of the node
+    int16_t mHierarchyIndex;
+
+    //! Rotation keys loaded from the file
+    std::vector<aiQuatKey> aRotationKeys;
+
+    //! Position keys loaded from the file
+    std::vector<aiVectorKey> aPositionKeys;
+
+    //! Scaling keys loaded from the file
+    std::vector<aiVectorKey> aScalingKeys;
+
+
+    // For target lights (spot lights and directional lights):
+    // The position of the target
+    std::vector< aiVectorKey > aTargetPositionKeys;
+
+    // For cameras: the camera roll angle
+    std::vector< aiFloatKey > aCameraRollKeys;
+
+    //! Pivot position loaded from the file
+    aiVector3D vPivot;
+
+    //instance count, will be kept only for the first node
+    int32_t mInstanceCount;
+
+    //! Add a child node, setup the right parent node for it
+    //! \param pc Node to be 'adopted'
+    inline Node& push_back(Node* pc)
+    {
+        mChildren.push_back(pc);
+        pc->mParent = this;
+        return *this;
+    }
+};
+// ---------------------------------------------------------------------------
+/** Helper structure analogue to aiScene */
+struct Scene
+{
+    //! List of all materials loaded
+    //! NOTE: 3ds references materials globally
+    std::vector<Material> mMaterials;
+
+    //! List of all meshes loaded
+    std::vector<Mesh> mMeshes;
+
+    //! List of all cameras loaded
+    std::vector<aiCamera*> mCameras;
+
+    //! List of all lights loaded
+    std::vector<aiLight*> mLights;
+
+    //! Pointer to the root node of the scene
+    // --- moved to main class
+    // Node* pcRootNode;
+};
+
+
+} // end of namespace D3DS
+} // end of namespace Assimp
+
+#endif // AI_XFILEHELPER_H_INC

+ 1432 - 0
Engine/lib/assimp/code/3DSLoader.cpp

@@ -0,0 +1,1432 @@
+/*
+---------------------------------------------------------------------------
+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  3DSLoader.cpp
+ *  @brief Implementation of the 3ds importer class
+ *
+ *  http://www.the-labs.com/Blender/3DS-details.html
+ */
+
+
+#ifndef ASSIMP_BUILD_NO_3DS_IMPORTER
+
+// internal headers
+#include "3DSLoader.h"
+#include <assimp/Macros.h>
+#include <assimp/IOSystem.hpp>
+#include <assimp/scene.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/importerdesc.h>
+#include <assimp/StringComparison.h>
+
+using namespace Assimp;
+
+static const aiImporterDesc desc = {
+    "Discreet 3DS Importer",
+    "",
+    "",
+    "Limited animation support",
+    aiImporterFlags_SupportBinaryFlavour,
+    0,
+    0,
+    0,
+    0,
+	"3ds prj"
+};
+
+
+// ------------------------------------------------------------------------------------------------
+// Begins a new parsing block
+// - Reads the current chunk and validates it
+// - computes its length
+#define ASSIMP_3DS_BEGIN_CHUNK()                                         \
+    while (true) {                                                       \
+    if (stream->GetRemainingSizeToLimit() < sizeof(Discreet3DS::Chunk)){ \
+        return;                                                          \
+    }                                                                    \
+    Discreet3DS::Chunk chunk;                                            \
+    ReadChunk(&chunk);                                                   \
+    int chunkSize = chunk.Size-sizeof(Discreet3DS::Chunk);               \
+    if(chunkSize <= 0)                                                   \
+        continue;                                                        \
+    const unsigned int oldReadLimit = stream->SetReadLimit(              \
+        stream->GetCurrentPos() + chunkSize);                            \
+
+
+// ------------------------------------------------------------------------------------------------
+// End a parsing block
+// Must follow at the end of each parsing block, reset chunk end marker to previous value
+#define ASSIMP_3DS_END_CHUNK()                  \
+    stream->SkipToReadLimit();                  \
+    stream->SetReadLimit(oldReadLimit);         \
+    if (stream->GetRemainingSizeToLimit() == 0) \
+        return;                                 \
+    }
+
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by Importer
+Discreet3DSImporter::Discreet3DSImporter()
+: stream()
+, mLastNodeIndex()
+, mCurrentNode()
+, mRootNode()
+, mScene()
+, mMasterScale()
+, bHasBG()
+, bIsPrj() {
+    // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+Discreet3DSImporter::~Discreet3DSImporter() {
+    // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+// Returns whether the class can handle the format of the given file.
+bool Discreet3DSImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const {
+    std::string extension = GetExtension(pFile);
+	if(extension == "3ds" || extension == "prj") {
+        return true;
+    }
+
+    if (!extension.length() || checkSig) {
+        uint16_t token[3];
+        token[0] = 0x4d4d;
+        token[1] = 0x3dc2;
+        //token[2] = 0x3daa;
+        return CheckMagicToken(pIOHandler,pFile,token,2,0,2);
+    }
+    return false;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Loader registry entry
+const aiImporterDesc* Discreet3DSImporter::GetInfo () const
+{
+    return &desc;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Setup configuration properties
+void Discreet3DSImporter::SetupProperties(const Importer* /*pImp*/)
+{
+    // nothing to be done for the moment
+}
+
+// ------------------------------------------------------------------------------------------------
+// Imports the given file into the given scene structure.
+void Discreet3DSImporter::InternReadFile( const std::string& pFile,
+    aiScene* pScene, IOSystem* pIOHandler)
+{
+    StreamReaderLE stream(pIOHandler->Open(pFile,"rb"));
+
+    // We should have at least one chunk
+    if (stream.GetRemainingSize() < 16) {
+        throw DeadlyImportError("3DS file is either empty or corrupt: " + pFile);
+    }
+    this->stream = &stream;
+
+    // Allocate our temporary 3DS representation
+    D3DS::Scene _scene;
+    mScene = &_scene;
+
+    // Initialize members
+    D3DS::Node _rootNode("UNNAMED");
+    mLastNodeIndex             = -1;
+    mCurrentNode               = &_rootNode;
+    mRootNode                  = mCurrentNode;
+    mRootNode->mHierarchyPos   = -1;
+    mRootNode->mHierarchyIndex = -1;
+    mRootNode->mParent         = NULL;
+    mMasterScale               = 1.0f;
+    mBackgroundImage           = "";
+    bHasBG                     = false;
+    bIsPrj                     = false;
+
+    // Parse the file
+    ParseMainChunk();
+
+    // Process all meshes in the file. First check whether all
+    // face indices have valid values. The generate our
+    // internal verbose representation. Finally compute normal
+    // vectors from the smoothing groups we read from the
+    // file.
+    for (auto &mesh : mScene->mMeshes) {
+        if (mesh.mFaces.size() > 0 && mesh.mPositions.size() == 0)  {
+            throw DeadlyImportError("3DS file contains faces but no vertices: " + pFile);
+        }
+        CheckIndices(mesh);
+        MakeUnique  (mesh);
+        ComputeNormalsWithSmoothingsGroups<D3DS::Face>(mesh);
+    }
+
+    // Replace all occurences of the default material with a
+    // valid material. Generate it if no material containing
+    // DEFAULT in its name has been found in the file
+    ReplaceDefaultMaterial();
+
+    // Convert the scene from our internal representation to an
+    // aiScene object. This involves copying all meshes, lights
+    // and cameras to the scene
+    ConvertScene(pScene);
+
+    // Generate the node graph for the scene. This is a little bit
+    // tricky since we'll need to split some meshes into sub-meshes
+    GenerateNodeGraph(pScene);
+
+    // Now apply the master scaling factor to the scene
+    ApplyMasterScale(pScene);
+
+    // Our internal scene representation and the root
+    // node will be automatically deleted, so the whole hierarchy will follow
+
+    AI_DEBUG_INVALIDATE_PTR(mRootNode);
+    AI_DEBUG_INVALIDATE_PTR(mScene);
+    AI_DEBUG_INVALIDATE_PTR(this->stream);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Applies a master-scaling factor to the imported scene
+void Discreet3DSImporter::ApplyMasterScale(aiScene* pScene)
+{
+    // There are some 3DS files with a zero scaling factor
+    if (!mMasterScale)mMasterScale = 1.0f;
+    else mMasterScale = 1.0f / mMasterScale;
+
+    // Construct an uniform scaling matrix and multiply with it
+    pScene->mRootNode->mTransformation *= aiMatrix4x4(
+        mMasterScale,0.0f, 0.0f, 0.0f,
+        0.0f, mMasterScale,0.0f, 0.0f,
+        0.0f, 0.0f, mMasterScale,0.0f,
+        0.0f, 0.0f, 0.0f, 1.0f);
+
+    // Check whether a scaling track is assigned to the root node.
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a new chunk from the file
+void Discreet3DSImporter::ReadChunk(Discreet3DS::Chunk* pcOut)
+{
+    ai_assert(pcOut != nullptr);
+
+    pcOut->Flag = stream->GetI2();
+    pcOut->Size = stream->GetI4();
+
+    if (pcOut->Size - sizeof(Discreet3DS::Chunk) > stream->GetRemainingSize()) {
+        throw DeadlyImportError("Chunk is too large");
+    }
+
+    if (pcOut->Size - sizeof(Discreet3DS::Chunk) > stream->GetRemainingSizeToLimit()) {
+        ASSIMP_LOG_ERROR("3DS: Chunk overflow");
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Skip a chunk
+void Discreet3DSImporter::SkipChunk()
+{
+    Discreet3DS::Chunk psChunk;
+    ReadChunk(&psChunk);
+
+    stream->IncPtr(psChunk.Size-sizeof(Discreet3DS::Chunk));
+    return;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Process the primary chunk of the file
+void Discreet3DSImporter::ParseMainChunk()
+{
+    ASSIMP_3DS_BEGIN_CHUNK();
+
+    // get chunk type
+    switch (chunk.Flag)
+    {
+
+    case Discreet3DS::CHUNK_PRJ:
+        bIsPrj = true;
+    case Discreet3DS::CHUNK_MAIN:
+        ParseEditorChunk();
+        break;
+    };
+
+    ASSIMP_3DS_END_CHUNK();
+    // recursively continue processing this hierarchy level
+    return ParseMainChunk();
+}
+
+// ------------------------------------------------------------------------------------------------
+void Discreet3DSImporter::ParseEditorChunk()
+{
+    ASSIMP_3DS_BEGIN_CHUNK();
+
+    // get chunk type
+    switch (chunk.Flag)
+    {
+    case Discreet3DS::CHUNK_OBJMESH:
+
+        ParseObjectChunk();
+        break;
+
+    // NOTE: In several documentations in the internet this
+    // chunk appears at different locations
+    case Discreet3DS::CHUNK_KEYFRAMER:
+
+        ParseKeyframeChunk();
+        break;
+
+    case Discreet3DS::CHUNK_VERSION:
+        {
+        // print the version number
+        char buff[10];
+        ASSIMP_itoa10(buff,stream->GetI2());
+        ASSIMP_LOG_INFO_F(std::string("3DS file format version: "), buff);
+        }
+        break;
+    };
+    ASSIMP_3DS_END_CHUNK();
+}
+
+// ------------------------------------------------------------------------------------------------
+void Discreet3DSImporter::ParseObjectChunk()
+{
+    ASSIMP_3DS_BEGIN_CHUNK();
+
+    // get chunk type
+    switch (chunk.Flag)
+    {
+    case Discreet3DS::CHUNK_OBJBLOCK:
+        {
+        unsigned int cnt = 0;
+        const char* sz = (const char*)stream->GetPtr();
+
+        // Get the name of the geometry object
+        while (stream->GetI1())++cnt;
+        ParseChunk(sz,cnt);
+        }
+        break;
+
+    case Discreet3DS::CHUNK_MAT_MATERIAL:
+
+        // Add a new material to the list
+        mScene->mMaterials.push_back(D3DS::Material(std::string("UNNAMED_" + to_string(mScene->mMaterials.size()))));
+        ParseMaterialChunk();
+        break;
+
+    case Discreet3DS::CHUNK_AMBCOLOR:
+
+        // This is the ambient base color of the scene.
+        // We add it to the ambient color of all materials
+        ParseColorChunk(&mClrAmbient,true);
+        if (is_qnan(mClrAmbient.r))
+        {
+            // We failed to read the ambient base color.
+            ASSIMP_LOG_ERROR("3DS: Failed to read ambient base color");
+            mClrAmbient.r = mClrAmbient.g = mClrAmbient.b = 0.0f;
+        }
+        break;
+
+    case Discreet3DS::CHUNK_BIT_MAP:
+        {
+        // Specifies the background image. The string should already be
+        // properly 0 terminated but we need to be sure
+        unsigned int cnt = 0;
+        const char* sz = (const char*)stream->GetPtr();
+        while (stream->GetI1())++cnt;
+        mBackgroundImage = std::string(sz,cnt);
+        }
+        break;
+
+    case Discreet3DS::CHUNK_BIT_MAP_EXISTS:
+        bHasBG = true;
+        break;
+
+    case Discreet3DS::CHUNK_MASTER_SCALE:
+        // Scene master scaling factor
+        mMasterScale = stream->GetF4();
+        break;
+    };
+    ASSIMP_3DS_END_CHUNK();
+}
+
+// ------------------------------------------------------------------------------------------------
+void Discreet3DSImporter::ParseChunk(const char* name, unsigned int num)
+{
+    ASSIMP_3DS_BEGIN_CHUNK();
+
+    // IMPLEMENTATION NOTE;
+    // Cameras or lights define their transformation in their parent node and in the
+    // corresponding light or camera chunks. However, we read and process the latter
+    // to to be able to return valid cameras/lights even if no scenegraph is given.
+
+    // get chunk type
+    switch (chunk.Flag)
+    {
+    case Discreet3DS::CHUNK_TRIMESH:
+        {
+        // this starts a new triangle mesh
+        mScene->mMeshes.push_back(D3DS::Mesh(std::string(name, num)));
+
+        // Read mesh chunks
+        ParseMeshChunk();
+        }
+        break;
+
+    case Discreet3DS::CHUNK_LIGHT:
+        {
+        // This starts a new light
+        aiLight* light = new aiLight();
+        mScene->mLights.push_back(light);
+
+        light->mName.Set(std::string(name, num));
+
+        // First read the position of the light
+        light->mPosition.x = stream->GetF4();
+        light->mPosition.y = stream->GetF4();
+        light->mPosition.z = stream->GetF4();
+
+        light->mColorDiffuse = aiColor3D(1.f,1.f,1.f);
+
+        // Now check for further subchunks
+        if (!bIsPrj) /* fixme */
+            ParseLightChunk();
+
+        // The specular light color is identical the the diffuse light color. The ambient light color
+        // is equal to the ambient base color of the whole scene.
+        light->mColorSpecular = light->mColorDiffuse;
+        light->mColorAmbient  = mClrAmbient;
+
+        if (light->mType == aiLightSource_UNDEFINED)
+        {
+            // It must be a point light
+            light->mType = aiLightSource_POINT;
+        }}
+        break;
+
+    case Discreet3DS::CHUNK_CAMERA:
+        {
+        // This starts a new camera
+        aiCamera* camera = new aiCamera();
+        mScene->mCameras.push_back(camera);
+        camera->mName.Set(std::string(name, num));
+
+        // First read the position of the camera
+        camera->mPosition.x = stream->GetF4();
+        camera->mPosition.y = stream->GetF4();
+        camera->mPosition.z = stream->GetF4();
+
+        // Then the camera target
+        camera->mLookAt.x = stream->GetF4() - camera->mPosition.x;
+        camera->mLookAt.y = stream->GetF4() - camera->mPosition.y;
+        camera->mLookAt.z = stream->GetF4() - camera->mPosition.z;
+        ai_real len = camera->mLookAt.Length();
+        if (len < 1e-5) {
+
+            // There are some files with lookat == position. Don't know why or whether it's ok or not.
+            ASSIMP_LOG_ERROR("3DS: Unable to read proper camera look-at vector");
+            camera->mLookAt = aiVector3D(0.0,1.0,0.0);
+
+        }
+        else camera->mLookAt /= len;
+
+        // And finally - the camera rotation angle, in counter clockwise direction
+        const ai_real angle =  AI_DEG_TO_RAD( stream->GetF4() );
+        aiQuaternion quat(camera->mLookAt,angle);
+        camera->mUp = quat.GetMatrix() * aiVector3D(0.0,1.0,0.0);
+
+        // Read the lense angle
+        camera->mHorizontalFOV = AI_DEG_TO_RAD ( stream->GetF4() );
+        if (camera->mHorizontalFOV < 0.001f)  {
+            camera->mHorizontalFOV = AI_DEG_TO_RAD(45.f);
+        }
+
+        // Now check for further subchunks
+        if (!bIsPrj) /* fixme */ {
+            ParseCameraChunk();
+        }}
+        break;
+    };
+    ASSIMP_3DS_END_CHUNK();
+}
+
+// ------------------------------------------------------------------------------------------------
+void Discreet3DSImporter::ParseLightChunk()
+{
+    ASSIMP_3DS_BEGIN_CHUNK();
+    aiLight* light = mScene->mLights.back();
+
+    // get chunk type
+    switch (chunk.Flag)
+    {
+    case Discreet3DS::CHUNK_DL_SPOTLIGHT:
+        // Now we can be sure that the light is a spot light
+        light->mType = aiLightSource_SPOT;
+
+        // We wouldn't need to normalize here, but we do it
+        light->mDirection.x = stream->GetF4() - light->mPosition.x;
+        light->mDirection.y = stream->GetF4() - light->mPosition.y;
+        light->mDirection.z = stream->GetF4() - light->mPosition.z;
+        light->mDirection.Normalize();
+
+        // Now the hotspot and falloff angles - in degrees
+        light->mAngleInnerCone = AI_DEG_TO_RAD( stream->GetF4() );
+
+        // FIX: the falloff angle is just an offset
+        light->mAngleOuterCone = light->mAngleInnerCone+AI_DEG_TO_RAD( stream->GetF4() );
+        break;
+
+        // intensity multiplier
+    case Discreet3DS::CHUNK_DL_MULTIPLIER:
+        light->mColorDiffuse = light->mColorDiffuse * stream->GetF4();
+        break;
+
+        // light color
+    case Discreet3DS::CHUNK_RGBF:
+    case Discreet3DS::CHUNK_LINRGBF:
+        light->mColorDiffuse.r *= stream->GetF4();
+        light->mColorDiffuse.g *= stream->GetF4();
+        light->mColorDiffuse.b *= stream->GetF4();
+        break;
+
+        // light attenuation
+    case Discreet3DS::CHUNK_DL_ATTENUATE:
+        light->mAttenuationLinear = stream->GetF4();
+        break;
+    };
+
+    ASSIMP_3DS_END_CHUNK();
+}
+
+// ------------------------------------------------------------------------------------------------
+void Discreet3DSImporter::ParseCameraChunk()
+{
+    ASSIMP_3DS_BEGIN_CHUNK();
+    aiCamera* camera = mScene->mCameras.back();
+
+    // get chunk type
+    switch (chunk.Flag)
+    {
+        // near and far clip plane
+    case Discreet3DS::CHUNK_CAM_RANGES:
+        camera->mClipPlaneNear = stream->GetF4();
+        camera->mClipPlaneFar  = stream->GetF4();
+        break;
+    }
+
+    ASSIMP_3DS_END_CHUNK();
+}
+
+// ------------------------------------------------------------------------------------------------
+void Discreet3DSImporter::ParseKeyframeChunk()
+{
+    ASSIMP_3DS_BEGIN_CHUNK();
+
+    // get chunk type
+    switch (chunk.Flag)
+    {
+    case Discreet3DS::CHUNK_TRACKCAMTGT:
+    case Discreet3DS::CHUNK_TRACKSPOTL:
+    case Discreet3DS::CHUNK_TRACKCAMERA:
+    case Discreet3DS::CHUNK_TRACKINFO:
+    case Discreet3DS::CHUNK_TRACKLIGHT:
+    case Discreet3DS::CHUNK_TRACKLIGTGT:
+
+        // this starts a new mesh hierarchy chunk
+        ParseHierarchyChunk(chunk.Flag);
+        break;
+    };
+
+    ASSIMP_3DS_END_CHUNK();
+}
+
+// ------------------------------------------------------------------------------------------------
+// Little helper function for ParseHierarchyChunk
+void Discreet3DSImporter::InverseNodeSearch(D3DS::Node* pcNode,D3DS::Node* pcCurrent)
+{
+    if (!pcCurrent) {
+        mRootNode->push_back(pcNode);
+        return;
+    }
+
+    if (pcCurrent->mHierarchyPos == pcNode->mHierarchyPos)  {
+        if(pcCurrent->mParent) {
+            pcCurrent->mParent->push_back(pcNode);
+        }
+        else pcCurrent->push_back(pcNode);
+        return;
+    }
+    return InverseNodeSearch(pcNode,pcCurrent->mParent);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Find a node with a specific name in the import hierarchy
+D3DS::Node* FindNode(D3DS::Node* root, const std::string& name)
+{
+    if (root->mName == name)
+        return root;
+    for (std::vector<D3DS::Node*>::iterator it = root->mChildren.begin();it != root->mChildren.end(); ++it) {
+        D3DS::Node* nd;
+        if (( nd = FindNode(*it,name)))
+            return nd;
+    }
+    return NULL;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Binary predicate for std::unique()
+template <class T>
+bool KeyUniqueCompare(const T& first, const T& second)
+{
+    return first.mTime == second.mTime;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Skip some additional import data.
+void Discreet3DSImporter::SkipTCBInfo()
+{
+    unsigned int flags = stream->GetI2();
+
+    if (!flags) {
+        // Currently we can't do anything with these values. They occur
+        // quite rare, so it wouldn't be worth the effort implementing
+        // them. 3DS is not really suitable for complex animations,
+        // so full support is not required.
+        ASSIMP_LOG_WARN("3DS: Skipping TCB animation info");
+    }
+
+    if (flags & Discreet3DS::KEY_USE_TENS) {
+        stream->IncPtr(4);
+    }
+    if (flags & Discreet3DS::KEY_USE_BIAS) {
+        stream->IncPtr(4);
+    }
+    if (flags & Discreet3DS::KEY_USE_CONT) {
+        stream->IncPtr(4);
+    }
+    if (flags & Discreet3DS::KEY_USE_EASE_FROM) {
+        stream->IncPtr(4);
+    }
+    if (flags & Discreet3DS::KEY_USE_EASE_TO) {
+        stream->IncPtr(4);
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Read hierarchy and keyframe info
+void Discreet3DSImporter::ParseHierarchyChunk(uint16_t parent)
+{
+    ASSIMP_3DS_BEGIN_CHUNK();
+
+    // get chunk type
+    switch (chunk.Flag)
+    {
+    case Discreet3DS::CHUNK_TRACKOBJNAME:
+
+        // This is the name of the object to which the track applies. The chunk also
+        // defines the position of this object in the hierarchy.
+        {
+
+        // First of all: get the name of the object
+        unsigned int cnt = 0;
+        const char* sz = (const char*)stream->GetPtr();
+
+        while (stream->GetI1())++cnt;
+        std::string name = std::string(sz,cnt);
+
+        // Now find out whether we have this node already (target animation channels
+        // are stored with a separate object ID)
+        D3DS::Node* pcNode = FindNode(mRootNode,name);
+        int instanceNumber = 1;
+
+        if ( pcNode)
+        {
+            // if the source is not a CHUNK_TRACKINFO block it won't be an object instance
+            if (parent != Discreet3DS::CHUNK_TRACKINFO)
+            {
+                mCurrentNode = pcNode;
+                break;
+            }
+            pcNode->mInstanceCount++;
+            instanceNumber = pcNode->mInstanceCount;
+        }
+        pcNode = new D3DS::Node(name);
+        pcNode->mInstanceNumber = instanceNumber;
+
+        // There are two unknown values which we can safely ignore
+        stream->IncPtr(4);
+
+        // Now read the hierarchy position of the object
+        uint16_t hierarchy = stream->GetI2() + 1;
+        pcNode->mHierarchyPos   = hierarchy;
+        pcNode->mHierarchyIndex = mLastNodeIndex;
+
+        // And find a proper position in the graph for it
+        if (mCurrentNode && mCurrentNode->mHierarchyPos == hierarchy)   {
+
+            // add to the parent of the last touched node
+            mCurrentNode->mParent->push_back(pcNode);
+            mLastNodeIndex++;
+        }
+        else if(hierarchy >= mLastNodeIndex)    {
+
+            // place it at the current position in the hierarchy
+            mCurrentNode->push_back(pcNode);
+            mLastNodeIndex = hierarchy;
+        }
+        else    {
+            // need to go back to the specified position in the hierarchy.
+            InverseNodeSearch(pcNode,mCurrentNode);
+            mLastNodeIndex++;
+        }
+        // Make this node the current node
+        mCurrentNode = pcNode;
+        }
+        break;
+
+    case Discreet3DS::CHUNK_TRACKDUMMYOBJNAME:
+
+        // This is the "real" name of a $$$DUMMY object
+        {
+            const char* sz = (const char*) stream->GetPtr();
+            while (stream->GetI1());
+
+            // If object name is DUMMY, take this one instead
+            if (mCurrentNode->mName == "$$$DUMMY")  {
+                mCurrentNode->mName = std::string(sz);
+                break;
+            }
+        }
+        break;
+
+    case Discreet3DS::CHUNK_TRACKPIVOT:
+
+        if ( Discreet3DS::CHUNK_TRACKINFO != parent)
+        {
+            ASSIMP_LOG_WARN("3DS: Skipping pivot subchunk for non usual object");
+            break;
+        }
+
+        // Pivot = origin of rotation and scaling
+        mCurrentNode->vPivot.x = stream->GetF4();
+        mCurrentNode->vPivot.y = stream->GetF4();
+        mCurrentNode->vPivot.z = stream->GetF4();
+        break;
+
+
+        // ////////////////////////////////////////////////////////////////////
+        // POSITION KEYFRAME
+    case Discreet3DS::CHUNK_TRACKPOS:
+        {
+        stream->IncPtr(10);
+        const unsigned int numFrames = stream->GetI4();
+        bool sortKeys = false;
+
+        // This could also be meant as the target position for
+        // (targeted) lights and cameras
+        std::vector<aiVectorKey>* l;
+        if ( Discreet3DS::CHUNK_TRACKCAMTGT == parent || Discreet3DS::CHUNK_TRACKLIGTGT == parent)  {
+            l = & mCurrentNode->aTargetPositionKeys;
+        }
+        else l = & mCurrentNode->aPositionKeys;
+
+        l->reserve(numFrames);
+        for (unsigned int i = 0; i < numFrames;++i) {
+            const unsigned int fidx = stream->GetI4();
+
+            // Setup a new position key
+            aiVectorKey v;
+            v.mTime = (double)fidx;
+
+            SkipTCBInfo();
+            v.mValue.x = stream->GetF4();
+            v.mValue.y = stream->GetF4();
+            v.mValue.z = stream->GetF4();
+
+            // check whether we'll need to sort the keys
+            if (!l->empty() && v.mTime <= l->back().mTime)
+                sortKeys = true;
+
+            // Add the new keyframe to the list
+            l->push_back(v);
+        }
+
+        // Sort all keys with ascending time values and remove duplicates?
+        if (sortKeys)   {
+            std::stable_sort(l->begin(),l->end());
+            l->erase ( std::unique (l->begin(),l->end(),&KeyUniqueCompare<aiVectorKey>), l->end() );
+        }}
+
+        break;
+
+        // ////////////////////////////////////////////////////////////////////
+        // CAMERA ROLL KEYFRAME
+    case Discreet3DS::CHUNK_TRACKROLL:
+        {
+        // roll keys are accepted for cameras only
+        if (parent != Discreet3DS::CHUNK_TRACKCAMERA)   {
+            ASSIMP_LOG_WARN("3DS: Ignoring roll track for non-camera object");
+            break;
+        }
+        bool sortKeys = false;
+        std::vector<aiFloatKey>* l = &mCurrentNode->aCameraRollKeys;
+
+        stream->IncPtr(10);
+        const unsigned int numFrames = stream->GetI4();
+        l->reserve(numFrames);
+        for (unsigned int i = 0; i < numFrames;++i) {
+            const unsigned int fidx = stream->GetI4();
+
+            // Setup a new position key
+            aiFloatKey v;
+            v.mTime = (double)fidx;
+
+            // This is just a single float
+            SkipTCBInfo();
+            v.mValue = stream->GetF4();
+
+            // Check whether we'll need to sort the keys
+            if (!l->empty() && v.mTime <= l->back().mTime)
+                sortKeys = true;
+
+            // Add the new keyframe to the list
+            l->push_back(v);
+        }
+
+        // Sort all keys with ascending time values and remove duplicates?
+        if (sortKeys)   {
+            std::stable_sort(l->begin(),l->end());
+            l->erase ( std::unique (l->begin(),l->end(),&KeyUniqueCompare<aiFloatKey>), l->end() );
+        }}
+        break;
+
+
+        // ////////////////////////////////////////////////////////////////////
+        // CAMERA FOV KEYFRAME
+    case Discreet3DS::CHUNK_TRACKFOV:
+        {
+            ASSIMP_LOG_ERROR("3DS: Skipping FOV animation track. "
+                "This is not supported");
+        }
+        break;
+
+
+        // ////////////////////////////////////////////////////////////////////
+        // ROTATION KEYFRAME
+    case Discreet3DS::CHUNK_TRACKROTATE:
+        {
+        stream->IncPtr(10);
+        const unsigned int numFrames = stream->GetI4();
+
+        bool sortKeys = false;
+        std::vector<aiQuatKey>* l = &mCurrentNode->aRotationKeys;
+        l->reserve(numFrames);
+
+        for (unsigned int i = 0; i < numFrames;++i) {
+            const unsigned int fidx = stream->GetI4();
+            SkipTCBInfo();
+
+            aiQuatKey v;
+            v.mTime = (double)fidx;
+
+            // The rotation keyframe is given as an axis-angle pair
+            const float rad = stream->GetF4();
+            aiVector3D axis;
+            axis.x = stream->GetF4();
+            axis.y = stream->GetF4();
+            axis.z = stream->GetF4();
+
+            if (!axis.x && !axis.y && !axis.z)
+                axis.y = 1.f;
+
+            // Construct a rotation quaternion from the axis-angle pair
+            v.mValue = aiQuaternion(axis,rad);
+
+            // Check whether we'll need to sort the keys
+            if (!l->empty() && v.mTime <= l->back().mTime)
+                sortKeys = true;
+
+            // add the new keyframe to the list
+            l->push_back(v);
+        }
+        // Sort all keys with ascending time values and remove duplicates?
+        if (sortKeys)   {
+            std::stable_sort(l->begin(),l->end());
+            l->erase ( std::unique (l->begin(),l->end(),&KeyUniqueCompare<aiQuatKey>), l->end() );
+        }}
+        break;
+
+        // ////////////////////////////////////////////////////////////////////
+        // SCALING KEYFRAME
+    case Discreet3DS::CHUNK_TRACKSCALE:
+        {
+        stream->IncPtr(10);
+        const unsigned int numFrames = stream->GetI2();
+        stream->IncPtr(2);
+
+        bool sortKeys = false;
+        std::vector<aiVectorKey>* l = &mCurrentNode->aScalingKeys;
+        l->reserve(numFrames);
+
+        for (unsigned int i = 0; i < numFrames;++i) {
+            const unsigned int fidx = stream->GetI4();
+            SkipTCBInfo();
+
+            // Setup a new key
+            aiVectorKey v;
+            v.mTime = (double)fidx;
+
+            // ... and read its value
+            v.mValue.x = stream->GetF4();
+            v.mValue.y = stream->GetF4();
+            v.mValue.z = stream->GetF4();
+
+            // check whether we'll need to sort the keys
+            if (!l->empty() && v.mTime <= l->back().mTime)
+                sortKeys = true;
+
+            // Remove zero-scalings on singular axes - they've been reported to be there erroneously in some strange files
+            if (!v.mValue.x) v.mValue.x = 1.f;
+            if (!v.mValue.y) v.mValue.y = 1.f;
+            if (!v.mValue.z) v.mValue.z = 1.f;
+
+            l->push_back(v);
+        }
+        // Sort all keys with ascending time values and remove duplicates?
+        if (sortKeys)   {
+            std::stable_sort(l->begin(),l->end());
+            l->erase ( std::unique (l->begin(),l->end(),&KeyUniqueCompare<aiVectorKey>), l->end() );
+        }}
+        break;
+    };
+
+    ASSIMP_3DS_END_CHUNK();
+}
+
+// ------------------------------------------------------------------------------------------------
+// Read a face chunk - it contains smoothing groups and material assignments
+void Discreet3DSImporter::ParseFaceChunk()
+{
+    ASSIMP_3DS_BEGIN_CHUNK();
+
+    // Get the mesh we're currently working on
+    D3DS::Mesh& mMesh = mScene->mMeshes.back();
+
+    // Get chunk type
+    switch (chunk.Flag)
+    {
+    case Discreet3DS::CHUNK_SMOOLIST:
+        {
+        // This is the list of smoothing groups - a bitfield for every face.
+        // Up to 32 smoothing groups assigned to a single face.
+        unsigned int num = chunkSize/4, m = 0;
+        if (num > mMesh.mFaces.size())  {
+            throw DeadlyImportError("3DS: More smoothing groups than faces");
+        }
+        for (std::vector<D3DS::Face>::iterator i =  mMesh.mFaces.begin(); m != num;++i, ++m)    {
+            // nth bit is set for nth smoothing group
+            (*i).iSmoothGroup = stream->GetI4();
+        }}
+        break;
+
+    case Discreet3DS::CHUNK_FACEMAT:
+        {
+        // at fist an asciiz with the material name
+        const char* sz = (const char*)stream->GetPtr();
+        while (stream->GetI1());
+
+        // find the index of the material
+        unsigned int idx = 0xcdcdcdcd, cnt = 0;
+        for (std::vector<D3DS::Material>::const_iterator i =  mScene->mMaterials.begin();i != mScene->mMaterials.end();++i,++cnt)   {
+            // use case independent comparisons. hopefully it will work.
+            if ((*i).mName.length() && !ASSIMP_stricmp(sz, (*i).mName.c_str())) {
+                idx = cnt;
+                break;
+            }
+        }
+        if (0xcdcdcdcd == idx)  {
+            ASSIMP_LOG_ERROR_F( "3DS: Unknown material: ", sz);
+        }
+
+        // Now continue and read all material indices
+        cnt = (uint16_t)stream->GetI2();
+        for (unsigned int i = 0; i < cnt;++i)   {
+            unsigned int fidx = (uint16_t)stream->GetI2();
+
+            // check range
+            if (fidx >= mMesh.mFaceMaterials.size())    {
+                ASSIMP_LOG_ERROR("3DS: Invalid face index in face material list");
+            }
+            else mMesh.mFaceMaterials[fidx] = idx;
+        }}
+        break;
+    };
+    ASSIMP_3DS_END_CHUNK();
+}
+
+// ------------------------------------------------------------------------------------------------
+// Read a mesh chunk. Here's the actual mesh data
+void Discreet3DSImporter::ParseMeshChunk()
+{
+    ASSIMP_3DS_BEGIN_CHUNK();
+
+    // Get the mesh we're currently working on
+    D3DS::Mesh& mMesh = mScene->mMeshes.back();
+
+    // get chunk type
+    switch (chunk.Flag)
+    {
+    case Discreet3DS::CHUNK_VERTLIST:
+        {
+        // This is the list of all vertices in the current mesh
+        int num = (int)(uint16_t)stream->GetI2();
+        mMesh.mPositions.reserve(num);
+        while (num-- > 0)   {
+            aiVector3D v;
+            v.x = stream->GetF4();
+            v.y = stream->GetF4();
+            v.z = stream->GetF4();
+            mMesh.mPositions.push_back(v);
+        }}
+        break;
+    case Discreet3DS::CHUNK_TRMATRIX:
+        {
+        // This is the RLEATIVE transformation matrix of the current mesh. Vertices are
+        // pretransformed by this matrix wonder.
+        mMesh.mMat.a1 = stream->GetF4();
+        mMesh.mMat.b1 = stream->GetF4();
+        mMesh.mMat.c1 = stream->GetF4();
+        mMesh.mMat.a2 = stream->GetF4();
+        mMesh.mMat.b2 = stream->GetF4();
+        mMesh.mMat.c2 = stream->GetF4();
+        mMesh.mMat.a3 = stream->GetF4();
+        mMesh.mMat.b3 = stream->GetF4();
+        mMesh.mMat.c3 = stream->GetF4();
+        mMesh.mMat.a4 = stream->GetF4();
+        mMesh.mMat.b4 = stream->GetF4();
+        mMesh.mMat.c4 = stream->GetF4();
+        }
+        break;
+
+    case Discreet3DS::CHUNK_MAPLIST:
+        {
+        // This is the list of all UV coords in the current mesh
+        int num = (int)(uint16_t)stream->GetI2();
+        mMesh.mTexCoords.reserve(num);
+        while (num-- > 0)   {
+            aiVector3D v;
+            v.x = stream->GetF4();
+            v.y = stream->GetF4();
+            mMesh.mTexCoords.push_back(v);
+        }}
+        break;
+
+    case Discreet3DS::CHUNK_FACELIST:
+        {
+        // This is the list of all faces in the current mesh
+        int num = (int)(uint16_t)stream->GetI2();
+        mMesh.mFaces.reserve(num);
+        while (num-- > 0)   {
+            // 3DS faces are ALWAYS triangles
+            mMesh.mFaces.push_back(D3DS::Face());
+            D3DS::Face& sFace = mMesh.mFaces.back();
+
+            sFace.mIndices[0] = (uint16_t)stream->GetI2();
+            sFace.mIndices[1] = (uint16_t)stream->GetI2();
+            sFace.mIndices[2] = (uint16_t)stream->GetI2();
+
+            stream->IncPtr(2); // skip edge visibility flag
+        }
+
+        // Resize the material array (0xcdcdcdcd marks the default material; so if a face is
+        // not referenced by a material, $$DEFAULT will be assigned to it)
+        mMesh.mFaceMaterials.resize(mMesh.mFaces.size(),0xcdcdcdcd);
+
+        // Larger 3DS files could have multiple FACE chunks here
+        chunkSize = stream->GetRemainingSizeToLimit();
+        if ( chunkSize > (int) sizeof(Discreet3DS::Chunk ) )
+            ParseFaceChunk();
+        }
+        break;
+    };
+    ASSIMP_3DS_END_CHUNK();
+}
+
+// ------------------------------------------------------------------------------------------------
+// Read a 3DS material chunk
+void Discreet3DSImporter::ParseMaterialChunk()
+{
+    ASSIMP_3DS_BEGIN_CHUNK();
+    switch (chunk.Flag)
+    {
+    case Discreet3DS::CHUNK_MAT_MATNAME:
+
+        {
+        // The material name string is already zero-terminated, but we need to be sure ...
+        const char* sz = (const char*)stream->GetPtr();
+        unsigned int cnt = 0;
+        while (stream->GetI1())
+            ++cnt;
+
+        if (!cnt)   {
+            // This may not be, we use the default name instead
+            ASSIMP_LOG_ERROR("3DS: Empty material name");
+        }
+        else mScene->mMaterials.back().mName = std::string(sz,cnt);
+        }
+        break;
+
+    case Discreet3DS::CHUNK_MAT_DIFFUSE:
+        {
+        // This is the diffuse material color
+        aiColor3D* pc = &mScene->mMaterials.back().mDiffuse;
+        ParseColorChunk(pc);
+        if (is_qnan(pc->r)) {
+            // color chunk is invalid. Simply ignore it
+            ASSIMP_LOG_ERROR("3DS: Unable to read DIFFUSE chunk");
+            pc->r = pc->g = pc->b = 1.0f;
+        }}
+        break;
+
+    case Discreet3DS::CHUNK_MAT_SPECULAR:
+        {
+        // This is the specular material color
+        aiColor3D* pc = &mScene->mMaterials.back().mSpecular;
+        ParseColorChunk(pc);
+        if (is_qnan(pc->r)) {
+            // color chunk is invalid. Simply ignore it
+            ASSIMP_LOG_ERROR("3DS: Unable to read SPECULAR chunk");
+            pc->r = pc->g = pc->b = 1.0f;
+        }}
+        break;
+
+    case Discreet3DS::CHUNK_MAT_AMBIENT:
+        {
+        // This is the ambient material color
+        aiColor3D* pc = &mScene->mMaterials.back().mAmbient;
+        ParseColorChunk(pc);
+        if (is_qnan(pc->r)) {
+            // color chunk is invalid. Simply ignore it
+            ASSIMP_LOG_ERROR("3DS: Unable to read AMBIENT chunk");
+            pc->r = pc->g = pc->b = 0.0f;
+        }}
+        break;
+
+    case Discreet3DS::CHUNK_MAT_SELF_ILLUM:
+        {
+        // This is the emissive material color
+        aiColor3D* pc = &mScene->mMaterials.back().mEmissive;
+        ParseColorChunk(pc);
+        if (is_qnan(pc->r)) {
+            // color chunk is invalid. Simply ignore it
+            ASSIMP_LOG_ERROR("3DS: Unable to read EMISSIVE chunk");
+            pc->r = pc->g = pc->b = 0.0f;
+        }}
+        break;
+
+    case Discreet3DS::CHUNK_MAT_TRANSPARENCY:
+        {
+            // This is the material's transparency
+            ai_real* pcf = &mScene->mMaterials.back().mTransparency;
+            *pcf = ParsePercentageChunk();
+
+            // NOTE: transparency, not opacity
+            if (is_qnan(*pcf))
+                *pcf = ai_real( 1.0 );
+            else 
+                *pcf = ai_real( 1.0 ) - *pcf * (ai_real)0xFFFF / ai_real( 100.0 );
+        }
+        break;
+
+    case Discreet3DS::CHUNK_MAT_SHADING:
+        // This is the material shading mode
+        mScene->mMaterials.back().mShading = (D3DS::Discreet3DS::shadetype3ds)stream->GetI2();
+        break;
+
+    case Discreet3DS::CHUNK_MAT_TWO_SIDE:
+        // This is the two-sided flag
+        mScene->mMaterials.back().mTwoSided = true;
+        break;
+
+    case Discreet3DS::CHUNK_MAT_SHININESS:
+        { // This is the shininess of the material
+        ai_real* pcf = &mScene->mMaterials.back().mSpecularExponent;
+        *pcf = ParsePercentageChunk();
+        if (is_qnan(*pcf))
+            *pcf = 0.0;
+        else *pcf *= (ai_real)0xFFFF;
+        }
+        break;
+
+    case Discreet3DS::CHUNK_MAT_SHININESS_PERCENT:
+        { // This is the shininess strength of the material
+            ai_real* pcf = &mScene->mMaterials.back().mShininessStrength;
+            *pcf = ParsePercentageChunk();
+            if (is_qnan(*pcf))
+                *pcf = ai_real( 0.0 );
+            else 
+                *pcf *= (ai_real)0xffff / ai_real( 100.0 );
+        }
+        break;
+
+    case Discreet3DS::CHUNK_MAT_SELF_ILPCT:
+        { // This is the self illumination strength of the material
+            ai_real f = ParsePercentageChunk();
+            if (is_qnan(f))
+                f = ai_real( 0.0 );
+            else 
+                f *= (ai_real)0xFFFF / ai_real( 100.0 );
+            mScene->mMaterials.back().mEmissive = aiColor3D(f,f,f);
+        }
+        break;
+
+    // Parse texture chunks
+    case Discreet3DS::CHUNK_MAT_TEXTURE:
+        // Diffuse texture
+        ParseTextureChunk(&mScene->mMaterials.back().sTexDiffuse);
+        break;
+    case Discreet3DS::CHUNK_MAT_BUMPMAP:
+        // Height map
+        ParseTextureChunk(&mScene->mMaterials.back().sTexBump);
+        break;
+    case Discreet3DS::CHUNK_MAT_OPACMAP:
+        // Opacity texture
+        ParseTextureChunk(&mScene->mMaterials.back().sTexOpacity);
+        break;
+    case Discreet3DS::CHUNK_MAT_MAT_SHINMAP:
+        // Shininess map
+        ParseTextureChunk(&mScene->mMaterials.back().sTexShininess);
+        break;
+    case Discreet3DS::CHUNK_MAT_SPECMAP:
+        // Specular map
+        ParseTextureChunk(&mScene->mMaterials.back().sTexSpecular);
+        break;
+    case Discreet3DS::CHUNK_MAT_SELFIMAP:
+        // Self-illumination (emissive) map
+        ParseTextureChunk(&mScene->mMaterials.back().sTexEmissive);
+        break;
+    case Discreet3DS::CHUNK_MAT_REFLMAP:
+        // Reflection map
+        ParseTextureChunk(&mScene->mMaterials.back().sTexReflective);
+        break;
+    };
+    ASSIMP_3DS_END_CHUNK();
+}
+
+// ------------------------------------------------------------------------------------------------
+void Discreet3DSImporter::ParseTextureChunk(D3DS::Texture* pcOut)
+{
+    ASSIMP_3DS_BEGIN_CHUNK();
+
+    // get chunk type
+    switch (chunk.Flag)
+    {
+    case Discreet3DS::CHUNK_MAPFILE:
+        {
+        // The material name string is already zero-terminated, but we need to be sure ...
+        const char* sz = (const char*)stream->GetPtr();
+        unsigned int cnt = 0;
+        while (stream->GetI1())
+            ++cnt;
+        pcOut->mMapName = std::string(sz,cnt);
+        }
+        break;
+
+
+    case Discreet3DS::CHUNK_PERCENTD:
+        // Manually parse the blend factor
+        pcOut->mTextureBlend = ai_real( stream->GetF8() );
+        break;
+
+    case Discreet3DS::CHUNK_PERCENTF:
+        // Manually parse the blend factor
+        pcOut->mTextureBlend = stream->GetF4();
+        break;
+
+    case Discreet3DS::CHUNK_PERCENTW:
+        // Manually parse the blend factor
+        pcOut->mTextureBlend = (ai_real)((uint16_t)stream->GetI2()) / ai_real( 100.0 );
+        break;
+
+    case Discreet3DS::CHUNK_MAT_MAP_USCALE:
+        // Texture coordinate scaling in the U direction
+        pcOut->mScaleU = stream->GetF4();
+        if (0.0f == pcOut->mScaleU)
+        {
+            ASSIMP_LOG_WARN("Texture coordinate scaling in the x direction is zero. Assuming 1.");
+            pcOut->mScaleU = 1.0f;
+        }
+        break;
+    case Discreet3DS::CHUNK_MAT_MAP_VSCALE:
+        // Texture coordinate scaling in the V direction
+        pcOut->mScaleV = stream->GetF4();
+        if (0.0f == pcOut->mScaleV)
+        {
+            ASSIMP_LOG_WARN("Texture coordinate scaling in the y direction is zero. Assuming 1.");
+            pcOut->mScaleV = 1.0f;
+        }
+        break;
+
+    case Discreet3DS::CHUNK_MAT_MAP_UOFFSET:
+        // Texture coordinate offset in the U direction
+        pcOut->mOffsetU = -stream->GetF4();
+        break;
+
+    case Discreet3DS::CHUNK_MAT_MAP_VOFFSET:
+        // Texture coordinate offset in the V direction
+        pcOut->mOffsetV = stream->GetF4();
+        break;
+
+    case Discreet3DS::CHUNK_MAT_MAP_ANG:
+        // Texture coordinate rotation, CCW in DEGREES
+        pcOut->mRotation = -AI_DEG_TO_RAD( stream->GetF4() );
+        break;
+
+    case Discreet3DS::CHUNK_MAT_MAP_TILING:
+        {
+        const uint16_t iFlags = stream->GetI2();
+
+        // Get the mapping mode (for both axes)
+        if (iFlags & 0x2u)
+            pcOut->mMapMode = aiTextureMapMode_Mirror;
+
+        else if (iFlags & 0x10u)
+            pcOut->mMapMode = aiTextureMapMode_Decal;
+
+        // wrapping in all remaining cases
+        else pcOut->mMapMode = aiTextureMapMode_Wrap;
+        }
+        break;
+    };
+
+    ASSIMP_3DS_END_CHUNK();
+}
+
+// ------------------------------------------------------------------------------------------------
+// Read a percentage chunk
+ai_real Discreet3DSImporter::ParsePercentageChunk() {
+    Discreet3DS::Chunk chunk;
+    ReadChunk(&chunk);
+
+    if (Discreet3DS::CHUNK_PERCENTF == chunk.Flag) {
+        return stream->GetF4() * ai_real(100) / ai_real(0xFFFF);
+    } else if (Discreet3DS::CHUNK_PERCENTW == chunk.Flag) {
+        return (ai_real)((uint16_t)stream->GetI2()) / (ai_real)0xFFFF;
+    }
+
+    return get_qnan();
+}
+
+// ------------------------------------------------------------------------------------------------
+// Read a color chunk. If a percentage chunk is found instead it is read as a grayscale color
+void Discreet3DSImporter::ParseColorChunk( aiColor3D* out, bool acceptPercent )
+{
+    ai_assert(out != NULL);
+
+    // error return value
+    const ai_real qnan = get_qnan();
+    static const aiColor3D clrError = aiColor3D(qnan,qnan,qnan);
+
+    Discreet3DS::Chunk chunk;
+    ReadChunk(&chunk);
+    const unsigned int diff = chunk.Size - sizeof(Discreet3DS::Chunk);
+
+    bool bGamma = false;
+
+    // Get the type of the chunk
+    switch(chunk.Flag)
+    {
+    case Discreet3DS::CHUNK_LINRGBF:
+        bGamma = true;
+
+    case Discreet3DS::CHUNK_RGBF:
+        if (sizeof(float) * 3 > diff)   {
+            *out = clrError;
+            return;
+        }
+        out->r = stream->GetF4();
+        out->g = stream->GetF4();
+        out->b = stream->GetF4();
+        break;
+
+    case Discreet3DS::CHUNK_LINRGBB:
+        bGamma = true;
+    case Discreet3DS::CHUNK_RGBB:
+        {
+            if ( sizeof( char ) * 3 > diff ) {
+                *out = clrError;
+                return;
+            }
+            const ai_real invVal = ai_real( 1.0 ) / ai_real( 255.0 );
+            out->r = ( ai_real ) ( uint8_t ) stream->GetI1() * invVal;
+            out->g = ( ai_real ) ( uint8_t ) stream->GetI1() * invVal;
+            out->b = ( ai_real ) ( uint8_t ) stream->GetI1() * invVal;
+        }
+        break;
+
+    // Percentage chunks are accepted, too.
+    case Discreet3DS::CHUNK_PERCENTF:
+        if (acceptPercent && 4 <= diff) {
+            out->g = out->b = out->r = stream->GetF4();
+            break;
+        }
+        *out = clrError;
+        return;
+
+    case Discreet3DS::CHUNK_PERCENTW:
+        if (acceptPercent && 1 <= diff) {
+            out->g = out->b = out->r = (ai_real)(uint8_t)stream->GetI1() / ai_real( 255.0 );
+            break;
+        }
+        *out = clrError;
+        return;
+
+    default:
+        stream->IncPtr(diff);
+        // Skip unknown chunks, hope this won't cause any problems.
+        return ParseColorChunk(out,acceptPercent);
+    };
+    (void)bGamma;
+}
+
+#endif // !! ASSIMP_BUILD_NO_3DS_IMPORTER

+ 284 - 0
Engine/lib/assimp/code/3DSLoader.h

@@ -0,0 +1,284 @@
+
+/*
+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  3DSLoader.h
+ *  @brief 3DS File format loader
+ */
+#ifndef AI_3DSIMPORTER_H_INC
+#define AI_3DSIMPORTER_H_INC
+
+#include <assimp/BaseImporter.h>
+#include <assimp/types.h>
+
+#ifndef ASSIMP_BUILD_NO_3DS_IMPORTER
+
+#include "3DSHelper.h"
+#include <assimp/StreamReader.h>
+
+struct aiNode;
+
+namespace Assimp    {
+
+
+using namespace D3DS;
+
+// ---------------------------------------------------------------------------------
+/** Importer class for 3D Studio r3 and r4 3DS files
+ */
+class Discreet3DSImporter : public BaseImporter
+{
+public:
+
+    Discreet3DSImporter();
+    ~Discreet3DSImporter();
+
+public:
+
+    // -------------------------------------------------------------------
+    /** Returns whether the class can handle the format of the given file.
+     * See BaseImporter::CanRead() for details.
+     */
+    bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
+        bool checkSig) const;
+
+    // -------------------------------------------------------------------
+    /** Called prior to ReadFile().
+     * The function is a request to the importer to update its configuration
+     * basing on the Importer's configuration property list.
+     */
+    void SetupProperties(const Importer* pImp);
+
+protected:
+
+    // -------------------------------------------------------------------
+    /** Return importer meta information.
+     * See #BaseImporter::GetInfo for the details
+     */
+    const aiImporterDesc* GetInfo () const;
+
+    // -------------------------------------------------------------------
+    /** Imports the given file into the given scene structure.
+     * See BaseImporter::InternReadFile() for details
+     */
+    void InternReadFile( const std::string& pFile, aiScene* pScene,
+        IOSystem* pIOHandler);
+
+    // -------------------------------------------------------------------
+    /** Converts a temporary material to the outer representation
+     */
+    void ConvertMaterial(D3DS::Material& p_cMat,
+        aiMaterial& p_pcOut);
+
+    // -------------------------------------------------------------------
+    /** Read a chunk
+     *
+     *  @param pcOut Receives the current chunk
+     */
+    void ReadChunk(Discreet3DS::Chunk* pcOut);
+
+    // -------------------------------------------------------------------
+    /** Parse a percentage chunk. mCurrent will point to the next
+    * chunk behind afterwards. If no percentage chunk is found
+    * QNAN is returned.
+    */
+    ai_real ParsePercentageChunk();
+
+    // -------------------------------------------------------------------
+    /** Parse a color chunk. mCurrent will point to the next
+    * chunk behind afterwards. If no color chunk is found
+    * QNAN is returned in all members.
+    */
+    void ParseColorChunk(aiColor3D* p_pcOut,
+        bool p_bAcceptPercent = true);
+
+
+    // -------------------------------------------------------------------
+    /** Skip a chunk in the file
+    */
+    void SkipChunk();
+
+    // -------------------------------------------------------------------
+    /** Generate the nodegraph
+    */
+    void GenerateNodeGraph(aiScene* pcOut);
+
+    // -------------------------------------------------------------------
+    /** Parse a main top-level chunk in the file
+    */
+    void ParseMainChunk();
+
+    // -------------------------------------------------------------------
+    /** Parse a top-level chunk in the file
+    */
+    void ParseChunk(const char* name, unsigned int num);
+
+    // -------------------------------------------------------------------
+    /** Parse a top-level editor chunk in the file
+    */
+    void ParseEditorChunk();
+
+    // -------------------------------------------------------------------
+    /** Parse a top-level object chunk in the file
+    */
+    void ParseObjectChunk();
+
+    // -------------------------------------------------------------------
+    /** Parse a material chunk in the file
+    */
+    void ParseMaterialChunk();
+
+    // -------------------------------------------------------------------
+    /** Parse a mesh chunk in the file
+    */
+    void ParseMeshChunk();
+
+    // -------------------------------------------------------------------
+    /** Parse a light chunk in the file
+    */
+    void ParseLightChunk();
+
+    // -------------------------------------------------------------------
+    /** Parse a camera chunk in the file
+    */
+    void ParseCameraChunk();
+
+    // -------------------------------------------------------------------
+    /** Parse a face list chunk in the file
+    */
+    void ParseFaceChunk();
+
+    // -------------------------------------------------------------------
+    /** Parse a keyframe chunk in the file
+    */
+    void ParseKeyframeChunk();
+
+    // -------------------------------------------------------------------
+    /** Parse a hierarchy chunk in the file
+    */
+    void ParseHierarchyChunk(uint16_t parent);
+
+    // -------------------------------------------------------------------
+    /** Parse a texture chunk in the file
+    */
+    void ParseTextureChunk(D3DS::Texture* pcOut);
+
+    // -------------------------------------------------------------------
+    /** Convert the meshes in the file
+    */
+    void ConvertMeshes(aiScene* pcOut);
+
+    // -------------------------------------------------------------------
+    /** Replace the default material in the scene
+    */
+    void ReplaceDefaultMaterial();
+
+    // -------------------------------------------------------------------
+    /** Convert the whole scene
+    */
+    void ConvertScene(aiScene* pcOut);
+
+    // -------------------------------------------------------------------
+    /** generate unique vertices for a mesh
+    */
+    void MakeUnique(D3DS::Mesh& sMesh);
+
+    // -------------------------------------------------------------------
+    /** Add a node to the node graph
+    */
+    void AddNodeToGraph(aiScene* pcSOut,aiNode* pcOut,D3DS::Node* pcIn,
+        aiMatrix4x4& absTrafo);
+
+    // -------------------------------------------------------------------
+    /** Search for a node in the graph.
+    * Called recursively
+    */
+    void InverseNodeSearch(D3DS::Node* pcNode,D3DS::Node* pcCurrent);
+
+    // -------------------------------------------------------------------
+    /** Apply the master scaling factor to the mesh
+    */
+    void ApplyMasterScale(aiScene* pScene);
+
+    // -------------------------------------------------------------------
+    /** Clamp all indices in the file to a valid range
+    */
+    void CheckIndices(D3DS::Mesh& sMesh);
+
+    // -------------------------------------------------------------------
+    /** Skip the TCB info in a track key
+    */
+    void SkipTCBInfo();
+
+protected:
+
+    /** Stream to read from */
+    StreamReaderLE* stream;
+
+    /** Last touched node index */
+    short mLastNodeIndex;
+
+    /** Current node, root node */
+    D3DS::Node* mCurrentNode, *mRootNode;
+
+    /** Scene under construction */
+    D3DS::Scene* mScene;
+
+    /** Ambient base color of the scene */
+    aiColor3D mClrAmbient;
+
+    /** Master scaling factor of the scene */
+    ai_real mMasterScale;
+
+    /** Path to the background image of the scene */
+    std::string mBackgroundImage;
+    bool bHasBG;
+
+    /** true if PRJ file */
+    bool bIsPrj;
+};
+
+} // end of namespace Assimp
+
+#endif // !! ASSIMP_BUILD_NO_3DS_IMPORTER
+
+#endif // AI_3DSIMPORTER_H_INC

+ 104 - 0
Engine/lib/assimp/code/3MFXmlTags.h

@@ -0,0 +1,104 @@
+/*
+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
+
+namespace Assimp {
+namespace D3MF {
+
+namespace XmlTag {
+    // Meta-data
+    static const std::string meta = "metadata";
+    static const std::string meta_name = "name";
+
+    // Model-data specific tags
+    static const std::string model = "model";
+    static const std::string model_unit = "unit";
+    static const std::string metadata = "metadata";
+    static const std::string resources = "resources";
+    static const std::string object = "object";
+    static const std::string mesh = "mesh";
+    static const std::string vertices = "vertices";
+    static const std::string vertex = "vertex";
+    static const std::string triangles = "triangles";
+    static const std::string triangle = "triangle";
+    static const std::string x = "x";
+    static const std::string y = "y";
+    static const std::string z = "z";
+    static const std::string v1 = "v1";
+    static const std::string v2 = "v2";
+    static const std::string v3 = "v3";
+    static const std::string id = "id";
+    static const std::string pid = "pid";
+    static const std::string p1 = "p1";
+    static const std::string name = "name";
+    static const std::string type = "type";
+    static const std::string build = "build";
+    static const std::string item = "item";
+    static const std::string objectid = "objectid";
+    static const std::string transform = "transform";
+
+    // Material definitions
+    static const std::string basematerials = "basematerials";
+    static const std::string basematerials_id = "id";
+    static const std::string basematerials_base = "base";
+    static const std::string basematerials_name = "name";
+    static const std::string basematerials_displaycolor = "displaycolor";
+
+    // Meta info tags
+    static const std::string CONTENT_TYPES_ARCHIVE = "[Content_Types].xml";
+    static const std::string ROOT_RELATIONSHIPS_ARCHIVE = "_rels/.rels";
+    static const std::string SCHEMA_CONTENTTYPES = "http://schemas.openxmlformats.org/package/2006/content-types";
+    static const std::string SCHEMA_RELATIONSHIPS = "http://schemas.openxmlformats.org/package/2006/relationships";
+    static const std::string RELS_RELATIONSHIP_CONTAINER = "Relationships";
+    static const std::string RELS_RELATIONSHIP_NODE = "Relationship";
+    static const std::string RELS_ATTRIB_TARGET = "Target";
+    static const std::string RELS_ATTRIB_TYPE = "Type";
+    static const std::string RELS_ATTRIB_ID = "Id";
+    static const std::string PACKAGE_START_PART_RELATIONSHIP_TYPE = "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel";
+    static const std::string PACKAGE_PRINT_TICKET_RELATIONSHIP_TYPE = "http://schemas.microsoft.com/3dmanufacturing/2013/01/printticket";
+    static const std::string PACKAGE_TEXTURE_RELATIONSHIP_TYPE = "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dtexture";
+    static const std::string PACKAGE_CORE_PROPERTIES_RELATIONSHIP_TYPE = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties";
+    static const std::string PACKAGE_THUMBNAIL_RELATIONSHIP_TYPE = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail";
+}
+
+} // Namespace D3MF
+} // Namespace Assimp

+ 910 - 0
Engine/lib/assimp/code/ACLoader.cpp

@@ -0,0 +1,910 @@
+
+/*
+---------------------------------------------------------------------------
+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 AC3D importer class */
+
+
+
+#ifndef ASSIMP_BUILD_NO_AC_IMPORTER
+
+// internal headers
+#include "ACLoader.h"
+#include <assimp/ParsingUtils.h>
+#include <assimp/fast_atof.h>
+#include <assimp/Subdivision.h>
+#include "Importer.h"
+#include <assimp/BaseImporter.h>
+#include <assimp/Importer.hpp>
+#include <assimp/light.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/material.h>
+#include <assimp/scene.h>
+#include <assimp/config.h>
+#include <assimp/IOSystem.hpp>
+#include <assimp/importerdesc.h>
+#include <memory>
+
+using namespace Assimp;
+
+static const aiImporterDesc desc = {
+    "AC3D Importer",
+    "",
+    "",
+    "",
+    aiImporterFlags_SupportTextFlavour,
+    0,
+    0,
+    0,
+    0,
+    "ac acc ac3d"
+};
+
+// ------------------------------------------------------------------------------------------------
+// skip to the next token
+#define AI_AC_SKIP_TO_NEXT_TOKEN() \
+    if (!SkipSpaces(&buffer)) \
+    { \
+        ASSIMP_LOG_ERROR("AC3D: Unexpected EOF/EOL"); \
+        continue; \
+    }
+
+// ------------------------------------------------------------------------------------------------
+// read a string (may be enclosed in double quotation marks). buffer must point to "
+#define AI_AC_GET_STRING(out) \
+    if (*buffer == '\0') { \
+        throw DeadlyImportError("AC3D: Unexpected EOF in string"); \
+    } \
+    ++buffer; \
+    const char* sz = buffer; \
+    while ('\"' != *buffer) \
+    { \
+        if (IsLineEnd( *buffer )) \
+        { \
+            ASSIMP_LOG_ERROR("AC3D: Unexpected EOF/EOL in string"); \
+            out = "ERROR"; \
+            break; \
+        } \
+        ++buffer; \
+    } \
+    if (IsLineEnd( *buffer ))continue; \
+    out = std::string(sz,(unsigned int)(buffer-sz)); \
+    ++buffer;
+
+
+// ------------------------------------------------------------------------------------------------
+// read 1 to n floats prefixed with an optional predefined identifier
+#define AI_AC_CHECKED_LOAD_FLOAT_ARRAY(name,name_length,num,out) \
+    AI_AC_SKIP_TO_NEXT_TOKEN(); \
+    if (name_length) \
+    { \
+        if (strncmp(buffer,name,name_length) || !IsSpace(buffer[name_length])) \
+        { \
+            ASSIMP_LOG_ERROR("AC3D: Unexpexted token. " name " was expected."); \
+            continue; \
+        } \
+        buffer += name_length+1; \
+    } \
+    for (unsigned int i = 0; i < num;++i) \
+    { \
+        AI_AC_SKIP_TO_NEXT_TOKEN(); \
+        buffer = fast_atoreal_move<float>(buffer,((float*)out)[i]); \
+    }
+
+
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by Importer
+AC3DImporter::AC3DImporter()
+    : buffer(),
+    configSplitBFCull(),
+    configEvalSubdivision(),
+    mNumMeshes(),
+    mLights(),
+    lights(),
+    groups(),
+    polys(),
+    worlds()
+{
+    // nothing to be done here
+}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+AC3DImporter::~AC3DImporter()
+{
+    // nothing to be done here
+}
+
+// ------------------------------------------------------------------------------------------------
+// Returns whether the class can handle the format of the given file.
+bool AC3DImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
+{
+    std::string extension = GetExtension(pFile);
+
+    // fixme: are acc and ac3d *really* used? Some sources say they are
+    if(extension == "ac" || extension == "ac3d" || extension == "acc") {
+        return true;
+    }
+    if (!extension.length() || checkSig) {
+        uint32_t token = AI_MAKE_MAGIC("AC3D");
+        return CheckMagicToken(pIOHandler,pFile,&token,1,0);
+    }
+    return false;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Loader meta information
+const aiImporterDesc* AC3DImporter::GetInfo () const
+{
+    return &desc;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Get a pointer to the next line from the file
+bool AC3DImporter::GetNextLine( )
+{
+    SkipLine(&buffer);
+    return SkipSpaces(&buffer);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Parse an object section in an AC file
+void AC3DImporter::LoadObjectSection(std::vector<Object>& objects)
+{
+    if (!TokenMatch(buffer,"OBJECT",6))
+        return;
+
+    SkipSpaces(&buffer);
+
+    ++mNumMeshes;
+
+    objects.push_back(Object());
+    Object& obj = objects.back();
+
+    aiLight* light = NULL;
+    if (!ASSIMP_strincmp(buffer,"light",5))
+    {
+        // This is a light source. Add it to the list
+        mLights->push_back(light = new aiLight());
+
+        // Return a point light with no attenuation
+        light->mType = aiLightSource_POINT;
+        light->mColorDiffuse = light->mColorSpecular = aiColor3D(1.f,1.f,1.f);
+        light->mAttenuationConstant = 1.f;
+
+        // Generate a default name for both the light source and the node
+        // FIXME - what's the right way to print a size_t? Is 'zu' universally available? stick with the safe version.
+        light->mName.length = ::ai_snprintf(light->mName.data, MAXLEN, "ACLight_%i",static_cast<unsigned int>(mLights->size())-1);
+        obj.name = std::string( light->mName.data );
+
+        ASSIMP_LOG_DEBUG("AC3D: Light source encountered");
+        obj.type = Object::Light;
+    }
+    else if (!ASSIMP_strincmp(buffer,"group",5))
+    {
+        obj.type = Object::Group;
+    }
+    else if (!ASSIMP_strincmp(buffer,"world",5))
+    {
+        obj.type = Object::World;
+    }
+    else obj.type = Object::Poly;
+    while (GetNextLine())
+    {
+        if (TokenMatch(buffer,"kids",4))
+        {
+            SkipSpaces(&buffer);
+            unsigned int num = strtoul10(buffer,&buffer);
+            GetNextLine();
+            if (num)
+            {
+                // load the children of this object recursively
+                obj.children.reserve(num);
+                for (unsigned int i = 0; i < num; ++i)
+                    LoadObjectSection(obj.children);
+            }
+            return;
+        }
+        else if (TokenMatch(buffer,"name",4))
+        {
+            SkipSpaces(&buffer);
+            AI_AC_GET_STRING(obj.name);
+
+            // If this is a light source, we'll also need to store
+            // the name of the node in it.
+            if (light)
+            {
+                light->mName.Set(obj.name);
+            }
+        }
+        else if (TokenMatch(buffer,"texture",7))
+        {
+            SkipSpaces(&buffer);
+            AI_AC_GET_STRING(obj.texture);
+        }
+        else if (TokenMatch(buffer,"texrep",6))
+        {
+            SkipSpaces(&buffer);
+            AI_AC_CHECKED_LOAD_FLOAT_ARRAY("",0,2,&obj.texRepeat);
+            if (!obj.texRepeat.x || !obj.texRepeat.y)
+                obj.texRepeat = aiVector2D (1.f,1.f);
+        }
+        else if (TokenMatch(buffer,"texoff",6))
+        {
+            SkipSpaces(&buffer);
+            AI_AC_CHECKED_LOAD_FLOAT_ARRAY("",0,2,&obj.texOffset);
+        }
+        else if (TokenMatch(buffer,"rot",3))
+        {
+            SkipSpaces(&buffer);
+            AI_AC_CHECKED_LOAD_FLOAT_ARRAY("",0,9,&obj.rotation);
+        }
+        else if (TokenMatch(buffer,"loc",3))
+        {
+            SkipSpaces(&buffer);
+            AI_AC_CHECKED_LOAD_FLOAT_ARRAY("",0,3,&obj.translation);
+        }
+        else if (TokenMatch(buffer,"subdiv",6))
+        {
+            SkipSpaces(&buffer);
+            obj.subDiv = strtoul10(buffer,&buffer);
+        }
+        else if (TokenMatch(buffer,"crease",6))
+        {
+            SkipSpaces(&buffer);
+            obj.crease = fast_atof(buffer);
+        }
+        else if (TokenMatch(buffer,"numvert",7))
+        {
+            SkipSpaces(&buffer);
+
+            unsigned int t = strtoul10(buffer,&buffer);
+            if (t >= AI_MAX_ALLOC(aiVector3D)) {
+                throw DeadlyImportError("AC3D: Too many vertices, would run out of memory");
+            }
+            obj.vertices.reserve(t);
+            for (unsigned int i = 0; i < t;++i)
+            {
+                if (!GetNextLine())
+                {
+                    ASSIMP_LOG_ERROR("AC3D: Unexpected EOF: not all vertices have been parsed yet");
+                    break;
+                }
+                else if (!IsNumeric(*buffer))
+                {
+                    ASSIMP_LOG_ERROR("AC3D: Unexpected token: not all vertices have been parsed yet");
+                    --buffer; // make sure the line is processed a second time
+                    break;
+                }
+                obj.vertices.push_back(aiVector3D());
+                aiVector3D& v = obj.vertices.back();
+                AI_AC_CHECKED_LOAD_FLOAT_ARRAY("",0,3,&v.x);
+            }
+        }
+        else if (TokenMatch(buffer,"numsurf",7))
+        {
+            SkipSpaces(&buffer);
+
+            bool Q3DWorkAround = false;
+
+            const unsigned int t = strtoul10(buffer,&buffer);
+            obj.surfaces.reserve(t);
+            for (unsigned int i = 0; i < t;++i)
+            {
+                GetNextLine();
+                if (!TokenMatch(buffer,"SURF",4))
+                {
+                    // FIX: this can occur for some files - Quick 3D for
+                    // example writes no surf chunks
+                    if (!Q3DWorkAround)
+                    {
+                        ASSIMP_LOG_WARN("AC3D: SURF token was expected");
+                        ASSIMP_LOG_DEBUG("Continuing with Quick3D Workaround enabled");
+                    }
+                    --buffer; // make sure the line is processed a second time
+                    // break; --- see fix notes above
+
+                    Q3DWorkAround = true;
+                }
+                SkipSpaces(&buffer);
+                obj.surfaces.push_back(Surface());
+                Surface& surf = obj.surfaces.back();
+                surf.flags = strtoul_cppstyle(buffer);
+
+                while (1)
+                {
+                    if(!GetNextLine())
+                    {
+                        throw DeadlyImportError("AC3D: Unexpected EOF: surface is incomplete");
+                    }
+                    if (TokenMatch(buffer,"mat",3))
+                    {
+                        SkipSpaces(&buffer);
+                        surf.mat = strtoul10(buffer);
+                    }
+                    else if (TokenMatch(buffer,"refs",4))
+                    {
+                        // --- see fix notes above
+                        if (Q3DWorkAround)
+                        {
+                            if (!surf.entries.empty())
+                            {
+                                buffer -= 6;
+                                break;
+                            }
+                        }
+
+                        SkipSpaces(&buffer);
+                        const unsigned int m = strtoul10(buffer);
+                        surf.entries.reserve(m);
+
+                        obj.numRefs += m;
+
+                        for (unsigned int k = 0; k < m; ++k)
+                        {
+                            if(!GetNextLine())
+                            {
+                                ASSIMP_LOG_ERROR("AC3D: Unexpected EOF: surface references are incomplete");
+                                break;
+                            }
+                            surf.entries.push_back(Surface::SurfaceEntry());
+                            Surface::SurfaceEntry& entry = surf.entries.back();
+
+                            entry.first = strtoul10(buffer,&buffer);
+                            SkipSpaces(&buffer);
+                            AI_AC_CHECKED_LOAD_FLOAT_ARRAY("",0,2,&entry.second);
+                        }
+                    }
+                    else
+                    {
+
+                        --buffer; // make sure the line is processed a second time
+                        break;
+                    }
+                }
+            }
+        }
+    }
+    ASSIMP_LOG_ERROR("AC3D: Unexpected EOF: \'kids\' line was expected");
+}
+
+// ------------------------------------------------------------------------------------------------
+// Convert a material from AC3DImporter::Material to aiMaterial
+void AC3DImporter::ConvertMaterial(const Object& object,
+    const Material& matSrc,
+    aiMaterial& matDest)
+{
+    aiString s;
+
+    if (matSrc.name.length())
+    {
+        s.Set(matSrc.name);
+        matDest.AddProperty(&s,AI_MATKEY_NAME);
+    }
+    if (object.texture.length())
+    {
+        s.Set(object.texture);
+        matDest.AddProperty(&s,AI_MATKEY_TEXTURE_DIFFUSE(0));
+
+        // UV transformation
+        if (1.f != object.texRepeat.x || 1.f != object.texRepeat.y ||
+            object.texOffset.x        || object.texOffset.y)
+        {
+            aiUVTransform transform;
+            transform.mScaling = object.texRepeat;
+            transform.mTranslation = object.texOffset;
+            matDest.AddProperty(&transform,1,AI_MATKEY_UVTRANSFORM_DIFFUSE(0));
+        }
+    }
+
+    matDest.AddProperty<aiColor3D>(&matSrc.rgb,1, AI_MATKEY_COLOR_DIFFUSE);
+    matDest.AddProperty<aiColor3D>(&matSrc.amb,1, AI_MATKEY_COLOR_AMBIENT);
+    matDest.AddProperty<aiColor3D>(&matSrc.emis,1,AI_MATKEY_COLOR_EMISSIVE);
+    matDest.AddProperty<aiColor3D>(&matSrc.spec,1,AI_MATKEY_COLOR_SPECULAR);
+
+    int n;
+    if (matSrc.shin)
+    {
+        n = aiShadingMode_Phong;
+        matDest.AddProperty<float>(&matSrc.shin,1,AI_MATKEY_SHININESS);
+    }
+    else n = aiShadingMode_Gouraud;
+    matDest.AddProperty<int>(&n,1,AI_MATKEY_SHADING_MODEL);
+
+    float f = 1.f - matSrc.trans;
+    matDest.AddProperty<float>(&f,1,AI_MATKEY_OPACITY);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Converts the loaded data to the internal verbose representation
+aiNode* AC3DImporter::ConvertObjectSection(Object& object,
+    std::vector<aiMesh*>& meshes,
+    std::vector<aiMaterial*>& outMaterials,
+    const std::vector<Material>& materials,
+    aiNode* parent)
+{
+    aiNode* node = new aiNode();
+    node->mParent = parent;
+    if (object.vertices.size())
+    {
+        if (!object.surfaces.size() || !object.numRefs)
+        {
+            /* " An object with 7 vertices (no surfaces, no materials defined).
+                 This is a good way of getting point data into AC3D.
+                 The Vertex->create convex-surface/object can be used on these
+                 vertices to 'wrap' a 3d shape around them "
+                 (http://www.opencity.info/html/ac3dfileformat.html)
+
+                 therefore: if no surfaces are defined return point data only
+             */
+
+            ASSIMP_LOG_INFO("AC3D: No surfaces defined in object definition, "
+                "a point list is returned");
+
+            meshes.push_back(new aiMesh());
+            aiMesh* mesh = meshes.back();
+
+            mesh->mNumFaces = mesh->mNumVertices = (unsigned int)object.vertices.size();
+            aiFace* faces = mesh->mFaces = new aiFace[mesh->mNumFaces];
+            aiVector3D* verts = mesh->mVertices = new aiVector3D[mesh->mNumVertices];
+
+            for (unsigned int i = 0; i < mesh->mNumVertices;++i,++faces,++verts)
+            {
+                *verts = object.vertices[i];
+                faces->mNumIndices = 1;
+                faces->mIndices = new unsigned int[1];
+                faces->mIndices[0] = i;
+            }
+
+            // use the primary material in this case. this should be the
+            // default material if all objects of the file contain points
+            // and no faces.
+            mesh->mMaterialIndex = 0;
+            outMaterials.push_back(new aiMaterial());
+            ConvertMaterial(object, materials[0], *outMaterials.back());
+        }
+        else
+        {
+            // need to generate one or more meshes for this object.
+            // find out how many different materials we have
+            typedef std::pair< unsigned int, unsigned int > IntPair;
+            typedef std::vector< IntPair > MatTable;
+            MatTable needMat(materials.size(),IntPair(0,0));
+
+            std::vector<Surface>::iterator it,end = object.surfaces.end();
+            std::vector<Surface::SurfaceEntry>::iterator it2,end2;
+
+            for (it = object.surfaces.begin(); it != end; ++it)
+            {
+                unsigned int idx = (*it).mat;
+                if (idx >= needMat.size())
+                {
+                    ASSIMP_LOG_ERROR("AC3D: material index is out of range");
+                    idx = 0;
+                }
+                if ((*it).entries.empty())
+                {
+                    ASSIMP_LOG_WARN("AC3D: surface her zero vertex references");
+                }
+
+                // validate all vertex indices to make sure we won't crash here
+                for (it2  = (*it).entries.begin(),
+                     end2 = (*it).entries.end(); it2 != end2; ++it2)
+                {
+                    if ((*it2).first >= object.vertices.size())
+                    {
+                        ASSIMP_LOG_WARN("AC3D: Invalid vertex reference");
+                        (*it2).first = 0;
+                    }
+                }
+
+                if (!needMat[idx].first)++node->mNumMeshes;
+
+                switch ((*it).flags & 0xf)
+                {
+                    // closed line
+                case 0x1:
+
+                    needMat[idx].first  += (unsigned int)(*it).entries.size();
+                    needMat[idx].second += (unsigned int)(*it).entries.size()<<1u;
+                    break;
+
+                    // unclosed line
+                case 0x2:
+
+                    needMat[idx].first  += (unsigned int)(*it).entries.size()-1;
+                    needMat[idx].second += ((unsigned int)(*it).entries.size()-1)<<1u;
+                    break;
+
+                    // 0 == polygon, else unknown
+                default:
+
+                    if ((*it).flags & 0xf)
+                    {
+                        ASSIMP_LOG_WARN("AC3D: The type flag of a surface is unknown");
+                        (*it).flags &= ~(0xf);
+                    }
+
+                    // the number of faces increments by one, the number
+                    // of vertices by surface.numref.
+                    needMat[idx].first++;
+                    needMat[idx].second += (unsigned int)(*it).entries.size();
+                };
+            }
+            unsigned int* pip = node->mMeshes = new unsigned int[node->mNumMeshes];
+            unsigned int mat = 0;
+            const size_t oldm = meshes.size();
+            for (MatTable::const_iterator cit = needMat.begin(), cend = needMat.end();
+                cit != cend; ++cit, ++mat)
+            {
+                if (!(*cit).first)continue;
+
+                // allocate a new aiMesh object
+                *pip++ = (unsigned int)meshes.size();
+                aiMesh* mesh = new aiMesh();
+                meshes.push_back(mesh);
+
+                mesh->mMaterialIndex = (unsigned int)outMaterials.size();
+                outMaterials.push_back(new aiMaterial());
+                ConvertMaterial(object, materials[mat], *outMaterials.back());
+
+                // allocate storage for vertices and normals
+                mesh->mNumFaces = (*cit).first;
+                if (mesh->mNumFaces == 0) {
+                    throw DeadlyImportError("AC3D: No faces");
+                } else if (mesh->mNumFaces > AI_MAX_ALLOC(aiFace)) {
+                    throw DeadlyImportError("AC3D: Too many faces, would run out of memory");
+                }
+                aiFace* faces = mesh->mFaces = new aiFace[mesh->mNumFaces];
+
+                mesh->mNumVertices = (*cit).second;
+                if (mesh->mNumVertices == 0) {
+                    throw DeadlyImportError("AC3D: No vertices");
+                } else if (mesh->mNumVertices > AI_MAX_ALLOC(aiVector3D)) {
+                    throw DeadlyImportError("AC3D: Too many vertices, would run out of memory");
+                }
+                aiVector3D* vertices = mesh->mVertices = new aiVector3D[mesh->mNumVertices];
+                unsigned int cur = 0;
+
+                // allocate UV coordinates, but only if the texture name for the
+                // surface is not empty
+                aiVector3D* uv = NULL;
+                if(object.texture.length())
+                {
+                    uv = mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices];
+                    mesh->mNumUVComponents[0] = 2;
+                }
+
+                for (it = object.surfaces.begin(); it != end; ++it)
+                {
+                    if (mat == (*it).mat)
+                    {
+                        const Surface& src = *it;
+
+                        // closed polygon
+                        unsigned int type = (*it).flags & 0xf;
+                        if (!type)
+                        {
+                            aiFace& face = *faces++;
+                            if((face.mNumIndices = (unsigned int)src.entries.size()))
+                            {
+                                face.mIndices = new unsigned int[face.mNumIndices];
+                                for (unsigned int i = 0; i < face.mNumIndices;++i,++vertices)
+                                {
+                                    const Surface::SurfaceEntry& entry = src.entries[i];
+                                    face.mIndices[i] = cur++;
+
+                                    // copy vertex positions
+                                    if (static_cast<unsigned>(vertices - mesh->mVertices) >= mesh->mNumVertices) {
+                                        throw DeadlyImportError("AC3D: Invalid number of vertices");
+                                    }
+                                    *vertices = object.vertices[entry.first] + object.translation;
+
+
+                                    // copy texture coordinates
+                                    if (uv)
+                                    {
+                                        uv->x =  entry.second.x;
+                                        uv->y =  entry.second.y;
+                                        ++uv;
+                                    }
+                                }
+                            }
+                        }
+                        else
+                        {
+
+                            it2  = (*it).entries.begin();
+
+                            // either a closed or an unclosed line
+                            unsigned int tmp = (unsigned int)(*it).entries.size();
+                            if (0x2 == type)--tmp;
+                            for (unsigned int m = 0; m < tmp;++m)
+                            {
+                                aiFace& face = *faces++;
+
+                                face.mNumIndices = 2;
+                                face.mIndices = new unsigned int[2];
+                                face.mIndices[0] = cur++;
+                                face.mIndices[1] = cur++;
+
+                                // copy vertex positions
+                                if (it2 == (*it).entries.end() ) {
+                                    throw DeadlyImportError("AC3D: Bad line");
+                                }
+                                ai_assert((*it2).first < object.vertices.size());
+                                *vertices++ = object.vertices[(*it2).first];
+
+                                // copy texture coordinates
+                                if (uv)
+                                {
+                                    uv->x =  (*it2).second.x;
+                                    uv->y =  (*it2).second.y;
+                                    ++uv;
+                                }
+
+
+                                if (0x1 == type && tmp-1 == m)
+                                {
+                                    // if this is a closed line repeat its beginning now
+                                    it2  = (*it).entries.begin();
+                                }
+                                else ++it2;
+
+                                // second point
+                                *vertices++ = object.vertices[(*it2).first];
+
+                                if (uv)
+                                {
+                                    uv->x =  (*it2).second.x;
+                                    uv->y =  (*it2).second.y;
+                                    ++uv;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+
+            // Now apply catmull clark subdivision if necessary. We split meshes into
+            // materials which is not done by AC3D during smoothing, so we need to
+            // collect all meshes using the same material group.
+            if (object.subDiv)  {
+                if (configEvalSubdivision) {
+                    std::unique_ptr<Subdivider> div(Subdivider::Create(Subdivider::CATMULL_CLARKE));
+                    ASSIMP_LOG_INFO("AC3D: Evaluating subdivision surface: "+object.name);
+
+                    std::vector<aiMesh*> cpy(meshes.size()-oldm,NULL);
+                    div->Subdivide(&meshes[oldm],cpy.size(),&cpy.front(),object.subDiv,true);
+                    std::copy(cpy.begin(),cpy.end(),meshes.begin()+oldm);
+
+                    // previous meshes are deleted vy Subdivide().
+                }
+                else {
+                    ASSIMP_LOG_INFO("AC3D: Letting the subdivision surface untouched due to my configuration: "
+                        +object.name);
+                }
+            }
+        }
+    }
+
+    if (object.name.length())
+        node->mName.Set(object.name);
+    else
+    {
+        // generate a name depending on the type of the node
+        switch (object.type)
+        {
+        case Object::Group:
+            node->mName.length = ::ai_snprintf(node->mName.data, MAXLEN, "ACGroup_%i",groups++);
+            break;
+        case Object::Poly:
+            node->mName.length = ::ai_snprintf(node->mName.data, MAXLEN, "ACPoly_%i",polys++);
+            break;
+        case Object::Light:
+            node->mName.length = ::ai_snprintf(node->mName.data, MAXLEN, "ACLight_%i",lights++);
+            break;
+
+            // there shouldn't be more than one world, but we don't care
+        case Object::World:
+            node->mName.length = ::ai_snprintf(node->mName.data, MAXLEN, "ACWorld_%i",worlds++);
+            break;
+        }
+    }
+
+
+    // setup the local transformation matrix of the object
+    // compute the transformation offset to the parent node
+    node->mTransformation = aiMatrix4x4 ( object.rotation );
+
+    if (object.type == Object::Group || !object.numRefs)
+    {
+        node->mTransformation.a4 = object.translation.x;
+        node->mTransformation.b4 = object.translation.y;
+        node->mTransformation.c4 = object.translation.z;
+    }
+
+    // add children to the object
+    if (object.children.size())
+    {
+        node->mNumChildren = (unsigned int)object.children.size();
+        node->mChildren = new aiNode*[node->mNumChildren];
+        for (unsigned int i = 0; i < node->mNumChildren;++i)
+        {
+            node->mChildren[i] = ConvertObjectSection(object.children[i],meshes,outMaterials,materials,node);
+        }
+    }
+
+    return node;
+}
+
+// ------------------------------------------------------------------------------------------------
+void AC3DImporter::SetupProperties(const Importer* pImp)
+{
+    configSplitBFCull = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_AC_SEPARATE_BFCULL,1) ? true : false;
+    configEvalSubdivision =  pImp->GetPropertyInteger(AI_CONFIG_IMPORT_AC_EVAL_SUBDIVISION,1) ? true : false;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Imports the given file into the given scene structure.
+void AC3DImporter::InternReadFile( const std::string& pFile,
+    aiScene* pScene, IOSystem* pIOHandler)
+{
+    std::unique_ptr<IOStream> file( pIOHandler->Open( pFile, "rb"));
+
+    // Check whether we can read from the file
+    if( file.get() == NULL)
+        throw DeadlyImportError( "Failed to open AC3D file " + pFile + ".");
+
+    // allocate storage and copy the contents of the file to a memory buffer
+    std::vector<char> mBuffer2;
+    TextFileToBuffer(file.get(),mBuffer2);
+
+    buffer = &mBuffer2[0];
+    mNumMeshes = 0;
+
+    lights = polys = worlds = groups = 0;
+
+    if (::strncmp(buffer,"AC3D",4)) {
+        throw DeadlyImportError("AC3D: No valid AC3D file, magic sequence not found");
+    }
+
+    // print the file format version to the console
+    unsigned int version = HexDigitToDecimal( buffer[4] );
+    char msg[3];
+    ASSIMP_itoa10(msg,3,version);
+    ASSIMP_LOG_INFO_F("AC3D file format version: ", msg);
+
+    std::vector<Material> materials;
+    materials.reserve(5);
+
+    std::vector<Object> rootObjects;
+    rootObjects.reserve(5);
+
+    std::vector<aiLight*> lights;
+    mLights = & lights;
+
+    while (GetNextLine())
+    {
+        if (TokenMatch(buffer,"MATERIAL",8))
+        {
+            materials.push_back(Material());
+            Material& mat = materials.back();
+
+            // manually parse the material ... sscanf would use the buldin atof ...
+            // Format: (name) rgb %f %f %f  amb %f %f %f  emis %f %f %f  spec %f %f %f  shi %d  trans %f
+
+            AI_AC_SKIP_TO_NEXT_TOKEN();
+            if ('\"' == *buffer)
+            {
+                AI_AC_GET_STRING(mat.name);
+                AI_AC_SKIP_TO_NEXT_TOKEN();
+            }
+
+            AI_AC_CHECKED_LOAD_FLOAT_ARRAY("rgb",3,3,&mat.rgb);
+            AI_AC_CHECKED_LOAD_FLOAT_ARRAY("amb",3,3,&mat.amb);
+            AI_AC_CHECKED_LOAD_FLOAT_ARRAY("emis",4,3,&mat.emis);
+            AI_AC_CHECKED_LOAD_FLOAT_ARRAY("spec",4,3,&mat.spec);
+            AI_AC_CHECKED_LOAD_FLOAT_ARRAY("shi",3,1,&mat.shin);
+            AI_AC_CHECKED_LOAD_FLOAT_ARRAY("trans",5,1,&mat.trans);
+        }
+        LoadObjectSection(rootObjects);
+    }
+
+    if (rootObjects.empty() || !mNumMeshes)
+    {
+        throw DeadlyImportError("AC3D: No meshes have been loaded");
+    }
+    if (materials.empty())
+    {
+        ASSIMP_LOG_WARN("AC3D: No material has been found");
+        materials.push_back(Material());
+    }
+
+    mNumMeshes += (mNumMeshes>>2u) + 1;
+    std::vector<aiMesh*> meshes;
+    meshes.reserve(mNumMeshes);
+
+    std::vector<aiMaterial*> omaterials;
+    materials.reserve(mNumMeshes);
+
+    // generate a dummy root if there are multiple objects on the top layer
+    Object* root;
+    if (1 == rootObjects.size())
+        root = &rootObjects[0];
+    else
+    {
+        root = new Object();
+    }
+
+    // now convert the imported stuff to our output data structure
+    pScene->mRootNode = ConvertObjectSection(*root,meshes,omaterials,materials);
+    if (1 != rootObjects.size())delete root;
+
+    if (!::strncmp( pScene->mRootNode->mName.data, "Node", 4))
+        pScene->mRootNode->mName.Set("<AC3DWorld>");
+
+    // copy meshes
+    if (meshes.empty())
+    {
+        throw DeadlyImportError("An unknown error occurred during converting");
+    }
+    pScene->mNumMeshes = (unsigned int)meshes.size();
+    pScene->mMeshes = new aiMesh*[pScene->mNumMeshes];
+    ::memcpy(pScene->mMeshes,&meshes[0],pScene->mNumMeshes*sizeof(void*));
+
+    // copy materials
+    pScene->mNumMaterials = (unsigned int)omaterials.size();
+    pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials];
+    ::memcpy(pScene->mMaterials,&omaterials[0],pScene->mNumMaterials*sizeof(void*));
+
+    // copy lights
+    pScene->mNumLights = (unsigned int)lights.size();
+    if (lights.size())
+    {
+        pScene->mLights = new aiLight*[lights.size()];
+        ::memcpy(pScene->mLights,&lights[0],lights.size()*sizeof(void*));
+    }
+}
+
+#endif //!defined ASSIMP_BUILD_NO_AC_IMPORTER

+ 276 - 0
Engine/lib/assimp/code/ACLoader.h

@@ -0,0 +1,276 @@
+/*
+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  ACLoader.h
+ *  @brief Declaration of the .ac importer class.
+ */
+#ifndef AI_AC3DLOADER_H_INCLUDED
+#define AI_AC3DLOADER_H_INCLUDED
+
+#include <vector>
+
+#include <assimp/BaseImporter.h>
+#include <assimp/types.h>
+
+struct aiNode;
+struct aiMesh;
+struct aiMaterial;
+struct aiLight;
+
+
+namespace Assimp    {
+
+// ---------------------------------------------------------------------------
+/** AC3D (*.ac) importer class
+*/
+class AC3DImporter : public BaseImporter
+{
+public:
+    AC3DImporter();
+    ~AC3DImporter();
+
+
+
+    // Represents an AC3D material
+    struct Material
+    {
+        Material()
+            :   rgb     (0.6f,0.6f,0.6f)
+            ,   spec    (1.f,1.f,1.f)
+            ,   shin    (0.f)
+            ,   trans   (0.f)
+        {}
+
+        // base color of the material
+        aiColor3D rgb;
+
+        // ambient color of the material
+        aiColor3D amb;
+
+        // emissive color of the material
+        aiColor3D emis;
+
+        // specular color of the material
+        aiColor3D spec;
+
+        // shininess exponent
+        float shin;
+
+        // transparency. 0 == opaque
+        float trans;
+
+        // name of the material. optional.
+        std::string name;
+    };
+
+    // Represents an AC3D surface
+    struct Surface
+    {
+        Surface()
+            :   mat     (0)
+            ,   flags   (0)
+        {}
+
+        unsigned int mat,flags;
+
+        typedef std::pair<unsigned int, aiVector2D > SurfaceEntry;
+        std::vector< SurfaceEntry > entries;
+    };
+
+    // Represents an AC3D object
+    struct Object
+    {
+        Object()
+            :   type    (World)
+            ,   name( "" )
+            ,   children()
+            ,   texture( "" )
+            ,   texRepeat( 1.f, 1.f )
+            ,   texOffset( 0.0f, 0.0f )
+            ,   rotation()
+            ,   translation()
+            ,   vertices()
+            ,   surfaces()
+            ,   numRefs (0)
+            ,   subDiv  (0)
+            ,   crease()
+        {}
+
+        // Type description
+        enum Type
+        {
+            World = 0x0,
+            Poly  = 0x1,
+            Group = 0x2,
+            Light = 0x4
+        } type;
+
+        // name of the object
+        std::string name;
+
+        // object children
+        std::vector<Object> children;
+
+        // texture to be assigned to all surfaces of the object
+        std::string texture;
+
+        // texture repat factors (scaling for all coordinates)
+        aiVector2D texRepeat, texOffset;
+
+        // rotation matrix
+        aiMatrix3x3 rotation;
+
+        // translation vector
+        aiVector3D translation;
+
+        // vertices
+        std::vector<aiVector3D> vertices;
+
+        // surfaces
+        std::vector<Surface> surfaces;
+
+        // number of indices (= num verts in verbose format)
+        unsigned int numRefs;
+
+        // number of subdivisions to be performed on the
+        // imported data
+        unsigned int subDiv;
+
+        // max angle limit for smoothing
+        float crease;
+    };
+
+
+public:
+
+    // -------------------------------------------------------------------
+    /** Returns whether the class can handle the format of the given file.
+     * See BaseImporter::CanRead() for details.
+     */
+    bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
+        bool checkSig) const;
+
+protected:
+
+    // -------------------------------------------------------------------
+    /** Return importer meta information.
+     * See #BaseImporter::GetInfo for the details */
+    const aiImporterDesc* GetInfo () const;
+
+    // -------------------------------------------------------------------
+    /** Imports the given file into the given scene structure.
+     * See BaseImporter::InternReadFile() for details*/
+    void InternReadFile( const std::string& pFile, aiScene* pScene,
+        IOSystem* pIOHandler);
+
+    // -------------------------------------------------------------------
+    /** Called prior to ReadFile().
+    * The function is a request to the importer to update its configuration
+    * basing on the Importer's configuration property list.*/
+    void SetupProperties(const Importer* pImp);
+
+private:
+
+    // -------------------------------------------------------------------
+    /** Get the next line from the file.
+     *  @return false if the end of the file was reached*/
+    bool GetNextLine();
+
+    // -------------------------------------------------------------------
+    /** Load the object section. This method is called recursively to
+     *  load subobjects, the method returns after a 'kids 0' was
+     *  encountered.
+     *  @objects List of output objects*/
+    void LoadObjectSection(std::vector<Object>& objects);
+
+    // -------------------------------------------------------------------
+    /** Convert all objects into meshes and nodes.
+     *  @param object Current object to work on
+     *  @param meshes Pointer to the list of output meshes
+     *  @param outMaterials List of output materials
+     *  @param materials Material list
+     *  @param Scenegraph node for the object */
+    aiNode* ConvertObjectSection(Object& object,
+        std::vector<aiMesh*>& meshes,
+        std::vector<aiMaterial*>& outMaterials,
+        const std::vector<Material>& materials,
+        aiNode* parent = NULL);
+
+    // -------------------------------------------------------------------
+    /** Convert a material
+     *  @param object Current object
+     *  @param matSrc Source material description
+     *  @param matDest Destination material to be filled */
+    void ConvertMaterial(const Object& object,
+        const Material& matSrc,
+        aiMaterial& matDest);
+
+private:
+
+
+    // points to the next data line
+    const char* buffer;
+
+    // Configuration option: if enabled, up to two meshes
+    // are generated per material: those faces who have
+    // their bf cull flags set are separated.
+    bool configSplitBFCull;
+
+    // Configuration switch: subdivision surfaces are only
+    // evaluated if the value is true.
+    bool configEvalSubdivision;
+
+    // counts how many objects we have in the tree.
+    // basing on this information we can find a
+    // good estimate how many meshes we'll have in the final scene.
+    unsigned int mNumMeshes;
+
+    // current list of light sources
+    std::vector<aiLight*>* mLights;
+
+    // name counters
+    unsigned int lights, groups, polys, worlds;
+};
+
+} // end of namespace Assimp
+
+#endif // AI_AC3DIMPORTER_H_INC

+ 705 - 0
Engine/lib/assimp/code/AMFImporter.cpp

@@ -0,0 +1,705 @@
+/*
+---------------------------------------------------------------------------
+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 AMFImporter.cpp
+/// \brief AMF-format files importer for Assimp: main algorithm implementation.
+/// \date 2016
+/// \author [email protected]
+
+#ifndef ASSIMP_BUILD_NO_AMF_IMPORTER
+
+// Header files, Assimp.
+#include "AMFImporter.hpp"
+#include "AMFImporter_Macro.hpp"
+
+#include <assimp/fast_atof.h>
+#include <assimp/DefaultIOSystem.h>
+
+// Header files, stdlib.
+#include <memory>
+
+namespace Assimp
+{
+
+/// \var aiImporterDesc AMFImporter::Description
+/// Conastant which hold importer description
+const aiImporterDesc AMFImporter::Description = {
+	"Additive manufacturing file format(AMF) Importer",
+	"smalcom",
+	"",
+	"See documentation in source code. Chapter: Limitations.",
+	aiImporterFlags_SupportTextFlavour | aiImporterFlags_LimitedSupport | aiImporterFlags_Experimental,
+	0,
+	0,
+	0,
+	0,
+	"amf"
+};
+
+void AMFImporter::Clear()
+{
+	mNodeElement_Cur = nullptr;
+	mUnit.clear();
+	mMaterial_Converted.clear();
+	mTexture_Converted.clear();
+	// Delete all elements
+	if(mNodeElement_List.size())
+	{
+		for(CAMFImporter_NodeElement* ne: mNodeElement_List) { delete ne; }
+
+		mNodeElement_List.clear();
+	}
+}
+
+AMFImporter::~AMFImporter()
+{
+	if(mReader != nullptr) delete mReader;
+	// Clear() is accounting if data already is deleted. So, just check again if all data is deleted.
+	Clear();
+}
+
+/*********************************************************************************************************************************************/
+/************************************************************ Functions: find set ************************************************************/
+/*********************************************************************************************************************************************/
+
+bool AMFImporter::Find_NodeElement(const std::string& pID, const CAMFImporter_NodeElement::EType pType, CAMFImporter_NodeElement** pNodeElement) const
+{
+	for(CAMFImporter_NodeElement* ne: mNodeElement_List)
+	{
+		if((ne->ID == pID) && (ne->Type == pType))
+		{
+			if(pNodeElement != nullptr) *pNodeElement = ne;
+
+			return true;
+		}
+	}// for(CAMFImporter_NodeElement* ne: mNodeElement_List)
+
+	return false;
+}
+
+bool AMFImporter::Find_ConvertedNode(const std::string& pID, std::list<aiNode*>& pNodeList, aiNode** pNode) const
+{
+aiString node_name(pID.c_str());
+
+	for(aiNode* node: pNodeList)
+	{
+		if(node->mName == node_name)
+		{
+			if(pNode != nullptr) *pNode = node;
+
+			return true;
+		}
+	}// for(aiNode* node: pNodeList)
+
+	return false;
+}
+
+bool AMFImporter::Find_ConvertedMaterial(const std::string& pID, const SPP_Material** pConvertedMaterial) const
+{
+	for(const SPP_Material& mat: mMaterial_Converted)
+	{
+		if(mat.ID == pID)
+		{
+			if(pConvertedMaterial != nullptr) *pConvertedMaterial = &mat;
+
+			return true;
+		}
+	}// for(const SPP_Material& mat: mMaterial_Converted)
+
+	return false;
+}
+
+/*********************************************************************************************************************************************/
+/************************************************************ Functions: throw set ***********************************************************/
+/*********************************************************************************************************************************************/
+
+void AMFImporter::Throw_CloseNotFound(const std::string& pNode)
+{
+	throw DeadlyImportError("Close tag for node <" + pNode + "> not found. Seems file is corrupt.");
+}
+
+void AMFImporter::Throw_IncorrectAttr(const std::string& pAttrName)
+{
+	throw DeadlyImportError("Node <" + std::string(mReader->getNodeName()) + "> has incorrect attribute \"" + pAttrName + "\".");
+}
+
+void AMFImporter::Throw_IncorrectAttrValue(const std::string& pAttrName)
+{
+	throw DeadlyImportError("Attribute \"" + pAttrName + "\" in node <" + std::string(mReader->getNodeName()) + "> has incorrect value.");
+}
+
+void AMFImporter::Throw_MoreThanOnceDefined(const std::string& pNodeType, const std::string& pDescription)
+{
+	throw DeadlyImportError("\"" + pNodeType + "\" node can be used only once in " + mReader->getNodeName() + ". Description: " + pDescription);
+}
+
+void AMFImporter::Throw_ID_NotFound(const std::string& pID) const
+{
+	throw DeadlyImportError("Not found node with name \"" + pID + "\".");
+}
+
+/*********************************************************************************************************************************************/
+/************************************************************* Functions: XML set ************************************************************/
+/*********************************************************************************************************************************************/
+
+void AMFImporter::XML_CheckNode_MustHaveChildren()
+{
+	if(mReader->isEmptyElement()) throw DeadlyImportError(std::string("Node <") + mReader->getNodeName() + "> must have children.");
+}
+
+void AMFImporter::XML_CheckNode_SkipUnsupported(const std::string& pParentNodeName)
+{
+    static const size_t Uns_Skip_Len = 3;
+    const char* Uns_Skip[Uns_Skip_Len] = { "composite", "edge", "normal" };
+
+    static bool skipped_before[Uns_Skip_Len] = { false, false, false };
+
+    std::string nn(mReader->getNodeName());
+    bool found = false;
+    bool close_found = false;
+    size_t sk_idx;
+
+	for(sk_idx = 0; sk_idx < Uns_Skip_Len; sk_idx++)
+	{
+		if(nn != Uns_Skip[sk_idx]) continue;
+
+		found = true;
+		if(mReader->isEmptyElement())
+		{
+			close_found = true;
+
+			goto casu_cres;
+		}
+
+		while(mReader->read())
+		{
+			if((mReader->getNodeType() == irr::io::EXN_ELEMENT_END) && (nn == mReader->getNodeName()))
+			{
+				close_found = true;
+
+				goto casu_cres;
+			}
+		}
+	}// for(sk_idx = 0; sk_idx < Uns_Skip_Len; sk_idx++)
+
+casu_cres:
+
+	if(!found) throw DeadlyImportError("Unknown node \"" + nn + "\" in " + pParentNodeName + ".");
+	if(!close_found) Throw_CloseNotFound(nn);
+
+	if(!skipped_before[sk_idx])
+	{
+		skipped_before[sk_idx] = true;
+        ASSIMP_LOG_WARN_F("Skipping node \"", nn, "\" in ", pParentNodeName, ".");
+	}
+}
+
+bool AMFImporter::XML_SearchNode(const std::string& pNodeName)
+{
+	while(mReader->read())
+	{
+		if((mReader->getNodeType() == irr::io::EXN_ELEMENT) && XML_CheckNode_NameEqual(pNodeName)) return true;
+	}
+
+	return false;
+}
+
+bool AMFImporter::XML_ReadNode_GetAttrVal_AsBool(const int pAttrIdx)
+{
+    std::string val(mReader->getAttributeValue(pAttrIdx));
+
+	if((val == "false") || (val == "0"))
+		return false;
+	else if((val == "true") || (val == "1"))
+		return true;
+	else
+		throw DeadlyImportError("Bool attribute value can contain \"false\"/\"0\" or \"true\"/\"1\" not the \"" + val + "\"");
+}
+
+float AMFImporter::XML_ReadNode_GetAttrVal_AsFloat(const int pAttrIdx)
+{
+    std::string val;
+    float tvalf;
+
+	ParseHelper_FixTruncatedFloatString(mReader->getAttributeValue(pAttrIdx), val);
+	fast_atoreal_move(val.c_str(), tvalf, false);
+
+	return tvalf;
+}
+
+uint32_t AMFImporter::XML_ReadNode_GetAttrVal_AsU32(const int pAttrIdx)
+{
+	return strtoul10(mReader->getAttributeValue(pAttrIdx));
+}
+
+float AMFImporter::XML_ReadNode_GetVal_AsFloat()
+{
+    std::string val;
+    float tvalf;
+
+	if(!mReader->read()) throw DeadlyImportError("XML_ReadNode_GetVal_AsFloat. No data, seems file is corrupt.");
+	if(mReader->getNodeType() != irr::io::EXN_TEXT) throw DeadlyImportError("XML_ReadNode_GetVal_AsFloat. Invalid type of XML element, seems file is corrupt.");
+
+	ParseHelper_FixTruncatedFloatString(mReader->getNodeData(), val);
+	fast_atoreal_move(val.c_str(), tvalf, false);
+
+	return tvalf;
+}
+
+uint32_t AMFImporter::XML_ReadNode_GetVal_AsU32()
+{
+	if(!mReader->read()) throw DeadlyImportError("XML_ReadNode_GetVal_AsU32. No data, seems file is corrupt.");
+	if(mReader->getNodeType() != irr::io::EXN_TEXT) throw DeadlyImportError("XML_ReadNode_GetVal_AsU32. Invalid type of XML element, seems file is corrupt.");
+
+	return strtoul10(mReader->getNodeData());
+}
+
+void AMFImporter::XML_ReadNode_GetVal_AsString(std::string& pValue)
+{
+	if(!mReader->read()) throw DeadlyImportError("XML_ReadNode_GetVal_AsString. No data, seems file is corrupt.");
+	if(mReader->getNodeType() != irr::io::EXN_TEXT)
+		throw DeadlyImportError("XML_ReadNode_GetVal_AsString. Invalid type of XML element, seems file is corrupt.");
+
+	pValue = mReader->getNodeData();
+}
+
+/*********************************************************************************************************************************************/
+/************************************************************ Functions: parse set ***********************************************************/
+/*********************************************************************************************************************************************/
+
+void AMFImporter::ParseHelper_Node_Enter(CAMFImporter_NodeElement* pNode)
+{
+	mNodeElement_Cur->Child.push_back(pNode);// add new element to current element child list.
+	mNodeElement_Cur = pNode;// switch current element to new one.
+}
+
+void AMFImporter::ParseHelper_Node_Exit()
+{
+	// check if we can walk up.
+	if(mNodeElement_Cur != nullptr) mNodeElement_Cur = mNodeElement_Cur->Parent;
+}
+
+void AMFImporter::ParseHelper_FixTruncatedFloatString(const char* pInStr, std::string& pOutString)
+{
+    size_t instr_len;
+
+	pOutString.clear();
+	instr_len = strlen(pInStr);
+	if(!instr_len) return;
+
+	pOutString.reserve(instr_len * 3 / 2);
+	// check and correct floats in format ".x". Must be "x.y".
+	if(pInStr[0] == '.') pOutString.push_back('0');
+
+	pOutString.push_back(pInStr[0]);
+	for(size_t ci = 1; ci < instr_len; ci++)
+	{
+		if((pInStr[ci] == '.') && ((pInStr[ci - 1] == ' ') || (pInStr[ci - 1] == '-') || (pInStr[ci - 1] == '+') || (pInStr[ci - 1] == '\t')))
+		{
+			pOutString.push_back('0');
+			pOutString.push_back('.');
+		}
+		else
+		{
+			pOutString.push_back(pInStr[ci]);
+		}
+	}
+}
+
+static bool ParseHelper_Decode_Base64_IsBase64(const char pChar)
+{
+	return (isalnum(pChar) || (pChar == '+') || (pChar == '/'));
+}
+
+void AMFImporter::ParseHelper_Decode_Base64(const std::string& pInputBase64, std::vector<uint8_t>& pOutputData) const
+{
+    // With help from
+    // René Nyffenegger http://www.adp-gmbh.ch/cpp/common/base64.html
+    const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+    uint8_t tidx = 0;
+    uint8_t arr4[4], arr3[3];
+
+	// check input data
+	if(pInputBase64.size() % 4) throw DeadlyImportError("Base64-encoded data must have size multiply of four.");
+	// prepare output place
+	pOutputData.clear();
+	pOutputData.reserve(pInputBase64.size() / 4 * 3);
+
+	for(size_t in_len = pInputBase64.size(), in_idx = 0; (in_len > 0) && (pInputBase64[in_idx] != '='); in_len--)
+	{
+		if(ParseHelper_Decode_Base64_IsBase64(pInputBase64[in_idx]))
+		{
+			arr4[tidx++] = pInputBase64[in_idx++];
+			if(tidx == 4)
+			{
+				for(tidx = 0; tidx < 4; tidx++) arr4[tidx] = (uint8_t)base64_chars.find(arr4[tidx]);
+
+				arr3[0] = (arr4[0] << 2) + ((arr4[1] & 0x30) >> 4);
+				arr3[1] = ((arr4[1] & 0x0F) << 4) + ((arr4[2] & 0x3C) >> 2);
+				arr3[2] = ((arr4[2] & 0x03) << 6) + arr4[3];
+				for(tidx = 0; tidx < 3; tidx++) pOutputData.push_back(arr3[tidx]);
+
+				tidx = 0;
+			}// if(tidx == 4)
+		}// if(ParseHelper_Decode_Base64_IsBase64(pInputBase64[in_idx]))
+		else
+		{
+			in_idx++;
+		}// if(ParseHelper_Decode_Base64_IsBase64(pInputBase64[in_idx])) else
+	}
+
+	if(tidx)
+	{
+		for(uint8_t i = tidx; i < 4; i++) arr4[i] = 0;
+		for(uint8_t i = 0; i < 4; i++) arr4[i] = (uint8_t)(base64_chars.find(arr4[i]));
+
+		arr3[0] = (arr4[0] << 2) + ((arr4[1] & 0x30) >> 4);
+		arr3[1] = ((arr4[1] & 0x0F) << 4) + ((arr4[2] & 0x3C) >> 2);
+		arr3[2] = ((arr4[2] & 0x03) << 6) + arr4[3];
+		for(uint8_t i = 0; i < (tidx - 1); i++) pOutputData.push_back(arr3[i]);
+	}
+}
+
+void AMFImporter::ParseFile(const std::string& pFile, IOSystem* pIOHandler)
+{
+    irr::io::IrrXMLReader* OldReader = mReader;// store current XMLreader.
+    std::unique_ptr<IOStream> file(pIOHandler->Open(pFile, "rb"));
+
+	// Check whether we can read from the file
+	if(file.get() == NULL) throw DeadlyImportError("Failed to open AMF file " + pFile + ".");
+
+	// generate a XML reader for it
+	std::unique_ptr<CIrrXML_IOStreamReader> mIOWrapper(new CIrrXML_IOStreamReader(file.get()));
+	mReader = irr::io::createIrrXMLReader(mIOWrapper.get());
+	if(!mReader) throw DeadlyImportError("Failed to create XML reader for file" + pFile + ".");
+	//
+	// start reading
+	// search for root tag <amf>
+	if(XML_SearchNode("amf"))
+		ParseNode_Root();
+	else
+		throw DeadlyImportError("Root node \"amf\" not found.");
+
+	delete mReader;
+	// restore old XMLreader
+	mReader = OldReader;
+}
+
+// <amf
+// unit="" - The units to be used. May be "inch", "millimeter", "meter", "feet", or "micron".
+// version="" - Version of file format.
+// >
+// </amf>
+// Root XML element.
+// Multi elements - No.
+void AMFImporter::ParseNode_Root()
+{
+    std::string unit, version;
+    CAMFImporter_NodeElement *ne( nullptr );
+
+	// Read attributes for node <amf>.
+	MACRO_ATTRREAD_LOOPBEG;
+		MACRO_ATTRREAD_CHECK_RET("unit", unit, mReader->getAttributeValue);
+		MACRO_ATTRREAD_CHECK_RET("version", version, mReader->getAttributeValue);
+	MACRO_ATTRREAD_LOOPEND_WSKIP;
+
+	// Check attributes
+	if(!mUnit.empty())
+	{
+		if((mUnit != "inch") && (mUnit != "millimeter") && (mUnit != "meter") && (mUnit != "feet") && (mUnit != "micron")) Throw_IncorrectAttrValue("unit");
+	}
+
+	// create root node element.
+	ne = new CAMFImporter_NodeElement_Root(nullptr);
+	mNodeElement_Cur = ne;// set first "current" element
+	// and assign attribute's values
+	((CAMFImporter_NodeElement_Root*)ne)->Unit = unit;
+	((CAMFImporter_NodeElement_Root*)ne)->Version = version;
+
+	// Check for child nodes
+	if(!mReader->isEmptyElement())
+	{
+		MACRO_NODECHECK_LOOPBEGIN("amf");
+			if(XML_CheckNode_NameEqual("object")) { ParseNode_Object(); continue; }
+			if(XML_CheckNode_NameEqual("material")) { ParseNode_Material(); continue; }
+			if(XML_CheckNode_NameEqual("texture")) { ParseNode_Texture(); continue; }
+			if(XML_CheckNode_NameEqual("constellation")) { ParseNode_Constellation(); continue; }
+			if(XML_CheckNode_NameEqual("metadata")) { ParseNode_Metadata(); continue; }
+		MACRO_NODECHECK_LOOPEND("amf");
+		mNodeElement_Cur = ne;// force restore "current" element
+	}// if(!mReader->isEmptyElement())
+
+	mNodeElement_List.push_back(ne);// add to node element list because its a new object in graph.
+}
+
+// <constellation
+// id="" - The Object ID of the new constellation being defined.
+// >
+// </constellation>
+// A collection of objects or constellations with specific relative locations.
+// Multi elements - Yes.
+// Parent element - <amf>.
+void AMFImporter::ParseNode_Constellation()
+{
+    std::string id;
+    CAMFImporter_NodeElement* ne( nullptr );
+
+	// Read attributes for node <constellation>.
+	MACRO_ATTRREAD_LOOPBEG;
+		MACRO_ATTRREAD_CHECK_RET("id", id, mReader->getAttributeValue);
+	MACRO_ATTRREAD_LOOPEND;
+
+	// create and if needed - define new grouping object.
+	ne = new CAMFImporter_NodeElement_Constellation(mNodeElement_Cur);
+
+	CAMFImporter_NodeElement_Constellation& als = *((CAMFImporter_NodeElement_Constellation*)ne);// alias for convenience
+
+	if(!id.empty()) als.ID = id;
+	// Check for child nodes
+	if(!mReader->isEmptyElement())
+	{
+		ParseHelper_Node_Enter(ne);
+		MACRO_NODECHECK_LOOPBEGIN("constellation");
+			if(XML_CheckNode_NameEqual("instance")) { ParseNode_Instance(); continue; }
+			if(XML_CheckNode_NameEqual("metadata")) { ParseNode_Metadata(); continue; }
+		MACRO_NODECHECK_LOOPEND("constellation");
+		ParseHelper_Node_Exit();
+	}// if(!mReader->isEmptyElement())
+	else
+	{
+		mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element
+	}// if(!mReader->isEmptyElement()) else
+
+	mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph.
+}
+
+// <instance
+// objectid="" - The Object ID of the new constellation being defined.
+// >
+// </instance>
+// A collection of objects or constellations with specific relative locations.
+// Multi elements - Yes.
+// Parent element - <amf>.
+void AMFImporter::ParseNode_Instance()
+{
+    std::string objectid;
+    CAMFImporter_NodeElement* ne( nullptr );
+
+	// Read attributes for node <constellation>.
+	MACRO_ATTRREAD_LOOPBEG;
+		MACRO_ATTRREAD_CHECK_RET("objectid", objectid, mReader->getAttributeValue);
+	MACRO_ATTRREAD_LOOPEND;
+
+	// used object id must be defined, check that.
+	if(objectid.empty()) throw DeadlyImportError("\"objectid\" in <instance> must be defined.");
+	// create and define new grouping object.
+	ne = new CAMFImporter_NodeElement_Instance(mNodeElement_Cur);
+
+	CAMFImporter_NodeElement_Instance& als = *((CAMFImporter_NodeElement_Instance*)ne);// alias for convenience
+
+	als.ObjectID = objectid;
+	// Check for child nodes
+	if(!mReader->isEmptyElement())
+	{
+		bool read_flag[6] = { false, false, false, false, false, false };
+
+		als.Delta.Set(0, 0, 0);
+		als.Rotation.Set(0, 0, 0);
+		ParseHelper_Node_Enter(ne);
+		MACRO_NODECHECK_LOOPBEGIN("instance");
+			MACRO_NODECHECK_READCOMP_F("deltax", read_flag[0], als.Delta.x);
+			MACRO_NODECHECK_READCOMP_F("deltay", read_flag[1], als.Delta.y);
+			MACRO_NODECHECK_READCOMP_F("deltaz", read_flag[2], als.Delta.z);
+			MACRO_NODECHECK_READCOMP_F("rx", read_flag[3], als.Rotation.x);
+			MACRO_NODECHECK_READCOMP_F("ry", read_flag[4], als.Rotation.y);
+			MACRO_NODECHECK_READCOMP_F("rz", read_flag[5], als.Rotation.z);
+		MACRO_NODECHECK_LOOPEND("instance");
+		ParseHelper_Node_Exit();
+		// also convert degrees to radians.
+		als.Rotation.x = AI_MATH_PI_F * als.Rotation.x / 180.0f;
+		als.Rotation.y = AI_MATH_PI_F * als.Rotation.y / 180.0f;
+		als.Rotation.z = AI_MATH_PI_F * als.Rotation.z / 180.0f;
+	}// if(!mReader->isEmptyElement())
+	else
+	{
+		mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element
+	}// if(!mReader->isEmptyElement()) else
+
+	mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph.
+}
+
+// <object
+// id="" - A unique ObjectID for the new object being defined.
+// >
+// </object>
+// An object definition.
+// Multi elements - Yes.
+// Parent element - <amf>.
+void AMFImporter::ParseNode_Object()
+{
+    std::string id;
+    CAMFImporter_NodeElement* ne( nullptr );
+
+	// Read attributes for node <object>.
+	MACRO_ATTRREAD_LOOPBEG;
+		MACRO_ATTRREAD_CHECK_RET("id", id, mReader->getAttributeValue);
+	MACRO_ATTRREAD_LOOPEND;
+
+	// create and if needed - define new geometry object.
+	ne = new CAMFImporter_NodeElement_Object(mNodeElement_Cur);
+
+	CAMFImporter_NodeElement_Object& als = *((CAMFImporter_NodeElement_Object*)ne);// alias for convenience
+
+	if(!id.empty()) als.ID = id;
+	// Check for child nodes
+	if(!mReader->isEmptyElement())
+	{
+		bool col_read = false;
+
+		ParseHelper_Node_Enter(ne);
+		MACRO_NODECHECK_LOOPBEGIN("object");
+			if(XML_CheckNode_NameEqual("color"))
+			{
+				// Check if color already defined for object.
+				if(col_read) Throw_MoreThanOnceDefined("color", "Only one color can be defined for <object>.");
+				// read data and set flag about it
+				ParseNode_Color();
+				col_read = true;
+
+				continue;
+			}
+
+			if(XML_CheckNode_NameEqual("mesh")) { ParseNode_Mesh(); continue; }
+			if(XML_CheckNode_NameEqual("metadata")) { ParseNode_Metadata(); continue; }
+		MACRO_NODECHECK_LOOPEND("object");
+		ParseHelper_Node_Exit();
+	}// if(!mReader->isEmptyElement())
+	else
+	{
+		mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element
+	}// if(!mReader->isEmptyElement()) else
+
+	mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph.
+}
+
+// <metadata
+// type="" - The type of the attribute.
+// >
+// </metadata>
+// Specify additional information about an entity.
+// Multi elements - Yes.
+// Parent element - <amf>, <object>, <volume>, <material>, <vertex>.
+//
+// Reserved types are:
+// "Name" - The alphanumeric label of the entity, to be used by the interpreter if interacting with the user.
+// "Description" - A description of the content of the entity
+// "URL" - A link to an external resource relating to the entity
+// "Author" - Specifies the name(s) of the author(s) of the entity
+// "Company" - Specifying the company generating the entity
+// "CAD" - specifies the name of the originating CAD software and version
+// "Revision" - specifies the revision of the entity
+// "Tolerance" - specifies the desired manufacturing tolerance of the entity in entity's unit system
+// "Volume" - specifies the total volume of the entity, in the entity's unit system, to be used for verification (object and volume only)
+void AMFImporter::ParseNode_Metadata()
+{
+    std::string type, value;
+    CAMFImporter_NodeElement* ne( nullptr );
+
+	// read attribute
+	MACRO_ATTRREAD_LOOPBEG;
+		MACRO_ATTRREAD_CHECK_RET("type", type, mReader->getAttributeValue);
+	MACRO_ATTRREAD_LOOPEND;
+	// and value of node.
+	value = mReader->getNodeData();
+	// Create node element and assign read data.
+	ne = new CAMFImporter_NodeElement_Metadata(mNodeElement_Cur);
+	((CAMFImporter_NodeElement_Metadata*)ne)->Type = type;
+	((CAMFImporter_NodeElement_Metadata*)ne)->Value = value;
+	mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element
+	mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph.
+}
+
+/*********************************************************************************************************************************************/
+/******************************************************** Functions: BaseImporter set ********************************************************/
+/*********************************************************************************************************************************************/
+
+bool AMFImporter::CanRead(const std::string& pFile, IOSystem* pIOHandler, bool pCheckSig) const
+{
+    const std::string extension = GetExtension(pFile);
+
+    if ( extension == "amf" ) {
+        return true;
+    }
+
+	if(!extension.length() || pCheckSig)
+	{
+		const char* tokens[] = { "<amf" };
+
+		return SearchFileHeaderForToken( pIOHandler, pFile, tokens, 1 );
+	}
+
+	return false;
+}
+
+void AMFImporter::GetExtensionList(std::set<std::string>& pExtensionList)
+{
+	pExtensionList.insert("amf");
+}
+
+const aiImporterDesc* AMFImporter::GetInfo () const
+{
+	return &Description;
+}
+
+void AMFImporter::InternReadFile(const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler)
+{
+	Clear();// delete old graph.
+	ParseFile(pFile, pIOHandler);
+	Postprocess_BuildScene(pScene);
+	// scene graph is ready, exit.
+}
+
+}// namespace Assimp
+
+#endif // !ASSIMP_BUILD_NO_AMF_IMPORTER

+ 432 - 0
Engine/lib/assimp/code/AMFImporter.hpp

@@ -0,0 +1,432 @@
+/*
+---------------------------------------------------------------------------
+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 AMFImporter.hpp
+/// \brief AMF-format files importer for Assimp.
+/// \date 2016
+/// \author [email protected]
+// Thanks to acorn89 for support.
+
+#pragma once
+#ifndef INCLUDED_AI_AMF_IMPORTER_H
+#define INCLUDED_AI_AMF_IMPORTER_H
+
+#include "AMFImporter_Node.hpp"
+
+// Header files, Assimp.
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/importerdesc.h>
+#include "assimp/types.h"
+#include <assimp/BaseImporter.h>
+#include <assimp/irrXMLWrapper.h>
+
+// Header files, stdlib.
+#include <set>
+
+namespace Assimp {
+
+/// \class AMFImporter
+/// Class that holding scene graph which include: geometry, metadata, materials etc.
+///
+/// Implementing features.
+///
+/// Limitations.
+///
+/// 1. When for texture mapping used set of source textures (r, g, b, a) not only one then attribute "tiled" for all set will be true if it true in any of
+///    source textures.
+///    Example. Triangle use for texture mapping three textures. Two of them has "tiled" set to false and one - set to true. In scene all three textures
+///    will be tiled.
+///
+/// Unsupported features:
+/// 1. Node <composite>, formulas in <composite> and <color>. For implementing this feature can be used expression parser "muParser" like in project
+///    "amf_tools".
+/// 2. Attribute "profile" in node <color>.
+/// 3. Curved geometry: <edge>, <normal> and children nodes of them.
+/// 4. Attributes: "unit" and "version" in <amf> read but do nothing.
+/// 5. <metadata> stored only for root node <amf>.
+/// 6. Color averaging of vertices for which <triangle>'s set different colors.
+///
+/// Supported nodes:
+///    General:
+///        <amf>; <constellation>; <instance> and children <deltax>, <deltay>, <deltaz>, <rx>, <ry>, <rz>; <metadata>;
+///
+///    Geometry:
+///        <object>; <mesh>; <vertices>; <vertex>; <coordinates> and children <x>, <y>, <z>; <volume>; <triangle> and children <v1>, <v2>, <v3>;
+///
+///    Material:
+///        <color> and children <r>, <g>, <b>, <a>; <texture>; <material>;
+///        two variants of texture coordinates:
+///            new - <texmap> and children <utex1>, <utex2>, <utex3>, <vtex1>, <vtex2>, <vtex3>
+///            old - <map> and children <u1>, <u2>, <u3>, <v1>, <v2>, <v3>
+///
+class AMFImporter : public BaseImporter {
+private:
+    struct SPP_Material;// forward declaration
+
+                        /// \struct SPP_Composite
+                        /// Data type for post-processing step. More suitable container for part of material's composition.
+    struct SPP_Composite {
+        SPP_Material* Material;///< Pointer to material - part of composition.
+        std::string Formula;///< Formula for calculating ratio of \ref Material.
+    };
+
+    /// \struct SPP_Material
+    /// Data type for post-processing step. More suitable container for material.
+    struct SPP_Material {
+        std::string ID;///< Material ID.
+        std::list<CAMFImporter_NodeElement_Metadata*> Metadata;///< Metadata of material.
+        CAMFImporter_NodeElement_Color* Color;///< Color of material.
+        std::list<SPP_Composite> Composition;///< List of child materials if current material is composition of few another.
+
+        /// Return color calculated for specified coordinate.
+        /// \param [in] pX - "x" coordinate.
+        /// \param [in] pY - "y" coordinate.
+        /// \param [in] pZ - "z" coordinate.
+        /// \return calculated color.
+        aiColor4D GetColor(const float pX, const float pY, const float pZ) const;
+    };
+
+    /// Data type for post-processing step. More suitable container for texture.
+    struct SPP_Texture {
+        std::string ID;
+        size_t      Width, Height, Depth;
+        bool        Tiled;
+        char        FormatHint[9];// 8 for string + 1 for terminator.
+        uint8_t    *Data;
+    };
+
+    /// Data type for post-processing step. Contain face data.
+    struct SComplexFace {
+        aiFace Face;///< Face vertices.
+        const CAMFImporter_NodeElement_Color* Color;///< Face color. Equal to nullptr if color is not set for the face.
+        const CAMFImporter_NodeElement_TexMap* TexMap;///< Face texture mapping data. Equal to nullptr if texture mapping is not set for the face.
+    };
+
+	/// Clear all temporary data.
+	void Clear();
+
+	/***********************************************/
+	/************* Functions: find set *************/
+	/***********************************************/
+
+	/// Find specified node element in node elements list ( \ref mNodeElement_List).
+	/// \param [in] pID - ID(name) of requested node element.
+	/// \param [in] pType - type of node element.
+	/// \param [out] pNode - pointer to pointer to item found.
+	/// \return true - if the node element is found, else - false.
+	bool Find_NodeElement(const std::string& pID, const CAMFImporter_NodeElement::EType pType, CAMFImporter_NodeElement** pNodeElement) const;
+
+	/// Find requested aiNode in node list.
+	/// \param [in] pID - ID(name) of requested node.
+	/// \param [in] pNodeList - list of nodes where to find the node.
+	/// \param [out] pNode - pointer to pointer to item found.
+	/// \return true - if the node is found, else - false.
+	bool Find_ConvertedNode(const std::string& pID, std::list<aiNode*>& pNodeList, aiNode** pNode) const;
+
+	/// Find material in list for converted materials. Use at postprocessing step.
+	/// \param [in] pID - material ID.
+	/// \param [out] pConvertedMaterial - pointer to found converted material (\ref SPP_Material).
+	/// \return true - if the material is found, else - false.
+	bool Find_ConvertedMaterial(const std::string& pID, const SPP_Material** pConvertedMaterial) const;
+
+    /// Find texture in list of converted textures. Use at postprocessing step,
+	/// \param [in] pID_R - ID of source "red" texture.
+	/// \param [in] pID_G - ID of source "green" texture.
+	/// \param [in] pID_B - ID of source "blue" texture.
+	/// \param [in] pID_A - ID of source "alpha" texture. Use empty string to find RGB-texture.
+	/// \param [out] pConvertedTextureIndex - pointer where index in list of found texture will be written. If equivalent to nullptr then nothing will be
+	/// written.
+	/// \return true - if the texture is found, else - false.
+	bool Find_ConvertedTexture(const std::string& pID_R, const std::string& pID_G, const std::string& pID_B, const std::string& pID_A,
+								uint32_t* pConvertedTextureIndex = nullptr) const;
+
+
+	/// Get data stored in <vertices> and place it to arrays.
+	/// \param [in] pNodeElement - reference to node element which kept <object> data.
+	/// \param [in] pVertexCoordinateArray - reference to vertices coordinates kept in <vertices>.
+	/// \param [in] pVertexColorArray - reference to vertices colors for all <vertex's. If color for vertex is not set then corresponding member of array
+	/// contain nullptr.
+	void PostprocessHelper_CreateMeshDataArray(const CAMFImporter_NodeElement_Mesh& pNodeElement, std::vector<aiVector3D>& pVertexCoordinateArray,
+												std::vector<CAMFImporter_NodeElement_Color*>& pVertexColorArray) const;
+
+	/// Return converted texture ID which related to specified source textures ID's. If converted texture does not exist then it will be created and ID on new
+	/// converted texture will be returned. Conversion: set of textures from \ref CAMFImporter_NodeElement_Texture to one \ref SPP_Texture and place it
+	/// to converted textures list.
+	/// Any of source ID's can be absent(empty string) or even one ID only specified. But at least one ID must be specified.
+	/// \param [in] pID_R - ID of source "red" texture.
+	/// \param [in] pID_G - ID of source "green" texture.
+	/// \param [in] pID_B - ID of source "blue" texture.
+	/// \param [in] pID_A - ID of source "alpha" texture.
+	/// \return index of the texture in array of the converted textures.
+	size_t PostprocessHelper_GetTextureID_Or_Create(const std::string& pID_R, const std::string& pID_G, const std::string& pID_B, const std::string& pID_A);
+
+	/// Separate input list by texture IDs. This step is needed because aiMesh can contain mesh which is use only one texture (or set: diffuse, bump etc).
+	/// \param [in] pInputList - input list with faces. Some of them can contain color or texture mapping, or both of them, or nothing. Will be cleared after
+	/// processing.
+	/// \param [out] pOutputList_Separated - output list of the faces lists. Separated faces list by used texture IDs. Will be cleared before processing.
+	void PostprocessHelper_SplitFacesByTextureID(std::list<SComplexFace>& pInputList, std::list<std::list<SComplexFace> >& pOutputList_Separated);
+
+	/// Check if child elements of node element is metadata and add it to scene node.
+	/// \param [in] pMetadataList - reference to list with collected metadata.
+	/// \param [out] pSceneNode - scene node in which metadata will be added.
+	void Postprocess_AddMetadata(const std::list<CAMFImporter_NodeElement_Metadata*>& pMetadataList, aiNode& pSceneNode) const;
+
+	/// To create aiMesh and aiNode for it from <object>.
+	/// \param [in] pNodeElement - reference to node element which kept <object> data.
+	/// \param [out] pMeshList - reference to a list with all aiMesh of the scene.
+	/// \param [out] pSceneNode - pointer to place where new aiNode will be created.
+	void Postprocess_BuildNodeAndObject(const CAMFImporter_NodeElement_Object& pNodeElement, std::list<aiMesh*>& pMeshList, aiNode** pSceneNode);
+
+	/// Create mesh for every <volume> in <mesh>.
+	/// \param [in] pNodeElement - reference to node element which kept <mesh> data.
+	/// \param [in] pVertexCoordinateArray - reference to vertices coordinates for all <volume>'s.
+	/// \param [in] pVertexColorArray - reference to vertices colors for all <volume>'s. If color for vertex is not set then corresponding member of array
+	/// contain nullptr.
+	/// \param [in] pObjectColor - pointer to colors for <object>. If color is not set then argument contain nullptr.
+	/// \param [in] pMaterialList - reference to a list with defined materials.
+	/// \param [out] pMeshList - reference to a list with all aiMesh of the scene.
+	/// \param [out] pSceneNode - reference to aiNode which will own new aiMesh's.
+	void Postprocess_BuildMeshSet(const CAMFImporter_NodeElement_Mesh& pNodeElement, const std::vector<aiVector3D>& pVertexCoordinateArray,
+									const std::vector<CAMFImporter_NodeElement_Color*>& pVertexColorArray, const CAMFImporter_NodeElement_Color* pObjectColor,
+									std::list<aiMesh*>& pMeshList, aiNode& pSceneNode);
+
+	/// Convert material from \ref CAMFImporter_NodeElement_Material to \ref SPP_Material.
+	/// \param [in] pMaterial - source CAMFImporter_NodeElement_Material.
+	void Postprocess_BuildMaterial(const CAMFImporter_NodeElement_Material& pMaterial);
+
+	/// Create and add to aiNode's list new part of scene graph defined by <constellation>.
+	/// \param [in] pConstellation - reference to <constellation> node.
+	/// \param [out] pNodeList - reference to aiNode's list.
+	void Postprocess_BuildConstellation(CAMFImporter_NodeElement_Constellation& pConstellation, std::list<aiNode*>& pNodeList) const;
+
+	/// Build Assimp scene graph in aiScene from collected data.
+	/// \param [out] pScene - pointer to aiScene where tree will be built.
+	void Postprocess_BuildScene(aiScene* pScene);
+
+
+	/// Call that function when close tag of node not found and exception must be raised.
+	/// E.g.:
+	/// <amf>
+	///     <object>
+	/// </amf> <!--- object not closed --->
+	/// \throw DeadlyImportError.
+	/// \param [in] pNode - node name in which exception happened.
+	void Throw_CloseNotFound(const std::string& pNode);
+
+	/// Call that function when attribute name is incorrect and exception must be raised.
+	/// \param [in] pAttrName - attribute name.
+	/// \throw DeadlyImportError.
+	void Throw_IncorrectAttr(const std::string& pAttrName);
+
+	/// Call that function when attribute value is incorrect and exception must be raised.
+	/// \param [in] pAttrName - attribute name.
+	/// \throw DeadlyImportError.
+	void Throw_IncorrectAttrValue(const std::string& pAttrName);
+
+	/// Call that function when some type of nodes are defined twice or more when must be used only once and exception must be raised.
+	/// E.g.:
+	/// <object>
+	///     <color>...    <!--- color defined --->
+	///     <color>...    <!--- color defined again --->
+	/// </object>
+	/// \throw DeadlyImportError.
+	/// \param [in] pNodeType - type of node which defined one more time.
+	/// \param [in] pDescription - message about error. E.g. what the node defined while exception raised.
+	void Throw_MoreThanOnceDefined(const std::string& pNodeType, const std::string& pDescription);
+
+	/// Call that function when referenced element ID are not found in graph and exception must be raised.
+	/// \param [in] pID - ID of of element which not found.
+	/// \throw DeadlyImportError.
+	void Throw_ID_NotFound(const std::string& pID) const;
+
+	/// Check if current node have children: <node>...</node>. If not then exception will throwed.
+	void XML_CheckNode_MustHaveChildren();
+
+	/// Check if current node name is equal to pNodeName.
+	/// \param [in] pNodeName - name for checking.
+	/// return true if current node name is equal to pNodeName, else - false.
+	bool XML_CheckNode_NameEqual(const std::string& pNodeName) { return mReader->getNodeName() == pNodeName; }
+
+	/// Skip unsupported node and report about that. Depend on node name can be skipped begin tag of node all whole node.
+	/// \param [in] pParentNodeName - parent node name. Used for reporting.
+	void XML_CheckNode_SkipUnsupported(const std::string& pParentNodeName);
+
+	/// Search for specified node in file. XML file read pointer(mReader) will point to found node or file end after search is end.
+	/// \param [in] pNodeName - requested node name.
+	/// return true - if node is found, else - false.
+	bool XML_SearchNode(const std::string& pNodeName);
+
+	/// Read attribute value.
+	/// \param [in] pAttrIdx - attribute index (\ref mReader->getAttribute* set).
+	/// \return read data.
+	bool XML_ReadNode_GetAttrVal_AsBool(const int pAttrIdx);
+
+	/// Read attribute value.
+	/// \param [in] pAttrIdx - attribute index (\ref mReader->getAttribute* set).
+	/// \return read data.
+	float XML_ReadNode_GetAttrVal_AsFloat(const int pAttrIdx);
+
+	/// Read attribute value.
+	/// \param [in] pAttrIdx - attribute index (\ref mReader->getAttribute* set).
+	/// \return read data.
+	uint32_t XML_ReadNode_GetAttrVal_AsU32(const int pAttrIdx);
+
+	/// Read node value.
+	/// \return read data.
+	float XML_ReadNode_GetVal_AsFloat();
+
+	/// Read node value.
+	/// \return read data.
+	uint32_t XML_ReadNode_GetVal_AsU32();
+
+	/// Read node value.
+	/// \return read data.
+	void XML_ReadNode_GetVal_AsString(std::string& pValue);
+
+	/// Make pNode as current and enter deeper for parsing child nodes. At end \ref ParseHelper_Node_Exit must be called.
+	/// \param [in] pNode - new current node.
+	void ParseHelper_Node_Enter(CAMFImporter_NodeElement* pNode);
+
+	/// This function must be called when exiting from grouping node. \ref ParseHelper_Group_Begin.
+	void ParseHelper_Node_Exit();
+
+	/// Attribute values of floating point types can take form ".x"(without leading zero). irrXMLReader can not read this form of values and it
+	/// must be converted to right form - "0.xxx".
+	/// \param [in] pInStr - pointer to input string which can contain incorrect form of values.
+	/// \param [out[ pOutString - output string with right form of values.
+	void ParseHelper_FixTruncatedFloatString(const char* pInStr, std::string& pOutString);
+
+	/// Decode Base64-encoded data.
+	/// \param [in] pInputBase64 - reference to input Base64-encoded string.
+	/// \param [out] pOutputData - reference to output array for decoded data.
+	void ParseHelper_Decode_Base64(const std::string& pInputBase64, std::vector<uint8_t>& pOutputData) const;
+
+	/// Parse <AMF> node of the file.
+	void ParseNode_Root();
+
+	/// Parse <constellation> node of the file.
+	void ParseNode_Constellation();
+
+	/// Parse <instance> node of the file.
+	void ParseNode_Instance();
+
+	/// Parse <material> node of the file.
+	void ParseNode_Material();
+
+	/// Parse <metadata> node.
+	void ParseNode_Metadata();
+
+	/// Parse <object> node of the file.
+	void ParseNode_Object();
+
+	/// Parse <texture> node of the file.
+	void ParseNode_Texture();
+
+	/// Parse <coordinates> node of the file.
+	void ParseNode_Coordinates();
+
+	/// Parse <edge> node of the file.
+	void ParseNode_Edge();
+
+	/// Parse <mesh> node of the file.
+	void ParseNode_Mesh();
+
+	/// Parse <triangle> node of the file.
+	void ParseNode_Triangle();
+
+	/// Parse <vertex> node of the file.
+	void ParseNode_Vertex();
+
+	/// Parse <vertices> node of the file.
+	void ParseNode_Vertices();
+
+	/// Parse <volume> node of the file.
+	void ParseNode_Volume();
+
+	/// Parse <color> node of the file.
+	void ParseNode_Color();
+
+	/// Parse <texmap> of <map> node of the file.
+	/// \param [in] pUseOldName - if true then use old name of node(and children) - <map>, instead of new name - <texmap>.
+	void ParseNode_TexMap(const bool pUseOldName = false);
+
+public:
+	/// Default constructor.
+	AMFImporter() AI_NO_EXCEPT
+    : mNodeElement_Cur(nullptr)
+    , mReader(nullptr) {
+        // empty
+    }
+
+	/// Default destructor.
+	~AMFImporter();
+
+	/// Parse AMF file and fill scene graph. The function has no return value. Result can be found by analyzing the generated graph.
+	/// Also exception can be thrown if trouble will found.
+	/// \param [in] pFile - name of file to be parsed.
+	/// \param [in] pIOHandler - pointer to IO helper object.
+	void ParseFile(const std::string& pFile, IOSystem* pIOHandler);
+
+	bool CanRead(const std::string& pFile, IOSystem* pIOHandler, bool pCheckSig) const;
+	void GetExtensionList(std::set<std::string>& pExtensionList);
+	void InternReadFile(const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler);
+	const aiImporterDesc* GetInfo ()const;
+
+    AMFImporter(const AMFImporter& pScene) = delete;
+    AMFImporter& operator=(const AMFImporter& pScene) = delete;
+
+private:
+    static const aiImporterDesc Description;
+
+    CAMFImporter_NodeElement* mNodeElement_Cur;///< Current element.
+    std::list<CAMFImporter_NodeElement*> mNodeElement_List;///< All elements of scene graph.
+    irr::io::IrrXMLReader* mReader;///< Pointer to XML-reader object
+    std::string mUnit;
+    std::list<SPP_Material> mMaterial_Converted;///< List of converted materials for postprocessing step.
+    std::list<SPP_Texture> mTexture_Converted;///< List of converted textures for postprocessing step.
+
+};
+
+}// namespace Assimp
+
+#endif // INCLUDED_AI_AMF_IMPORTER_H

+ 357 - 0
Engine/lib/assimp/code/AMFImporter_Geometry.cpp

@@ -0,0 +1,357 @@
+/*
+---------------------------------------------------------------------------
+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 AMFImporter_Geometry.cpp
+/// \brief Parsing data from geometry nodes.
+/// \date 2016
+/// \author [email protected]
+
+#ifndef ASSIMP_BUILD_NO_AMF_IMPORTER
+
+#include "AMFImporter.hpp"
+#include "AMFImporter_Macro.hpp"
+
+namespace Assimp
+{
+
+// <mesh>
+// </mesh>
+// A 3D mesh hull.
+// Multi elements - Yes.
+// Parent element - <object>.
+void AMFImporter::ParseNode_Mesh()
+{
+CAMFImporter_NodeElement* ne;
+
+	// create new mesh object.
+	ne = new CAMFImporter_NodeElement_Mesh(mNodeElement_Cur);
+	// Check for child nodes
+	if(!mReader->isEmptyElement())
+	{
+		bool vert_read = false;
+
+		ParseHelper_Node_Enter(ne);
+		MACRO_NODECHECK_LOOPBEGIN("mesh");
+			if(XML_CheckNode_NameEqual("vertices"))
+			{
+				// Check if data already defined.
+				if(vert_read) Throw_MoreThanOnceDefined("vertices", "Only one vertices set can be defined for <mesh>.");
+				// read data and set flag about it
+				ParseNode_Vertices();
+				vert_read = true;
+
+				continue;
+			}
+
+			if(XML_CheckNode_NameEqual("volume")) { ParseNode_Volume(); continue; }
+		MACRO_NODECHECK_LOOPEND("mesh");
+		ParseHelper_Node_Exit();
+	}// if(!mReader->isEmptyElement())
+	else
+	{
+		mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element
+	}// if(!mReader->isEmptyElement()) else
+
+	mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph.
+}
+
+// <vertices>
+// </vertices>
+// The list of vertices to be used in defining triangles.
+// Multi elements - No.
+// Parent element - <mesh>.
+void AMFImporter::ParseNode_Vertices()
+{
+CAMFImporter_NodeElement* ne;
+
+	// create new mesh object.
+	ne = new CAMFImporter_NodeElement_Vertices(mNodeElement_Cur);
+	// Check for child nodes
+	if(!mReader->isEmptyElement())
+	{
+		ParseHelper_Node_Enter(ne);
+		MACRO_NODECHECK_LOOPBEGIN("vertices");
+			if(XML_CheckNode_NameEqual("vertex")) { ParseNode_Vertex(); continue; }
+		MACRO_NODECHECK_LOOPEND("vertices");
+		ParseHelper_Node_Exit();
+	}// if(!mReader->isEmptyElement())
+	else
+	{
+		mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element
+	}// if(!mReader->isEmptyElement()) else
+
+	mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph.
+}
+
+// <vertex>
+// </vertex>
+// A vertex to be referenced in triangles.
+// Multi elements - Yes.
+// Parent element - <vertices>.
+void AMFImporter::ParseNode_Vertex()
+{
+CAMFImporter_NodeElement* ne;
+
+	// create new mesh object.
+	ne = new CAMFImporter_NodeElement_Vertex(mNodeElement_Cur);
+	// Check for child nodes
+	if(!mReader->isEmptyElement())
+	{
+		bool col_read = false;
+		bool coord_read = false;
+
+		ParseHelper_Node_Enter(ne);
+		MACRO_NODECHECK_LOOPBEGIN("vertex");
+			if(XML_CheckNode_NameEqual("color"))
+			{
+				// Check if data already defined.
+				if(col_read) Throw_MoreThanOnceDefined("color", "Only one color can be defined for <vertex>.");
+				// read data and set flag about it
+				ParseNode_Color();
+				col_read = true;
+
+				continue;
+			}
+
+			if(XML_CheckNode_NameEqual("coordinates"))
+			{
+				// Check if data already defined.
+				if(coord_read) Throw_MoreThanOnceDefined("coordinates", "Only one coordinates set can be defined for <vertex>.");
+				// read data and set flag about it
+				ParseNode_Coordinates();
+				coord_read = true;
+
+				continue;
+			}
+
+			if(XML_CheckNode_NameEqual("metadata")) { ParseNode_Metadata(); continue; }
+		MACRO_NODECHECK_LOOPEND("vertex");
+		ParseHelper_Node_Exit();
+	}// if(!mReader->isEmptyElement())
+	else
+	{
+		mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element
+	}// if(!mReader->isEmptyElement()) else
+
+	mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph.
+}
+
+// <coordinates>
+// </coordinates>
+// Specifies the 3D location of this vertex.
+// Multi elements - No.
+// Parent element - <vertex>.
+//
+// Children elements:
+//   <x>, <y>, <z>
+//   Multi elements - No.
+//   X, Y, or Z coordinate, respectively, of a vertex position in space.
+void AMFImporter::ParseNode_Coordinates()
+{
+CAMFImporter_NodeElement* ne;
+
+	// create new color object.
+	ne = new CAMFImporter_NodeElement_Coordinates(mNodeElement_Cur);
+
+	CAMFImporter_NodeElement_Coordinates& als = *((CAMFImporter_NodeElement_Coordinates*)ne);// alias for convenience
+
+	// Check for child nodes
+	if(!mReader->isEmptyElement())
+	{
+		bool read_flag[3] = { false, false, false };
+
+		ParseHelper_Node_Enter(ne);
+		MACRO_NODECHECK_LOOPBEGIN("coordinates");
+			MACRO_NODECHECK_READCOMP_F("x", read_flag[0], als.Coordinate.x);
+			MACRO_NODECHECK_READCOMP_F("y", read_flag[1], als.Coordinate.y);
+			MACRO_NODECHECK_READCOMP_F("z", read_flag[2], als.Coordinate.z);
+		MACRO_NODECHECK_LOOPEND("coordinates");
+		ParseHelper_Node_Exit();
+		// check that all components was defined
+		if((read_flag[0] && read_flag[1] && read_flag[2]) == 0) throw DeadlyImportError("Not all coordinate's components are defined.");
+
+	}// if(!mReader->isEmptyElement())
+	else
+	{
+		mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element
+	}// if(!mReader->isEmptyElement()) else
+
+	mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph.
+}
+
+// <volume
+// materialid="" - Which material to use.
+// type=""       - What this volume describes can be “region” or “support”. If none specified, “object” is assumed. If support, then the geometric
+//                 requirements 1-8 listed in section 5 do not need to be maintained.
+// >
+// </volume>
+// Defines a volume from the established vertex list.
+// Multi elements - Yes.
+// Parent element - <mesh>.
+void AMFImporter::ParseNode_Volume()
+{
+std::string materialid;
+std::string type;
+CAMFImporter_NodeElement* ne;
+
+	// Read attributes for node <color>.
+	MACRO_ATTRREAD_LOOPBEG;
+		MACRO_ATTRREAD_CHECK_RET("materialid", materialid, mReader->getAttributeValue);
+		MACRO_ATTRREAD_CHECK_RET("type", type, mReader->getAttributeValue);
+	MACRO_ATTRREAD_LOOPEND;
+
+	// create new object.
+	ne = new CAMFImporter_NodeElement_Volume(mNodeElement_Cur);
+	// and assign read data
+	((CAMFImporter_NodeElement_Volume*)ne)->MaterialID = materialid;
+	((CAMFImporter_NodeElement_Volume*)ne)->Type = type;
+	// Check for child nodes
+	if(!mReader->isEmptyElement())
+	{
+		bool col_read = false;
+
+		ParseHelper_Node_Enter(ne);
+		MACRO_NODECHECK_LOOPBEGIN("volume");
+			if(XML_CheckNode_NameEqual("color"))
+			{
+				// Check if data already defined.
+				if(col_read) Throw_MoreThanOnceDefined("color", "Only one color can be defined for <volume>.");
+				// read data and set flag about it
+				ParseNode_Color();
+				col_read = true;
+
+				continue;
+			}
+
+			if(XML_CheckNode_NameEqual("triangle")) { ParseNode_Triangle(); continue; }
+			if(XML_CheckNode_NameEqual("metadata")) { ParseNode_Metadata(); continue; }
+		MACRO_NODECHECK_LOOPEND("volume");
+		ParseHelper_Node_Exit();
+	}// if(!mReader->isEmptyElement())
+	else
+	{
+		mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element
+	}// if(!mReader->isEmptyElement()) else
+
+	mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph.
+}
+
+// <triangle>
+// </triangle>
+// Defines a 3D triangle from three vertices, according to the right-hand rule (counter-clockwise when looking from the outside).
+// Multi elements - Yes.
+// Parent element - <volume>.
+//
+// Children elements:
+//   <v1>, <v2>, <v3>
+//   Multi elements - No.
+//   Index of the desired vertices in a triangle or edge.
+void AMFImporter::ParseNode_Triangle()
+{
+CAMFImporter_NodeElement* ne;
+
+	// create new color object.
+	ne = new CAMFImporter_NodeElement_Triangle(mNodeElement_Cur);
+
+	CAMFImporter_NodeElement_Triangle& als = *((CAMFImporter_NodeElement_Triangle*)ne);// alias for convenience
+
+	// Check for child nodes
+	if(!mReader->isEmptyElement())
+	{
+		bool col_read = false, tex_read = false;
+		bool read_flag[3] = { false, false, false };
+
+		ParseHelper_Node_Enter(ne);
+		MACRO_NODECHECK_LOOPBEGIN("triangle");
+			if(XML_CheckNode_NameEqual("color"))
+			{
+				// Check if data already defined.
+				if(col_read) Throw_MoreThanOnceDefined("color", "Only one color can be defined for <triangle>.");
+				// read data and set flag about it
+				ParseNode_Color();
+				col_read = true;
+
+				continue;
+			}
+
+			if(XML_CheckNode_NameEqual("texmap"))// new name of node: "texmap".
+			{
+				// Check if data already defined.
+				if(tex_read) Throw_MoreThanOnceDefined("texmap", "Only one texture coordinate can be defined for <triangle>.");
+				// read data and set flag about it
+				ParseNode_TexMap();
+				tex_read = true;
+
+				continue;
+			}
+			else if(XML_CheckNode_NameEqual("map"))// old name of node: "map".
+			{
+				// Check if data already defined.
+				if(tex_read) Throw_MoreThanOnceDefined("map", "Only one texture coordinate can be defined for <triangle>.");
+				// read data and set flag about it
+				ParseNode_TexMap(true);
+				tex_read = true;
+
+				continue;
+			}
+
+			MACRO_NODECHECK_READCOMP_U32("v1", read_flag[0], als.V[0]);
+			MACRO_NODECHECK_READCOMP_U32("v2", read_flag[1], als.V[1]);
+			MACRO_NODECHECK_READCOMP_U32("v3", read_flag[2], als.V[2]);
+		MACRO_NODECHECK_LOOPEND("triangle");
+		ParseHelper_Node_Exit();
+		// check that all components was defined
+		if((read_flag[0] && read_flag[1] && read_flag[2]) == 0) throw DeadlyImportError("Not all vertices of the triangle are defined.");
+
+	}// if(!mReader->isEmptyElement())
+	else
+	{
+		mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element
+	}// if(!mReader->isEmptyElement()) else
+
+	mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph.
+}
+
+}// namespace Assimp
+
+#endif // !ASSIMP_BUILD_NO_AMF_IMPORTER

+ 166 - 0
Engine/lib/assimp/code/AMFImporter_Macro.hpp

@@ -0,0 +1,166 @@
+/*
+---------------------------------------------------------------------------
+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 AMFImporter_Macro.hpp
+/// \brief Useful macrodefines.
+/// \date 2016
+/// \author [email protected]
+
+#pragma once
+#ifndef AMFIMPORTER_MACRO_HPP_INCLUDED
+#define AMFIMPORTER_MACRO_HPP_INCLUDED
+
+/// \def MACRO_ATTRREAD_LOOPBEG
+/// Begin of loop that read attributes values.
+#define MACRO_ATTRREAD_LOOPBEG \
+	for(int idx = 0, idx_end = mReader->getAttributeCount(); idx < idx_end; idx++) \
+	{ \
+		std::string an(mReader->getAttributeName(idx));
+
+/// \def MACRO_ATTRREAD_LOOPEND
+/// End of loop that read attributes values.
+#define MACRO_ATTRREAD_LOOPEND \
+		Throw_IncorrectAttr(an); \
+	}
+
+/// \def MACRO_ATTRREAD_LOOPEND_WSKIP
+/// End of loop that read attributes values. Difference from \ref MACRO_ATTRREAD_LOOPEND in that: current macro skip unknown attributes, but
+/// \ref MACRO_ATTRREAD_LOOPEND throw an exception.
+#define MACRO_ATTRREAD_LOOPEND_WSKIP \
+		continue; \
+	}
+
+/// \def MACRO_ATTRREAD_CHECK_REF
+/// Check current attribute name and if it equal to requested then read value. Result write to output variable by reference. If result was read then
+/// "continue" will called.
+/// \param [in] pAttrName - attribute name.
+/// \param [out] pVarName - output variable name.
+/// \param [in] pFunction - function which read attribute value and write it to pVarName.
+#define MACRO_ATTRREAD_CHECK_REF(pAttrName, pVarName, pFunction) \
+	if(an == pAttrName) \
+	{ \
+		pFunction(idx, pVarName); \
+		continue; \
+	}
+
+/// \def MACRO_ATTRREAD_CHECK_RET
+/// Check current attribute name and if it equal to requested then read value. Result write to output variable using return value of \ref pFunction.
+/// If result was read then  "continue" will called.
+/// \param [in] pAttrName - attribute name.
+/// \param [out] pVarName - output variable name.
+/// \param [in] pFunction - function which read attribute value and write it to pVarName.
+#define MACRO_ATTRREAD_CHECK_RET(pAttrName, pVarName, pFunction) \
+	if(an == pAttrName) \
+	{ \
+		pVarName = pFunction(idx); \
+		continue; \
+	}
+
+/// \def MACRO_NODECHECK_LOOPBEGIN(pNodeName)
+/// Begin of loop of parsing child nodes. Do not add ';' at end.
+/// \param [in] pNodeName - current node name.
+#define MACRO_NODECHECK_LOOPBEGIN(pNodeName) \
+	do { \
+	bool close_found = false; \
+	 \
+	while(mReader->read()) \
+	{ \
+		if(mReader->getNodeType() == irr::io::EXN_ELEMENT) \
+		{
+
+/// \def MACRO_NODECHECK_LOOPEND(pNodeName)
+/// End of loop of parsing child nodes.
+/// \param [in] pNodeName - current node name.
+#define MACRO_NODECHECK_LOOPEND(pNodeName) \
+			XML_CheckNode_SkipUnsupported(pNodeName); \
+		}/* if(mReader->getNodeType() == irr::io::EXN_ELEMENT) */ \
+		else if(mReader->getNodeType() == irr::io::EXN_ELEMENT_END) \
+		{ \
+			if(XML_CheckNode_NameEqual(pNodeName)) \
+			{ \
+				close_found = true; \
+	 \
+				break; \
+			} \
+		}/* else if(mReader->getNodeType() == irr::io::EXN_ELEMENT_END) */ \
+	}/* while(mReader->read()) */ \
+	 \
+	if(!close_found) Throw_CloseNotFound(pNodeName); \
+	 \
+	} while(false)
+
+/// \def MACRO_NODECHECK_READCOMP_F
+/// Check current node name and if it equal to requested then read value. Result write to output variable of type "float".
+/// If result was read then  "continue" will called. Also check if node data already read then raise exception.
+/// \param [in] pNodeName - node name.
+/// \param [in, out] pReadFlag - read flag.
+/// \param [out] pVarName - output variable name.
+#define MACRO_NODECHECK_READCOMP_F(pNodeName, pReadFlag, pVarName) \
+	if(XML_CheckNode_NameEqual(pNodeName)) \
+	{ \
+		/* Check if field already read before. */ \
+		if(pReadFlag) Throw_MoreThanOnceDefined(pNodeName, "Only one component can be defined."); \
+		/* Read color component and assign it to object. */ \
+		pVarName = XML_ReadNode_GetVal_AsFloat(); \
+		pReadFlag = true; \
+		continue; \
+	}
+
+/// \def MACRO_NODECHECK_READCOMP_U32
+/// Check current node name and if it equal to requested then read value. Result write to output variable of type "uint32_t".
+/// If result was read then  "continue" will called. Also check if node data already read then raise exception.
+/// \param [in] pNodeName - node name.
+/// \param [in, out] pReadFlag - read flag.
+/// \param [out] pVarName - output variable name.
+#define MACRO_NODECHECK_READCOMP_U32(pNodeName, pReadFlag, pVarName) \
+	if(XML_CheckNode_NameEqual(pNodeName)) \
+	{ \
+		/* Check if field already read before. */ \
+		if(pReadFlag) Throw_MoreThanOnceDefined(pNodeName, "Only one component can be defined."); \
+		/* Read color component and assign it to object. */ \
+		pVarName = XML_ReadNode_GetVal_AsU32(); \
+		pReadFlag = true; \
+		continue; \
+	}
+
+#endif // AMFIMPORTER_MACRO_HPP_INCLUDED

+ 329 - 0
Engine/lib/assimp/code/AMFImporter_Material.cpp

@@ -0,0 +1,329 @@
+/*
+---------------------------------------------------------------------------
+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 AMFImporter_Material.cpp
+/// \brief Parsing data from material nodes.
+/// \date 2016
+/// \author [email protected]
+
+#ifndef ASSIMP_BUILD_NO_AMF_IMPORTER
+
+#include "AMFImporter.hpp"
+#include "AMFImporter_Macro.hpp"
+
+namespace Assimp
+{
+
+// <color
+// profile="" - The ICC color space used to interpret the three color channels <r>, <g> and <b>.
+// >
+// </color>
+// A color definition.
+// Multi elements - No.
+// Parent element - <material>, <object>, <volume>, <vertex>, <triangle>.
+//
+// "profile" can be one of "sRGB", "AdobeRGB", "Wide-Gamut-RGB", "CIERGB", "CIELAB", or "CIEXYZ".
+// Children elements:
+//   <r>, <g>, <b>, <a>
+//   Multi elements - No.
+//   Red, Greed, Blue and Alpha (transparency) component of a color in sRGB space, values ranging from 0 to 1. The
+//   values can be specified as constants, or as a formula depending on the coordinates.
+void AMFImporter::ParseNode_Color() {
+    std::string profile;
+    CAMFImporter_NodeElement* ne;
+
+	// Read attributes for node <color>.
+	MACRO_ATTRREAD_LOOPBEG;
+		MACRO_ATTRREAD_CHECK_RET("profile", profile, mReader->getAttributeValue);
+	MACRO_ATTRREAD_LOOPEND;
+
+	// create new color object.
+	ne = new CAMFImporter_NodeElement_Color(mNodeElement_Cur);
+
+	CAMFImporter_NodeElement_Color& als = *((CAMFImporter_NodeElement_Color*)ne);// alias for convenience
+
+	als.Profile = profile;
+	// Check for child nodes
+	if(!mReader->isEmptyElement())
+	{
+		bool read_flag[4] = { false, false, false, false };
+
+		ParseHelper_Node_Enter(ne);
+		MACRO_NODECHECK_LOOPBEGIN("color");
+			MACRO_NODECHECK_READCOMP_F("r", read_flag[0], als.Color.r);
+			MACRO_NODECHECK_READCOMP_F("g", read_flag[1], als.Color.g);
+			MACRO_NODECHECK_READCOMP_F("b", read_flag[2], als.Color.b);
+			MACRO_NODECHECK_READCOMP_F("a", read_flag[3], als.Color.a);
+		MACRO_NODECHECK_LOOPEND("color");
+		ParseHelper_Node_Exit();
+		// check that all components was defined
+        if (!(read_flag[0] && read_flag[1] && read_flag[2])) {
+            throw DeadlyImportError("Not all color components are defined.");
+        }
+
+        // check if <a> is absent. Then manually add "a == 1".
+        if (!read_flag[3]) {
+            als.Color.a = 1;
+        }
+	}
+	else
+	{
+		mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element
+	}
+
+	als.Composed = false;
+	mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph.
+}
+
+// <material
+// id="" - A unique material id. material ID "0" is reserved to denote no material (void) or sacrificial material.
+// >
+// </material>
+// An available material.
+// Multi elements - Yes.
+// Parent element - <amf>.
+void AMFImporter::ParseNode_Material() {
+    std::string id;
+    CAMFImporter_NodeElement* ne;
+
+	// Read attributes for node <color>.
+	MACRO_ATTRREAD_LOOPBEG;
+		MACRO_ATTRREAD_CHECK_RET("id", id, mReader->getAttributeValue);
+	MACRO_ATTRREAD_LOOPEND;
+
+	// create new object.
+	ne = new CAMFImporter_NodeElement_Material(mNodeElement_Cur);
+
+    // and assign read data
+	((CAMFImporter_NodeElement_Material*)ne)->ID = id;
+
+    // Check for child nodes
+	if(!mReader->isEmptyElement())
+	{
+		bool col_read = false;
+
+		ParseHelper_Node_Enter(ne);
+		MACRO_NODECHECK_LOOPBEGIN("material");
+			if(XML_CheckNode_NameEqual("color"))
+			{
+				// Check if data already defined.
+				if(col_read) Throw_MoreThanOnceDefined("color", "Only one color can be defined for <material>.");
+				// read data and set flag about it
+				ParseNode_Color();
+				col_read = true;
+
+				continue;
+			}
+
+			if(XML_CheckNode_NameEqual("metadata")) { ParseNode_Metadata(); continue; }
+		MACRO_NODECHECK_LOOPEND("material");
+		ParseHelper_Node_Exit();
+	}
+	else
+	{
+		mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element
+	}
+
+	mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph.
+}
+
+// <texture
+// id=""     - Assigns a unique texture id for the new texture.
+// width=""  - Width (horizontal size, x) of the texture, in pixels.
+// height="" - Height (lateral size, y) of the texture, in pixels.
+// depth=""  - Depth (vertical size, z) of the texture, in pixels.
+// type=""   - Encoding of the data in the texture. Currently allowed values are "grayscale" only. In grayscale mode, each pixel is represented by one byte
+//   in the range of 0-255. When the texture is referenced using the tex function, these values are converted into a single floating point number in the
+//   range of 0-1 (see Annex 2). A full color graphics will typically require three textures, one for each of the color channels. A graphic involving
+//   transparency may require a fourth channel.
+// tiled=""  - If true then texture repeated when UV-coordinates is greater than 1.
+// >
+// </triangle>
+// Specifies an texture data to be used as a map. Lists a sequence of Base64 values specifying values for pixels from left to right then top to bottom,
+// then layer by layer.
+// Multi elements - Yes.
+// Parent element - <amf>.
+void AMFImporter::ParseNode_Texture()
+{
+    std::string id;
+    uint32_t width = 0;
+    uint32_t height = 0;
+    uint32_t depth = 1;
+    std::string type;
+    bool tiled = false;
+    std::string enc64_data;
+
+	// Read attributes for node <color>.
+	MACRO_ATTRREAD_LOOPBEG;
+		MACRO_ATTRREAD_CHECK_RET("id", id, mReader->getAttributeValue);
+		MACRO_ATTRREAD_CHECK_RET("width", width, XML_ReadNode_GetAttrVal_AsU32);
+		MACRO_ATTRREAD_CHECK_RET("height", height, XML_ReadNode_GetAttrVal_AsU32);
+		MACRO_ATTRREAD_CHECK_RET("depth", depth, XML_ReadNode_GetAttrVal_AsU32);
+		MACRO_ATTRREAD_CHECK_RET("type", type, mReader->getAttributeValue);
+		MACRO_ATTRREAD_CHECK_RET("tiled", tiled, XML_ReadNode_GetAttrVal_AsBool);
+	MACRO_ATTRREAD_LOOPEND;
+
+	// create new texture object.
+    CAMFImporter_NodeElement *ne = new CAMFImporter_NodeElement_Texture(mNodeElement_Cur);
+
+	CAMFImporter_NodeElement_Texture& als = *((CAMFImporter_NodeElement_Texture*)ne);// alias for convenience
+
+	// Check for child nodes
+    if (!mReader->isEmptyElement()) {
+        XML_ReadNode_GetVal_AsString(enc64_data);
+    }
+
+	// check that all components was defined
+    if (id.empty()) {
+        throw DeadlyImportError("ID for texture must be defined.");
+    }
+    if (width < 1) {
+        Throw_IncorrectAttrValue("width");
+    }
+    if (height < 1) {
+        Throw_IncorrectAttrValue("height");
+    }
+    if (depth < 1) {
+        Throw_IncorrectAttrValue("depth");
+    }
+    if (type != "grayscale") {
+        Throw_IncorrectAttrValue("type");
+    }
+    if (enc64_data.empty()) {
+        throw DeadlyImportError("Texture data not defined.");
+    }
+	// copy data
+	als.ID = id;
+	als.Width = width;
+	als.Height = height;
+	als.Depth = depth;
+	als.Tiled = tiled;
+	ParseHelper_Decode_Base64(enc64_data, als.Data);
+
+    // check data size
+    if ((width * height * depth) != als.Data.size()) {
+        throw DeadlyImportError("Texture has incorrect data size.");
+    }
+
+	mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element
+	mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph.
+}
+
+// <texmap
+// rtexid="" - Texture ID for red color component.
+// gtexid="" - Texture ID for green color component.
+// btexid="" - Texture ID for blue color component.
+// atexid="" - Texture ID for alpha color component. Optional.
+// >
+// </texmap>, old name: <map>
+// Specifies texture coordinates for triangle.
+// Multi elements - No.
+// Parent element - <triangle>.
+// Children elements:
+//   <utex1>, <utex2>, <utex3>, <vtex1>, <vtex2>, <vtex3>. Old name: <u1>, <u2>, <u3>, <v1>, <v2>, <v3>.
+//   Multi elements - No.
+//   Texture coordinates for every vertex of triangle.
+void AMFImporter::ParseNode_TexMap(const bool pUseOldName) {
+    std::string rtexid, gtexid, btexid, atexid;
+
+	// Read attributes for node <color>.
+	MACRO_ATTRREAD_LOOPBEG;
+		MACRO_ATTRREAD_CHECK_RET("rtexid", rtexid, mReader->getAttributeValue);
+		MACRO_ATTRREAD_CHECK_RET("gtexid", gtexid, mReader->getAttributeValue);
+		MACRO_ATTRREAD_CHECK_RET("btexid", btexid, mReader->getAttributeValue);
+		MACRO_ATTRREAD_CHECK_RET("atexid", atexid, mReader->getAttributeValue);
+	MACRO_ATTRREAD_LOOPEND;
+
+	// create new texture coordinates object.
+    CAMFImporter_NodeElement *ne = new CAMFImporter_NodeElement_TexMap(mNodeElement_Cur);
+
+	CAMFImporter_NodeElement_TexMap& als = *((CAMFImporter_NodeElement_TexMap*)ne);// alias for convenience
+	// check data
+	if(rtexid.empty() && gtexid.empty() && btexid.empty()) throw DeadlyImportError("ParseNode_TexMap. At least one texture ID must be defined.");
+	// Check for children nodes
+	XML_CheckNode_MustHaveChildren();
+	// read children nodes
+	bool read_flag[6] = { false, false, false, false, false, false };
+
+	ParseHelper_Node_Enter(ne);
+	if(!pUseOldName)
+	{
+		MACRO_NODECHECK_LOOPBEGIN("texmap");
+			MACRO_NODECHECK_READCOMP_F("utex1", read_flag[0], als.TextureCoordinate[0].x);
+			MACRO_NODECHECK_READCOMP_F("utex2", read_flag[1], als.TextureCoordinate[1].x);
+			MACRO_NODECHECK_READCOMP_F("utex3", read_flag[2], als.TextureCoordinate[2].x);
+			MACRO_NODECHECK_READCOMP_F("vtex1", read_flag[3], als.TextureCoordinate[0].y);
+			MACRO_NODECHECK_READCOMP_F("vtex2", read_flag[4], als.TextureCoordinate[1].y);
+			MACRO_NODECHECK_READCOMP_F("vtex3", read_flag[5], als.TextureCoordinate[2].y);
+		MACRO_NODECHECK_LOOPEND("texmap");
+	}
+	else
+	{
+		MACRO_NODECHECK_LOOPBEGIN("map");
+			MACRO_NODECHECK_READCOMP_F("u1", read_flag[0], als.TextureCoordinate[0].x);
+			MACRO_NODECHECK_READCOMP_F("u2", read_flag[1], als.TextureCoordinate[1].x);
+			MACRO_NODECHECK_READCOMP_F("u3", read_flag[2], als.TextureCoordinate[2].x);
+			MACRO_NODECHECK_READCOMP_F("v1", read_flag[3], als.TextureCoordinate[0].y);
+			MACRO_NODECHECK_READCOMP_F("v2", read_flag[4], als.TextureCoordinate[1].y);
+			MACRO_NODECHECK_READCOMP_F("v3", read_flag[5], als.TextureCoordinate[2].y);
+		MACRO_NODECHECK_LOOPEND("map");
+	}// if(!pUseOldName) else
+
+	ParseHelper_Node_Exit();
+
+	// check that all components was defined
+	if(!(read_flag[0] && read_flag[1] && read_flag[2] && read_flag[3] && read_flag[4] && read_flag[5]))
+		throw DeadlyImportError("Not all texture coordinates are defined.");
+
+	// copy attributes data
+	als.TextureID_R = rtexid;
+	als.TextureID_G = gtexid;
+	als.TextureID_B = btexid;
+	als.TextureID_A = atexid;
+
+	mNodeElement_List.push_back(ne);// add to node element list because its a new object in graph.
+}
+
+}// namespace Assimp
+
+#endif // !ASSIMP_BUILD_NO_AMF_IMPORTER

+ 340 - 0
Engine/lib/assimp/code/AMFImporter_Node.hpp

@@ -0,0 +1,340 @@
+/*
+---------------------------------------------------------------------------
+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 AMFImporter_Node.hpp
+/// \brief Elements of scene graph.
+/// \date 2016
+/// \author [email protected]
+
+#pragma once
+#ifndef INCLUDED_AI_AMF_IMPORTER_NODE_H
+#define INCLUDED_AI_AMF_IMPORTER_NODE_H
+
+// Header files, stdlib.
+#include <list>
+#include <string>
+#include <vector>
+
+// Header files, Assimp.
+#include "assimp/types.h"
+#include "assimp/scene.h"
+
+/// \class CAMFImporter_NodeElement
+/// Base class for elements of nodes.
+class CAMFImporter_NodeElement {
+public:
+	/// Define what data type contain node element.
+	enum EType {
+		ENET_Color,        ///< Color element: <color>.
+		ENET_Constellation,///< Grouping element: <constellation>.
+		ENET_Coordinates,  ///< Coordinates element: <coordinates>.
+		ENET_Edge,         ///< Edge element: <edge>.
+		ENET_Instance,     ///< Grouping element: <constellation>.
+		ENET_Material,     ///< Material element: <material>.
+		ENET_Metadata,     ///< Metadata element: <metadata>.
+		ENET_Mesh,         ///< Metadata element: <mesh>.
+		ENET_Object,       ///< Element which hold object: <object>.
+		ENET_Root,         ///< Root element: <amf>.
+		ENET_Triangle,     ///< Triangle element: <triangle>.
+		ENET_TexMap,       ///< Texture coordinates element: <texmap> or <map>.
+		ENET_Texture,      ///< Texture element: <texture>.
+		ENET_Vertex,       ///< Vertex element: <vertex>.
+		ENET_Vertices,     ///< Vertex element: <vertices>.
+		ENET_Volume,       ///< Volume element: <volume>.
+
+		ENET_Invalid       ///< Element has invalid type and possible contain invalid data.
+	};
+
+	const EType Type;///< Type of element.
+	std::string ID;///< ID of element.
+	CAMFImporter_NodeElement* Parent;///< Parent element. If nullptr then this node is root.
+	std::list<CAMFImporter_NodeElement*> Child;///< Child elements.
+
+public:                                               /// Destructor, virtual..
+    virtual ~CAMFImporter_NodeElement() {
+        // empty
+    }
+
+	/// Disabled copy constructor and co.
+	CAMFImporter_NodeElement(const CAMFImporter_NodeElement& pNodeElement) = delete;
+    CAMFImporter_NodeElement(CAMFImporter_NodeElement&&) = delete;
+    CAMFImporter_NodeElement& operator=(const CAMFImporter_NodeElement& pNodeElement) = delete;
+	CAMFImporter_NodeElement() = delete;
+
+protected:
+	/// In constructor inheritor must set element type.
+	/// \param [in] pType - element type.
+	/// \param [in] pParent - parent element.
+	CAMFImporter_NodeElement(const EType pType, CAMFImporter_NodeElement* pParent)
+	: Type(pType)
+    , ID()
+    , Parent(pParent)
+    , Child() {
+        // empty
+    }
+};// class IAMFImporter_NodeElement
+
+/// \struct CAMFImporter_NodeElement_Constellation
+/// A collection of objects or constellations with specific relative locations.
+struct CAMFImporter_NodeElement_Constellation : public CAMFImporter_NodeElement {
+	/// Constructor.
+	/// \param [in] pParent - pointer to parent node.
+	CAMFImporter_NodeElement_Constellation(CAMFImporter_NodeElement* pParent)
+		: CAMFImporter_NodeElement(ENET_Constellation, pParent)
+	{}
+
+};// struct CAMFImporter_NodeElement_Constellation
+
+/// \struct CAMFImporter_NodeElement_Instance
+/// Part of constellation.
+struct CAMFImporter_NodeElement_Instance : public CAMFImporter_NodeElement {
+
+	std::string ObjectID;///< ID of object for instantiation.
+	/// \var Delta - The distance of translation in the x, y, or z direction, respectively, in the referenced object's coordinate system, to
+	/// create an instance of the object in the current constellation.
+	aiVector3D Delta;
+
+	/// \var Rotation - The rotation, in degrees, to rotate the referenced object about its x, y, and z axes, respectively, to create an
+	/// instance of the object in the current constellation. Rotations shall be executed in order of x first, then y, then z.
+	aiVector3D Rotation;
+
+	/// Constructor.
+	/// \param [in] pParent - pointer to parent node.
+	CAMFImporter_NodeElement_Instance(CAMFImporter_NodeElement* pParent)
+		: CAMFImporter_NodeElement(ENET_Instance, pParent)
+	{}
+};
+
+/// \struct CAMFImporter_NodeElement_Metadata
+/// Structure that define metadata node.
+struct CAMFImporter_NodeElement_Metadata : public CAMFImporter_NodeElement {
+
+	std::string Type;///< Type of "Value". 
+	std::string Value;///< Value.
+
+	/// Constructor.
+	/// \param [in] pParent - pointer to parent node.
+	CAMFImporter_NodeElement_Metadata(CAMFImporter_NodeElement* pParent)
+		: CAMFImporter_NodeElement(ENET_Metadata, pParent)
+	{}
+};
+
+/// \struct CAMFImporter_NodeElement_Root
+/// Structure that define root node.
+struct CAMFImporter_NodeElement_Root : public CAMFImporter_NodeElement {
+
+	std::string Unit;///< The units to be used. May be "inch", "millimeter", "meter", "feet", or "micron".
+	std::string Version;///< Version of format.
+
+	/// Constructor.
+	/// \param [in] pParent - pointer to parent node.
+	CAMFImporter_NodeElement_Root(CAMFImporter_NodeElement* pParent)
+		: CAMFImporter_NodeElement(ENET_Root, pParent)
+	{}
+};
+
+/// \struct CAMFImporter_NodeElement_Color
+/// Structure that define object node.
+struct CAMFImporter_NodeElement_Color : public CAMFImporter_NodeElement {
+	bool Composed;                  ///< Type of color stored: if true then look for formula in \ref Color_Composed[4], else - in \ref Color.
+	std::string Color_Composed[4];  ///< By components formulas of composed color. [0..3] - RGBA.
+	aiColor4D Color;                ///< Constant color.
+	std::string Profile;            ///< The ICC color space used to interpret the three color channels r, g and b..
+
+	/// @brief  Constructor.
+	/// @param [in] pParent - pointer to parent node.
+	CAMFImporter_NodeElement_Color(CAMFImporter_NodeElement* pParent)
+	: CAMFImporter_NodeElement(ENET_Color, pParent)
+    , Composed( false )
+    , Color()
+    , Profile() {
+        // empty
+    }
+};
+
+/// \struct CAMFImporter_NodeElement_Material
+/// Structure that define material node.
+struct CAMFImporter_NodeElement_Material : public CAMFImporter_NodeElement {
+	
+	/// Constructor.
+	/// \param [in] pParent - pointer to parent node.
+	CAMFImporter_NodeElement_Material(CAMFImporter_NodeElement* pParent)
+		: CAMFImporter_NodeElement(ENET_Material, pParent)
+	{}
+
+};
+
+/// \struct CAMFImporter_NodeElement_Object
+/// Structure that define object node.
+struct CAMFImporter_NodeElement_Object : public CAMFImporter_NodeElement {
+
+    /// Constructor.
+	/// \param [in] pParent - pointer to parent node.
+	CAMFImporter_NodeElement_Object(CAMFImporter_NodeElement* pParent)
+		: CAMFImporter_NodeElement(ENET_Object, pParent)
+	{}
+};
+
+/// \struct CAMFImporter_NodeElement_Mesh
+/// Structure that define mesh node.
+struct CAMFImporter_NodeElement_Mesh : public CAMFImporter_NodeElement {
+	/// Constructor.
+	/// \param [in] pParent - pointer to parent node.
+	CAMFImporter_NodeElement_Mesh(CAMFImporter_NodeElement* pParent)
+		: CAMFImporter_NodeElement(ENET_Mesh, pParent)
+	{}
+};
+
+/// \struct CAMFImporter_NodeElement_Vertex
+/// Structure that define vertex node.
+struct CAMFImporter_NodeElement_Vertex : public CAMFImporter_NodeElement {
+	/// Constructor.
+	/// \param [in] pParent - pointer to parent node.
+	CAMFImporter_NodeElement_Vertex(CAMFImporter_NodeElement* pParent)
+		: CAMFImporter_NodeElement(ENET_Vertex, pParent)
+	{}
+};
+
+/// \struct CAMFImporter_NodeElement_Edge
+/// Structure that define edge node.
+struct CAMFImporter_NodeElement_Edge : public CAMFImporter_NodeElement {
+	/// Constructor.
+	/// \param [in] pParent - pointer to parent node.
+	CAMFImporter_NodeElement_Edge(CAMFImporter_NodeElement* pParent)
+		: CAMFImporter_NodeElement(ENET_Edge, pParent)
+	{}
+
+};
+
+/// \struct CAMFImporter_NodeElement_Vertices
+/// Structure that define vertices node.
+struct CAMFImporter_NodeElement_Vertices : public CAMFImporter_NodeElement {
+	/// Constructor.
+	/// \param [in] pParent - pointer to parent node.
+	CAMFImporter_NodeElement_Vertices(CAMFImporter_NodeElement* pParent)
+		: CAMFImporter_NodeElement(ENET_Vertices, pParent)
+	{}
+};
+
+/// \struct CAMFImporter_NodeElement_Volume
+/// Structure that define volume node.
+struct CAMFImporter_NodeElement_Volume : public CAMFImporter_NodeElement {
+	std::string MaterialID;///< Which material to use.
+	std::string Type;///< What this volume describes can be “region” or “support”. If none specified, “object” is assumed.
+
+	/// Constructor.
+	/// \param [in] pParent - pointer to parent node.
+	CAMFImporter_NodeElement_Volume(CAMFImporter_NodeElement* pParent)
+		: CAMFImporter_NodeElement(ENET_Volume, pParent)
+	{}
+};
+
+/// \struct CAMFImporter_NodeElement_Coordinates
+/// Structure that define coordinates node.
+struct CAMFImporter_NodeElement_Coordinates : public CAMFImporter_NodeElement
+{
+	aiVector3D Coordinate;///< Coordinate.
+
+	/// Constructor.
+	/// \param [in] pParent - pointer to parent node.
+	CAMFImporter_NodeElement_Coordinates(CAMFImporter_NodeElement* pParent)
+		: CAMFImporter_NodeElement(ENET_Coordinates, pParent)
+	{}
+
+};
+
+/// \struct CAMFImporter_NodeElement_TexMap
+/// Structure that define texture coordinates node.
+struct CAMFImporter_NodeElement_TexMap : public CAMFImporter_NodeElement {
+	aiVector3D TextureCoordinate[3];///< Texture coordinates.
+	std::string TextureID_R;///< Texture ID for red color component.
+	std::string TextureID_G;///< Texture ID for green color component.
+	std::string TextureID_B;///< Texture ID for blue color component.
+	std::string TextureID_A;///< Texture ID for alpha color component.
+
+	/// Constructor.
+	/// \param [in] pParent - pointer to parent node.
+	CAMFImporter_NodeElement_TexMap(CAMFImporter_NodeElement* pParent)
+	: CAMFImporter_NodeElement(ENET_TexMap, pParent)
+    , TextureCoordinate{}
+    , TextureID_R()
+    , TextureID_G()
+    , TextureID_B()
+    , TextureID_A()	{
+        // empty
+    }
+};
+
+/// \struct CAMFImporter_NodeElement_Triangle
+/// Structure that define triangle node.
+struct CAMFImporter_NodeElement_Triangle : public CAMFImporter_NodeElement {
+	size_t V[3];///< Triangle vertices.
+
+	/// Constructor.
+	/// \param [in] pParent - pointer to parent node.
+	CAMFImporter_NodeElement_Triangle(CAMFImporter_NodeElement* pParent)
+	: CAMFImporter_NodeElement(ENET_Triangle, pParent) {
+        // empty
+    }
+};
+
+/// Structure that define texture node.
+struct CAMFImporter_NodeElement_Texture : public CAMFImporter_NodeElement {
+	size_t Width, Height, Depth;///< Size of the texture.
+	std::vector<uint8_t> Data;///< Data of the texture.
+	bool Tiled;
+
+	/// Constructor.
+	/// \param [in] pParent - pointer to parent node.
+	CAMFImporter_NodeElement_Texture(CAMFImporter_NodeElement* pParent)
+	: CAMFImporter_NodeElement(ENET_Texture, pParent)
+    , Width( 0 )
+    , Height( 0 )
+    , Depth( 0 )
+    , Data()
+    , Tiled( false ){
+        // empty
+    }
+};
+
+#endif // INCLUDED_AI_AMF_IMPORTER_NODE_H

+ 978 - 0
Engine/lib/assimp/code/AMFImporter_Postprocess.cpp

@@ -0,0 +1,978 @@
+/*
+---------------------------------------------------------------------------
+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 AMFImporter_Postprocess.cpp
+/// \brief Convert built scenegraph and objects to Assimp scenegraph.
+/// \date 2016
+/// \author [email protected]
+
+#ifndef ASSIMP_BUILD_NO_AMF_IMPORTER
+
+#include "AMFImporter.hpp"
+
+// Header files, Assimp.
+#include <assimp/SceneCombiner.h>
+#include <assimp/StandardShapes.h>
+#include <assimp/StringUtils.h>
+
+// Header files, stdlib.
+#include <iterator>
+
+namespace Assimp
+{
+
+aiColor4D AMFImporter::SPP_Material::GetColor(const float /*pX*/, const float /*pY*/, const float /*pZ*/) const
+{
+    aiColor4D tcol;
+
+	// Check if stored data are supported.
+	if(Composition.size() != 0)
+	{
+		throw DeadlyImportError("IME. GetColor for composition");
+	}
+	else if(Color->Composed)
+	{
+		throw DeadlyImportError("IME. GetColor, composed color");
+	}
+	else
+	{
+		tcol = Color->Color;
+	}
+
+	// Check if default color must be used
+	if((tcol.r == 0) && (tcol.g == 0) && (tcol.b == 0) && (tcol.a == 0))
+	{
+		tcol.r = 0.5f;
+		tcol.g = 0.5f;
+		tcol.b = 0.5f;
+		tcol.a = 1;
+	}
+
+	return tcol;
+}
+
+void AMFImporter::PostprocessHelper_CreateMeshDataArray(const CAMFImporter_NodeElement_Mesh& pNodeElement, std::vector<aiVector3D>& pVertexCoordinateArray,
+														std::vector<CAMFImporter_NodeElement_Color*>& pVertexColorArray) const
+{
+    CAMFImporter_NodeElement_Vertices* vn = nullptr;
+    size_t col_idx;
+
+	// All data stored in "vertices", search for it.
+	for(CAMFImporter_NodeElement* ne_child: pNodeElement.Child)
+	{
+		if(ne_child->Type == CAMFImporter_NodeElement::ENET_Vertices) vn = (CAMFImporter_NodeElement_Vertices*)ne_child;
+	}
+
+	// If "vertices" not found then no work for us.
+	if(vn == nullptr) return;
+
+	pVertexCoordinateArray.reserve(vn->Child.size());// all coordinates stored as child and we need to reserve space for future push_back's.
+	pVertexColorArray.resize(vn->Child.size());// colors count equal vertices count.
+	col_idx = 0;
+	// Inside vertices collect all data and place to arrays
+	for(CAMFImporter_NodeElement* vn_child: vn->Child)
+	{
+		// vertices, colors
+		if(vn_child->Type == CAMFImporter_NodeElement::ENET_Vertex)
+		{
+			// by default clear color for current vertex
+			pVertexColorArray[col_idx] = nullptr;
+
+			for(CAMFImporter_NodeElement* vtx: vn_child->Child)
+			{
+				if(vtx->Type == CAMFImporter_NodeElement::ENET_Coordinates)
+				{
+					pVertexCoordinateArray.push_back(((CAMFImporter_NodeElement_Coordinates*)vtx)->Coordinate);
+
+					continue;
+				}
+
+				if(vtx->Type == CAMFImporter_NodeElement::ENET_Color)
+				{
+					pVertexColorArray[col_idx] = (CAMFImporter_NodeElement_Color*)vtx;
+
+					continue;
+				}
+			}// for(CAMFImporter_NodeElement* vtx: vn_child->Child)
+
+			col_idx++;
+		}// if(vn_child->Type == CAMFImporter_NodeElement::ENET_Vertex)
+	}// for(CAMFImporter_NodeElement* vn_child: vn->Child)
+}
+
+size_t AMFImporter::PostprocessHelper_GetTextureID_Or_Create(const std::string& pID_R, const std::string& pID_G, const std::string& pID_B,
+																const std::string& pID_A)
+{
+    size_t TextureConverted_Index;
+    std::string TextureConverted_ID;
+
+	// check input data
+	if(pID_R.empty() && pID_G.empty() && pID_B.empty() && pID_A.empty())
+		throw DeadlyImportError("PostprocessHelper_GetTextureID_Or_Create. At least one texture ID must be defined.");
+
+	// Create ID
+	TextureConverted_ID = pID_R + "_" + pID_G + "_" + pID_B + "_" + pID_A;
+	// Check if texture specified by set of IDs is converted already.
+	TextureConverted_Index = 0;
+	for(const SPP_Texture& tex_convd: mTexture_Converted)
+	{
+        if ( tex_convd.ID == TextureConverted_ID ) {
+            return TextureConverted_Index;
+        } else {
+            ++TextureConverted_Index;
+        }
+	}
+
+	//
+	// Converted texture not found, create it.
+	//
+	CAMFImporter_NodeElement_Texture* src_texture[4]{nullptr};
+	std::vector<CAMFImporter_NodeElement_Texture*> src_texture_4check;
+	SPP_Texture converted_texture;
+
+	{// find all specified source textures
+		CAMFImporter_NodeElement* t_tex;
+
+		// R
+		if(!pID_R.empty())
+		{
+			if(!Find_NodeElement(pID_R, CAMFImporter_NodeElement::ENET_Texture, &t_tex)) Throw_ID_NotFound(pID_R);
+
+			src_texture[0] = (CAMFImporter_NodeElement_Texture*)t_tex;
+			src_texture_4check.push_back((CAMFImporter_NodeElement_Texture*)t_tex);
+		}
+		else
+		{
+			src_texture[0] = nullptr;
+		}
+
+		// G
+		if(!pID_G.empty())
+		{
+			if(!Find_NodeElement(pID_G, CAMFImporter_NodeElement::ENET_Texture, &t_tex)) Throw_ID_NotFound(pID_G);
+
+			src_texture[1] = (CAMFImporter_NodeElement_Texture*)t_tex;
+			src_texture_4check.push_back((CAMFImporter_NodeElement_Texture*)t_tex);
+		}
+		else
+		{
+			src_texture[1] = nullptr;
+		}
+
+		// B
+		if(!pID_B.empty())
+		{
+			if(!Find_NodeElement(pID_B, CAMFImporter_NodeElement::ENET_Texture, &t_tex)) Throw_ID_NotFound(pID_B);
+
+			src_texture[2] = (CAMFImporter_NodeElement_Texture*)t_tex;
+			src_texture_4check.push_back((CAMFImporter_NodeElement_Texture*)t_tex);
+		}
+		else
+		{
+			src_texture[2] = nullptr;
+		}
+
+		// A
+		if(!pID_A.empty())
+		{
+			if(!Find_NodeElement(pID_A, CAMFImporter_NodeElement::ENET_Texture, &t_tex)) Throw_ID_NotFound(pID_A);
+
+			src_texture[3] = (CAMFImporter_NodeElement_Texture*)t_tex;
+			src_texture_4check.push_back((CAMFImporter_NodeElement_Texture*)t_tex);
+		}
+		else
+		{
+			src_texture[3] = nullptr;
+		}
+	}// END: find all specified source textures
+
+	// check that all textures has same size
+	if(src_texture_4check.size() > 1)
+	{
+		for (size_t i = 0, i_e = (src_texture_4check.size() - 1); i < i_e; i++)
+		{
+			if((src_texture_4check[i]->Width != src_texture_4check[i + 1]->Width) || (src_texture_4check[i]->Height != src_texture_4check[i + 1]->Height) ||
+				(src_texture_4check[i]->Depth != src_texture_4check[i + 1]->Depth))
+			{
+				throw DeadlyImportError("PostprocessHelper_GetTextureID_Or_Create. Source texture must has the same size.");
+			}
+		}
+	}// if(src_texture_4check.size() > 1)
+
+	// set texture attributes
+	converted_texture.Width = src_texture_4check[0]->Width;
+	converted_texture.Height = src_texture_4check[0]->Height;
+	converted_texture.Depth = src_texture_4check[0]->Depth;
+	// if one of source texture is tiled then converted texture is tiled too.
+	converted_texture.Tiled = false;
+	for(uint8_t i = 0; i < src_texture_4check.size(); i++) converted_texture.Tiled |= src_texture_4check[i]->Tiled;
+
+	// Create format hint.
+	strcpy(converted_texture.FormatHint, "rgba0000");// copy initial string.
+	if(!pID_R.empty()) converted_texture.FormatHint[4] = '8';
+	if(!pID_G.empty()) converted_texture.FormatHint[5] = '8';
+	if(!pID_B.empty()) converted_texture.FormatHint[6] = '8';
+	if(!pID_A.empty()) converted_texture.FormatHint[7] = '8';
+
+	//
+	// Сopy data of textures.
+	//
+	size_t tex_size = 0;
+	size_t step = 0;
+	size_t off_g = 0;
+	size_t off_b = 0;
+
+	// Calculate size of the target array and rule how data will be copied.
+    if(!pID_R.empty() && nullptr != src_texture[ 0 ] ) {
+        tex_size += src_texture[0]->Data.size(); step++, off_g++, off_b++;
+    }
+    if(!pID_G.empty() && nullptr != src_texture[ 1 ] ) {
+        tex_size += src_texture[1]->Data.size(); step++, off_b++;
+    }
+    if(!pID_B.empty() && nullptr != src_texture[ 2 ] ) {
+        tex_size += src_texture[2]->Data.size(); step++;
+    }
+    if(!pID_A.empty() && nullptr != src_texture[ 3 ] ) {
+        tex_size += src_texture[3]->Data.size(); step++;
+    }
+
+    // Create target array.
+	converted_texture.Data = new uint8_t[tex_size];
+	// And copy data
+	auto CopyTextureData = [&](const std::string& pID, const size_t pOffset, const size_t pStep, const uint8_t pSrcTexNum) -> void
+	{
+		if(!pID.empty())
+		{
+			for(size_t idx_target = pOffset, idx_src = 0; idx_target < tex_size; idx_target += pStep, idx_src++) {
+				CAMFImporter_NodeElement_Texture* tex = src_texture[pSrcTexNum];
+				ai_assert(tex);
+				converted_texture.Data[idx_target] = tex->Data.at(idx_src);
+			}
+		}
+	};// auto CopyTextureData = [&](const size_t pOffset, const size_t pStep, const uint8_t pSrcTexNum) -> void
+
+	CopyTextureData(pID_R, 0, step, 0);
+	CopyTextureData(pID_G, off_g, step, 1);
+	CopyTextureData(pID_B, off_b, step, 2);
+	CopyTextureData(pID_A, step - 1, step, 3);
+
+	// Store new converted texture ID
+	converted_texture.ID = TextureConverted_ID;
+	// Store new converted texture
+	mTexture_Converted.push_back(converted_texture);
+
+	return TextureConverted_Index;
+}
+
+void AMFImporter::PostprocessHelper_SplitFacesByTextureID(std::list<SComplexFace>& pInputList, std::list<std::list<SComplexFace> >& pOutputList_Separated)
+{
+    auto texmap_is_equal = [](const CAMFImporter_NodeElement_TexMap* pTexMap1, const CAMFImporter_NodeElement_TexMap* pTexMap2) -> bool
+    {
+	    if((pTexMap1 == nullptr) && (pTexMap2 == nullptr)) return true;
+	    if(pTexMap1 == nullptr) return false;
+	    if(pTexMap2 == nullptr) return false;
+
+	    if(pTexMap1->TextureID_R != pTexMap2->TextureID_R) return false;
+	    if(pTexMap1->TextureID_G != pTexMap2->TextureID_G) return false;
+	    if(pTexMap1->TextureID_B != pTexMap2->TextureID_B) return false;
+	    if(pTexMap1->TextureID_A != pTexMap2->TextureID_A) return false;
+
+	    return true;
+    };
+
+	pOutputList_Separated.clear();
+	if(pInputList.size() == 0) return;
+
+	do
+	{
+		SComplexFace face_start = pInputList.front();
+		std::list<SComplexFace> face_list_cur;
+
+		for(std::list<SComplexFace>::iterator it = pInputList.begin(), it_end = pInputList.end(); it != it_end;)
+		{
+			if(texmap_is_equal(face_start.TexMap, it->TexMap))
+			{
+				auto it_old = it;
+
+				it++;
+				face_list_cur.push_back(*it_old);
+				pInputList.erase(it_old);
+			}
+			else
+			{
+				it++;
+			}
+		}
+
+		if(face_list_cur.size() > 0) pOutputList_Separated.push_back(face_list_cur);
+
+	} while(pInputList.size() > 0);
+}
+
+void AMFImporter::Postprocess_AddMetadata(const std::list<CAMFImporter_NodeElement_Metadata*>& metadataList, aiNode& sceneNode) const
+{
+	if ( !metadataList.empty() )
+	{
+		if(sceneNode.mMetaData != nullptr) throw DeadlyImportError("Postprocess. MetaData member in node are not nullptr. Something went wrong.");
+
+		// copy collected metadata to output node.
+        sceneNode.mMetaData = aiMetadata::Alloc( static_cast<unsigned int>(metadataList.size()) );
+		size_t meta_idx( 0 );
+
+		for(const CAMFImporter_NodeElement_Metadata& metadata: metadataList)
+		{
+			sceneNode.mMetaData->Set(static_cast<unsigned int>(meta_idx++), metadata.Type, aiString(metadata.Value));
+		}
+	}// if(!metadataList.empty())
+}
+
+void AMFImporter::Postprocess_BuildNodeAndObject(const CAMFImporter_NodeElement_Object& pNodeElement, std::list<aiMesh*>& pMeshList, aiNode** pSceneNode)
+{
+CAMFImporter_NodeElement_Color* object_color = nullptr;
+
+	// create new aiNode and set name as <object> has.
+	*pSceneNode = new aiNode;
+	(*pSceneNode)->mName = pNodeElement.ID;
+	// read mesh and color
+	for(const CAMFImporter_NodeElement* ne_child: pNodeElement.Child)
+	{
+		std::vector<aiVector3D> vertex_arr;
+		std::vector<CAMFImporter_NodeElement_Color*> color_arr;
+
+		// color for object
+		if(ne_child->Type == CAMFImporter_NodeElement::ENET_Color) object_color = (CAMFImporter_NodeElement_Color*)ne_child;
+
+		if(ne_child->Type == CAMFImporter_NodeElement::ENET_Mesh)
+		{
+			// Create arrays from children of mesh: vertices.
+			PostprocessHelper_CreateMeshDataArray(*((CAMFImporter_NodeElement_Mesh*)ne_child), vertex_arr, color_arr);
+			// Use this arrays as a source when creating every aiMesh
+			Postprocess_BuildMeshSet(*((CAMFImporter_NodeElement_Mesh*)ne_child), vertex_arr, color_arr, object_color, pMeshList, **pSceneNode);
+		}
+	}// for(const CAMFImporter_NodeElement* ne_child: pNodeElement)
+}
+
+void AMFImporter::Postprocess_BuildMeshSet(const CAMFImporter_NodeElement_Mesh& pNodeElement, const std::vector<aiVector3D>& pVertexCoordinateArray,
+											const std::vector<CAMFImporter_NodeElement_Color*>& pVertexColorArray,
+											const CAMFImporter_NodeElement_Color* pObjectColor, std::list<aiMesh*>& pMeshList, aiNode& pSceneNode)
+{
+std::list<unsigned int> mesh_idx;
+
+	// all data stored in "volume", search for it.
+	for(const CAMFImporter_NodeElement* ne_child: pNodeElement.Child)
+	{
+		const CAMFImporter_NodeElement_Color* ne_volume_color = nullptr;
+		const SPP_Material* cur_mat = nullptr;
+
+		if(ne_child->Type == CAMFImporter_NodeElement::ENET_Volume)
+		{
+			/******************* Get faces *******************/
+			const CAMFImporter_NodeElement_Volume* ne_volume = reinterpret_cast<const CAMFImporter_NodeElement_Volume*>(ne_child);
+
+			std::list<SComplexFace> complex_faces_list;// List of the faces of the volume.
+			std::list<std::list<SComplexFace> > complex_faces_toplist;// List of the face list for every mesh.
+
+			// check if volume use material
+			if(!ne_volume->MaterialID.empty())
+			{
+				if(!Find_ConvertedMaterial(ne_volume->MaterialID, &cur_mat)) Throw_ID_NotFound(ne_volume->MaterialID);
+			}
+
+			// inside "volume" collect all data and place to arrays or create new objects
+			for(const CAMFImporter_NodeElement* ne_volume_child: ne_volume->Child)
+			{
+				// color for volume
+				if(ne_volume_child->Type == CAMFImporter_NodeElement::ENET_Color)
+				{
+					ne_volume_color = reinterpret_cast<const CAMFImporter_NodeElement_Color*>(ne_volume_child);
+				}
+				else if(ne_volume_child->Type == CAMFImporter_NodeElement::ENET_Triangle)// triangles, triangles colors
+				{
+					const CAMFImporter_NodeElement_Triangle& tri_al = *reinterpret_cast<const CAMFImporter_NodeElement_Triangle*>(ne_volume_child);
+
+					SComplexFace complex_face;
+
+					// initialize pointers
+					complex_face.Color = nullptr;
+					complex_face.TexMap = nullptr;
+					// get data from triangle children: color, texture coordinates.
+					if(tri_al.Child.size())
+					{
+						for(const CAMFImporter_NodeElement* ne_triangle_child: tri_al.Child)
+						{
+							if(ne_triangle_child->Type == CAMFImporter_NodeElement::ENET_Color)
+								complex_face.Color = reinterpret_cast<const CAMFImporter_NodeElement_Color*>(ne_triangle_child);
+							else if(ne_triangle_child->Type == CAMFImporter_NodeElement::ENET_TexMap)
+								complex_face.TexMap = reinterpret_cast<const CAMFImporter_NodeElement_TexMap*>(ne_triangle_child);
+						}
+					}// if(tri_al.Child.size())
+
+					// create new face and store it.
+					complex_face.Face.mNumIndices = 3;
+					complex_face.Face.mIndices = new unsigned int[3];
+					complex_face.Face.mIndices[0] = static_cast<unsigned int>(tri_al.V[0]);
+					complex_face.Face.mIndices[1] = static_cast<unsigned int>(tri_al.V[1]);
+					complex_face.Face.mIndices[2] = static_cast<unsigned int>(tri_al.V[2]);
+					complex_faces_list.push_back(complex_face);
+				}
+			}// for(const CAMFImporter_NodeElement* ne_volume_child: ne_volume->Child)
+
+			/**** Split faces list: one list per mesh ****/
+			PostprocessHelper_SplitFacesByTextureID(complex_faces_list, complex_faces_toplist);
+
+			/***** Create mesh for every faces list ******/
+			for(std::list<SComplexFace>& face_list_cur: complex_faces_toplist)
+			{
+				auto VertexIndex_GetMinimal = [](const std::list<SComplexFace>& pFaceList, const size_t* pBiggerThan) -> size_t
+				{
+					size_t rv;
+
+					if(pBiggerThan != nullptr)
+					{
+						bool found = false;
+
+						for(const SComplexFace& face: pFaceList)
+						{
+							for(size_t idx_vert = 0; idx_vert < face.Face.mNumIndices; idx_vert++)
+							{
+								if(face.Face.mIndices[idx_vert] > *pBiggerThan)
+								{
+									rv = face.Face.mIndices[idx_vert];
+									found = true;
+
+									break;
+								}
+							}
+
+							if(found) break;
+						}
+
+						if(!found) return *pBiggerThan;
+					}
+					else
+					{
+						rv = pFaceList.front().Face.mIndices[0];
+					}// if(pBiggerThan != nullptr) else
+
+					for(const SComplexFace& face: pFaceList)
+					{
+						for(size_t vi = 0; vi < face.Face.mNumIndices; vi++)
+						{
+							if(face.Face.mIndices[vi] < rv)
+							{
+								if(pBiggerThan != nullptr)
+								{
+									if(face.Face.mIndices[vi] > *pBiggerThan) rv = face.Face.mIndices[vi];
+								}
+								else
+								{
+									rv = face.Face.mIndices[vi];
+								}
+							}
+						}
+					}// for(const SComplexFace& face: pFaceList)
+
+					return rv;
+				};// auto VertexIndex_GetMinimal = [](const std::list<SComplexFace>& pFaceList, const size_t* pBiggerThan) -> size_t
+
+				auto VertexIndex_Replace = [](std::list<SComplexFace>& pFaceList, const size_t pIdx_From, const size_t pIdx_To) -> void
+				{
+					for(const SComplexFace& face: pFaceList)
+					{
+						for(size_t vi = 0; vi < face.Face.mNumIndices; vi++)
+						{
+							if(face.Face.mIndices[vi] == pIdx_From) face.Face.mIndices[vi] = static_cast<unsigned int>(pIdx_To);
+						}
+					}
+				};// auto VertexIndex_Replace = [](std::list<SComplexFace>& pFaceList, const size_t pIdx_From, const size_t pIdx_To) -> void
+
+				auto Vertex_CalculateColor = [&](const size_t pIdx) -> aiColor4D
+				{
+					// Color priorities(In descending order):
+					// 1. triangle color;
+					// 2. vertex color;
+					// 3. volume color;
+					// 4. object color;
+					// 5. material;
+					// 6. default - invisible coat.
+					//
+					// Fill vertices colors in color priority list above that's points from 1 to 6.
+					if((pIdx < pVertexColorArray.size()) && (pVertexColorArray[pIdx] != nullptr))// check for vertex color
+					{
+						if(pVertexColorArray[pIdx]->Composed)
+							throw DeadlyImportError("IME: vertex color composed");
+						else
+							return pVertexColorArray[pIdx]->Color;
+					}
+					else if(ne_volume_color != nullptr)// check for volume color
+					{
+						if(ne_volume_color->Composed)
+							throw DeadlyImportError("IME: volume color composed");
+						else
+							return ne_volume_color->Color;
+					}
+					else if(pObjectColor != nullptr)// check for object color
+					{
+						if(pObjectColor->Composed)
+							throw DeadlyImportError("IME: object color composed");
+						else
+							return pObjectColor->Color;
+					}
+					else if(cur_mat != nullptr)// check for material
+					{
+						return cur_mat->GetColor(pVertexCoordinateArray.at(pIdx).x, pVertexCoordinateArray.at(pIdx).y, pVertexCoordinateArray.at(pIdx).z);
+					}
+					else// set default color.
+					{
+						return {0, 0, 0, 0};
+					}// if((vi < pVertexColorArray.size()) && (pVertexColorArray[vi] != nullptr)) else
+
+				};// auto Vertex_CalculateColor = [&](const size_t pIdx) -> aiColor4D
+
+				aiMesh* tmesh = new aiMesh;
+
+				tmesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;// Only triangles is supported by AMF.
+				//
+				// set geometry and colors (vertices)
+				//
+				// copy faces/triangles
+				tmesh->mNumFaces = static_cast<unsigned int>(face_list_cur.size());
+				tmesh->mFaces = new aiFace[tmesh->mNumFaces];
+
+				// Create vertices list and optimize indices. Optimisation mean following.In AMF all volumes use one big list of vertices. And one volume
+				// can use only part of vertices list, for example: vertices list contain few thousands of vertices and volume use vertices 1, 3, 10.
+				// Do you need all this thousands of garbage? Of course no. So, optimisation step transformate sparse indices set to continuous.
+				size_t VertexCount_Max = tmesh->mNumFaces * 3;// 3 - triangles.
+				std::vector<aiVector3D> vert_arr, texcoord_arr;
+				std::vector<aiColor4D> col_arr;
+
+				vert_arr.reserve(VertexCount_Max * 2);// "* 2" - see below TODO.
+				col_arr.reserve(VertexCount_Max * 2);
+
+				{// fill arrays
+					size_t vert_idx_from, vert_idx_to;
+
+					// first iteration.
+					vert_idx_to = 0;
+					vert_idx_from = VertexIndex_GetMinimal(face_list_cur, nullptr);
+					vert_arr.push_back(pVertexCoordinateArray.at(vert_idx_from));
+					col_arr.push_back(Vertex_CalculateColor(vert_idx_from));
+					if(vert_idx_from != vert_idx_to) VertexIndex_Replace(face_list_cur, vert_idx_from, vert_idx_to);
+
+					// rest iterations
+					do
+					{
+						vert_idx_from = VertexIndex_GetMinimal(face_list_cur, &vert_idx_to);
+						if(vert_idx_from == vert_idx_to) break;// all indices are transferred,
+
+						vert_arr.push_back(pVertexCoordinateArray.at(vert_idx_from));
+						col_arr.push_back(Vertex_CalculateColor(vert_idx_from));
+						vert_idx_to++;
+						if(vert_idx_from != vert_idx_to) VertexIndex_Replace(face_list_cur, vert_idx_from, vert_idx_to);
+
+					} while(true);
+				}// fill arrays. END.
+
+				//
+				// check if triangle colors are used and create additional faces if needed.
+				//
+				for(const SComplexFace& face_cur: face_list_cur)
+				{
+					if(face_cur.Color != nullptr)
+					{
+						aiColor4D face_color;
+						size_t vert_idx_new = vert_arr.size();
+
+						if(face_cur.Color->Composed)
+							throw DeadlyImportError("IME: face color composed");
+						else
+							face_color = face_cur.Color->Color;
+
+						for(size_t idx_ind = 0; idx_ind < face_cur.Face.mNumIndices; idx_ind++)
+						{
+							vert_arr.push_back(vert_arr.at(face_cur.Face.mIndices[idx_ind]));
+							col_arr.push_back(face_color);
+							face_cur.Face.mIndices[idx_ind] = static_cast<unsigned int>(vert_idx_new++);
+						}
+					}// if(face_cur.Color != nullptr)
+				}// for(const SComplexFace& face_cur: face_list_cur)
+
+				//
+				// if texture is used then copy texture coordinates too.
+				//
+				if(face_list_cur.front().TexMap != nullptr)
+				{
+					size_t idx_vert_new = vert_arr.size();
+					///TODO: clean unused vertices. "* 2": in certain cases - mesh full of triangle colors - vert_arr will contain duplicated vertices for
+					/// colored triangles and initial vertices (for colored vertices) which in real became unused. This part need more thinking about
+					/// optimisation.
+					bool* idx_vert_used;
+
+					idx_vert_used = new bool[VertexCount_Max * 2];
+					for(size_t i = 0, i_e = VertexCount_Max * 2; i < i_e; i++) idx_vert_used[i] = false;
+
+					// This ID's will be used when set materials ID in scene.
+					tmesh->mMaterialIndex = static_cast<unsigned int>(PostprocessHelper_GetTextureID_Or_Create(face_list_cur.front().TexMap->TextureID_R,
+																						face_list_cur.front().TexMap->TextureID_G,
+																						face_list_cur.front().TexMap->TextureID_B,
+																						face_list_cur.front().TexMap->TextureID_A));
+					texcoord_arr.resize(VertexCount_Max * 2);
+					for(const SComplexFace& face_cur: face_list_cur)
+					{
+						for(size_t idx_ind = 0; idx_ind < face_cur.Face.mNumIndices; idx_ind++)
+						{
+							const size_t idx_vert = face_cur.Face.mIndices[idx_ind];
+
+							if(!idx_vert_used[idx_vert])
+							{
+								texcoord_arr.at(idx_vert) = face_cur.TexMap->TextureCoordinate[idx_ind];
+								idx_vert_used[idx_vert] = true;
+							}
+							else if(texcoord_arr.at(idx_vert) != face_cur.TexMap->TextureCoordinate[idx_ind])
+							{
+								// in that case one vertex is shared with many texture coordinates. We need to duplicate vertex with another texture
+								// coordinates.
+								vert_arr.push_back(vert_arr.at(idx_vert));
+								col_arr.push_back(col_arr.at(idx_vert));
+								texcoord_arr.at(idx_vert_new) = face_cur.TexMap->TextureCoordinate[idx_ind];
+								face_cur.Face.mIndices[idx_ind] = static_cast<unsigned int>(idx_vert_new++);
+							}
+						}// for(size_t idx_ind = 0; idx_ind < face_cur.Face.mNumIndices; idx_ind++)
+					}// for(const SComplexFace& face_cur: face_list_cur)
+
+					delete [] idx_vert_used;
+					// shrink array
+					texcoord_arr.resize(idx_vert_new);
+				}// if(face_list_cur.front().TexMap != nullptr)
+
+				//
+				// copy collected data to mesh
+				//
+				tmesh->mNumVertices = static_cast<unsigned int>(vert_arr.size());
+				tmesh->mVertices = new aiVector3D[tmesh->mNumVertices];
+				tmesh->mColors[0] = new aiColor4D[tmesh->mNumVertices];
+
+				memcpy(tmesh->mVertices, vert_arr.data(), tmesh->mNumVertices * sizeof(aiVector3D));
+				memcpy(tmesh->mColors[0], col_arr.data(), tmesh->mNumVertices * sizeof(aiColor4D));
+				if(texcoord_arr.size() > 0)
+				{
+					tmesh->mTextureCoords[0] = new aiVector3D[tmesh->mNumVertices];
+					memcpy(tmesh->mTextureCoords[0], texcoord_arr.data(), tmesh->mNumVertices * sizeof(aiVector3D));
+					tmesh->mNumUVComponents[0] = 2;// U and V stored in "x", "y" of aiVector3D.
+				}
+
+				size_t idx_face = 0;
+				for(const SComplexFace& face_cur: face_list_cur) tmesh->mFaces[idx_face++] = face_cur.Face;
+
+				// store new aiMesh
+				mesh_idx.push_back(static_cast<unsigned int>(pMeshList.size()));
+				pMeshList.push_back(tmesh);
+			}// for(const std::list<SComplexFace>& face_list_cur: complex_faces_toplist)
+		}// if(ne_child->Type == CAMFImporter_NodeElement::ENET_Volume)
+	}// for(const CAMFImporter_NodeElement* ne_child: pNodeElement.Child)
+
+	// if meshes was created then assign new indices with current aiNode
+	if(mesh_idx.size() > 0)
+	{
+		std::list<unsigned int>::const_iterator mit = mesh_idx.begin();
+
+		pSceneNode.mNumMeshes = static_cast<unsigned int>(mesh_idx.size());
+		pSceneNode.mMeshes = new unsigned int[pSceneNode.mNumMeshes];
+		for(size_t i = 0; i < pSceneNode.mNumMeshes; i++) pSceneNode.mMeshes[i] = *mit++;
+	}// if(mesh_idx.size() > 0)
+}
+
+void AMFImporter::Postprocess_BuildMaterial(const CAMFImporter_NodeElement_Material& pMaterial)
+{
+SPP_Material new_mat;
+
+	new_mat.ID = pMaterial.ID;
+	for(const CAMFImporter_NodeElement* mat_child: pMaterial.Child)
+	{
+		if(mat_child->Type == CAMFImporter_NodeElement::ENET_Color)
+		{
+			new_mat.Color = (CAMFImporter_NodeElement_Color*)mat_child;
+		}
+		else if(mat_child->Type == CAMFImporter_NodeElement::ENET_Metadata)
+		{
+			new_mat.Metadata.push_back((CAMFImporter_NodeElement_Metadata*)mat_child);
+		}
+	}// for(const CAMFImporter_NodeElement* mat_child; pMaterial.Child)
+
+	// place converted material to special list
+	mMaterial_Converted.push_back(new_mat);
+}
+
+void AMFImporter::Postprocess_BuildConstellation(CAMFImporter_NodeElement_Constellation& pConstellation, std::list<aiNode*>& pNodeList) const
+{
+aiNode* con_node;
+std::list<aiNode*> ch_node;
+
+	// We will build next hierarchy:
+	// aiNode as parent (<constellation>) for set of nodes as a children
+	//  |- aiNode for transformation (<instance> -> <delta...>, <r...>) - aiNode for pointing to object ("objectid")
+	//  ...
+	//  \_ aiNode for transformation (<instance> -> <delta...>, <r...>) - aiNode for pointing to object ("objectid")
+	con_node = new aiNode;
+	con_node->mName = pConstellation.ID;
+	// Walk through children and search for instances of another objects, constellations.
+	for(const CAMFImporter_NodeElement* ne: pConstellation.Child)
+	{
+		aiMatrix4x4 tmat;
+		aiNode* t_node;
+		aiNode* found_node;
+
+		if(ne->Type == CAMFImporter_NodeElement::ENET_Metadata) continue;
+		if(ne->Type != CAMFImporter_NodeElement::ENET_Instance) throw DeadlyImportError("Only <instance> nodes can be in <constellation>.");
+
+		// create alias for conveniance
+		CAMFImporter_NodeElement_Instance& als = *((CAMFImporter_NodeElement_Instance*)ne);
+		// find referenced object
+		if(!Find_ConvertedNode(als.ObjectID, pNodeList, &found_node)) Throw_ID_NotFound(als.ObjectID);
+
+		// create node for applying transformation
+		t_node = new aiNode;
+		t_node->mParent = con_node;
+		// apply transformation
+		aiMatrix4x4::Translation(als.Delta, tmat), t_node->mTransformation *= tmat;
+		aiMatrix4x4::RotationX(als.Rotation.x, tmat), t_node->mTransformation *= tmat;
+		aiMatrix4x4::RotationY(als.Rotation.y, tmat), t_node->mTransformation *= tmat;
+		aiMatrix4x4::RotationZ(als.Rotation.z, tmat), t_node->mTransformation *= tmat;
+		// create array for one child node
+		t_node->mNumChildren = 1;
+		t_node->mChildren = new aiNode*[t_node->mNumChildren];
+		SceneCombiner::Copy(&t_node->mChildren[0], found_node);
+		t_node->mChildren[0]->mParent = t_node;
+		ch_node.push_back(t_node);
+	}// for(const CAMFImporter_NodeElement* ne: pConstellation.Child)
+
+	// copy found aiNode's as children
+	if(ch_node.size() == 0) throw DeadlyImportError("<constellation> must have at least one <instance>.");
+
+	size_t ch_idx = 0;
+
+	con_node->mNumChildren = static_cast<unsigned int>(ch_node.size());
+	con_node->mChildren = new aiNode*[con_node->mNumChildren];
+	for(aiNode* node: ch_node) con_node->mChildren[ch_idx++] = node;
+
+	// and place "root" of <constellation> node to node list
+	pNodeList.push_back(con_node);
+}
+
+void AMFImporter::Postprocess_BuildScene(aiScene* pScene)
+{
+std::list<aiNode*> node_list;
+std::list<aiMesh*> mesh_list;
+std::list<CAMFImporter_NodeElement_Metadata*> meta_list;
+
+	//
+	// Because for AMF "material" is just complex colors mixing so aiMaterial will not be used.
+	// For building aiScene we are must to do few steps:
+	// at first creating root node for aiScene.
+	pScene->mRootNode = new aiNode;
+	pScene->mRootNode->mParent = nullptr;
+	pScene->mFlags |= AI_SCENE_FLAGS_ALLOW_SHARED;
+	// search for root(<amf>) element
+	CAMFImporter_NodeElement* root_el = nullptr;
+
+	for(CAMFImporter_NodeElement* ne: mNodeElement_List)
+	{
+		if(ne->Type != CAMFImporter_NodeElement::ENET_Root) continue;
+
+		root_el = ne;
+
+		break;
+	}// for(const CAMFImporter_NodeElement* ne: mNodeElement_List)
+
+	// Check if root element are found.
+	if(root_el == nullptr) throw DeadlyImportError("Root(<amf>) element not found.");
+
+	// after that walk through children of root and collect data. Five types of nodes can be placed at top level - in <amf>: <object>, <material>, <texture>,
+	// <constellation> and <metadata>. But at first we must read <material> and <texture> because they will be used in <object>. <metadata> can be read
+	// at any moment.
+	//
+	// 1. <material>
+	// 2. <texture> will be converted later when processing triangles list. \sa Postprocess_BuildMeshSet
+	for(const CAMFImporter_NodeElement* root_child: root_el->Child)
+	{
+		if(root_child->Type == CAMFImporter_NodeElement::ENET_Material) Postprocess_BuildMaterial(*((CAMFImporter_NodeElement_Material*)root_child));
+	}
+
+	// After "appearance" nodes we must read <object> because it will be used in <constellation> -> <instance>.
+	//
+	// 3. <object>
+	for(const CAMFImporter_NodeElement* root_child: root_el->Child)
+	{
+		if(root_child->Type == CAMFImporter_NodeElement::ENET_Object)
+		{
+			aiNode* tnode = nullptr;
+
+			// for <object> mesh and node must be built: object ID assigned to aiNode name and will be used in future for <instance>
+			Postprocess_BuildNodeAndObject(*((CAMFImporter_NodeElement_Object*)root_child), mesh_list, &tnode);
+			if(tnode != nullptr) node_list.push_back(tnode);
+
+		}
+	}// for(const CAMFImporter_NodeElement* root_child: root_el->Child)
+
+	// And finally read rest of nodes.
+	//
+	for(const CAMFImporter_NodeElement* root_child: root_el->Child)
+	{
+		// 4. <constellation>
+		if(root_child->Type == CAMFImporter_NodeElement::ENET_Constellation)
+		{
+			// <object> and <constellation> at top of self abstraction use aiNode. So we can use only aiNode list for creating new aiNode's.
+			Postprocess_BuildConstellation(*((CAMFImporter_NodeElement_Constellation*)root_child), node_list);
+		}
+
+		// 5, <metadata>
+		if(root_child->Type == CAMFImporter_NodeElement::ENET_Metadata) meta_list.push_back((CAMFImporter_NodeElement_Metadata*)root_child);
+	}// for(const CAMFImporter_NodeElement* root_child: root_el->Child)
+
+	// at now we can add collected metadata to root node
+	Postprocess_AddMetadata(meta_list, *pScene->mRootNode);
+	//
+	// Check constellation children
+	//
+	// As said in specification:
+	// "When multiple objects and constellations are defined in a single file, only the top level objects and constellations are available for printing."
+	// What that means? For example: if some object is used in constellation then you must show only constellation but not original object.
+	// And at this step we are checking that relations.
+nl_clean_loop:
+
+	if(node_list.size() > 1)
+	{
+		// walk through all nodes
+		for(std::list<aiNode*>::iterator nl_it = node_list.begin(); nl_it != node_list.end(); nl_it++)
+		{
+			// and try to find them in another top nodes.
+			std::list<aiNode*>::const_iterator next_it = nl_it;
+
+			next_it++;
+			for(; next_it != node_list.end(); next_it++)
+			{
+				if((*next_it)->FindNode((*nl_it)->mName) != nullptr)
+				{
+					// if current top node(nl_it) found in another top node then erase it from node_list and restart search loop.
+					node_list.erase(nl_it);
+
+					goto nl_clean_loop;
+				}
+			}// for(; next_it != node_list.end(); next_it++)
+		}// for(std::list<aiNode*>::const_iterator nl_it = node_list.begin(); nl_it != node_list.end(); nl_it++)
+	}
+
+	//
+	// move created objects to aiScene
+	//
+	//
+	// Nodes
+	if(node_list.size() > 0)
+	{
+		std::list<aiNode*>::const_iterator nl_it = node_list.begin();
+
+		pScene->mRootNode->mNumChildren = static_cast<unsigned int>(node_list.size());
+		pScene->mRootNode->mChildren = new aiNode*[pScene->mRootNode->mNumChildren];
+		for(size_t i = 0; i < pScene->mRootNode->mNumChildren; i++)
+		{
+			// Objects and constellation that must be showed placed at top of hierarchy in <amf> node. So all aiNode's in node_list must have
+			// mRootNode only as parent.
+			(*nl_it)->mParent = pScene->mRootNode;
+			pScene->mRootNode->mChildren[i] = *nl_it++;
+		}
+	}// if(node_list.size() > 0)
+
+	//
+	// Meshes
+	if(mesh_list.size() > 0)
+	{
+		std::list<aiMesh*>::const_iterator ml_it = mesh_list.begin();
+
+		pScene->mNumMeshes = static_cast<unsigned int>(mesh_list.size());
+		pScene->mMeshes = new aiMesh*[pScene->mNumMeshes];
+		for(size_t i = 0; i < pScene->mNumMeshes; i++) pScene->mMeshes[i] = *ml_it++;
+	}// if(mesh_list.size() > 0)
+
+	//
+	// Textures
+	pScene->mNumTextures = static_cast<unsigned int>(mTexture_Converted.size());
+	if(pScene->mNumTextures > 0)
+	{
+		size_t idx;
+
+		idx = 0;
+		pScene->mTextures = new aiTexture*[pScene->mNumTextures];
+		for(const SPP_Texture& tex_convd: mTexture_Converted)
+		{
+			pScene->mTextures[idx] = new aiTexture;
+			pScene->mTextures[idx]->mWidth = static_cast<unsigned int>(tex_convd.Width);
+			pScene->mTextures[idx]->mHeight = static_cast<unsigned int>(tex_convd.Height);
+			pScene->mTextures[idx]->pcData = (aiTexel*)tex_convd.Data;
+			// texture format description.
+			strcpy(pScene->mTextures[idx]->achFormatHint, tex_convd.FormatHint);
+			idx++;
+		}// for(const SPP_Texture& tex_convd: mTexture_Converted)
+
+		// Create materials for embedded textures.
+		idx = 0;
+		pScene->mNumMaterials = static_cast<unsigned int>(mTexture_Converted.size());
+		pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials];
+		for(const SPP_Texture& tex_convd: mTexture_Converted)
+		{
+			const aiString texture_id(AI_EMBEDDED_TEXNAME_PREFIX + to_string(idx));
+			const int mode = aiTextureOp_Multiply;
+			const int repeat = tex_convd.Tiled ? 1 : 0;
+
+			pScene->mMaterials[idx] = new aiMaterial;
+			pScene->mMaterials[idx]->AddProperty(&texture_id, AI_MATKEY_TEXTURE_DIFFUSE(0));
+			pScene->mMaterials[idx]->AddProperty(&mode, 1, AI_MATKEY_TEXOP_DIFFUSE(0));
+			pScene->mMaterials[idx]->AddProperty(&repeat, 1, AI_MATKEY_MAPPINGMODE_U_DIFFUSE(0));
+			pScene->mMaterials[idx]->AddProperty(&repeat, 1, AI_MATKEY_MAPPINGMODE_V_DIFFUSE(0));
+			idx++;
+		}
+	}// if(pScene->mNumTextures > 0)
+}// END: after that walk through children of root and collect data
+
+}// namespace Assimp
+
+#endif // !ASSIMP_BUILD_NO_AMF_IMPORTER

+ 1328 - 0
Engine/lib/assimp/code/ASELoader.cpp

@@ -0,0 +1,1328 @@
+/*
+---------------------------------------------------------------------------
+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  ASELoader.cpp
+ *  @brief Implementation of the ASE importer class
+ */
+
+#ifndef ASSIMP_BUILD_NO_ASE_IMPORTER
+
+#ifndef ASSIMP_BUILD_NO_3DS_IMPORTER
+
+// internal headers
+#include "ASELoader.h"
+#include <assimp/StringComparison.h>
+#include <assimp/SkeletonMeshBuilder.h>
+#include "TargetAnimation.h"
+#include <assimp/Importer.hpp>
+#include <assimp/IOSystem.hpp>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/scene.h>
+#include <assimp/importerdesc.h>
+
+#include <memory>
+
+// utilities
+#include <assimp/fast_atof.h>
+
+using namespace Assimp;
+using namespace Assimp::ASE;
+
+static const aiImporterDesc desc = {
+    "ASE Importer",
+    "",
+    "",
+    "Similar to 3DS but text-encoded",
+    aiImporterFlags_SupportTextFlavour,
+    0,
+    0,
+    0,
+    0,
+    "ase ask"
+};
+
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by Importer
+ASEImporter::ASEImporter()
+: mParser()
+, mBuffer()
+, pcScene()
+, configRecomputeNormals()
+, noSkeletonMesh()
+{}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+ASEImporter::~ASEImporter()
+{}
+
+// ------------------------------------------------------------------------------------------------
+// Returns whether the class can handle the format of the given file.
+bool ASEImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool cs) const
+{
+    // check file extension
+    const std::string extension = GetExtension(pFile);
+
+    if( extension == "ase" || extension == "ask")
+        return true;
+
+    if ((!extension.length() || cs) && pIOHandler) {
+        const char* tokens[] = {"*3dsmax_asciiexport"};
+        return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1);
+    }
+    return false;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Loader meta information
+const aiImporterDesc* ASEImporter::GetInfo () const
+{
+    return &desc;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Setup configuration options
+void ASEImporter::SetupProperties(const Importer* pImp)
+{
+    configRecomputeNormals = (pImp->GetPropertyInteger(
+        AI_CONFIG_IMPORT_ASE_RECONSTRUCT_NORMALS,1) ? true : false);
+
+    noSkeletonMesh = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_NO_SKELETON_MESHES,0) != 0;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Imports the given file into the given scene structure.
+void ASEImporter::InternReadFile( const std::string& pFile,
+    aiScene* pScene, IOSystem* pIOHandler)
+{
+    std::unique_ptr<IOStream> file( pIOHandler->Open( pFile, "rb"));
+
+    // Check whether we can read from the file
+    if( file.get() == NULL) {
+        throw DeadlyImportError( "Failed to open ASE file " + pFile + ".");
+    }
+
+    // Allocate storage and copy the contents of the file to a memory buffer
+    std::vector<char> mBuffer2;
+    TextFileToBuffer(file.get(),mBuffer2);
+
+    this->mBuffer = &mBuffer2[0];
+    this->pcScene = pScene;
+
+    // ------------------------------------------------------------------
+    // Guess the file format by looking at the extension
+    // ASC is considered to be the older format 110,
+    // ASE is the actual version 200 (that is currently written by max)
+    // ------------------------------------------------------------------
+    unsigned int defaultFormat;
+    std::string::size_type s = pFile.length()-1;
+    switch (pFile.c_str()[s])   {
+
+    case 'C':
+    case 'c':
+        defaultFormat = AI_ASE_OLD_FILE_FORMAT;
+        break;
+    default:
+        defaultFormat = AI_ASE_NEW_FILE_FORMAT;
+    };
+
+    // Construct an ASE parser and parse the file
+    ASE::Parser parser(mBuffer,defaultFormat);
+    mParser = &parser;
+    mParser->Parse();
+
+    //------------------------------------------------------------------
+    // Check whether we god at least one mesh. If we did - generate
+    // materials and copy meshes.
+    // ------------------------------------------------------------------
+    if ( !mParser->m_vMeshes.empty())   {
+
+        // If absolutely no material has been loaded from the file
+        // we need to generate a default material
+        GenerateDefaultMaterial();
+
+        // process all meshes
+        bool tookNormals = false;
+        std::vector<aiMesh*> avOutMeshes;
+        avOutMeshes.reserve(mParser->m_vMeshes.size()*2);
+        for (std::vector<ASE::Mesh>::iterator i =  mParser->m_vMeshes.begin();i != mParser->m_vMeshes.end();++i)    {
+            if ((*i).bSkip) {
+                continue;
+            }
+            BuildUniqueRepresentation(*i);
+
+            // Need to generate proper vertex normals if necessary
+            if(GenerateNormals(*i)) {
+                tookNormals = true;
+            }
+
+            // Convert all meshes to aiMesh objects
+            ConvertMeshes(*i,avOutMeshes);
+        }
+        if (tookNormals)    {
+            ASSIMP_LOG_DEBUG("ASE: Taking normals from the file. Use "
+                "the AI_CONFIG_IMPORT_ASE_RECONSTRUCT_NORMALS setting if you "
+                "experience problems");
+        }
+
+        // Now build the output mesh list. Remove dummies
+        pScene->mNumMeshes = (unsigned int)avOutMeshes.size();
+        aiMesh** pp = pScene->mMeshes = new aiMesh*[pScene->mNumMeshes];
+        for (std::vector<aiMesh*>::const_iterator i =  avOutMeshes.begin();i != avOutMeshes.end();++i) {
+            if (!(*i)->mNumFaces) {
+                continue;
+            }
+            *pp++ = *i;
+        }
+        pScene->mNumMeshes = (unsigned int)(pp - pScene->mMeshes);
+
+        // Build final material indices (remove submaterials and setup
+        // the final list)
+        BuildMaterialIndices();
+    }
+
+    // ------------------------------------------------------------------
+    // Copy all scene graph nodes - lights, cameras, dummies and meshes
+    // into one huge list.
+    //------------------------------------------------------------------
+    std::vector<BaseNode*> nodes;
+    nodes.reserve(mParser->m_vMeshes.size() +mParser->m_vLights.size()
+        + mParser->m_vCameras.size() + mParser->m_vDummies.size());
+
+    // Lights
+    for (auto &light : mParser->m_vLights)nodes.push_back(&light);
+    // Cameras
+    for (auto &camera : mParser->m_vCameras)nodes.push_back(&camera);
+    // Meshes
+    for (auto &mesh : mParser->m_vMeshes)nodes.push_back(&mesh);
+    // Dummies
+    for (auto &dummy : mParser->m_vDummies)nodes.push_back(&dummy);
+
+    // build the final node graph
+    BuildNodes(nodes);
+
+    // build output animations
+    BuildAnimations(nodes);
+
+    // build output cameras
+    BuildCameras();
+
+    // build output lights
+    BuildLights();
+
+    // ------------------------------------------------------------------
+    // If we have no meshes use the SkeletonMeshBuilder helper class
+    // to build a mesh for the animation skeleton
+    // FIXME: very strange results
+    // ------------------------------------------------------------------
+    if (!pScene->mNumMeshes)    {
+        pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
+        if (!noSkeletonMesh) {
+            SkeletonMeshBuilder skeleton(pScene);
+        }
+    }
+}
+// ------------------------------------------------------------------------------------------------
+void ASEImporter::GenerateDefaultMaterial()
+{
+    ai_assert(NULL != mParser);
+
+    bool bHas = false;
+    for (std::vector<ASE::Mesh>::iterator i =  mParser->m_vMeshes.begin();i != mParser->m_vMeshes.end();++i) {
+        if ((*i).bSkip)continue;
+        if (ASE::Face::DEFAULT_MATINDEX == (*i).iMaterialIndex) {
+            (*i).iMaterialIndex = (unsigned int)mParser->m_vMaterials.size();
+            bHas = true;
+        }
+    }
+    if (bHas || mParser->m_vMaterials.empty())  {
+        // add a simple material without submaterials to the parser's list
+        mParser->m_vMaterials.push_back ( ASE::Material(AI_DEFAULT_MATERIAL_NAME) );
+        ASE::Material& mat = mParser->m_vMaterials.back();
+
+        mat.mDiffuse  = aiColor3D(0.6f,0.6f,0.6f);
+        mat.mSpecular = aiColor3D(1.0f,1.0f,1.0f);
+        mat.mAmbient  = aiColor3D(0.05f,0.05f,0.05f);
+        mat.mShading  = Discreet3DS::Gouraud;
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void ASEImporter::BuildAnimations(const std::vector<BaseNode*>& nodes)
+{
+    // check whether we have at least one mesh which has animations
+    std::vector<ASE::BaseNode*>::const_iterator i =  nodes.begin();
+    unsigned int iNum = 0;
+    for (;i != nodes.end();++i) {
+
+        // TODO: Implement Bezier & TCB support
+        if ((*i)->mAnim.mPositionType != ASE::Animation::TRACK) {
+            ASSIMP_LOG_WARN("ASE: Position controller uses Bezier/TCB keys. "
+                "This is not supported.");
+        }
+        if ((*i)->mAnim.mRotationType != ASE::Animation::TRACK) {
+            ASSIMP_LOG_WARN("ASE: Rotation controller uses Bezier/TCB keys. "
+                "This is not supported.");
+        }
+        if ((*i)->mAnim.mScalingType != ASE::Animation::TRACK)  {
+            ASSIMP_LOG_WARN("ASE: Position controller uses Bezier/TCB keys. "
+                "This is not supported.");
+        }
+
+        // We compare against 1 here - firstly one key is not
+        // really an animation and secondly MAX writes dummies
+        // that represent the node transformation.
+        if ((*i)->mAnim.akeyPositions.size()>1 || (*i)->mAnim.akeyRotations.size()>1 || (*i)->mAnim.akeyScaling.size()>1){
+            ++iNum;
+        }
+        if ((*i)->mTargetAnim.akeyPositions.size() > 1 && is_not_qnan( (*i)->mTargetPosition.x )) {
+            ++iNum;
+        }
+    }
+    if (iNum)   {
+        // Generate a new animation channel and setup everything for it
+        pcScene->mNumAnimations = 1;
+        pcScene->mAnimations    = new aiAnimation*[1];
+        aiAnimation* pcAnim     = pcScene->mAnimations[0] = new aiAnimation();
+        pcAnim->mNumChannels    = iNum;
+        pcAnim->mChannels       = new aiNodeAnim*[iNum];
+        pcAnim->mTicksPerSecond = mParser->iFrameSpeed * mParser->iTicksPerFrame;
+
+        iNum = 0;
+
+        // Now iterate through all meshes and collect all data we can find
+        for (i =  nodes.begin();i != nodes.end();++i)   {
+
+            ASE::BaseNode* me = *i;
+            if ( me->mTargetAnim.akeyPositions.size() > 1 && is_not_qnan( me->mTargetPosition.x ))  {
+                // Generate an extra channel for the camera/light target.
+                // BuildNodes() does also generate an extra node, named
+                // <baseName>.Target.
+                aiNodeAnim* nd = pcAnim->mChannels[iNum++] = new aiNodeAnim();
+                nd->mNodeName.Set(me->mName + ".Target");
+
+                // If there is no input position channel we will need
+                // to supply the default position from the node's
+                // local transformation matrix.
+                /*TargetAnimationHelper helper;
+                if (me->mAnim.akeyPositions.empty())
+                {
+                    aiMatrix4x4& mat = (*i)->mTransform;
+                    helper.SetFixedMainAnimationChannel(aiVector3D(
+                        mat.a4, mat.b4, mat.c4));
+                }
+                else helper.SetMainAnimationChannel (&me->mAnim.akeyPositions);
+                helper.SetTargetAnimationChannel (&me->mTargetAnim.akeyPositions);
+
+                helper.Process(&me->mTargetAnim.akeyPositions);*/
+
+                // Allocate the key array and fill it
+                nd->mNumPositionKeys = (unsigned int) me->mTargetAnim.akeyPositions.size();
+                nd->mPositionKeys = new aiVectorKey[nd->mNumPositionKeys];
+
+                ::memcpy(nd->mPositionKeys,&me->mTargetAnim.akeyPositions[0],
+                    nd->mNumPositionKeys * sizeof(aiVectorKey));
+            }
+
+            if (me->mAnim.akeyPositions.size() > 1 || me->mAnim.akeyRotations.size() > 1 || me->mAnim.akeyScaling.size() > 1)   {
+                // Begin a new node animation channel for this node
+                aiNodeAnim* nd = pcAnim->mChannels[iNum++] = new aiNodeAnim();
+                nd->mNodeName.Set(me->mName);
+
+                // copy position keys
+                if (me->mAnim.akeyPositions.size() > 1 )
+                {
+                    // Allocate the key array and fill it
+                    nd->mNumPositionKeys = (unsigned int) me->mAnim.akeyPositions.size();
+                    nd->mPositionKeys = new aiVectorKey[nd->mNumPositionKeys];
+
+                    ::memcpy(nd->mPositionKeys,&me->mAnim.akeyPositions[0],
+                        nd->mNumPositionKeys * sizeof(aiVectorKey));
+                }
+                // copy rotation keys
+                if (me->mAnim.akeyRotations.size() > 1 )    {
+                    // Allocate the key array and fill it
+                    nd->mNumRotationKeys = (unsigned int) me->mAnim.akeyRotations.size();
+                    nd->mRotationKeys = new aiQuatKey[nd->mNumRotationKeys];
+
+                    // --------------------------------------------------------------------
+                    // Rotation keys are offsets to the previous keys.
+                    // We have the quaternion representations of all
+                    // of them, so we just need to concatenate all
+                    // (unit-length) quaternions to get the absolute
+                    // rotations.
+                    // Rotation keys are ABSOLUTE for older files
+                    // --------------------------------------------------------------------
+
+                    aiQuaternion cur;
+                    for (unsigned int a = 0; a < nd->mNumRotationKeys;++a)  {
+                        aiQuatKey q = me->mAnim.akeyRotations[a];
+
+                        if (mParser->iFileFormat > 110) {
+                            cur = (a ? cur*q.mValue : q.mValue);
+                            q.mValue = cur.Normalize();
+                        }
+                        nd->mRotationKeys[a] = q;
+
+                        // need this to get to Assimp quaternion conventions
+                        nd->mRotationKeys[a].mValue.w *= -1.f;
+                    }
+                }
+                // copy scaling keys
+                if (me->mAnim.akeyScaling.size() > 1 )  {
+                    // Allocate the key array and fill it
+                    nd->mNumScalingKeys = (unsigned int) me->mAnim.akeyScaling.size();
+                    nd->mScalingKeys = new aiVectorKey[nd->mNumScalingKeys];
+
+                    ::memcpy(nd->mScalingKeys,&me->mAnim.akeyScaling[0],
+                        nd->mNumScalingKeys * sizeof(aiVectorKey));
+                }
+            }
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Build output cameras
+void ASEImporter::BuildCameras()
+{
+    if (!mParser->m_vCameras.empty())   {
+        pcScene->mNumCameras = (unsigned int)mParser->m_vCameras.size();
+        pcScene->mCameras = new aiCamera*[pcScene->mNumCameras];
+
+        for (unsigned int i = 0; i < pcScene->mNumCameras;++i)  {
+            aiCamera* out = pcScene->mCameras[i] = new aiCamera();
+            ASE::Camera& in = mParser->m_vCameras[i];
+
+            // copy members
+            out->mClipPlaneFar  = in.mFar;
+            out->mClipPlaneNear = (in.mNear ? in.mNear : 0.1f);
+            out->mHorizontalFOV = in.mFOV;
+
+            out->mName.Set(in.mName);
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Build output lights
+void ASEImporter::BuildLights()
+{
+    if (!mParser->m_vLights.empty())    {
+        pcScene->mNumLights = (unsigned int)mParser->m_vLights.size();
+        pcScene->mLights    = new aiLight*[pcScene->mNumLights];
+
+        for (unsigned int i = 0; i < pcScene->mNumLights;++i)   {
+            aiLight* out = pcScene->mLights[i] = new aiLight();
+            ASE::Light& in = mParser->m_vLights[i];
+
+            // The direction is encoded in the transformation matrix of the node.
+            // In 3DS MAX the light source points into negative Z direction if
+            // the node transformation is the identity.
+            out->mDirection = aiVector3D(0.f,0.f,-1.f);
+
+            out->mName.Set(in.mName);
+            switch (in.mLightType)
+            {
+            case ASE::Light::TARGET:
+                out->mType = aiLightSource_SPOT;
+                out->mAngleInnerCone = AI_DEG_TO_RAD(in.mAngle);
+                out->mAngleOuterCone = (in.mFalloff ? AI_DEG_TO_RAD(in.mFalloff) : out->mAngleInnerCone);
+                break;
+
+            case ASE::Light::DIRECTIONAL:
+                out->mType = aiLightSource_DIRECTIONAL;
+                break;
+
+            default:
+            //case ASE::Light::OMNI:
+                out->mType = aiLightSource_POINT;
+                break;
+            };
+            out->mColorDiffuse = out->mColorSpecular = in.mColor * in.mIntensity;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void ASEImporter::AddNodes(const std::vector<BaseNode*>& nodes,
+    aiNode* pcParent,const char* szName)
+{
+    aiMatrix4x4 m;
+    AddNodes(nodes,pcParent,szName,m);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Add meshes to a given node
+void ASEImporter::AddMeshes(const ASE::BaseNode* snode,aiNode* node)
+{
+    for (unsigned int i = 0; i < pcScene->mNumMeshes;++i)   {
+        // Get the name of the mesh (the mesh instance has been temporarily stored in the third vertex color)
+        const aiMesh* pcMesh  = pcScene->mMeshes[i];
+        const ASE::Mesh* mesh = (const ASE::Mesh*)pcMesh->mColors[2];
+
+        if (mesh == snode) {
+            ++node->mNumMeshes;
+        }
+    }
+
+    if(node->mNumMeshes)    {
+        node->mMeshes = new unsigned int[node->mNumMeshes];
+        for (unsigned int i = 0, p = 0; i < pcScene->mNumMeshes;++i)    {
+
+            const aiMesh* pcMesh  = pcScene->mMeshes[i];
+            const ASE::Mesh* mesh = (const ASE::Mesh*)pcMesh->mColors[2];
+            if (mesh == snode)  {
+                node->mMeshes[p++] = i;
+
+                // Transform all vertices of the mesh back into their local space ->
+                // at the moment they are pretransformed
+                aiMatrix4x4 m  = mesh->mTransform;
+                m.Inverse();
+
+                aiVector3D* pvCurPtr = pcMesh->mVertices;
+                const aiVector3D* pvEndPtr = pvCurPtr + pcMesh->mNumVertices;
+                while (pvCurPtr != pvEndPtr)    {
+                    *pvCurPtr = m * (*pvCurPtr);
+                    pvCurPtr++;
+                }
+
+                // Do the same for the normal vectors, if we have them.
+                // As always, inverse transpose.
+                if (pcMesh->mNormals)   {
+                    aiMatrix3x3 m3 = aiMatrix3x3( mesh->mTransform );
+                    m3.Transpose();
+
+                    pvCurPtr = pcMesh->mNormals;
+                    pvEndPtr = pvCurPtr + pcMesh->mNumVertices;
+                    while (pvCurPtr != pvEndPtr)    {
+                        *pvCurPtr = m3 * (*pvCurPtr);
+                        pvCurPtr++;
+                    }
+                }
+            }
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Add child nodes to a given parent node
+void ASEImporter::AddNodes (const std::vector<BaseNode*>& nodes,
+    aiNode* pcParent, const char* szName,
+    const aiMatrix4x4& mat)
+{
+    const size_t len = szName ? ::strlen(szName) : 0;
+    ai_assert(4 <= AI_MAX_NUMBER_OF_COLOR_SETS);
+
+    // Receives child nodes for the pcParent node
+    std::vector<aiNode*> apcNodes;
+
+    // Now iterate through all nodes in the scene and search for one
+    // which has *us* as parent.
+    for (std::vector<BaseNode*>::const_iterator it = nodes.begin(), end = nodes.end(); it != end; ++it) {
+        const BaseNode* snode = *it;
+        if (szName) {
+            if (len != snode->mParent.length() || ::strcmp(szName,snode->mParent.c_str()))
+                continue;
+        }
+        else if (snode->mParent.length())
+            continue;
+
+        (*it)->mProcessed = true;
+
+        // Allocate a new node and add it to the output data structure
+        apcNodes.push_back(new aiNode());
+        aiNode* node = apcNodes.back();
+
+        node->mName.Set((snode->mName.length() ? snode->mName.c_str() : "Unnamed_Node"));
+        node->mParent = pcParent;
+
+        // Setup the transformation matrix of the node
+        aiMatrix4x4 mParentAdjust  = mat;
+        mParentAdjust.Inverse();
+        node->mTransformation = mParentAdjust*snode->mTransform;
+
+        // Add sub nodes - prevent stack overflow due to recursive parenting
+        if (node->mName != node->mParent->mName && node->mName != node->mParent->mParent->mName ) {
+            AddNodes(nodes,node,node->mName.data,snode->mTransform);
+        }
+
+        // Further processing depends on the type of the node
+        if (snode->mType == ASE::BaseNode::Mesh)    {
+            // If the type of this node is "Mesh" we need to search
+            // the list of output meshes in the data structure for
+            // all those that belonged to this node once. This is
+            // slightly inconvinient here and a better solution should
+            // be used when this code is refactored next.
+            AddMeshes(snode,node);
+        }
+        else if (is_not_qnan( snode->mTargetPosition.x ))   {
+            // If this is a target camera or light we generate a small
+            // child node which marks the position of the camera
+            // target (the direction information is contained in *this*
+            // node's animation track but the exact target position
+            // would be lost otherwise)
+            if (!node->mNumChildren)    {
+                node->mChildren = new aiNode*[1];
+            }
+
+            aiNode* nd = new aiNode();
+
+            nd->mName.Set ( snode->mName + ".Target" );
+
+            nd->mTransformation.a4 = snode->mTargetPosition.x - snode->mTransform.a4;
+            nd->mTransformation.b4 = snode->mTargetPosition.y - snode->mTransform.b4;
+            nd->mTransformation.c4 = snode->mTargetPosition.z - snode->mTransform.c4;
+
+            nd->mParent = node;
+
+            // The .Target node is always the first child node
+            for (unsigned int m = 0; m < node->mNumChildren;++m)
+                node->mChildren[m+1] = node->mChildren[m];
+
+            node->mChildren[0] = nd;
+            node->mNumChildren++;
+
+            // What we did is so great, it is at least worth a debug message
+            ASSIMP_LOG_DEBUG("ASE: Generating separate target node ("+snode->mName+")");
+        }
+    }
+
+    // Allocate enough space for the child nodes
+    // We allocate one slot more  in case this is a target camera/light
+    pcParent->mNumChildren = (unsigned int)apcNodes.size();
+    if (pcParent->mNumChildren) {
+        pcParent->mChildren = new aiNode*[apcNodes.size()+1 /* PLUS ONE !!! */];
+
+        // now build all nodes for our nice new children
+        for (unsigned int p = 0; p < apcNodes.size();++p)
+            pcParent->mChildren[p] = apcNodes[p];
+    }
+    return;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Build the output node graph
+void ASEImporter::BuildNodes(std::vector<BaseNode*>& nodes) {
+    ai_assert(NULL != pcScene);
+
+    // allocate the one and only root node
+    aiNode* root = pcScene->mRootNode = new aiNode();
+    root->mName.Set("<ASERoot>");
+
+    // Setup the coordinate system transformation
+    pcScene->mRootNode->mNumChildren = 1;
+    pcScene->mRootNode->mChildren = new aiNode*[1];
+    aiNode* ch = pcScene->mRootNode->mChildren[0] = new aiNode();
+    ch->mParent = root;
+
+    // Change the transformation matrix of all nodes
+    for (BaseNode *node : nodes) {
+        aiMatrix4x4& m = node->mTransform;
+        m.Transpose(); // row-order vs column-order
+    }
+
+    // add all nodes
+    AddNodes(nodes,ch,NULL);
+
+    // now iterate through al nodes and find those that have not yet
+    // been added to the nodegraph (= their parent could not be recognized)
+    std::vector<const BaseNode*> aiList;
+    for (std::vector<BaseNode*>::iterator it = nodes.begin(), end = nodes.end();it != end; ++it)    {
+        if ((*it)->mProcessed) {
+            continue;
+        }
+
+        // check whether our parent is known
+        bool bKnowParent = false;
+
+        // search the list another time, starting *here* and try to find out whether
+        // there is a node that references *us* as a parent
+        for (std::vector<BaseNode*>::const_iterator it2 = nodes.begin();it2 != end; ++it2) {
+            if (it2 == it) {
+                continue;
+            }
+
+            if ((*it2)->mName == (*it)->mParent)    {
+                bKnowParent = true;
+                break;
+            }
+        }
+        if (!bKnowParent)   {
+            aiList.push_back(*it);
+        }
+    }
+
+    // Are there ane orphaned nodes?
+    if (!aiList.empty())    {
+        std::vector<aiNode*> apcNodes;
+        apcNodes.reserve(aiList.size() + pcScene->mRootNode->mNumChildren);
+
+        for (unsigned int i = 0; i < pcScene->mRootNode->mNumChildren;++i)
+            apcNodes.push_back(pcScene->mRootNode->mChildren[i]);
+
+        delete[] pcScene->mRootNode->mChildren;
+        for (std::vector<const BaseNode*>::/*const_*/iterator i =  aiList.begin();i != aiList.end();++i)    {
+            const ASE::BaseNode* src = *i;
+
+            // The parent is not known, so we can assume that we must add
+            // this node to the root node of the whole scene
+            aiNode* pcNode = new aiNode();
+            pcNode->mParent = pcScene->mRootNode;
+            pcNode->mName.Set(src->mName);
+            AddMeshes(src,pcNode);
+            AddNodes(nodes,pcNode,pcNode->mName.data);
+            apcNodes.push_back(pcNode);
+        }
+
+        // Regenerate our output array
+        pcScene->mRootNode->mChildren = new aiNode*[apcNodes.size()];
+        for (unsigned int i = 0; i < apcNodes.size();++i)
+            pcScene->mRootNode->mChildren[i] = apcNodes[i];
+
+        pcScene->mRootNode->mNumChildren = (unsigned int)apcNodes.size();
+    }
+
+    // Reset the third color set to NULL - we used this field to store a temporary pointer
+    for (unsigned int i = 0; i < pcScene->mNumMeshes;++i)
+        pcScene->mMeshes[i]->mColors[2] = NULL;
+
+    // The root node should not have at least one child or the file is valid
+    if (!pcScene->mRootNode->mNumChildren) {
+        throw DeadlyImportError("ASE: No nodes loaded. The file is either empty or corrupt");
+    }
+
+    // Now rotate the whole scene 90 degrees around the x axis to convert to internal coordinate system
+    pcScene->mRootNode->mTransformation = aiMatrix4x4(1.f,0.f,0.f,0.f,
+        0.f,0.f,1.f,0.f,0.f,-1.f,0.f,0.f,0.f,0.f,0.f,1.f);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Convert the imported data to the internal verbose representation
+void ASEImporter::BuildUniqueRepresentation(ASE::Mesh& mesh)    {
+    // allocate output storage
+    std::vector<aiVector3D> mPositions;
+    std::vector<aiVector3D> amTexCoords[AI_MAX_NUMBER_OF_TEXTURECOORDS];
+    std::vector<aiColor4D>  mVertexColors;
+    std::vector<aiVector3D> mNormals;
+    std::vector<BoneVertex> mBoneVertices;
+
+    unsigned int iSize = (unsigned int)mesh.mFaces.size() * 3;
+    mPositions.resize(iSize);
+
+    // optional texture coordinates
+    for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS;++i)    {
+        if (!mesh.amTexCoords[i].empty())   {
+            amTexCoords[i].resize(iSize);
+        }
+    }
+    // optional vertex colors
+    if (!mesh.mVertexColors.empty())    {
+        mVertexColors.resize(iSize);
+    }
+
+    // optional vertex normals (vertex normals can simply be copied)
+    if (!mesh.mNormals.empty()) {
+        mNormals.resize(iSize);
+    }
+    // bone vertices. There is no need to change the bone list
+    if (!mesh.mBoneVertices.empty())    {
+        mBoneVertices.resize(iSize);
+    }
+
+    // iterate through all faces in the mesh
+    unsigned int iCurrent = 0, fi = 0;
+    for (std::vector<ASE::Face>::iterator i =  mesh.mFaces.begin();i != mesh.mFaces.end();++i,++fi) {
+        for (unsigned int n = 0; n < 3;++n,++iCurrent)
+        {
+            mPositions[iCurrent] = mesh.mPositions[(*i).mIndices[n]];
+
+            // add texture coordinates
+            for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS;++c)    {
+                if (mesh.amTexCoords[c].empty())break;
+                amTexCoords[c][iCurrent] = mesh.amTexCoords[c][(*i).amUVIndices[c][n]];
+            }
+            // add vertex colors
+            if (!mesh.mVertexColors.empty())    {
+                mVertexColors[iCurrent] = mesh.mVertexColors[(*i).mColorIndices[n]];
+            }
+            // add normal vectors
+            if (!mesh.mNormals.empty()) {
+                mNormals[iCurrent] = mesh.mNormals[fi*3+n];
+                mNormals[iCurrent].Normalize();
+            }
+
+            // handle bone vertices
+            if ((*i).mIndices[n] < mesh.mBoneVertices.size())   {
+                // (sometimes this will cause bone verts to be duplicated
+                //  however, I' quite sure Schrompf' JoinVerticesStep
+                //  will fix that again ...)
+                mBoneVertices[iCurrent] =  mesh.mBoneVertices[(*i).mIndices[n]];
+            }
+            (*i).mIndices[n] = iCurrent;
+        }
+    }
+
+    // replace the old arrays
+    mesh.mNormals = mNormals;
+    mesh.mPositions = mPositions;
+    mesh.mVertexColors = mVertexColors;
+
+    for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS;++c)
+        mesh.amTexCoords[c] = amTexCoords[c];
+}
+
+// ------------------------------------------------------------------------------------------------
+// Copy a texture from the ASE structs to the output material
+void CopyASETexture(aiMaterial& mat, ASE::Texture& texture, aiTextureType type)
+{
+    // Setup the texture name
+    aiString tex;
+    tex.Set( texture.mMapName);
+    mat.AddProperty( &tex, AI_MATKEY_TEXTURE(type,0));
+
+    // Setup the texture blend factor
+    if (is_not_qnan(texture.mTextureBlend))
+        mat.AddProperty<ai_real>( &texture.mTextureBlend, 1, AI_MATKEY_TEXBLEND(type,0));
+
+    // Setup texture UV transformations
+    mat.AddProperty<ai_real>(&texture.mOffsetU,5,AI_MATKEY_UVTRANSFORM(type,0));
+}
+
+// ------------------------------------------------------------------------------------------------
+// Convert from ASE material to output material
+void ASEImporter::ConvertMaterial(ASE::Material& mat)
+{
+    // LARGE TODO: Much code her is copied from 3DS ... join them maybe?
+
+    // Allocate the output material
+    mat.pcInstance = new aiMaterial();
+
+    // At first add the base ambient color of the
+    // scene to the material
+    mat.mAmbient.r += mParser->m_clrAmbient.r;
+    mat.mAmbient.g += mParser->m_clrAmbient.g;
+    mat.mAmbient.b += mParser->m_clrAmbient.b;
+
+    aiString name;
+    name.Set( mat.mName);
+    mat.pcInstance->AddProperty( &name, AI_MATKEY_NAME);
+
+    // material colors
+    mat.pcInstance->AddProperty( &mat.mAmbient, 1, AI_MATKEY_COLOR_AMBIENT);
+    mat.pcInstance->AddProperty( &mat.mDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE);
+    mat.pcInstance->AddProperty( &mat.mSpecular, 1, AI_MATKEY_COLOR_SPECULAR);
+    mat.pcInstance->AddProperty( &mat.mEmissive, 1, AI_MATKEY_COLOR_EMISSIVE);
+
+    // shininess
+    if (0.0f != mat.mSpecularExponent && 0.0f != mat.mShininessStrength)
+    {
+        mat.pcInstance->AddProperty( &mat.mSpecularExponent, 1, AI_MATKEY_SHININESS);
+        mat.pcInstance->AddProperty( &mat.mShininessStrength, 1, AI_MATKEY_SHININESS_STRENGTH);
+    }
+    // If there is no shininess, we can disable phong lighting
+    else if (D3DS::Discreet3DS::Metal == mat.mShading ||
+        D3DS::Discreet3DS::Phong == mat.mShading ||
+        D3DS::Discreet3DS::Blinn == mat.mShading)
+    {
+        mat.mShading = D3DS::Discreet3DS::Gouraud;
+    }
+
+    // opacity
+    mat.pcInstance->AddProperty<ai_real>( &mat.mTransparency,1,AI_MATKEY_OPACITY);
+
+    // Two sided rendering?
+    if (mat.mTwoSided)
+    {
+        int i = 1;
+        mat.pcInstance->AddProperty<int>(&i,1,AI_MATKEY_TWOSIDED);
+    }
+
+    // shading mode
+    aiShadingMode eShading = aiShadingMode_NoShading;
+    switch (mat.mShading)
+    {
+        case D3DS::Discreet3DS::Flat:
+            eShading = aiShadingMode_Flat; break;
+        case D3DS::Discreet3DS::Phong :
+            eShading = aiShadingMode_Phong; break;
+        case D3DS::Discreet3DS::Blinn :
+            eShading = aiShadingMode_Blinn; break;
+
+            // I don't know what "Wire" shading should be,
+            // assume it is simple lambertian diffuse (L dot N) shading
+        case D3DS::Discreet3DS::Wire:
+            {
+                // set the wireframe flag
+                unsigned int iWire = 1;
+                mat.pcInstance->AddProperty<int>( (int*)&iWire,1,AI_MATKEY_ENABLE_WIREFRAME);
+            }
+        case D3DS::Discreet3DS::Gouraud:
+            eShading = aiShadingMode_Gouraud; break;
+        case D3DS::Discreet3DS::Metal :
+            eShading = aiShadingMode_CookTorrance; break;
+    }
+    mat.pcInstance->AddProperty<int>( (int*)&eShading,1,AI_MATKEY_SHADING_MODEL);
+
+    // DIFFUSE texture
+    if( mat.sTexDiffuse.mMapName.length() > 0)
+        CopyASETexture(*mat.pcInstance,mat.sTexDiffuse, aiTextureType_DIFFUSE);
+
+    // SPECULAR texture
+    if( mat.sTexSpecular.mMapName.length() > 0)
+        CopyASETexture(*mat.pcInstance,mat.sTexSpecular, aiTextureType_SPECULAR);
+
+    // AMBIENT texture
+    if( mat.sTexAmbient.mMapName.length() > 0)
+        CopyASETexture(*mat.pcInstance,mat.sTexAmbient, aiTextureType_AMBIENT);
+
+    // OPACITY texture
+    if( mat.sTexOpacity.mMapName.length() > 0)
+        CopyASETexture(*mat.pcInstance,mat.sTexOpacity, aiTextureType_OPACITY);
+
+    // EMISSIVE texture
+    if( mat.sTexEmissive.mMapName.length() > 0)
+        CopyASETexture(*mat.pcInstance,mat.sTexEmissive, aiTextureType_EMISSIVE);
+
+    // BUMP texture
+    if( mat.sTexBump.mMapName.length() > 0)
+        CopyASETexture(*mat.pcInstance,mat.sTexBump, aiTextureType_HEIGHT);
+
+    // SHININESS texture
+    if( mat.sTexShininess.mMapName.length() > 0)
+        CopyASETexture(*mat.pcInstance,mat.sTexShininess, aiTextureType_SHININESS);
+
+    // store the name of the material itself, too
+    if( mat.mName.length() > 0) {
+        aiString tex;tex.Set( mat.mName);
+        mat.pcInstance->AddProperty( &tex, AI_MATKEY_NAME);
+    }
+    return;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Build output meshes
+void ASEImporter::ConvertMeshes(ASE::Mesh& mesh, std::vector<aiMesh*>& avOutMeshes)
+{
+    // validate the material index of the mesh
+    if (mesh.iMaterialIndex >= mParser->m_vMaterials.size())    {
+        mesh.iMaterialIndex = (unsigned int)mParser->m_vMaterials.size()-1;
+        ASSIMP_LOG_WARN("Material index is out of range");
+    }
+
+    // If the material the mesh is assigned to is consisting of submeshes, split it
+    if (!mParser->m_vMaterials[mesh.iMaterialIndex].avSubMaterials.empty()) {
+        std::vector<ASE::Material> vSubMaterials = mParser->
+            m_vMaterials[mesh.iMaterialIndex].avSubMaterials;
+
+        std::vector<unsigned int>* aiSplit = new std::vector<unsigned int>[vSubMaterials.size()];
+
+        // build a list of all faces per sub-material
+        for (unsigned int i = 0; i < mesh.mFaces.size();++i)    {
+            // check range
+            if (mesh.mFaces[i].iMaterial >= vSubMaterials.size()) {
+                ASSIMP_LOG_WARN("Submaterial index is out of range");
+
+                // use the last material instead
+                aiSplit[vSubMaterials.size()-1].push_back(i);
+            }
+            else aiSplit[mesh.mFaces[i].iMaterial].push_back(i);
+        }
+
+        // now generate submeshes
+        for (unsigned int p = 0; p < vSubMaterials.size();++p)  {
+            if (!aiSplit[p].empty())    {
+
+                aiMesh* p_pcOut = new aiMesh();
+                p_pcOut->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
+
+                // let the sub material index
+                p_pcOut->mMaterialIndex = p;
+
+                // we will need this material
+                mParser->m_vMaterials[mesh.iMaterialIndex].avSubMaterials[p].bNeed = true;
+
+                // store the real index here ... color channel 3
+                p_pcOut->mColors[3] = (aiColor4D*)(uintptr_t)mesh.iMaterialIndex;
+
+                // store a pointer to the mesh in color channel 2
+                p_pcOut->mColors[2] = (aiColor4D*) &mesh;
+                avOutMeshes.push_back(p_pcOut);
+
+                // convert vertices
+                p_pcOut->mNumVertices = (unsigned int)aiSplit[p].size()*3;
+                p_pcOut->mNumFaces = (unsigned int)aiSplit[p].size();
+
+                // receive output vertex weights
+                std::vector<std::pair<unsigned int, float> > *avOutputBones = NULL;
+                if (!mesh.mBones.empty())   {
+                    avOutputBones = new std::vector<std::pair<unsigned int, float> >[mesh.mBones.size()];
+                }
+
+                // allocate enough storage for faces
+                p_pcOut->mFaces = new aiFace[p_pcOut->mNumFaces];
+
+                unsigned int iBase = 0,iIndex;
+                if (p_pcOut->mNumVertices)  {
+                    p_pcOut->mVertices = new aiVector3D[p_pcOut->mNumVertices];
+                    p_pcOut->mNormals  = new aiVector3D[p_pcOut->mNumVertices];
+                    for (unsigned int q = 0; q < aiSplit[p].size();++q) {
+
+                        iIndex = aiSplit[p][q];
+
+                        p_pcOut->mFaces[q].mIndices = new unsigned int[3];
+                        p_pcOut->mFaces[q].mNumIndices = 3;
+
+                        for (unsigned int t = 0; t < 3;++t, ++iBase)    {
+                            const uint32_t iIndex2 = mesh.mFaces[iIndex].mIndices[t];
+
+                            p_pcOut->mVertices[iBase] = mesh.mPositions [iIndex2];
+                            p_pcOut->mNormals [iBase] = mesh.mNormals   [iIndex2];
+
+                            // convert bones, if existing
+                            if (!mesh.mBones.empty()) {
+                                ai_assert(avOutputBones);
+                                // check whether there is a vertex weight for this vertex index
+                                if (iIndex2 < mesh.mBoneVertices.size())    {
+
+                                    for (std::vector<std::pair<int,float> >::const_iterator
+                                        blubb =  mesh.mBoneVertices[iIndex2].mBoneWeights.begin();
+                                        blubb != mesh.mBoneVertices[iIndex2].mBoneWeights.end();++blubb)    {
+
+                                        // NOTE: illegal cases have already been filtered out
+                                        avOutputBones[(*blubb).first].push_back(std::pair<unsigned int, float>(
+                                            iBase,(*blubb).second));
+                                    }
+                                }
+                            }
+                            p_pcOut->mFaces[q].mIndices[t] = iBase;
+                        }
+                    }
+                }
+                // convert texture coordinates (up to AI_MAX_NUMBER_OF_TEXTURECOORDS sets supported)
+                for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS;++c) {
+                    if (!mesh.amTexCoords[c].empty())
+                    {
+                        p_pcOut->mTextureCoords[c] = new aiVector3D[p_pcOut->mNumVertices];
+                        iBase = 0;
+                        for (unsigned int q = 0; q < aiSplit[p].size();++q) {
+                            iIndex = aiSplit[p][q];
+                            for (unsigned int t = 0; t < 3;++t) {
+                                p_pcOut->mTextureCoords[c][iBase++] = mesh.amTexCoords[c][mesh.mFaces[iIndex].mIndices[t]];
+                            }
+                        }
+                        // Setup the number of valid vertex components
+                        p_pcOut->mNumUVComponents[c] = mesh.mNumUVComponents[c];
+                    }
+                }
+
+                // Convert vertex colors (only one set supported)
+                if (!mesh.mVertexColors.empty()){
+                    p_pcOut->mColors[0] = new aiColor4D[p_pcOut->mNumVertices];
+                    iBase = 0;
+                    for (unsigned int q = 0; q < aiSplit[p].size();++q) {
+                        iIndex = aiSplit[p][q];
+                        for (unsigned int t = 0; t < 3;++t) {
+                            p_pcOut->mColors[0][iBase++] = mesh.mVertexColors[mesh.mFaces[iIndex].mIndices[t]];
+                        }
+                    }
+                }
+                // Copy bones
+                if (!mesh.mBones.empty())   {
+                    p_pcOut->mNumBones = 0;
+                    for (unsigned int mrspock = 0; mrspock < mesh.mBones.size();++mrspock)
+                        if (!avOutputBones[mrspock].empty())p_pcOut->mNumBones++;
+
+                    p_pcOut->mBones = new aiBone* [ p_pcOut->mNumBones ];
+                    aiBone** pcBone = p_pcOut->mBones;
+                    for (unsigned int mrspock = 0; mrspock < mesh.mBones.size();++mrspock)
+                    {
+                        if (!avOutputBones[mrspock].empty())    {
+                            // we will need this bone. add it to the output mesh and
+                            // add all per-vertex weights
+                            aiBone* pc = *pcBone = new aiBone();
+                            pc->mName.Set(mesh.mBones[mrspock].mName);
+
+                            pc->mNumWeights = (unsigned int)avOutputBones[mrspock].size();
+                            pc->mWeights = new aiVertexWeight[pc->mNumWeights];
+
+                            for (unsigned int captainkirk = 0; captainkirk < pc->mNumWeights;++captainkirk)
+                            {
+                                const std::pair<unsigned int,float>& ref = avOutputBones[mrspock][captainkirk];
+                                pc->mWeights[captainkirk].mVertexId = ref.first;
+                                pc->mWeights[captainkirk].mWeight = ref.second;
+                            }
+                            ++pcBone;
+                        }
+                    }
+                    // delete allocated storage
+                    delete[] avOutputBones;
+                }
+            }
+        }
+        // delete storage
+        delete[] aiSplit;
+    }
+    else
+    {
+        // Otherwise we can simply copy the data to one output mesh
+        // This codepath needs less memory and uses fast memcpy()s
+        // to do the actual copying. So I think it is worth the
+        // effort here.
+
+        aiMesh* p_pcOut = new aiMesh();
+        p_pcOut->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
+
+        // set an empty sub material index
+        p_pcOut->mMaterialIndex = ASE::Face::DEFAULT_MATINDEX;
+        mParser->m_vMaterials[mesh.iMaterialIndex].bNeed = true;
+
+        // store the real index here ... in color channel 3
+        p_pcOut->mColors[3] = (aiColor4D*)(uintptr_t)mesh.iMaterialIndex;
+
+        // store a pointer to the mesh in color channel 2
+        p_pcOut->mColors[2] = (aiColor4D*) &mesh;
+        avOutMeshes.push_back(p_pcOut);
+
+        // If the mesh hasn't faces or vertices, there are two cases
+        // possible: 1. the model is invalid. 2. This is a dummy
+        // helper object which we are going to remove later ...
+        if (mesh.mFaces.empty() || mesh.mPositions.empty()) {
+            return;
+        }
+
+        // convert vertices
+        p_pcOut->mNumVertices = (unsigned int)mesh.mPositions.size();
+        p_pcOut->mNumFaces = (unsigned int)mesh.mFaces.size();
+
+        // allocate enough storage for faces
+        p_pcOut->mFaces = new aiFace[p_pcOut->mNumFaces];
+
+        // copy vertices
+        p_pcOut->mVertices = new aiVector3D[mesh.mPositions.size()];
+        memcpy(p_pcOut->mVertices,&mesh.mPositions[0],
+            mesh.mPositions.size() * sizeof(aiVector3D));
+
+        // copy normals
+        p_pcOut->mNormals = new aiVector3D[mesh.mNormals.size()];
+        memcpy(p_pcOut->mNormals,&mesh.mNormals[0],
+            mesh.mNormals.size() * sizeof(aiVector3D));
+
+        // copy texture coordinates
+        for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS;++c)    {
+            if (!mesh.amTexCoords[c].empty())   {
+                p_pcOut->mTextureCoords[c] = new aiVector3D[mesh.amTexCoords[c].size()];
+                memcpy(p_pcOut->mTextureCoords[c],&mesh.amTexCoords[c][0],
+                    mesh.amTexCoords[c].size() * sizeof(aiVector3D));
+
+                // setup the number of valid vertex components
+                p_pcOut->mNumUVComponents[c] = mesh.mNumUVComponents[c];
+            }
+        }
+
+        // copy vertex colors
+        if (!mesh.mVertexColors.empty())    {
+            p_pcOut->mColors[0] = new aiColor4D[mesh.mVertexColors.size()];
+            memcpy(p_pcOut->mColors[0],&mesh.mVertexColors[0],
+                mesh.mVertexColors.size() * sizeof(aiColor4D));
+        }
+
+        // copy faces
+        for (unsigned int iFace = 0; iFace < p_pcOut->mNumFaces;++iFace)    {
+            p_pcOut->mFaces[iFace].mNumIndices = 3;
+            p_pcOut->mFaces[iFace].mIndices = new unsigned int[3];
+
+            // copy indices
+            p_pcOut->mFaces[iFace].mIndices[0] = mesh.mFaces[iFace].mIndices[0];
+            p_pcOut->mFaces[iFace].mIndices[1] = mesh.mFaces[iFace].mIndices[1];
+            p_pcOut->mFaces[iFace].mIndices[2] = mesh.mFaces[iFace].mIndices[2];
+        }
+
+        // copy vertex bones
+        if (!mesh.mBones.empty() && !mesh.mBoneVertices.empty())    {
+            std::vector<std::vector<aiVertexWeight> > avBonesOut( mesh.mBones.size() );
+
+            // find all vertex weights for this bone
+            unsigned int quak = 0;
+            for (std::vector<BoneVertex>::const_iterator harrypotter =  mesh.mBoneVertices.begin();
+                harrypotter != mesh.mBoneVertices.end();++harrypotter,++quak)   {
+
+                for (std::vector<std::pair<int,float> >::const_iterator
+                    ronaldweasley  = (*harrypotter).mBoneWeights.begin();
+                    ronaldweasley != (*harrypotter).mBoneWeights.end();++ronaldweasley)
+                {
+                    aiVertexWeight weight;
+                    weight.mVertexId = quak;
+                    weight.mWeight = (*ronaldweasley).second;
+                    avBonesOut[(*ronaldweasley).first].push_back(weight);
+                }
+            }
+
+            // now build a final bone list
+            p_pcOut->mNumBones = 0;
+            for (unsigned int jfkennedy = 0; jfkennedy < mesh.mBones.size();++jfkennedy)
+                if (!avBonesOut[jfkennedy].empty())p_pcOut->mNumBones++;
+
+            p_pcOut->mBones = new aiBone*[p_pcOut->mNumBones];
+            aiBone** pcBone = p_pcOut->mBones;
+            for (unsigned int jfkennedy = 0; jfkennedy < mesh.mBones.size();++jfkennedy)    {
+                if (!avBonesOut[jfkennedy].empty()) {
+                    aiBone* pc = *pcBone = new aiBone();
+                    pc->mName.Set(mesh.mBones[jfkennedy].mName);
+                    pc->mNumWeights = (unsigned int)avBonesOut[jfkennedy].size();
+                    pc->mWeights = new aiVertexWeight[pc->mNumWeights];
+                    ::memcpy(pc->mWeights,&avBonesOut[jfkennedy][0],
+                        sizeof(aiVertexWeight) * pc->mNumWeights);
+                    ++pcBone;
+                }
+            }
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Setup proper material indices and build output materials
+void ASEImporter::BuildMaterialIndices()
+{
+    ai_assert(NULL != pcScene);
+
+    // iterate through all materials and check whether we need them
+    for (unsigned int iMat = 0; iMat < mParser->m_vMaterials.size();++iMat)
+    {
+        ASE::Material& mat = mParser->m_vMaterials[iMat];
+        if (mat.bNeed)  {
+            // Convert it to the aiMaterial layout
+            ConvertMaterial(mat);
+            ++pcScene->mNumMaterials;
+        }
+        for (unsigned int iSubMat = 0; iSubMat < mat.avSubMaterials.size();++iSubMat)
+        {
+            ASE::Material& submat = mat.avSubMaterials[iSubMat];
+            if (submat.bNeed)   {
+                // Convert it to the aiMaterial layout
+                ConvertMaterial(submat);
+                ++pcScene->mNumMaterials;
+            }
+        }
+    }
+
+    // allocate the output material array
+    pcScene->mMaterials = new aiMaterial*[pcScene->mNumMaterials];
+    D3DS::Material** pcIntMaterials = new D3DS::Material*[pcScene->mNumMaterials];
+
+    unsigned int iNum = 0;
+    for (unsigned int iMat = 0; iMat < mParser->m_vMaterials.size();++iMat) {
+        ASE::Material& mat = mParser->m_vMaterials[iMat];
+        if (mat.bNeed)
+        {
+            ai_assert(NULL != mat.pcInstance);
+            pcScene->mMaterials[iNum] = mat.pcInstance;
+
+            // Store the internal material, too
+            pcIntMaterials[iNum] = &mat;
+
+            // Iterate through all meshes and search for one which is using
+            // this top-level material index
+            for (unsigned int iMesh = 0; iMesh < pcScene->mNumMeshes;++iMesh)
+            {
+                aiMesh* mesh = pcScene->mMeshes[iMesh];
+                if (ASE::Face::DEFAULT_MATINDEX == mesh->mMaterialIndex &&
+                    iMat == (uintptr_t)mesh->mColors[3])
+                {
+                    mesh->mMaterialIndex = iNum;
+                    mesh->mColors[3] = NULL;
+                }
+            }
+            iNum++;
+        }
+        for (unsigned int iSubMat = 0; iSubMat < mat.avSubMaterials.size();++iSubMat)   {
+            ASE::Material& submat = mat.avSubMaterials[iSubMat];
+            if (submat.bNeed)   {
+                ai_assert(NULL != submat.pcInstance);
+                pcScene->mMaterials[iNum] = submat.pcInstance;
+
+                // Store the internal material, too
+                pcIntMaterials[iNum] = &submat;
+
+                // Iterate through all meshes and search for one which is using
+                // this sub-level material index
+                for (unsigned int iMesh = 0; iMesh < pcScene->mNumMeshes;++iMesh)   {
+                    aiMesh* mesh = pcScene->mMeshes[iMesh];
+
+                    if (iSubMat == mesh->mMaterialIndex && iMat == (uintptr_t)mesh->mColors[3]) {
+                        mesh->mMaterialIndex = iNum;
+                        mesh->mColors[3]     = NULL;
+                    }
+                }
+                iNum++;
+            }
+        }
+    }
+
+    // Dekete our temporary array
+    delete[] pcIntMaterials;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Generate normal vectors basing on smoothing groups
+bool ASEImporter::GenerateNormals(ASE::Mesh& mesh)  {
+
+    if (!mesh.mNormals.empty() && !configRecomputeNormals)
+    {
+        // Check whether there are only uninitialized normals. If there are
+        // some, skip all normals from the file and compute them on our own
+        for (std::vector<aiVector3D>::const_iterator qq =  mesh.mNormals.begin();qq != mesh.mNormals.end();++qq) {
+            if ((*qq).x || (*qq).y || (*qq).z)
+            {
+                return true;
+            }
+        }
+    }
+    // The array is reused.
+    ComputeNormalsWithSmoothingsGroups<ASE::Face>(mesh);
+    return false;
+}
+
+#endif // ASSIMP_BUILD_NO_3DS_IMPORTER
+
+#endif // !! ASSIMP_BUILD_NO_BASE_IMPORTER

+ 208 - 0
Engine/lib/assimp/code/ASELoader.h

@@ -0,0 +1,208 @@
+/*
+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  ASELoader.h
+ *  @brief Definition of the .ASE importer class.
+ */
+#ifndef AI_ASELOADER_H_INCLUDED
+#define AI_ASELOADER_H_INCLUDED
+
+#include <assimp/BaseImporter.h>
+#include <assimp/types.h>
+#include "ASEParser.h"
+
+struct aiNode;
+
+namespace Assimp {
+
+#ifndef ASSIMP_BUILD_NO_3DS_IMPORTER
+
+// --------------------------------------------------------------------------------
+/** Importer class for the 3DS ASE ASCII format.
+ *
+ */
+class ASEImporter : public BaseImporter {
+public:
+    ASEImporter();
+    ~ASEImporter();
+
+    // -------------------------------------------------------------------
+    /** Returns whether the class can handle the format of the given file.
+     * See BaseImporter::CanRead() for details.
+     */
+    bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
+        bool checkSig) const;
+
+protected:
+
+    // -------------------------------------------------------------------
+    /** Return importer meta information.
+     * See #BaseImporter::GetInfo for the details
+     */
+    const aiImporterDesc* GetInfo () const;
+
+
+    // -------------------------------------------------------------------
+    /** Imports the given file into the given scene structure.
+    * See BaseImporter::InternReadFile() for details
+    */
+    void InternReadFile( const std::string& pFile, aiScene* pScene,
+        IOSystem* pIOHandler);
+
+
+    // -------------------------------------------------------------------
+    /** Called prior to ReadFile().
+    * The function is a request to the importer to update its configuration
+    * basing on the Importer's configuration property list.
+    */
+    void SetupProperties(const Importer* pImp);
+
+
+private:
+
+    // -------------------------------------------------------------------
+    /** Generate normal vectors basing on smoothing groups
+     * (in some cases the normal are already contained in the file)
+     * \param mesh Mesh to work on
+     * \return false if the normals have been recomputed
+     */
+    bool GenerateNormals(ASE::Mesh& mesh);
+
+
+    // -------------------------------------------------------------------
+    /** Create valid vertex/normal/UV/color/face lists.
+     *  All elements are unique, faces have only one set of indices
+     *  after this step occurs.
+     * \param mesh Mesh to work on
+     */
+    void BuildUniqueRepresentation(ASE::Mesh& mesh);
+
+
+    /** Create one-material-per-mesh meshes ;-)
+     * \param mesh Mesh to work with
+     *  \param Receives the list of all created meshes
+     */
+    void ConvertMeshes(ASE::Mesh& mesh, std::vector<aiMesh*>& avOut);
+
+
+    // -------------------------------------------------------------------
+    /** Convert a material to a aiMaterial object
+     * \param mat Input material
+     */
+    void ConvertMaterial(ASE::Material& mat);
+
+
+    // -------------------------------------------------------------------
+    /** Setup the final material indices for each mesh
+     */
+    void BuildMaterialIndices();
+
+
+    // -------------------------------------------------------------------
+    /** Build the node graph
+     */
+    void BuildNodes(std::vector<ASE::BaseNode*>& nodes);
+
+
+    // -------------------------------------------------------------------
+    /** Build output cameras
+     */
+    void BuildCameras();
+
+
+    // -------------------------------------------------------------------
+    /** Build output lights
+     */
+    void BuildLights();
+
+
+    // -------------------------------------------------------------------
+    /** Build output animations
+     */
+    void BuildAnimations(const std::vector<ASE::BaseNode*>& nodes);
+
+
+    // -------------------------------------------------------------------
+    /** Add sub nodes to a node
+     *  \param pcParent parent node to be filled
+     *  \param szName Name of the parent node
+     *  \param matrix Current transform
+     */
+    void AddNodes(const std::vector<ASE::BaseNode*>& nodes,
+        aiNode* pcParent,const char* szName);
+
+    void AddNodes(const std::vector<ASE::BaseNode*>& nodes,
+        aiNode* pcParent,const char* szName,
+        const aiMatrix4x4& matrix);
+
+    void AddMeshes(const ASE::BaseNode* snode,aiNode* node);
+
+    // -------------------------------------------------------------------
+    /** Generate a default material and add it to the parser's list
+     *  Called if no material has been found in the file (rare for ASE,
+     *  but not impossible)
+     */
+    void GenerateDefaultMaterial();
+
+protected:
+
+    /** Parser instance */
+    ASE::Parser* mParser;
+
+    /** Buffer to hold the loaded file */
+    char* mBuffer;
+
+    /** Scene to be filled */
+    aiScene* pcScene;
+
+    /** Config options: Recompute the normals in every case - WA
+        for 3DS Max broken ASE normal export */
+    bool configRecomputeNormals;
+    bool noSkeletonMesh;
+};
+
+#endif // ASSIMP_BUILD_NO_3DS_IMPORTER
+
+} // end of namespace Assimp
+
+
+#endif // AI_3DSIMPORTER_H_INC

+ 2160 - 0
Engine/lib/assimp/code/ASEParser.cpp

@@ -0,0 +1,2160 @@
+/*
+---------------------------------------------------------------------------
+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  ASEParser.cpp
+ *  @brief Implementation of the ASE parser class
+ */
+
+
+#ifndef ASSIMP_BUILD_NO_ASE_IMPORTER
+#ifndef ASSIMP_BUILD_NO_3DS_IMPORTER
+
+// internal headers
+#include "TextureTransform.h"
+#include "ASELoader.h"
+#include <assimp/fast_atof.h>
+#include <assimp/DefaultLogger.hpp>
+
+using namespace Assimp;
+using namespace Assimp::ASE;
+
+
+// ------------------------------------------------------------------------------------------------
+// Begin an ASE parsing function
+
+#define AI_ASE_PARSER_INIT() \
+    int iDepth = 0;
+
+// ------------------------------------------------------------------------------------------------
+// Handle a "top-level" section in the file. EOF is no error in this case.
+
+#define AI_ASE_HANDLE_TOP_LEVEL_SECTION() \
+    else if ('{' == *filePtr)iDepth++; \
+    else if ('}' == *filePtr) \
+    { \
+        if (0 == --iDepth) \
+        { \
+            ++filePtr; \
+            SkipToNextToken(); \
+            return; \
+        } \
+    } \
+    else if ('\0' == *filePtr) \
+    { \
+        return; \
+    } \
+    if(IsLineEnd(*filePtr) && !bLastWasEndLine) \
+    { \
+        ++iLineNumber; \
+        bLastWasEndLine = true; \
+    } else bLastWasEndLine = false; \
+    ++filePtr;
+
+// ------------------------------------------------------------------------------------------------
+// Handle a nested section in the file. EOF is an error in this case
+// @param level "Depth" of the section
+// @param msg Full name of the section (including the asterisk)
+
+#define AI_ASE_HANDLE_SECTION(level, msg) \
+    if ('{' == *filePtr)iDepth++; \
+    else if ('}' == *filePtr) \
+    { \
+        if (0 == --iDepth) \
+        { \
+            ++filePtr; \
+            SkipToNextToken(); \
+            return; \
+        } \
+    } \
+    else if ('\0' == *filePtr) \
+    { \
+        LogError("Encountered unexpected EOL while parsing a " msg \
+        " chunk (Level " level ")"); \
+    } \
+    if(IsLineEnd(*filePtr) && !bLastWasEndLine) \
+        { \
+        ++iLineNumber; \
+        bLastWasEndLine = true; \
+    } else bLastWasEndLine = false; \
+    ++filePtr;
+
+// ------------------------------------------------------------------------------------------------
+Parser::Parser (const char* szFile, unsigned int fileFormatDefault)
+{
+    ai_assert(NULL != szFile);
+    filePtr = szFile;
+    iFileFormat = fileFormatDefault;
+
+    // make sure that the color values are invalid
+    m_clrBackground.r = get_qnan();
+    m_clrAmbient.r    = get_qnan();
+
+    // setup some default values
+    iLineNumber = 0;
+    iFirstFrame = 0;
+    iLastFrame = 0;
+    iFrameSpeed = 30;        // use 30 as default value for this property
+    iTicksPerFrame = 1;      // use 1 as default value for this property
+    bLastWasEndLine = false; // need to handle \r\n seqs due to binary file mapping
+}
+
+// ------------------------------------------------------------------------------------------------
+void Parser::LogWarning(const char* szWarn)
+{
+    ai_assert(NULL != szWarn);
+
+    char szTemp[1024];
+#if _MSC_VER >= 1400
+    sprintf_s(szTemp, "Line %u: %s",iLineNumber,szWarn);
+#else
+    ai_snprintf(szTemp,1024,"Line %u: %s",iLineNumber,szWarn);
+#endif
+
+    // output the warning to the logger ...
+    ASSIMP_LOG_WARN(szTemp);
+}
+
+// ------------------------------------------------------------------------------------------------
+void Parser::LogInfo(const char* szWarn)
+{
+    ai_assert(NULL != szWarn);
+
+    char szTemp[1024];
+#if _MSC_VER >= 1400
+    sprintf_s(szTemp,"Line %u: %s",iLineNumber,szWarn);
+#else
+    ai_snprintf(szTemp,1024,"Line %u: %s",iLineNumber,szWarn);
+#endif
+
+    // output the information to the logger ...
+    ASSIMP_LOG_INFO(szTemp);
+}
+
+// ------------------------------------------------------------------------------------------------
+AI_WONT_RETURN void Parser::LogError(const char* szWarn)
+{
+    ai_assert(NULL != szWarn);
+
+    char szTemp[1024];
+#if _MSC_VER >= 1400
+    sprintf_s(szTemp,"Line %u: %s",iLineNumber,szWarn);
+#else
+    ai_snprintf(szTemp,1024,"Line %u: %s",iLineNumber,szWarn);
+#endif
+
+    // throw an exception
+    throw DeadlyImportError(szTemp);
+}
+
+// ------------------------------------------------------------------------------------------------
+bool Parser::SkipToNextToken()
+{
+    while (true)
+    {
+        char me = *filePtr;
+
+        // increase the line number counter if necessary
+        if (IsLineEnd(me) && !bLastWasEndLine)
+        {
+            ++iLineNumber;
+            bLastWasEndLine = true;
+        }
+        else bLastWasEndLine = false;
+        if ('*' == me || '}' == me || '{' == me)return true;
+        if ('\0' == me)return false;
+
+        ++filePtr;
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+bool Parser::SkipSection()
+{
+    // must handle subsections ...
+    int iCnt = 0;
+    while (true)
+    {
+        if ('}' == *filePtr)
+        {
+            --iCnt;
+            if (0 == iCnt)
+            {
+                // go to the next valid token ...
+                ++filePtr;
+                SkipToNextToken();
+                return true;
+            }
+        }
+        else if ('{' == *filePtr)
+        {
+            ++iCnt;
+        }
+        else if ('\0' == *filePtr)
+        {
+            LogWarning("Unable to parse block: Unexpected EOF, closing bracket \'}\' was expected [#1]");
+            return false;
+        }
+        else if(IsLineEnd(*filePtr))++iLineNumber;
+        ++filePtr;
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void Parser::Parse()
+{
+    AI_ASE_PARSER_INIT();
+    while (true)
+    {
+        if ('*' == *filePtr)
+        {
+            ++filePtr;
+
+            // Version should be 200. Validate this ...
+            if (TokenMatch(filePtr,"3DSMAX_ASCIIEXPORT",18))
+            {
+                unsigned int fmt;
+                ParseLV4MeshLong(fmt);
+
+                if (fmt > 200)
+                {
+                    LogWarning("Unknown file format version: *3DSMAX_ASCIIEXPORT should \
+                               be <= 200");
+                }
+                // *************************************************************
+                // - fmt will be 0 if we're unable to read the version number
+                // there are some faulty files without a version number ...
+                // in this case we'll guess the exact file format by looking
+                // at the file extension (ASE, ASK, ASC)
+                // *************************************************************
+
+                if ( fmt ) {
+                    iFileFormat = fmt;
+                }
+                continue;
+            }
+            // main scene information
+            if (TokenMatch(filePtr,"SCENE",5))
+            {
+                ParseLV1SceneBlock();
+                continue;
+            }
+            // "group" - no implementation yet, in facte
+            // we're just ignoring them for the moment
+            if (TokenMatch(filePtr,"GROUP",5))
+            {
+                Parse();
+                continue;
+            }
+            // material list
+            if (TokenMatch(filePtr,"MATERIAL_LIST",13))
+            {
+                ParseLV1MaterialListBlock();
+                continue;
+            }
+            // geometric object (mesh)
+            if (TokenMatch(filePtr,"GEOMOBJECT",10))
+
+            {
+                m_vMeshes.push_back(Mesh("UNNAMED"));
+                ParseLV1ObjectBlock(m_vMeshes.back());
+                continue;
+            }
+            // helper object = dummy in the hierarchy
+            if (TokenMatch(filePtr,"HELPEROBJECT",12))
+
+            {
+                m_vDummies.push_back(Dummy());
+                ParseLV1ObjectBlock(m_vDummies.back());
+                continue;
+            }
+            // light object
+            if (TokenMatch(filePtr,"LIGHTOBJECT",11))
+
+            {
+                m_vLights.push_back(Light("UNNAMED"));
+                ParseLV1ObjectBlock(m_vLights.back());
+                continue;
+            }
+            // camera object
+            if (TokenMatch(filePtr,"CAMERAOBJECT",12))
+            {
+                m_vCameras.push_back(Camera("UNNAMED"));
+                ParseLV1ObjectBlock(m_vCameras.back());
+                continue;
+            }
+            // comment - print it on the console
+            if (TokenMatch(filePtr,"COMMENT",7))
+            {
+                std::string out = "<unknown>";
+                ParseString(out,"*COMMENT");
+                LogInfo(("Comment: " + out).c_str());
+                continue;
+            }
+            // ASC bone weights
+            if (AI_ASE_IS_OLD_FILE_FORMAT() && TokenMatch(filePtr,"MESH_SOFTSKINVERTS",18))
+            {
+                ParseLV1SoftSkinBlock();
+            }
+        }
+        AI_ASE_HANDLE_TOP_LEVEL_SECTION();
+    }
+    return;
+}
+
+// ------------------------------------------------------------------------------------------------
+void Parser::ParseLV1SoftSkinBlock()
+{
+    // TODO: fix line counting here
+
+    // **************************************************************
+    // The soft skin block is formatted differently. There are no
+    // nested sections supported and the single elements aren't
+    // marked by keywords starting with an asterisk.
+
+    /**
+    FORMAT BEGIN
+
+    *MESH_SOFTSKINVERTS {
+    <nodename>
+    <number of vertices>
+
+    [for <number of vertices> times:]
+        <number of weights> [for <number of weights> times:] <bone name> <weight>
+    }
+
+    FORMAT END
+    */
+    // **************************************************************
+    while (true)
+    {
+        if (*filePtr == '}'      )  {++filePtr;return;}
+        else if (*filePtr == '\0')  return;
+        else if (*filePtr == '{' )  ++filePtr;
+
+        else // if (!IsSpace(*filePtr) && !IsLineEnd(*filePtr))
+        {
+            ASE::Mesh* curMesh      = NULL;
+            unsigned int numVerts   = 0;
+
+            const char* sz = filePtr;
+            while (!IsSpaceOrNewLine(*filePtr))++filePtr;
+
+            const unsigned int diff = (unsigned int)(filePtr-sz);
+            if (diff)
+            {
+                std::string name = std::string(sz,diff);
+                for (std::vector<ASE::Mesh>::iterator it = m_vMeshes.begin();
+                    it != m_vMeshes.end(); ++it)
+                {
+                    if ((*it).mName == name)
+                    {
+                        curMesh = & (*it);
+                        break;
+                    }
+                }
+                if (!curMesh)
+                {
+                    LogWarning("Encountered unknown mesh in *MESH_SOFTSKINVERTS section");
+
+                    // Skip the mesh data - until we find a new mesh
+                    // or the end of the *MESH_SOFTSKINVERTS section
+                    while (true)
+                    {
+                        SkipSpacesAndLineEnd(&filePtr);
+                        if (*filePtr == '}')
+                            {++filePtr;return;}
+                        else if (!IsNumeric(*filePtr))
+                            break;
+
+                        SkipLine(&filePtr);
+                    }
+                }
+                else
+                {
+                    SkipSpacesAndLineEnd(&filePtr);
+                    ParseLV4MeshLong(numVerts);
+
+                    // Reserve enough storage
+                    curMesh->mBoneVertices.reserve(numVerts);
+
+                    for (unsigned int i = 0; i < numVerts;++i)
+                    {
+                        SkipSpacesAndLineEnd(&filePtr);
+                        unsigned int numWeights;
+                        ParseLV4MeshLong(numWeights);
+
+                        curMesh->mBoneVertices.push_back(ASE::BoneVertex());
+                        ASE::BoneVertex& vert = curMesh->mBoneVertices.back();
+
+                        // Reserve enough storage
+                        vert.mBoneWeights.reserve(numWeights);
+
+                        std::string bone;
+                        for (unsigned int w = 0; w < numWeights;++w) {
+                            bone.clear();
+                            ParseString(bone,"*MESH_SOFTSKINVERTS.Bone");
+
+                            // Find the bone in the mesh's list
+                            std::pair<int,ai_real> me;
+                            me.first = -1;
+
+                            for (unsigned int n = 0; n < curMesh->mBones.size();++n) {
+                                if (curMesh->mBones[n].mName == bone) {
+                                    me.first = n;
+                                    break;
+                                }
+                            }
+                            if (-1 == me.first) {
+                                // We don't have this bone yet, so add it to the list
+                                me.first = static_cast<int>( curMesh->mBones.size() );
+                                curMesh->mBones.push_back( ASE::Bone( bone ) );
+                            }
+                            ParseLV4MeshFloat( me.second );
+
+                            // Add the new bone weight to list
+                            vert.mBoneWeights.push_back(me);
+                        }
+                    }
+                }
+            }
+        }
+        ++filePtr;
+        SkipSpacesAndLineEnd(&filePtr);
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void Parser::ParseLV1SceneBlock()
+{
+    AI_ASE_PARSER_INIT();
+    while (true)
+    {
+        if ('*' == *filePtr)
+        {
+            ++filePtr;
+            if (TokenMatch(filePtr,"SCENE_BACKGROUND_STATIC",23))
+
+            {
+                // parse a color triple and assume it is really the bg color
+                ParseLV4MeshFloatTriple( &m_clrBackground.r );
+                continue;
+            }
+            if (TokenMatch(filePtr,"SCENE_AMBIENT_STATIC",20))
+
+            {
+                // parse a color triple and assume it is really the bg color
+                ParseLV4MeshFloatTriple( &m_clrAmbient.r );
+                continue;
+            }
+            if (TokenMatch(filePtr,"SCENE_FIRSTFRAME",16))
+            {
+                ParseLV4MeshLong(iFirstFrame);
+                continue;
+            }
+            if (TokenMatch(filePtr,"SCENE_LASTFRAME",15))
+            {
+                ParseLV4MeshLong(iLastFrame);
+                continue;
+            }
+            if (TokenMatch(filePtr,"SCENE_FRAMESPEED",16))
+            {
+                ParseLV4MeshLong(iFrameSpeed);
+                continue;
+            }
+            if (TokenMatch(filePtr,"SCENE_TICKSPERFRAME",19))
+            {
+                ParseLV4MeshLong(iTicksPerFrame);
+                continue;
+            }
+        }
+        AI_ASE_HANDLE_TOP_LEVEL_SECTION();
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void Parser::ParseLV1MaterialListBlock()
+{
+    AI_ASE_PARSER_INIT();
+
+    unsigned int iMaterialCount = 0;
+    unsigned int iOldMaterialCount = (unsigned int)m_vMaterials.size();
+    while (true)
+    {
+        if ('*' == *filePtr)
+        {
+            ++filePtr;
+            if (TokenMatch(filePtr,"MATERIAL_COUNT",14))
+            {
+                ParseLV4MeshLong(iMaterialCount);
+
+                // now allocate enough storage to hold all materials
+                m_vMaterials.resize(iOldMaterialCount+iMaterialCount, Material("INVALID"));
+                continue;
+            }
+            if (TokenMatch(filePtr,"MATERIAL",8))
+            {
+                unsigned int iIndex = 0;
+                ParseLV4MeshLong(iIndex);
+
+                if (iIndex >= iMaterialCount)
+                {
+                    LogWarning("Out of range: material index is too large");
+                    iIndex = iMaterialCount-1;
+                }
+
+                // get a reference to the material
+                Material& sMat = m_vMaterials[iIndex+iOldMaterialCount];
+                // parse the material block
+                ParseLV2MaterialBlock(sMat);
+                continue;
+            }
+        }
+        AI_ASE_HANDLE_TOP_LEVEL_SECTION();
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void Parser::ParseLV2MaterialBlock(ASE::Material& mat)
+{
+    AI_ASE_PARSER_INIT();
+
+    unsigned int iNumSubMaterials = 0;
+    while (true)
+    {
+        if ('*' == *filePtr)
+        {
+            ++filePtr;
+            if (TokenMatch(filePtr,"MATERIAL_NAME",13))
+            {
+                if (!ParseString(mat.mName,"*MATERIAL_NAME"))
+                    SkipToNextToken();
+                continue;
+            }
+            // ambient material color
+            if (TokenMatch(filePtr,"MATERIAL_AMBIENT",16))
+            {
+                ParseLV4MeshFloatTriple(&mat.mAmbient.r);
+                continue;
+            }
+            // diffuse material color
+            if (TokenMatch(filePtr,"MATERIAL_DIFFUSE",16) )
+            {
+                ParseLV4MeshFloatTriple(&mat.mDiffuse.r);
+                continue;
+            }
+            // specular material color
+            if (TokenMatch(filePtr,"MATERIAL_SPECULAR",17))
+            {
+                ParseLV4MeshFloatTriple(&mat.mSpecular.r);
+                continue;
+            }
+            // material shading type
+            if (TokenMatch(filePtr,"MATERIAL_SHADING",16))
+            {
+                if (TokenMatch(filePtr,"Blinn",5))
+                {
+                    mat.mShading = Discreet3DS::Blinn;
+                }
+                else if (TokenMatch(filePtr,"Phong",5))
+                {
+                    mat.mShading = Discreet3DS::Phong;
+                }
+                else if (TokenMatch(filePtr,"Flat",4))
+                {
+                    mat.mShading = Discreet3DS::Flat;
+                }
+                else if (TokenMatch(filePtr,"Wire",4))
+                {
+                    mat.mShading = Discreet3DS::Wire;
+                }
+                else
+                {
+                    // assume gouraud shading
+                    mat.mShading = Discreet3DS::Gouraud;
+                    SkipToNextToken();
+                }
+                continue;
+            }
+            // material transparency
+            if (TokenMatch(filePtr,"MATERIAL_TRANSPARENCY",21))
+            {
+                ParseLV4MeshFloat(mat.mTransparency);
+                mat.mTransparency = ai_real( 1.0 ) - mat.mTransparency;
+                continue;
+            }
+            // material self illumination
+            if (TokenMatch(filePtr,"MATERIAL_SELFILLUM",18))
+            {
+                ai_real f = 0.0;
+                ParseLV4MeshFloat(f);
+
+                mat.mEmissive.r = f;
+                mat.mEmissive.g = f;
+                mat.mEmissive.b = f;
+                continue;
+            }
+            // material shininess
+            if (TokenMatch(filePtr,"MATERIAL_SHINE",14) )
+            {
+                ParseLV4MeshFloat(mat.mSpecularExponent);
+                mat.mSpecularExponent *= 15;
+                continue;
+            }
+            // two-sided material
+            if (TokenMatch(filePtr,"MATERIAL_TWOSIDED",17) )
+            {
+                mat.mTwoSided = true;
+                continue;
+            }
+            // material shininess strength
+            if (TokenMatch(filePtr,"MATERIAL_SHINESTRENGTH",22))
+            {
+                ParseLV4MeshFloat(mat.mShininessStrength);
+                continue;
+            }
+            // diffuse color map
+            if (TokenMatch(filePtr,"MAP_DIFFUSE",11))
+            {
+                // parse the texture block
+                ParseLV3MapBlock(mat.sTexDiffuse);
+                continue;
+            }
+            // ambient color map
+            if (TokenMatch(filePtr,"MAP_AMBIENT",11))
+            {
+                // parse the texture block
+                ParseLV3MapBlock(mat.sTexAmbient);
+                continue;
+            }
+            // specular color map
+            if (TokenMatch(filePtr,"MAP_SPECULAR",12))
+            {
+                // parse the texture block
+                ParseLV3MapBlock(mat.sTexSpecular);
+                continue;
+            }
+            // opacity map
+            if (TokenMatch(filePtr,"MAP_OPACITY",11))
+            {
+                // parse the texture block
+                ParseLV3MapBlock(mat.sTexOpacity);
+                continue;
+            }
+            // emissive map
+            if (TokenMatch(filePtr,"MAP_SELFILLUM",13))
+            {
+                // parse the texture block
+                ParseLV3MapBlock(mat.sTexEmissive);
+                continue;
+            }
+            // bump map
+            if (TokenMatch(filePtr,"MAP_BUMP",8))
+            {
+                // parse the texture block
+                ParseLV3MapBlock(mat.sTexBump);
+            }
+            // specular/shininess map
+            if (TokenMatch(filePtr,"MAP_SHINESTRENGTH",17))
+            {
+                // parse the texture block
+                ParseLV3MapBlock(mat.sTexShininess);
+                continue;
+            }
+            // number of submaterials
+            if (TokenMatch(filePtr,"NUMSUBMTLS",10))
+            {
+                ParseLV4MeshLong(iNumSubMaterials);
+
+                // allocate enough storage
+                mat.avSubMaterials.resize(iNumSubMaterials, Material("INVALID SUBMATERIAL"));
+            }
+            // submaterial chunks
+            if (TokenMatch(filePtr,"SUBMATERIAL",11))
+            {
+
+                unsigned int iIndex = 0;
+                ParseLV4MeshLong(iIndex);
+
+                if (iIndex >= iNumSubMaterials)
+                {
+                    LogWarning("Out of range: submaterial index is too large");
+                    iIndex = iNumSubMaterials-1;
+                }
+
+                // get a reference to the material
+                Material& sMat = mat.avSubMaterials[iIndex];
+
+                // parse the material block
+                ParseLV2MaterialBlock(sMat);
+                continue;
+            }
+        }
+        AI_ASE_HANDLE_SECTION("2","*MATERIAL");
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void Parser::ParseLV3MapBlock(Texture& map)
+{
+    AI_ASE_PARSER_INIT();
+
+    // ***********************************************************
+    // *BITMAP should not be there if *MAP_CLASS is not BITMAP,
+    // but we need to expect that case ... if the path is
+    // empty the texture won't be used later.
+    // ***********************************************************
+    bool parsePath = true;
+    std::string temp;
+    while (true)
+    {
+        if ('*' == *filePtr)
+        {
+            ++filePtr;
+            // type of map
+            if (TokenMatch(filePtr,"MAP_CLASS" ,9))
+            {
+                temp.clear();
+                if(!ParseString(temp,"*MAP_CLASS"))
+                    SkipToNextToken();
+                if (temp != "Bitmap" && temp != "Normal Bump")
+                {
+                    ASSIMP_LOG_WARN_F("ASE: Skipping unknown map type: ", temp);
+                    parsePath = false;
+                }
+                continue;
+            }
+            // path to the texture
+            if (parsePath && TokenMatch(filePtr,"BITMAP" ,6))
+            {
+                if(!ParseString(map.mMapName,"*BITMAP"))
+                    SkipToNextToken();
+
+                if (map.mMapName == "None")
+                {
+                    // Files with 'None' as map name are produced by
+                    // an Maja to ASE exporter which name I forgot ..
+                    ASSIMP_LOG_WARN("ASE: Skipping invalid map entry");
+                    map.mMapName = "";
+                }
+
+                continue;
+            }
+            // offset on the u axis
+            if (TokenMatch(filePtr,"UVW_U_OFFSET" ,12))
+            {
+                ParseLV4MeshFloat(map.mOffsetU);
+                continue;
+            }
+            // offset on the v axis
+            if (TokenMatch(filePtr,"UVW_V_OFFSET" ,12))
+            {
+                ParseLV4MeshFloat(map.mOffsetV);
+                continue;
+            }
+            // tiling on the u axis
+            if (TokenMatch(filePtr,"UVW_U_TILING" ,12))
+            {
+                ParseLV4MeshFloat(map.mScaleU);
+                continue;
+            }
+            // tiling on the v axis
+            if (TokenMatch(filePtr,"UVW_V_TILING" ,12))
+            {
+                ParseLV4MeshFloat(map.mScaleV);
+                continue;
+            }
+            // rotation around the z-axis
+            if (TokenMatch(filePtr,"UVW_ANGLE" ,9))
+            {
+                ParseLV4MeshFloat(map.mRotation);
+                continue;
+            }
+            // map blending factor
+            if (TokenMatch(filePtr,"MAP_AMOUNT" ,10))
+            {
+                ParseLV4MeshFloat(map.mTextureBlend);
+                continue;
+            }
+        }
+        AI_ASE_HANDLE_SECTION("3","*MAP_XXXXXX");
+    }
+    return;
+}
+
+// ------------------------------------------------------------------------------------------------
+bool Parser::ParseString(std::string& out,const char* szName)
+{
+    char szBuffer[1024];
+    if (!SkipSpaces(&filePtr))
+    {
+
+        ai_snprintf(szBuffer, 1024, "Unable to parse %s block: Unexpected EOL",szName);
+        LogWarning(szBuffer);
+        return false;
+    }
+    // there must be '"'
+    if ('\"' != *filePtr)
+    {
+
+        ai_snprintf(szBuffer, 1024, "Unable to parse %s block: Strings are expected "
+            "to be enclosed in double quotation marks",szName);
+        LogWarning(szBuffer);
+        return false;
+    }
+    ++filePtr;
+    const char* sz = filePtr;
+    while (true)
+    {
+        if ('\"' == *sz)break;
+        else if ('\0' == *sz)
+        {
+            ai_snprintf(szBuffer, 1024, "Unable to parse %s block: Strings are expected to "
+                "be enclosed in double quotation marks but EOF was reached before "
+                "a closing quotation mark was encountered",szName);
+            LogWarning(szBuffer);
+            return false;
+        }
+        sz++;
+    }
+    out = std::string(filePtr,(uintptr_t)sz-(uintptr_t)filePtr);
+    filePtr = sz+1;
+    return true;
+}
+
+// ------------------------------------------------------------------------------------------------
+void Parser::ParseLV1ObjectBlock(ASE::BaseNode& node)
+{
+    AI_ASE_PARSER_INIT();
+    while (true)
+    {
+        if ('*' == *filePtr)
+        {
+            ++filePtr;
+
+            // first process common tokens such as node name and transform
+            // name of the mesh/node
+            if (TokenMatch(filePtr,"NODE_NAME" ,9))
+            {
+                if(!ParseString(node.mName,"*NODE_NAME"))
+                    SkipToNextToken();
+                continue;
+            }
+            // name of the parent of the node
+            if (TokenMatch(filePtr,"NODE_PARENT" ,11) )
+            {
+                if(!ParseString(node.mParent,"*NODE_PARENT"))
+                    SkipToNextToken();
+                continue;
+            }
+            // transformation matrix of the node
+            if (TokenMatch(filePtr,"NODE_TM" ,7))
+            {
+                ParseLV2NodeTransformBlock(node);
+                continue;
+            }
+            // animation data of the node
+            if (TokenMatch(filePtr,"TM_ANIMATION" ,12))
+            {
+                ParseLV2AnimationBlock(node);
+                continue;
+            }
+
+            if (node.mType == BaseNode::Light)
+            {
+                // light settings
+                if (TokenMatch(filePtr,"LIGHT_SETTINGS" ,14))
+                {
+                    ParseLV2LightSettingsBlock((ASE::Light&)node);
+                    continue;
+                }
+                // type of the light source
+                if (TokenMatch(filePtr,"LIGHT_TYPE" ,10))
+                {
+                    if (!ASSIMP_strincmp("omni",filePtr,4))
+                    {
+                        ((ASE::Light&)node).mLightType = ASE::Light::OMNI;
+                    }
+                    else if (!ASSIMP_strincmp("target",filePtr,6))
+                    {
+                        ((ASE::Light&)node).mLightType = ASE::Light::TARGET;
+                    }
+                    else if (!ASSIMP_strincmp("free",filePtr,4))
+                    {
+                        ((ASE::Light&)node).mLightType = ASE::Light::FREE;
+                    }
+                    else if (!ASSIMP_strincmp("directional",filePtr,11))
+                    {
+                        ((ASE::Light&)node).mLightType = ASE::Light::DIRECTIONAL;
+                    }
+                    else
+                    {
+                        LogWarning("Unknown kind of light source");
+                    }
+                    continue;
+                }
+            }
+            else if (node.mType == BaseNode::Camera)
+            {
+                // Camera settings
+                if (TokenMatch(filePtr,"CAMERA_SETTINGS" ,15))
+                {
+                    ParseLV2CameraSettingsBlock((ASE::Camera&)node);
+                    continue;
+                }
+                else if (TokenMatch(filePtr,"CAMERA_TYPE" ,11))
+                {
+                    if (!ASSIMP_strincmp("target",filePtr,6))
+                    {
+                        ((ASE::Camera&)node).mCameraType = ASE::Camera::TARGET;
+                    }
+                    else if (!ASSIMP_strincmp("free",filePtr,4))
+                    {
+                        ((ASE::Camera&)node).mCameraType = ASE::Camera::FREE;
+                    }
+                    else
+                    {
+                        LogWarning("Unknown kind of camera");
+                    }
+                    continue;
+                }
+            }
+            else if (node.mType == BaseNode::Mesh)
+            {
+                // mesh data
+                // FIX: Older files use MESH_SOFTSKIN
+                if (TokenMatch(filePtr,"MESH" ,4) ||
+                    TokenMatch(filePtr,"MESH_SOFTSKIN",13))
+                {
+                    ParseLV2MeshBlock((ASE::Mesh&)node);
+                    continue;
+                }
+                // mesh material index
+                if (TokenMatch(filePtr,"MATERIAL_REF" ,12))
+                {
+                    ParseLV4MeshLong(((ASE::Mesh&)node).iMaterialIndex);
+                    continue;
+                }
+            }
+        }
+        AI_ASE_HANDLE_TOP_LEVEL_SECTION();
+    }
+    return;
+}
+
+// ------------------------------------------------------------------------------------------------
+void Parser::ParseLV2CameraSettingsBlock(ASE::Camera& camera)
+{
+    AI_ASE_PARSER_INIT();
+    while (true)
+    {
+        if ('*' == *filePtr)
+        {
+            ++filePtr;
+            if (TokenMatch(filePtr,"CAMERA_NEAR" ,11))
+            {
+                ParseLV4MeshFloat(camera.mNear);
+                continue;
+            }
+            if (TokenMatch(filePtr,"CAMERA_FAR" ,10))
+            {
+                ParseLV4MeshFloat(camera.mFar);
+                continue;
+            }
+            if (TokenMatch(filePtr,"CAMERA_FOV" ,10))
+            {
+                ParseLV4MeshFloat(camera.mFOV);
+                continue;
+            }
+        }
+        AI_ASE_HANDLE_SECTION("2","CAMERA_SETTINGS");
+    }
+    return;
+}
+
+// ------------------------------------------------------------------------------------------------
+void Parser::ParseLV2LightSettingsBlock(ASE::Light& light)
+{
+    AI_ASE_PARSER_INIT();
+    while (true)
+    {
+        if ('*' == *filePtr)
+        {
+            ++filePtr;
+            if (TokenMatch(filePtr,"LIGHT_COLOR" ,11))
+            {
+                ParseLV4MeshFloatTriple(&light.mColor.r);
+                continue;
+            }
+            if (TokenMatch(filePtr,"LIGHT_INTENS" ,12))
+            {
+                ParseLV4MeshFloat(light.mIntensity);
+                continue;
+            }
+            if (TokenMatch(filePtr,"LIGHT_HOTSPOT" ,13))
+            {
+                ParseLV4MeshFloat(light.mAngle);
+                continue;
+            }
+            if (TokenMatch(filePtr,"LIGHT_FALLOFF" ,13))
+            {
+                ParseLV4MeshFloat(light.mFalloff);
+                continue;
+            }
+        }
+        AI_ASE_HANDLE_SECTION("2","LIGHT_SETTINGS");
+    }
+    return;
+}
+
+// ------------------------------------------------------------------------------------------------
+void Parser::ParseLV2AnimationBlock(ASE::BaseNode& mesh)
+{
+    AI_ASE_PARSER_INIT();
+
+    ASE::Animation* anim = &mesh.mAnim;
+    while (true)
+    {
+        if ('*' == *filePtr)
+        {
+            ++filePtr;
+            if (TokenMatch(filePtr,"NODE_NAME" ,9))
+            {
+                std::string temp;
+                if(!ParseString(temp,"*NODE_NAME"))
+                    SkipToNextToken();
+
+                // If the name of the node contains .target it
+                // represents an animated camera or spot light
+                // target.
+                if (std::string::npos != temp.find(".Target"))
+                {
+                    if  ((mesh.mType != BaseNode::Camera || ((ASE::Camera&)mesh).mCameraType != ASE::Camera::TARGET)  &&
+                        ( mesh.mType != BaseNode::Light  || ((ASE::Light&)mesh).mLightType   != ASE::Light::TARGET))
+                    {
+
+                        ASSIMP_LOG_ERROR("ASE: Found target animation channel "
+                            "but the node is neither a camera nor a spot light");
+                        anim = NULL;
+                    }
+                    else anim = &mesh.mTargetAnim;
+                }
+                continue;
+            }
+
+            // position keyframes
+            if (TokenMatch(filePtr,"CONTROL_POS_TRACK"  ,17)  ||
+                TokenMatch(filePtr,"CONTROL_POS_BEZIER" ,18)  ||
+                TokenMatch(filePtr,"CONTROL_POS_TCB"    ,15))
+            {
+                if (!anim)SkipSection();
+                else ParseLV3PosAnimationBlock(*anim);
+                continue;
+            }
+            // scaling keyframes
+            if (TokenMatch(filePtr,"CONTROL_SCALE_TRACK"  ,19) ||
+                TokenMatch(filePtr,"CONTROL_SCALE_BEZIER" ,20) ||
+                TokenMatch(filePtr,"CONTROL_SCALE_TCB"    ,17))
+            {
+                if (!anim || anim == &mesh.mTargetAnim)
+                {
+                    // Target animation channels may have no rotation channels
+                    ASSIMP_LOG_ERROR("ASE: Ignoring scaling channel in target animation");
+                    SkipSection();
+                }
+                else ParseLV3ScaleAnimationBlock(*anim);
+                continue;
+            }
+            // rotation keyframes
+            if (TokenMatch(filePtr,"CONTROL_ROT_TRACK"  ,17) ||
+                TokenMatch(filePtr,"CONTROL_ROT_BEZIER" ,18) ||
+                TokenMatch(filePtr,"CONTROL_ROT_TCB"    ,15))
+            {
+                if (!anim || anim == &mesh.mTargetAnim)
+                {
+                    // Target animation channels may have no rotation channels
+                    ASSIMP_LOG_ERROR("ASE: Ignoring rotation channel in target animation");
+                    SkipSection();
+                }
+                else ParseLV3RotAnimationBlock(*anim);
+                continue;
+            }
+        }
+        AI_ASE_HANDLE_SECTION("2","TM_ANIMATION");
+    }
+}
+// ------------------------------------------------------------------------------------------------
+void Parser::ParseLV3ScaleAnimationBlock(ASE::Animation& anim)
+{
+    AI_ASE_PARSER_INIT();
+    unsigned int iIndex;
+
+    while (true)
+    {
+        if ('*' == *filePtr)
+        {
+            ++filePtr;
+
+            bool b = false;
+
+            // For the moment we're just reading the three floats -
+            // we ignore the additional information for bezier's and TCBs
+
+            // simple scaling keyframe
+            if (TokenMatch(filePtr,"CONTROL_SCALE_SAMPLE" ,20))
+            {
+                b = true;
+                anim.mScalingType = ASE::Animation::TRACK;
+            }
+
+            // Bezier scaling keyframe
+            if (TokenMatch(filePtr,"CONTROL_BEZIER_SCALE_KEY" ,24))
+            {
+                b = true;
+                anim.mScalingType = ASE::Animation::BEZIER;
+            }
+            // TCB scaling keyframe
+            if (TokenMatch(filePtr,"CONTROL_TCB_SCALE_KEY" ,21))
+            {
+                b = true;
+                anim.mScalingType = ASE::Animation::TCB;
+            }
+            if (b)
+            {
+                anim.akeyScaling.push_back(aiVectorKey());
+                aiVectorKey& key = anim.akeyScaling.back();
+                ParseLV4MeshFloatTriple(&key.mValue.x,iIndex);
+                key.mTime = (double)iIndex;
+            }
+        }
+        AI_ASE_HANDLE_SECTION("3","*CONTROL_POS_TRACK");
+    }
+}
+// ------------------------------------------------------------------------------------------------
+void Parser::ParseLV3PosAnimationBlock(ASE::Animation& anim)
+{
+    AI_ASE_PARSER_INIT();
+    unsigned int iIndex;
+    while (true)
+    {
+        if ('*' == *filePtr)
+        {
+            ++filePtr;
+
+            bool b = false;
+
+            // For the moment we're just reading the three floats -
+            // we ignore the additional information for bezier's and TCBs
+
+            // simple scaling keyframe
+            if (TokenMatch(filePtr,"CONTROL_POS_SAMPLE" ,18))
+            {
+                b = true;
+                anim.mPositionType = ASE::Animation::TRACK;
+            }
+
+            // Bezier scaling keyframe
+            if (TokenMatch(filePtr,"CONTROL_BEZIER_POS_KEY" ,22))
+            {
+                b = true;
+                anim.mPositionType = ASE::Animation::BEZIER;
+            }
+            // TCB scaling keyframe
+            if (TokenMatch(filePtr,"CONTROL_TCB_POS_KEY" ,19))
+            {
+                b = true;
+                anim.mPositionType = ASE::Animation::TCB;
+            }
+            if (b)
+            {
+                anim.akeyPositions.push_back(aiVectorKey());
+                aiVectorKey& key = anim.akeyPositions.back();
+                ParseLV4MeshFloatTriple(&key.mValue.x,iIndex);
+                key.mTime = (double)iIndex;
+            }
+        }
+        AI_ASE_HANDLE_SECTION("3","*CONTROL_POS_TRACK");
+    }
+}
+// ------------------------------------------------------------------------------------------------
+void Parser::ParseLV3RotAnimationBlock(ASE::Animation& anim)
+{
+    AI_ASE_PARSER_INIT();
+    unsigned int iIndex;
+    while (true)
+    {
+        if ('*' == *filePtr)
+        {
+            ++filePtr;
+
+            bool b = false;
+
+            // For the moment we're just reading the  floats -
+            // we ignore the additional information for bezier's and TCBs
+
+            // simple scaling keyframe
+            if (TokenMatch(filePtr,"CONTROL_ROT_SAMPLE" ,18))
+            {
+                b = true;
+                anim.mRotationType = ASE::Animation::TRACK;
+            }
+
+            // Bezier scaling keyframe
+            if (TokenMatch(filePtr,"CONTROL_BEZIER_ROT_KEY" ,22))
+            {
+                b = true;
+                anim.mRotationType = ASE::Animation::BEZIER;
+            }
+            // TCB scaling keyframe
+            if (TokenMatch(filePtr,"CONTROL_TCB_ROT_KEY" ,19))
+            {
+                b = true;
+                anim.mRotationType = ASE::Animation::TCB;
+            }
+            if (b)
+            {
+                anim.akeyRotations.push_back(aiQuatKey());
+                aiQuatKey& key = anim.akeyRotations.back();
+                aiVector3D v;ai_real f;
+                ParseLV4MeshFloatTriple(&v.x,iIndex);
+                ParseLV4MeshFloat(f);
+                key.mTime = (double)iIndex;
+                key.mValue = aiQuaternion(v,f);
+            }
+        }
+        AI_ASE_HANDLE_SECTION("3","*CONTROL_ROT_TRACK");
+    }
+}
+// ------------------------------------------------------------------------------------------------
+void Parser::ParseLV2NodeTransformBlock(ASE::BaseNode& mesh)
+{
+    AI_ASE_PARSER_INIT();
+    int mode   = 0;
+    while (true)
+    {
+        if ('*' == *filePtr)
+        {
+            ++filePtr;
+            // name of the node
+            if (TokenMatch(filePtr,"NODE_NAME" ,9))
+            {
+                std::string temp;
+                if(!ParseString(temp,"*NODE_NAME"))
+                    SkipToNextToken();
+
+                std::string::size_type s;
+                if (temp == mesh.mName)
+                {
+                    mode = 1;
+                }
+                else if (std::string::npos != (s = temp.find(".Target")) &&
+                    mesh.mName == temp.substr(0,s))
+                {
+                    // This should be either a target light or a target camera
+                    if ( (mesh.mType == BaseNode::Light &&  ((ASE::Light&)mesh) .mLightType  == ASE::Light::TARGET) ||
+                         (mesh.mType == BaseNode::Camera && ((ASE::Camera&)mesh).mCameraType == ASE::Camera::TARGET))
+                    {
+                        mode = 2;
+                    }
+                    else {
+                        ASSIMP_LOG_ERROR("ASE: Ignoring target transform, "
+                            "this is no spot light or target camera");
+                    }
+                }
+                else
+                {
+                    ASSIMP_LOG_ERROR("ASE: Unknown node transformation: " + temp);
+                    // mode = 0
+                }
+                continue;
+            }
+            if (mode)
+            {
+                // fourth row of the transformation matrix - and also the
+                // only information here that is interesting for targets
+                if (TokenMatch(filePtr,"TM_ROW3" ,7))
+                {
+                    ParseLV4MeshFloatTriple((mode == 1 ? mesh.mTransform[3] : &mesh.mTargetPosition.x));
+                    continue;
+                }
+                if (mode == 1)
+                {
+                    // first row of the transformation matrix
+                    if (TokenMatch(filePtr,"TM_ROW0" ,7))
+                    {
+                        ParseLV4MeshFloatTriple(mesh.mTransform[0]);
+                        continue;
+                    }
+                    // second row of the transformation matrix
+                    if (TokenMatch(filePtr,"TM_ROW1" ,7))
+                    {
+                        ParseLV4MeshFloatTriple(mesh.mTransform[1]);
+                        continue;
+                    }
+                    // third row of the transformation matrix
+                    if (TokenMatch(filePtr,"TM_ROW2" ,7))
+                    {
+                        ParseLV4MeshFloatTriple(mesh.mTransform[2]);
+                        continue;
+                    }
+                    // inherited position axes
+                    if (TokenMatch(filePtr,"INHERIT_POS" ,11))
+                    {
+                        unsigned int aiVal[3];
+                        ParseLV4MeshLongTriple(aiVal);
+
+                        for (unsigned int i = 0; i < 3;++i)
+                            mesh.inherit.abInheritPosition[i] = aiVal[i] != 0;
+                        continue;
+                    }
+                    // inherited rotation axes
+                    if (TokenMatch(filePtr,"INHERIT_ROT" ,11))
+                    {
+                        unsigned int aiVal[3];
+                        ParseLV4MeshLongTriple(aiVal);
+
+                        for (unsigned int i = 0; i < 3;++i)
+                            mesh.inherit.abInheritRotation[i] = aiVal[i] != 0;
+                        continue;
+                    }
+                    // inherited scaling axes
+                    if (TokenMatch(filePtr,"INHERIT_SCL" ,11))
+                    {
+                        unsigned int aiVal[3];
+                        ParseLV4MeshLongTriple(aiVal);
+
+                        for (unsigned int i = 0; i < 3;++i)
+                            mesh.inherit.abInheritScaling[i] = aiVal[i] != 0;
+                        continue;
+                    }
+                }
+            }
+        }
+        AI_ASE_HANDLE_SECTION("2","*NODE_TM");
+    }
+    return;
+}
+// ------------------------------------------------------------------------------------------------
+void Parser::ParseLV2MeshBlock(ASE::Mesh& mesh)
+{
+    AI_ASE_PARSER_INIT();
+
+    unsigned int iNumVertices = 0;
+    unsigned int iNumFaces = 0;
+    unsigned int iNumTVertices = 0;
+    unsigned int iNumTFaces = 0;
+    unsigned int iNumCVertices = 0;
+    unsigned int iNumCFaces = 0;
+    while (true)
+    {
+        if ('*' == *filePtr)
+        {
+            ++filePtr;
+            // Number of vertices in the mesh
+            if (TokenMatch(filePtr,"MESH_NUMVERTEX" ,14))
+            {
+                ParseLV4MeshLong(iNumVertices);
+                continue;
+            }
+            // Number of texture coordinates in the mesh
+            if (TokenMatch(filePtr,"MESH_NUMTVERTEX" ,15))
+            {
+                ParseLV4MeshLong(iNumTVertices);
+                continue;
+            }
+            // Number of vertex colors in the mesh
+            if (TokenMatch(filePtr,"MESH_NUMCVERTEX" ,15))
+            {
+                ParseLV4MeshLong(iNumCVertices);
+                continue;
+            }
+            // Number of regular faces in the mesh
+            if (TokenMatch(filePtr,"MESH_NUMFACES" ,13))
+            {
+                ParseLV4MeshLong(iNumFaces);
+                continue;
+            }
+            // Number of UVWed faces in the mesh
+            if (TokenMatch(filePtr,"MESH_NUMTVFACES" ,15))
+            {
+                ParseLV4MeshLong(iNumTFaces);
+                continue;
+            }
+            // Number of colored faces in the mesh
+            if (TokenMatch(filePtr,"MESH_NUMCVFACES" ,15))
+            {
+                ParseLV4MeshLong(iNumCFaces);
+                continue;
+            }
+            // mesh vertex list block
+            if (TokenMatch(filePtr,"MESH_VERTEX_LIST" ,16))
+            {
+                ParseLV3MeshVertexListBlock(iNumVertices,mesh);
+                continue;
+            }
+            // mesh face list block
+            if (TokenMatch(filePtr,"MESH_FACE_LIST" ,14))
+            {
+                ParseLV3MeshFaceListBlock(iNumFaces,mesh);
+                continue;
+            }
+            // mesh texture vertex list block
+            if (TokenMatch(filePtr,"MESH_TVERTLIST" ,14))
+            {
+                ParseLV3MeshTListBlock(iNumTVertices,mesh);
+                continue;
+            }
+            // mesh texture face block
+            if (TokenMatch(filePtr,"MESH_TFACELIST" ,14))
+            {
+                ParseLV3MeshTFaceListBlock(iNumTFaces,mesh);
+                continue;
+            }
+            // mesh color vertex list block
+            if (TokenMatch(filePtr,"MESH_CVERTLIST" ,14))
+            {
+                ParseLV3MeshCListBlock(iNumCVertices,mesh);
+                continue;
+            }
+            // mesh color face block
+            if (TokenMatch(filePtr,"MESH_CFACELIST" ,14))
+            {
+                ParseLV3MeshCFaceListBlock(iNumCFaces,mesh);
+                continue;
+            }
+            // mesh normals
+            if (TokenMatch(filePtr,"MESH_NORMALS" ,12))
+            {
+                ParseLV3MeshNormalListBlock(mesh);
+                continue;
+            }
+            // another mesh UV channel ...
+            if (TokenMatch(filePtr,"MESH_MAPPINGCHANNEL" ,19)) {
+                unsigned int iIndex( 0 );
+                ParseLV4MeshLong(iIndex);
+                if ( 0 == iIndex ) {
+                    LogWarning( "Mapping channel has an invalid index. Skipping UV channel" );
+                    // skip it ...
+                    SkipSection();
+                } else {
+                    if ( iIndex < 2 ) {
+                        LogWarning( "Mapping channel has an invalid index. Skipping UV channel" );
+                        // skip it ...
+                        SkipSection();
+                    }
+                    if ( iIndex > AI_MAX_NUMBER_OF_TEXTURECOORDS ) {
+                        LogWarning( "Too many UV channels specified. Skipping channel .." );
+                        // skip it ...
+                        SkipSection();
+                    } else {
+                        // parse the mapping channel
+                        ParseLV3MappingChannel( iIndex - 1, mesh );
+                    }
+                    continue;
+                }
+            }
+            // mesh animation keyframe. Not supported
+            if (TokenMatch(filePtr,"MESH_ANIMATION" ,14))
+            {
+
+                LogWarning("Found *MESH_ANIMATION element in ASE/ASK file. "
+                    "Keyframe animation is not supported by Assimp, this element "
+                    "will be ignored");
+                //SkipSection();
+                continue;
+            }
+            if (TokenMatch(filePtr,"MESH_WEIGHTS" ,12))
+            {
+                ParseLV3MeshWeightsBlock(mesh);continue;
+            }
+        }
+        AI_ASE_HANDLE_SECTION("2","*MESH");
+    }
+    return;
+}
+// ------------------------------------------------------------------------------------------------
+void Parser::ParseLV3MeshWeightsBlock(ASE::Mesh& mesh)
+{
+    AI_ASE_PARSER_INIT();
+
+    unsigned int iNumVertices = 0, iNumBones = 0;
+    while (true)
+    {
+        if ('*' == *filePtr)
+        {
+            ++filePtr;
+
+            // Number of bone vertices ...
+            if (TokenMatch(filePtr,"MESH_NUMVERTEX" ,14))
+            {
+                ParseLV4MeshLong(iNumVertices);
+                continue;
+            }
+            // Number of bones
+            if (TokenMatch(filePtr,"MESH_NUMBONE" ,12))
+            {
+                ParseLV4MeshLong(iNumBones);
+                continue;
+            }
+            // parse the list of bones
+            if (TokenMatch(filePtr,"MESH_BONE_LIST" ,14))
+            {
+                ParseLV4MeshBones(iNumBones,mesh);
+                continue;
+            }
+            // parse the list of bones vertices
+            if (TokenMatch(filePtr,"MESH_BONE_VERTEX_LIST" ,21) )
+            {
+                ParseLV4MeshBonesVertices(iNumVertices,mesh);
+                continue;
+            }
+        }
+        AI_ASE_HANDLE_SECTION("3","*MESH_WEIGHTS");
+    }
+    return;
+}
+// ------------------------------------------------------------------------------------------------
+void Parser::ParseLV4MeshBones(unsigned int iNumBones,ASE::Mesh& mesh)
+{
+    AI_ASE_PARSER_INIT();
+    mesh.mBones.resize(iNumBones, Bone("UNNAMED"));
+    while (true)
+    {
+        if ('*' == *filePtr)
+        {
+            ++filePtr;
+
+            // Mesh bone with name ...
+            if (TokenMatch(filePtr,"MESH_BONE_NAME" ,14))
+            {
+                // parse an index ...
+                if(SkipSpaces(&filePtr))
+                {
+                    unsigned int iIndex = strtoul10(filePtr,&filePtr);
+                    if (iIndex >= iNumBones)
+                    {
+                        LogWarning("Bone index is out of bounds");
+                        continue;
+                    }
+                    if (!ParseString(mesh.mBones[iIndex].mName,"*MESH_BONE_NAME"))
+                        SkipToNextToken();
+                    continue;
+                }
+            }
+        }
+        AI_ASE_HANDLE_SECTION("3","*MESH_BONE_LIST");
+    }
+}
+// ------------------------------------------------------------------------------------------------
+void Parser::ParseLV4MeshBonesVertices(unsigned int iNumVertices,ASE::Mesh& mesh)
+{
+    AI_ASE_PARSER_INIT();
+    mesh.mBoneVertices.resize(iNumVertices);
+    while (true)
+    {
+        if ('*' == *filePtr)
+        {
+            ++filePtr;
+
+            // Mesh bone vertex
+            if (TokenMatch(filePtr,"MESH_BONE_VERTEX" ,16))
+            {
+                // read the vertex index
+                unsigned int iIndex = strtoul10(filePtr,&filePtr);
+                if (iIndex >= mesh.mPositions.size())
+                {
+                    iIndex = (unsigned int)mesh.mPositions.size()-1;
+                    LogWarning("Bone vertex index is out of bounds. Using the largest valid "
+                        "bone vertex index instead");
+                }
+
+                // --- ignored
+                ai_real afVert[3];
+                ParseLV4MeshFloatTriple(afVert);
+
+                std::pair<int,float> pairOut;
+                while (true)
+                {
+                    // first parse the bone index ...
+                    if (!SkipSpaces(&filePtr))break;
+                    pairOut.first = strtoul10(filePtr,&filePtr);
+
+                    // then parse the vertex weight
+                    if (!SkipSpaces(&filePtr))break;
+                    filePtr = fast_atoreal_move<float>(filePtr,pairOut.second);
+
+                    // -1 marks unused entries
+                    if (-1 != pairOut.first)
+                    {
+                        mesh.mBoneVertices[iIndex].mBoneWeights.push_back(pairOut);
+                    }
+                }
+                continue;
+            }
+        }
+        AI_ASE_HANDLE_SECTION("4","*MESH_BONE_VERTEX");
+    }
+    return;
+}
+// ------------------------------------------------------------------------------------------------
+void Parser::ParseLV3MeshVertexListBlock(
+    unsigned int iNumVertices, ASE::Mesh& mesh)
+{
+    AI_ASE_PARSER_INIT();
+
+    // allocate enough storage in the array
+    mesh.mPositions.resize(iNumVertices);
+    while (true)
+    {
+        if ('*' == *filePtr)
+        {
+            ++filePtr;
+
+            // Vertex entry
+            if (TokenMatch(filePtr,"MESH_VERTEX" ,11))
+            {
+
+                aiVector3D vTemp;
+                unsigned int iIndex;
+                ParseLV4MeshFloatTriple(&vTemp.x,iIndex);
+
+                if (iIndex >= iNumVertices)
+                {
+                    LogWarning("Invalid vertex index. It will be ignored");
+                }
+                else mesh.mPositions[iIndex] = vTemp;
+                continue;
+            }
+        }
+        AI_ASE_HANDLE_SECTION("3","*MESH_VERTEX_LIST");
+    }
+    return;
+}
+// ------------------------------------------------------------------------------------------------
+void Parser::ParseLV3MeshFaceListBlock(unsigned int iNumFaces, ASE::Mesh& mesh)
+{
+    AI_ASE_PARSER_INIT();
+
+    // allocate enough storage in the face array
+    mesh.mFaces.resize(iNumFaces);
+    while (true)
+    {
+        if ('*' == *filePtr)
+        {
+            ++filePtr;
+
+            // Face entry
+            if (TokenMatch(filePtr,"MESH_FACE" ,9))
+            {
+
+                ASE::Face mFace;
+                ParseLV4MeshFace(mFace);
+
+                if (mFace.iFace >= iNumFaces)
+                {
+                    LogWarning("Face has an invalid index. It will be ignored");
+                }
+                else mesh.mFaces[mFace.iFace] = mFace;
+                continue;
+            }
+        }
+        AI_ASE_HANDLE_SECTION("3","*MESH_FACE_LIST");
+    }
+    return;
+}
+// ------------------------------------------------------------------------------------------------
+void Parser::ParseLV3MeshTListBlock(unsigned int iNumVertices,
+    ASE::Mesh& mesh, unsigned int iChannel)
+{
+    AI_ASE_PARSER_INIT();
+
+    // allocate enough storage in the array
+    mesh.amTexCoords[iChannel].resize(iNumVertices);
+    while (true)
+    {
+        if ('*' == *filePtr)
+        {
+            ++filePtr;
+
+            // Vertex entry
+            if (TokenMatch(filePtr,"MESH_TVERT" ,10))
+            {
+                aiVector3D vTemp;
+                unsigned int iIndex;
+                ParseLV4MeshFloatTriple(&vTemp.x,iIndex);
+
+                if (iIndex >= iNumVertices)
+                {
+                    LogWarning("Tvertex has an invalid index. It will be ignored");
+                }
+                else mesh.amTexCoords[iChannel][iIndex] = vTemp;
+
+                if (0.0f != vTemp.z)
+                {
+                    // we need 3 coordinate channels
+                    mesh.mNumUVComponents[iChannel] = 3;
+                }
+                continue;
+            }
+        }
+        AI_ASE_HANDLE_SECTION("3","*MESH_TVERT_LIST");
+    }
+    return;
+}
+// ------------------------------------------------------------------------------------------------
+void Parser::ParseLV3MeshTFaceListBlock(unsigned int iNumFaces,
+    ASE::Mesh& mesh, unsigned int iChannel)
+{
+    AI_ASE_PARSER_INIT();
+    while (true)
+    {
+        if ('*' == *filePtr)
+        {
+            ++filePtr;
+
+            // Face entry
+            if (TokenMatch(filePtr,"MESH_TFACE" ,10))
+            {
+                unsigned int aiValues[3];
+                unsigned int iIndex = 0;
+
+                ParseLV4MeshLongTriple(aiValues,iIndex);
+                if (iIndex >= iNumFaces || iIndex >= mesh.mFaces.size())
+                {
+                    LogWarning("UV-Face has an invalid index. It will be ignored");
+                }
+                else
+                {
+                    // copy UV indices
+                    mesh.mFaces[iIndex].amUVIndices[iChannel][0] = aiValues[0];
+                    mesh.mFaces[iIndex].amUVIndices[iChannel][1] = aiValues[1];
+                    mesh.mFaces[iIndex].amUVIndices[iChannel][2] = aiValues[2];
+                }
+                continue;
+            }
+        }
+        AI_ASE_HANDLE_SECTION("3","*MESH_TFACE_LIST");
+    }
+    return;
+}
+// ------------------------------------------------------------------------------------------------
+void Parser::ParseLV3MappingChannel(unsigned int iChannel, ASE::Mesh& mesh)
+{
+    AI_ASE_PARSER_INIT();
+
+    unsigned int iNumTVertices = 0;
+    unsigned int iNumTFaces = 0;
+    while (true)
+    {
+        if ('*' == *filePtr)
+        {
+            ++filePtr;
+
+            // Number of texture coordinates in the mesh
+            if (TokenMatch(filePtr,"MESH_NUMTVERTEX" ,15))
+            {
+                ParseLV4MeshLong(iNumTVertices);
+                continue;
+            }
+            // Number of UVWed faces in the mesh
+            if (TokenMatch(filePtr,"MESH_NUMTVFACES" ,15))
+            {
+                ParseLV4MeshLong(iNumTFaces);
+                continue;
+            }
+            // mesh texture vertex list block
+            if (TokenMatch(filePtr,"MESH_TVERTLIST" ,14))
+            {
+                ParseLV3MeshTListBlock(iNumTVertices,mesh,iChannel);
+                continue;
+            }
+            // mesh texture face block
+            if (TokenMatch(filePtr,"MESH_TFACELIST" ,14))
+            {
+                ParseLV3MeshTFaceListBlock(iNumTFaces,mesh, iChannel);
+                continue;
+            }
+        }
+        AI_ASE_HANDLE_SECTION("3","*MESH_MAPPING_CHANNEL");
+    }
+    return;
+}
+// ------------------------------------------------------------------------------------------------
+void Parser::ParseLV3MeshCListBlock(unsigned int iNumVertices, ASE::Mesh& mesh)
+{
+    AI_ASE_PARSER_INIT();
+
+    // allocate enough storage in the array
+    mesh.mVertexColors.resize(iNumVertices);
+    while (true)
+    {
+        if ('*' == *filePtr)
+        {
+            ++filePtr;
+
+            // Vertex entry
+            if (TokenMatch(filePtr,"MESH_VERTCOL" ,12))
+            {
+                aiColor4D vTemp;
+                vTemp.a = 1.0f;
+                unsigned int iIndex;
+                ParseLV4MeshFloatTriple(&vTemp.r,iIndex);
+
+                if (iIndex >= iNumVertices)
+                {
+                    LogWarning("Vertex color has an invalid index. It will be ignored");
+                }
+                else mesh.mVertexColors[iIndex] = vTemp;
+                continue;
+            }
+        }
+        AI_ASE_HANDLE_SECTION("3","*MESH_CVERTEX_LIST");
+    }
+    return;
+}
+// ------------------------------------------------------------------------------------------------
+void Parser::ParseLV3MeshCFaceListBlock(unsigned int iNumFaces, ASE::Mesh& mesh)
+{
+    AI_ASE_PARSER_INIT();
+    while (true)
+    {
+        if ('*' == *filePtr)
+        {
+            ++filePtr;
+
+            // Face entry
+            if (TokenMatch(filePtr,"MESH_CFACE" ,10))
+            {
+                unsigned int aiValues[3];
+                unsigned int iIndex = 0;
+
+                ParseLV4MeshLongTriple(aiValues,iIndex);
+                if (iIndex >= iNumFaces || iIndex >= mesh.mFaces.size())
+                {
+                    LogWarning("UV-Face has an invalid index. It will be ignored");
+                }
+                else
+                {
+                    // copy color indices
+                    mesh.mFaces[iIndex].mColorIndices[0] = aiValues[0];
+                    mesh.mFaces[iIndex].mColorIndices[1] = aiValues[1];
+                    mesh.mFaces[iIndex].mColorIndices[2] = aiValues[2];
+                }
+                continue;
+            }
+        }
+        AI_ASE_HANDLE_SECTION("3","*MESH_CFACE_LIST");
+    }
+    return;
+}
+// ------------------------------------------------------------------------------------------------
+void Parser::ParseLV3MeshNormalListBlock(ASE::Mesh& sMesh)
+{
+    AI_ASE_PARSER_INIT();
+
+    // Allocate enough storage for the normals
+    sMesh.mNormals.resize(sMesh.mFaces.size()*3,aiVector3D( 0.f, 0.f, 0.f ));
+    unsigned int index, faceIdx = UINT_MAX;
+
+    // FIXME: rewrite this and find out how to interpret the normals
+    // correctly. This is crap.
+
+    // Smooth the vertex and face normals together. The result
+    // will be edgy then, but otherwise everything would be soft ...
+    while (true)    {
+        if ('*' == *filePtr)    {
+            ++filePtr;
+            if (faceIdx != UINT_MAX && TokenMatch(filePtr,"MESH_VERTEXNORMAL",17))  {
+                aiVector3D vNormal;
+                ParseLV4MeshFloatTriple(&vNormal.x,index);
+                if (faceIdx >=  sMesh.mFaces.size())
+                    continue;
+
+                // Make sure we assign it to the correct face
+                const ASE::Face& face = sMesh.mFaces[faceIdx];
+                if (index == face.mIndices[0])
+                    index = 0;
+                else if (index == face.mIndices[1])
+                    index = 1;
+                else if (index == face.mIndices[2])
+                    index = 2;
+                else    {
+                    ASSIMP_LOG_ERROR("ASE: Invalid vertex index in MESH_VERTEXNORMAL section");
+                    continue;
+                }
+                // We'll renormalize later
+                sMesh.mNormals[faceIdx*3+index] += vNormal;
+                continue;
+            }
+            if (TokenMatch(filePtr,"MESH_FACENORMAL",15))   {
+                aiVector3D vNormal;
+                ParseLV4MeshFloatTriple(&vNormal.x,faceIdx);
+
+                if (faceIdx >= sMesh.mFaces.size()) {
+                    ASSIMP_LOG_ERROR("ASE: Invalid vertex index in MESH_FACENORMAL section");
+                    continue;
+                }
+
+                // We'll renormalize later
+                sMesh.mNormals[faceIdx*3] += vNormal;
+                sMesh.mNormals[faceIdx*3+1] += vNormal;
+                sMesh.mNormals[faceIdx*3+2] += vNormal;
+                continue;
+            }
+        }
+        AI_ASE_HANDLE_SECTION("3","*MESH_NORMALS");
+    }
+    return;
+}
+// ------------------------------------------------------------------------------------------------
+void Parser::ParseLV4MeshFace(ASE::Face& out)
+{
+    // skip spaces and tabs
+    if(!SkipSpaces(&filePtr))
+    {
+        LogWarning("Unable to parse *MESH_FACE Element: Unexpected EOL [#1]");
+        SkipToNextToken();
+        return;
+    }
+
+    // parse the face index
+    out.iFace = strtoul10(filePtr,&filePtr);
+
+    // next character should be ':'
+    if(!SkipSpaces(&filePtr))
+    {
+        // FIX: there are some ASE files which haven't got : here ....
+        LogWarning("Unable to parse *MESH_FACE Element: Unexpected EOL. \':\' expected [#2]");
+        SkipToNextToken();
+        return;
+    }
+    // FIX: There are some ASE files which haven't got ':' here
+    if(':' == *filePtr)++filePtr;
+
+    // Parse all mesh indices
+    for (unsigned int i = 0; i < 3;++i)
+    {
+        unsigned int iIndex = 0;
+        if(!SkipSpaces(&filePtr))
+        {
+            LogWarning("Unable to parse *MESH_FACE Element: Unexpected EOL");
+            SkipToNextToken();
+            return;
+        }
+        switch (*filePtr)
+        {
+        case 'A':
+        case 'a':
+            break;
+        case 'B':
+        case 'b':
+            iIndex = 1;
+            break;
+        case 'C':
+        case 'c':
+            iIndex = 2;
+            break;
+        default:
+            LogWarning("Unable to parse *MESH_FACE Element: Unexpected EOL. "
+                "A,B or C expected [#3]");
+            SkipToNextToken();
+            return;
+        };
+        ++filePtr;
+
+        // next character should be ':'
+        if(!SkipSpaces(&filePtr) || ':' != *filePtr)
+        {
+            LogWarning("Unable to parse *MESH_FACE Element: "
+                "Unexpected EOL. \':\' expected [#2]");
+            SkipToNextToken();
+            return;
+        }
+
+        ++filePtr;
+        if(!SkipSpaces(&filePtr))
+        {
+            LogWarning("Unable to parse *MESH_FACE Element: Unexpected EOL. "
+                "Vertex index ecpected [#4]");
+            SkipToNextToken();
+            return;
+        }
+        out.mIndices[iIndex] = strtoul10(filePtr,&filePtr);
+    }
+
+    // now we need to skip the AB, BC, CA blocks.
+    while (true)
+    {
+        if ('*' == *filePtr)break;
+        if (IsLineEnd(*filePtr))
+        {
+            //iLineNumber++;
+            return;
+        }
+        filePtr++;
+    }
+
+    // parse the smoothing group of the face
+    if (TokenMatch(filePtr,"*MESH_SMOOTHING",15))
+    {
+        if(!SkipSpaces(&filePtr))
+        {
+            LogWarning("Unable to parse *MESH_SMOOTHING Element: "
+                "Unexpected EOL. Smoothing group(s) expected [#5]");
+            SkipToNextToken();
+            return;
+        }
+
+        // Parse smoothing groups until we don't anymore see commas
+        // FIX: There needn't always be a value, sad but true
+        while (true)
+        {
+            if (*filePtr < '9' && *filePtr >= '0')
+            {
+                out.iSmoothGroup |= (1 << strtoul10(filePtr,&filePtr));
+            }
+            SkipSpaces(&filePtr);
+            if (',' != *filePtr)
+            {
+                break;
+            }
+            ++filePtr;
+            SkipSpaces(&filePtr);
+        }
+    }
+
+    // *MESH_MTLID  is optional, too
+    while (true)
+    {
+        if ('*' == *filePtr)break;
+        if (IsLineEnd(*filePtr))
+        {
+            return;
+        }
+        filePtr++;
+    }
+
+    if (TokenMatch(filePtr,"*MESH_MTLID",11))
+    {
+        if(!SkipSpaces(&filePtr))
+        {
+            LogWarning("Unable to parse *MESH_MTLID Element: Unexpected EOL. "
+                "Material index expected [#6]");
+            SkipToNextToken();
+            return;
+        }
+        out.iMaterial = strtoul10(filePtr,&filePtr);
+    }
+    return;
+}
+// ------------------------------------------------------------------------------------------------
+void Parser::ParseLV4MeshLongTriple(unsigned int* apOut)
+{
+    ai_assert(NULL != apOut);
+
+    for (unsigned int i = 0; i < 3;++i)
+        ParseLV4MeshLong(apOut[i]);
+}
+// ------------------------------------------------------------------------------------------------
+void Parser::ParseLV4MeshLongTriple(unsigned int* apOut, unsigned int& rIndexOut)
+{
+    ai_assert(NULL != apOut);
+
+    // parse the index
+    ParseLV4MeshLong(rIndexOut);
+
+    // parse the three others
+    ParseLV4MeshLongTriple(apOut);
+}
+// ------------------------------------------------------------------------------------------------
+void Parser::ParseLV4MeshFloatTriple(ai_real* apOut, unsigned int& rIndexOut)
+{
+    ai_assert(NULL != apOut);
+
+    // parse the index
+    ParseLV4MeshLong(rIndexOut);
+
+    // parse the three others
+    ParseLV4MeshFloatTriple(apOut);
+}
+// ------------------------------------------------------------------------------------------------
+void Parser::ParseLV4MeshFloatTriple(ai_real* apOut)
+{
+    ai_assert(NULL != apOut);
+
+    for (unsigned int i = 0; i < 3;++i)
+        ParseLV4MeshFloat(apOut[i]);
+}
+// ------------------------------------------------------------------------------------------------
+void Parser::ParseLV4MeshFloat(ai_real& fOut)
+{
+    // skip spaces and tabs
+    if(!SkipSpaces(&filePtr))
+    {
+        // LOG
+        LogWarning("Unable to parse float: unexpected EOL [#1]");
+        fOut = 0.0;
+        ++iLineNumber;
+        return;
+    }
+    // parse the first float
+    filePtr = fast_atoreal_move<ai_real>(filePtr,fOut);
+}
+// ------------------------------------------------------------------------------------------------
+void Parser::ParseLV4MeshLong(unsigned int& iOut)
+{
+    // Skip spaces and tabs
+    if(!SkipSpaces(&filePtr))
+    {
+        // LOG
+        LogWarning("Unable to parse long: unexpected EOL [#1]");
+        iOut = 0;
+        ++iLineNumber;
+        return;
+    }
+    // parse the value
+    iOut = strtoul10(filePtr,&filePtr);
+}
+
+#endif // ASSIMP_BUILD_NO_3DS_IMPORTER
+
+#endif // !! ASSIMP_BUILD_NO_BASE_IMPORTER

+ 693 - 0
Engine/lib/assimp/code/ASEParser.h

@@ -0,0 +1,693 @@
+/*
+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 the helper data structures for importing ASE files  */
+#ifndef AI_ASEFILEHELPER_H_INC
+#define AI_ASEFILEHELPER_H_INC
+
+// public ASSIMP headers
+#include <assimp/types.h>
+#include <assimp/mesh.h>
+#include <assimp/anim.h>
+
+#ifndef ASSIMP_BUILD_NO_3DS_IMPORTER
+
+// for some helper routines like IsSpace()
+#include <assimp/ParsingUtils.h>
+#include <assimp/qnan.h>
+
+// ASE is quite similar to 3ds. We can reuse some structures
+#include "3DSLoader.h"
+
+namespace Assimp    {
+namespace ASE   {
+
+using namespace D3DS;
+
+// ---------------------------------------------------------------------------
+/** Helper structure representing an ASE material */
+struct Material : public D3DS::Material
+{
+    //! Default constructor has been deleted
+    Material() = delete;
+
+    //! Constructor with explicit name
+    explicit Material(const std::string &name)
+    : D3DS::Material(name)
+    , pcInstance(NULL)
+    , bNeed (false) {
+        // empty
+    }
+
+    Material(const Material &other)            = default;
+    Material &operator=(const Material &other) = default;
+
+
+    //! Move constructor. This is explicitly written because MSVC doesn't support defaulting it
+    Material(Material &&other) AI_NO_EXCEPT
+    : D3DS::Material(std::move(other))
+    , avSubMaterials(std::move(other.avSubMaterials))
+    , pcInstance(std::move(other.pcInstance))
+    , bNeed(std::move(other.bNeed))
+    {
+        other.pcInstance = nullptr;
+    }
+
+
+    Material &operator=(Material &&other) AI_NO_EXCEPT {
+        if (this == &other) {
+            return *this;
+        }
+
+        D3DS::Material::operator=(std::move(other));
+
+        avSubMaterials = std::move(other.avSubMaterials);
+        pcInstance = std::move(other.pcInstance);
+        bNeed = std::move(other.bNeed);
+
+        other.pcInstance = nullptr;
+
+        return *this;
+    }
+
+
+    ~Material() {}
+
+
+    //! Contains all sub materials of this material
+    std::vector<Material> avSubMaterials;
+
+    //! aiMaterial object
+    aiMaterial* pcInstance;
+
+    //! Can we remove this material?
+    bool bNeed;
+};
+
+// ---------------------------------------------------------------------------
+/** Helper structure to represent an ASE file face */
+struct Face : public FaceWithSmoothingGroup {
+    //! Default constructor. Initializes everything with 0
+    Face() AI_NO_EXCEPT
+    : iMaterial(DEFAULT_MATINDEX)
+    , iFace(0) {
+        // empty
+    }
+
+    //! special value to indicate that no material index has
+    //! been assigned to a face. The default material index
+    //! will replace this value later.
+    static const unsigned int DEFAULT_MATINDEX = 0xFFFFFFFF;
+
+    //! Indices into each list of texture coordinates
+    unsigned int amUVIndices[AI_MAX_NUMBER_OF_TEXTURECOORDS][3];
+
+    //! Index into the list of vertex colors
+    unsigned int mColorIndices[3];
+
+    //! (Sub)Material index to be assigned to this face
+    unsigned int iMaterial;
+
+    //! Index of the face. It is not specified whether it is
+    //! a requirement of the file format that all faces are
+    //! written in sequential order, so we have to expect this case
+    unsigned int iFace;
+};
+
+// ---------------------------------------------------------------------------
+/** Helper structure to represent an ASE file bone */
+struct Bone {
+    //! Constructor
+    Bone() = delete;
+
+    //! Construction from an existing name
+    explicit Bone( const std::string& name)
+    : mName(name) {
+        // empty
+    }
+
+    //! Name of the bone
+    std::string mName;
+};
+
+// ---------------------------------------------------------------------------
+/** Helper structure to represent an ASE file bone vertex */
+struct BoneVertex {
+    //! Bone and corresponding vertex weight.
+    //! -1 for unrequired bones ....
+    std::vector<std::pair<int,float> > mBoneWeights;
+};
+
+// ---------------------------------------------------------------------------
+/** Helper structure to represent an ASE file animation */
+struct Animation {
+    enum Type {
+        TRACK   = 0x0,
+        BEZIER  = 0x1,
+        TCB     = 0x2
+    } mRotationType, mScalingType, mPositionType;
+
+    Animation() AI_NO_EXCEPT
+        :   mRotationType   (TRACK)
+        ,   mScalingType    (TRACK)
+        ,   mPositionType   (TRACK)
+    {}
+
+    //! List of track rotation keyframes
+    std::vector< aiQuatKey > akeyRotations;
+
+    //! List of track position keyframes
+    std::vector< aiVectorKey > akeyPositions;
+
+    //! List of track scaling keyframes
+    std::vector< aiVectorKey > akeyScaling;
+};
+
+// ---------------------------------------------------------------------------
+/** Helper structure to represent the inheritance information of an ASE node */
+struct InheritanceInfo {
+    //! Default constructor
+    InheritanceInfo() AI_NO_EXCEPT {
+        for ( size_t i=0; i<3; ++i ) {
+            abInheritPosition[i] = abInheritRotation[i] = abInheritScaling[i] = true;
+        }
+    }
+
+    //! Inherit the parent's position?, axis order is x,y,z
+    bool abInheritPosition[3];
+
+    //! Inherit the parent's rotation?, axis order is x,y,z
+    bool abInheritRotation[3];
+
+    //! Inherit the parent's scaling?, axis order is x,y,z
+    bool abInheritScaling[3];
+};
+
+// ---------------------------------------------------------------------------
+/** Represents an ASE file node. Base class for mesh, light and cameras */
+struct BaseNode {
+    enum Type {
+        Light, 
+        Camera, 
+        Mesh, 
+        Dummy
+    } mType;
+
+    //! Construction from an existing name
+    BaseNode(Type _mType, const std::string &name)
+    : mType         (_mType)
+    , mName         (name)
+    , mProcessed    (false) {
+        // Set mTargetPosition to qnan
+        const ai_real qnan = get_qnan();
+        mTargetPosition.x = qnan;
+    }
+
+
+    //! Name of the mesh
+    std::string mName;
+
+    //! Name of the parent of the node
+    //! "" if there is no parent ...
+    std::string mParent;
+
+    //! Transformation matrix of the node
+    aiMatrix4x4 mTransform;
+
+    //! Target position (target lights and cameras)
+    aiVector3D mTargetPosition;
+
+    //! Specifies which axes transformations a node inherits
+    //! from its parent ...
+    InheritanceInfo inherit;
+
+    //! Animation channels for the node
+    Animation mAnim;
+
+    //! Needed for lights and cameras: target animation channel
+    //! Should contain position keys only.
+    Animation mTargetAnim;
+
+    bool mProcessed;
+};
+
+// ---------------------------------------------------------------------------
+/** Helper structure to represent an ASE file mesh */
+struct Mesh : public MeshWithSmoothingGroups<ASE::Face>, public BaseNode {
+    //! Default constructor has been deleted
+    Mesh() = delete;
+
+    //! Construction from an existing name
+    explicit Mesh(const std::string &name)
+    : BaseNode( BaseNode::Mesh, name )
+    , mVertexColors()
+    , mBoneVertices()
+    , mBones()
+    , iMaterialIndex(Face::DEFAULT_MATINDEX)
+    , bSkip     (false) {
+        for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS;++c) {
+            this->mNumUVComponents[c] = 2;
+        }
+    }
+
+    //! List of all texture coordinate sets
+    std::vector<aiVector3D> amTexCoords[AI_MAX_NUMBER_OF_TEXTURECOORDS];
+
+    //! List of all vertex color sets.
+    std::vector<aiColor4D> mVertexColors;
+
+    //! List of all bone vertices
+    std::vector<BoneVertex> mBoneVertices;
+
+    //! List of all bones
+    std::vector<Bone> mBones;
+
+    //! Material index of the mesh
+    unsigned int iMaterialIndex;
+
+    //! Number of vertex components for each UVW set
+    unsigned int mNumUVComponents[AI_MAX_NUMBER_OF_TEXTURECOORDS];
+
+    //! used internally
+    bool bSkip;
+};
+
+// ---------------------------------------------------------------------------
+/** Helper structure to represent an ASE light source */
+struct Light : public BaseNode
+{
+    enum LightType
+    {
+        OMNI,
+        TARGET,
+        FREE,
+        DIRECTIONAL
+    };
+
+    //! Default constructor has been deleted
+    Light() = delete;
+
+    //! Construction from an existing name
+    explicit Light(const std::string &name)
+    : BaseNode   (BaseNode::Light, name)
+    , mLightType (OMNI)
+    , mColor     (1.f,1.f,1.f)
+    , mIntensity (1.f) // light is white by default
+    , mAngle     (45.f)
+    , mFalloff   (0.f)
+    {
+    }
+
+
+    LightType mLightType;
+    aiColor3D mColor;
+    ai_real mIntensity;
+    ai_real mAngle; // in degrees
+    ai_real mFalloff;
+};
+
+// ---------------------------------------------------------------------------
+/** Helper structure to represent an ASE camera */
+struct Camera : public BaseNode
+{
+    enum CameraType
+    {
+        FREE,
+        TARGET
+    };
+
+    //! Default constructor has been deleted
+    Camera() = delete;
+
+
+    //! Construction from an existing name
+    explicit Camera(const std::string &name)
+    : BaseNode    (BaseNode::Camera, name)
+    , mFOV        (0.75f)   // in radians
+    , mNear       (0.1f)
+    , mFar        (1000.f)  // could be zero
+    , mCameraType (FREE)
+    {
+    }
+
+
+    ai_real mFOV, mNear, mFar;
+    CameraType mCameraType;
+};
+
+// ---------------------------------------------------------------------------
+/** Helper structure to represent an ASE helper object (dummy) */
+struct Dummy : public BaseNode {
+    //! Constructor
+    Dummy() AI_NO_EXCEPT
+    : BaseNode  (BaseNode::Dummy, "DUMMY") {
+        // empty
+    }
+};
+
+// Parameters to Parser::Parse()
+#define AI_ASE_NEW_FILE_FORMAT 200
+#define AI_ASE_OLD_FILE_FORMAT 110
+
+// Internally we're a little bit more tolerant
+#define AI_ASE_IS_NEW_FILE_FORMAT() (iFileFormat >= 200)
+#define AI_ASE_IS_OLD_FILE_FORMAT() (iFileFormat < 200)
+
+// -------------------------------------------------------------------------------
+/** \brief Class to parse ASE files
+ */
+class Parser {
+private:
+    Parser() AI_NO_EXCEPT {
+        // empty
+    }
+
+public:
+
+    // -------------------------------------------------------------------
+    //! Construct a parser from a given input file which is
+    //! guaranteed to be terminated with zero.
+    //! @param szFile Input file
+    //! @param fileFormatDefault Assumed file format version. If the
+    //!   file format is specified in the file the new value replaces
+    //!   the default value.
+    Parser (const char* szFile, unsigned int fileFormatDefault);
+
+    // -------------------------------------------------------------------
+    //! Parses the file into the parsers internal representation
+    void Parse();
+
+
+private:
+
+    // -------------------------------------------------------------------
+    //! Parse the *SCENE block in a file
+    void ParseLV1SceneBlock();
+
+    // -------------------------------------------------------------------
+    //! Parse the *MESH_SOFTSKINVERTS block in a file
+    void ParseLV1SoftSkinBlock();
+
+    // -------------------------------------------------------------------
+    //! Parse the *MATERIAL_LIST block in a file
+    void ParseLV1MaterialListBlock();
+
+    // -------------------------------------------------------------------
+    //! Parse a *<xxx>OBJECT block in a file
+    //! \param mesh Node to be filled
+    void ParseLV1ObjectBlock(BaseNode& mesh);
+
+    // -------------------------------------------------------------------
+    //! Parse a *MATERIAL blocks in a material list
+    //! \param mat Material structure to be filled
+    void ParseLV2MaterialBlock(Material& mat);
+
+    // -------------------------------------------------------------------
+    //! Parse a *NODE_TM block in a file
+    //! \param mesh Node (!) object to be filled
+    void ParseLV2NodeTransformBlock(BaseNode& mesh);
+
+    // -------------------------------------------------------------------
+    //! Parse a *TM_ANIMATION block in a file
+    //! \param mesh Mesh object to be filled
+    void ParseLV2AnimationBlock(BaseNode& mesh);
+    void ParseLV3PosAnimationBlock(ASE::Animation& anim);
+    void ParseLV3ScaleAnimationBlock(ASE::Animation& anim);
+    void ParseLV3RotAnimationBlock(ASE::Animation& anim);
+
+    // -------------------------------------------------------------------
+    //! Parse a *MESH block in a file
+    //! \param mesh Mesh object to be filled
+    void ParseLV2MeshBlock(Mesh& mesh);
+
+    // -------------------------------------------------------------------
+    //! Parse a *LIGHT_SETTINGS block in a file
+    //! \param light Light object to be filled
+    void ParseLV2LightSettingsBlock(Light& light);
+
+    // -------------------------------------------------------------------
+    //! Parse a *CAMERA_SETTINGS block in a file
+    //! \param cam Camera object to be filled
+    void ParseLV2CameraSettingsBlock(Camera& cam);
+
+    // -------------------------------------------------------------------
+    //! Parse the *MAP_XXXXXX blocks in a material
+    //! \param map Texture structure to be filled
+    void ParseLV3MapBlock(Texture& map);
+
+    // -------------------------------------------------------------------
+    //! Parse a *MESH_VERTEX_LIST block in a file
+    //! \param iNumVertices Value of *MESH_NUMVERTEX, if present.
+    //! Otherwise zero. This is used to check the consistency of the file.
+    //! A warning is sent to the logger if the validations fails.
+    //! \param mesh Mesh object to be filled
+    void ParseLV3MeshVertexListBlock(
+        unsigned int iNumVertices,Mesh& mesh);
+
+    // -------------------------------------------------------------------
+    //! Parse a *MESH_FACE_LIST block in a file
+    //! \param iNumFaces Value of *MESH_NUMFACES, if present.
+    //! Otherwise zero. This is used to check the consistency of the file.
+    //! A warning is sent to the logger if the validations fails.
+    //! \param mesh Mesh object to be filled
+    void ParseLV3MeshFaceListBlock(
+        unsigned int iNumFaces,Mesh& mesh);
+
+    // -------------------------------------------------------------------
+    //! Parse a *MESH_TVERT_LIST block in a file
+    //! \param iNumVertices Value of *MESH_NUMTVERTEX, if present.
+    //! Otherwise zero. This is used to check the consistency of the file.
+    //! A warning is sent to the logger if the validations fails.
+    //! \param mesh Mesh object to be filled
+    //! \param iChannel Output UVW channel
+    void ParseLV3MeshTListBlock(
+        unsigned int iNumVertices,Mesh& mesh, unsigned int iChannel = 0);
+
+    // -------------------------------------------------------------------
+    //! Parse a *MESH_TFACELIST block in a file
+    //! \param iNumFaces Value of *MESH_NUMTVFACES, if present.
+    //! Otherwise zero. This is used to check the consistency of the file.
+    //! A warning is sent to the logger if the validations fails.
+    //! \param mesh Mesh object to be filled
+    //! \param iChannel Output UVW channel
+    void ParseLV3MeshTFaceListBlock(
+        unsigned int iNumFaces,Mesh& mesh, unsigned int iChannel = 0);
+
+    // -------------------------------------------------------------------
+    //! Parse an additional mapping channel
+    //! (specified via *MESH_MAPPINGCHANNEL)
+    //! \param iChannel Channel index to be filled
+    //! \param mesh Mesh object to be filled
+    void ParseLV3MappingChannel(
+        unsigned int iChannel, Mesh& mesh);
+
+    // -------------------------------------------------------------------
+    //! Parse a *MESH_CVERTLIST block in a file
+    //! \param iNumVertices Value of *MESH_NUMCVERTEX, if present.
+    //! Otherwise zero. This is used to check the consistency of the file.
+    //! A warning is sent to the logger if the validations fails.
+    //! \param mesh Mesh object to be filled
+    void ParseLV3MeshCListBlock(
+        unsigned int iNumVertices, Mesh& mesh);
+
+    // -------------------------------------------------------------------
+    //! Parse a *MESH_CFACELIST block in a file
+    //! \param iNumFaces Value of *MESH_NUMCVFACES, if present.
+    //! Otherwise zero. This is used to check the consistency of the file.
+    //! A warning is sent to the logger if the validations fails.
+    //! \param mesh Mesh object to be filled
+    void ParseLV3MeshCFaceListBlock(
+        unsigned int iNumFaces, Mesh& mesh);
+
+    // -------------------------------------------------------------------
+    //! Parse a *MESH_NORMALS block in a file
+    //! \param mesh Mesh object to be filled
+    void ParseLV3MeshNormalListBlock(Mesh& mesh);
+
+    // -------------------------------------------------------------------
+    //! Parse a *MESH_WEIGHTSblock in a file
+    //! \param mesh Mesh object to be filled
+    void ParseLV3MeshWeightsBlock(Mesh& mesh);
+
+    // -------------------------------------------------------------------
+    //! Parse the bone list of a file
+    //! \param mesh Mesh object to be filled
+    //! \param iNumBones Number of bones in the mesh
+    void ParseLV4MeshBones(unsigned int iNumBones,Mesh& mesh);
+
+    // -------------------------------------------------------------------
+    //! Parse the bone vertices list of a file
+    //! \param mesh Mesh object to be filled
+    //! \param iNumVertices Number of vertices to be parsed
+    void ParseLV4MeshBonesVertices(unsigned int iNumVertices,Mesh& mesh);
+
+    // -------------------------------------------------------------------
+    //! Parse a *MESH_FACE block in a file
+    //! \param out receive the face data
+    void ParseLV4MeshFace(ASE::Face& out);
+
+    // -------------------------------------------------------------------
+    //! Parse a *MESH_VERT block in a file
+    //! (also works for MESH_TVERT, MESH_CFACE, MESH_VERTCOL  ...)
+    //! \param apOut Output buffer (3 floats)
+    //! \param rIndexOut Output index
+    void ParseLV4MeshFloatTriple(ai_real* apOut, unsigned int& rIndexOut);
+
+    // -------------------------------------------------------------------
+    //! Parse a *MESH_VERT block in a file
+    //! (also works for MESH_TVERT, MESH_CFACE, MESH_VERTCOL  ...)
+    //! \param apOut Output buffer (3 floats)
+    void ParseLV4MeshFloatTriple(ai_real* apOut);
+
+    // -------------------------------------------------------------------
+    //! Parse a *MESH_TFACE block in a file
+    //! (also works for MESH_CFACE)
+    //! \param apOut Output buffer (3 ints)
+    //! \param rIndexOut Output index
+    void ParseLV4MeshLongTriple(unsigned int* apOut, unsigned int& rIndexOut);
+
+    // -------------------------------------------------------------------
+    //! Parse a *MESH_TFACE block in a file
+    //! (also works for MESH_CFACE)
+    //! \param apOut Output buffer (3 ints)
+    void ParseLV4MeshLongTriple(unsigned int* apOut);
+
+    // -------------------------------------------------------------------
+    //! Parse a single float element
+    //! \param fOut Output float
+    void ParseLV4MeshFloat(ai_real& fOut);
+
+    // -------------------------------------------------------------------
+    //! Parse a single int element
+    //! \param iOut Output integer
+    void ParseLV4MeshLong(unsigned int& iOut);
+
+    // -------------------------------------------------------------------
+    //! Skip everything to the next: '*' or '\0'
+    bool SkipToNextToken();
+
+    // -------------------------------------------------------------------
+    //! Skip the current section until the token after the closing }.
+    //! This function handles embedded subsections correctly
+    bool SkipSection();
+
+    // -------------------------------------------------------------------
+    //! Output a warning to the logger
+    //! \param szWarn Warn message
+    void LogWarning(const char* szWarn);
+
+    // -------------------------------------------------------------------
+    //! Output a message to the logger
+    //! \param szWarn Message
+    void LogInfo(const char* szWarn);
+
+    // -------------------------------------------------------------------
+    //! Output an error to the logger
+    //! \param szWarn Error message
+    AI_WONT_RETURN void LogError(const char* szWarn) AI_WONT_RETURN_SUFFIX;
+
+    // -------------------------------------------------------------------
+    //! Parse a string, enclosed in double quotation marks
+    //! \param out Output string
+    //! \param szName Name of the enclosing element -> used in error
+    //! messages.
+    //! \return false if an error occurred
+    bool ParseString(std::string& out,const char* szName);
+
+public:
+
+    //! Pointer to current data
+    const char* filePtr;
+
+    //! background color to be passed to the viewer
+    //! QNAN if none was found
+    aiColor3D m_clrBackground;
+
+    //! Base ambient color to be passed to all materials
+    //! QNAN if none was found
+    aiColor3D m_clrAmbient;
+
+    //! List of all materials found in the file
+    std::vector<Material> m_vMaterials;
+
+    //! List of all meshes found in the file
+    std::vector<Mesh> m_vMeshes;
+
+    //! List of all dummies found in the file
+    std::vector<Dummy> m_vDummies;
+
+    //! List of all lights found in the file
+    std::vector<Light> m_vLights;
+
+    //! List of all cameras found in the file
+    std::vector<Camera> m_vCameras;
+
+    //! Current line in the file
+    unsigned int iLineNumber;
+
+    //! First frame
+    unsigned int iFirstFrame;
+
+    //! Last frame
+    unsigned int iLastFrame;
+
+    //! Frame speed - frames per second
+    unsigned int iFrameSpeed;
+
+    //! Ticks per frame
+    unsigned int iTicksPerFrame;
+
+    //! true if the last character read was an end-line character
+    bool bLastWasEndLine;
+
+    //! File format version
+    unsigned int iFileFormat;
+};
+
+
+} // Namespace ASE
+} // Namespace ASSIMP
+
+#endif // ASSIMP_BUILD_NO_3DS_IMPORTER
+
+#endif // !! include guard

+ 845 - 0
Engine/lib/assimp/code/AssbinExporter.cpp

@@ -0,0 +1,845 @@
+/*
+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  AssbinExporter.cpp
+ *  ASSBIN exporter main code
+ */
+
+#ifndef ASSIMP_BUILD_NO_EXPORT
+#ifndef ASSIMP_BUILD_NO_ASSBIN_EXPORTER
+
+#include "assbin_chunks.h"
+#include <assimp/version.h>
+#include <assimp/IOStream.hpp>
+#include <assimp/IOSystem.hpp>
+#include <assimp/Exporter.hpp>
+#include "ProcessHelper.h"
+#include <assimp/Exceptional.h>
+
+#ifdef ASSIMP_BUILD_NO_OWN_ZLIB
+#   include <zlib.h>
+#else
+#   include "../contrib/zlib/zlib.h"
+#endif
+
+#include <time.h>
+
+namespace Assimp {
+
+template <typename T>
+size_t Write(IOStream * stream, const T& v) {
+    return stream->Write( &v, sizeof(T), 1 );
+}
+
+// -----------------------------------------------------------------------------------
+// Serialize an aiString
+template <>
+inline
+size_t Write<aiString>(IOStream * stream, const aiString& s) {
+    const size_t s2 = (uint32_t)s.length;
+    stream->Write(&s,4,1);
+    stream->Write(s.data,s2,1);
+
+    return s2+4;
+}
+
+// -----------------------------------------------------------------------------------
+// Serialize an unsigned int as uint32_t
+template <>
+inline
+size_t Write<unsigned int>(IOStream * stream, const unsigned int& w) {
+    const uint32_t t = (uint32_t)w;
+    if (w > t) {
+        // this shouldn't happen, integers in Assimp data structures never exceed 2^32
+        throw DeadlyExportError("loss of data due to 64 -> 32 bit integer conversion");
+    }
+
+    stream->Write(&t,4,1);
+
+    return 4;
+}
+
+// -----------------------------------------------------------------------------------
+// Serialize an unsigned int as uint16_t
+template <>
+inline
+size_t Write<uint16_t>(IOStream * stream, const uint16_t& w) {
+    static_assert(sizeof(uint16_t)==2, "sizeof(uint16_t)==2");
+    stream->Write(&w,2,1);
+
+    return 2;
+}
+
+// -----------------------------------------------------------------------------------
+// Serialize a float
+template <>
+inline
+size_t Write<float>(IOStream * stream, const float& f) {
+    static_assert(sizeof(float)==4, "sizeof(float)==4");
+    stream->Write(&f,4,1);
+
+    return 4;
+}
+
+// -----------------------------------------------------------------------------------
+// Serialize a double
+template <>
+inline
+size_t Write<double>(IOStream * stream, const double& f) {
+    static_assert(sizeof(double)==8, "sizeof(double)==8");
+    stream->Write(&f,8,1);
+
+    return 8;
+}
+
+// -----------------------------------------------------------------------------------
+// Serialize a vec3
+template <>
+inline
+size_t Write<aiVector3D>(IOStream * stream, const aiVector3D& v) {
+    size_t t = Write<float>(stream,v.x);
+    t += Write<float>(stream,v.y);
+    t += Write<float>(stream,v.z);
+
+    return t;
+}
+
+// -----------------------------------------------------------------------------------
+// Serialize a color value
+template <>
+inline
+size_t Write<aiColor3D>(IOStream * stream, const aiColor3D& v) {
+    size_t t = Write<float>(stream,v.r);
+    t += Write<float>(stream,v.g);
+    t += Write<float>(stream,v.b);
+
+    return t;
+}
+
+// -----------------------------------------------------------------------------------
+// Serialize a color value
+template <>
+inline
+size_t Write<aiColor4D>(IOStream * stream, const aiColor4D& v) {
+    size_t t = Write<float>(stream,v.r);
+    t += Write<float>(stream,v.g);
+    t += Write<float>(stream,v.b);
+    t += Write<float>(stream,v.a);
+
+    return t;
+}
+
+// -----------------------------------------------------------------------------------
+// Serialize a quaternion
+template <>
+inline
+size_t Write<aiQuaternion>(IOStream * stream, const aiQuaternion& v) {
+    size_t t = Write<float>(stream,v.w);
+    t += Write<float>(stream,v.x);
+    t += Write<float>(stream,v.y);
+    t += Write<float>(stream,v.z);
+    ai_assert(t == 16);
+
+    return 16;
+}
+
+// -----------------------------------------------------------------------------------
+// Serialize a vertex weight
+template <>
+inline
+size_t Write<aiVertexWeight>(IOStream * stream, const aiVertexWeight& v) {
+    size_t t = Write<unsigned int>(stream,v.mVertexId);
+
+    return t+Write<float>(stream,v.mWeight);
+}
+
+// -----------------------------------------------------------------------------------
+// Serialize a mat4x4
+template <>
+inline
+size_t Write<aiMatrix4x4>(IOStream * stream, const aiMatrix4x4& m) {
+    for (unsigned int i = 0; i < 4;++i) {
+        for (unsigned int i2 = 0; i2 < 4;++i2) {
+            Write<float>(stream,m[i][i2]);
+        }
+    }
+
+    return 64;
+}
+
+// -----------------------------------------------------------------------------------
+// Serialize an aiVectorKey
+template <>
+inline
+size_t Write<aiVectorKey>(IOStream * stream, const aiVectorKey& v) {
+    const size_t t = Write<double>(stream,v.mTime);
+    return t + Write<aiVector3D>(stream,v.mValue);
+}
+
+// -----------------------------------------------------------------------------------
+// Serialize an aiQuatKey
+template <>
+inline
+size_t Write<aiQuatKey>(IOStream * stream, const aiQuatKey& v) {
+    const size_t t = Write<double>(stream,v.mTime);
+    return t + Write<aiQuaternion>(stream,v.mValue);
+}
+
+template <typename T>
+inline
+size_t WriteBounds(IOStream * stream, const T* in, unsigned int size) {
+    T minc, maxc;
+    ArrayBounds(in,size,minc,maxc);
+
+    const size_t t = Write<T>(stream,minc);
+    return t + Write<T>(stream,maxc);
+}
+
+// We use this to write out non-byte arrays so that we write using the specializations.
+// This way we avoid writing out extra bytes that potentially come from struct alignment.
+template <typename T>
+inline
+size_t WriteArray(IOStream * stream, const T* in, unsigned int size) {
+    size_t n = 0;
+    for (unsigned int i=0; i<size; i++) n += Write<T>(stream,in[i]);
+
+    return n;
+}
+
+// ----------------------------------------------------------------------------------
+/** @class  AssbinChunkWriter
+ *  @brief  Chunk writer mechanism for the .assbin file structure
+ *
+ *  This is a standard in-memory IOStream (most of the code is based on BlobIOStream),
+ *  the difference being that this takes another IOStream as a "container" in the
+ *  constructor, and when it is destroyed, it appends the magic number, the chunk size,
+ *  and the chunk contents to the container stream. This allows relatively easy chunk
+ *  chunk construction, even recursively.
+ */
+class AssbinChunkWriter : public IOStream
+{
+private:
+
+    uint8_t* buffer;
+    uint32_t magic;
+    IOStream * container;
+    size_t cur_size, cursor, initial;
+
+private:
+    // -------------------------------------------------------------------
+    void Grow(size_t need = 0)
+    {
+        size_t new_size = std::max(initial, std::max( need, cur_size+(cur_size>>1) ));
+
+        const uint8_t* const old = buffer;
+        buffer = new uint8_t[new_size];
+
+        if (old) {
+            memcpy(buffer,old,cur_size);
+            delete[] old;
+        }
+
+        cur_size = new_size;
+    }
+
+public:
+
+    AssbinChunkWriter( IOStream * container, uint32_t magic, size_t initial = 4096)
+        : buffer(NULL), magic(magic), container(container), cur_size(0), cursor(0), initial(initial)
+    {
+    }
+
+    virtual ~AssbinChunkWriter()
+    {
+        if (container) {
+            container->Write( &magic, sizeof(uint32_t), 1 );
+            container->Write( &cursor, sizeof(uint32_t), 1 );
+            container->Write( buffer, 1, cursor );
+        }
+        if (buffer) delete[] buffer;
+    }
+
+    void * GetBufferPointer() { return buffer; }
+
+    // -------------------------------------------------------------------
+    virtual size_t Read(void* /*pvBuffer*/, size_t /*pSize*/, size_t /*pCount*/) {
+        return 0;
+    }
+    virtual aiReturn Seek(size_t /*pOffset*/, aiOrigin /*pOrigin*/) {
+        return aiReturn_FAILURE;
+    }
+    virtual size_t Tell() const {
+        return cursor;
+    }
+    virtual void Flush() {
+        // not implemented
+    }
+
+    virtual size_t FileSize() const {
+        return cursor;
+    }
+
+    // -------------------------------------------------------------------
+    virtual size_t Write(const void* pvBuffer, size_t pSize, size_t pCount) {
+        pSize *= pCount;
+        if (cursor + pSize > cur_size) {
+            Grow(cursor + pSize);
+        }
+
+        memcpy(buffer+cursor, pvBuffer, pSize);
+        cursor += pSize;
+
+        return pCount;
+    }
+
+};
+
+// ----------------------------------------------------------------------------------
+/** @class  AssbinExport
+ *  @brief  Assbin exporter class
+ *
+ *  This class performs the .assbin exporting, and is responsible for the file layout.
+ */
+class AssbinExport
+{
+private:
+    bool shortened;
+    bool compressed;
+
+protected:
+    // -----------------------------------------------------------------------------------
+    void WriteBinaryNode( IOStream * container, const aiNode* node)
+    {
+        AssbinChunkWriter chunk( container, ASSBIN_CHUNK_AINODE );
+
+        unsigned int nb_metadata = (node->mMetaData != NULL ? node->mMetaData->mNumProperties : 0);
+
+        Write<aiString>(&chunk,node->mName);
+        Write<aiMatrix4x4>(&chunk,node->mTransformation);
+        Write<unsigned int>(&chunk,node->mNumChildren);
+        Write<unsigned int>(&chunk,node->mNumMeshes);
+        Write<unsigned int>(&chunk,nb_metadata);
+
+        for (unsigned int i = 0; i < node->mNumMeshes;++i) {
+            Write<unsigned int>(&chunk,node->mMeshes[i]);
+        }
+
+        for (unsigned int i = 0; i < node->mNumChildren;++i) {
+            WriteBinaryNode( &chunk, node->mChildren[i] );
+        }
+
+        for (unsigned int i = 0; i < nb_metadata; ++i) {
+            const aiString& key = node->mMetaData->mKeys[i];
+            aiMetadataType type = node->mMetaData->mValues[i].mType;
+            void* value = node->mMetaData->mValues[i].mData;
+
+            Write<aiString>(&chunk, key);
+            Write<uint16_t>(&chunk, type);
+
+            switch (type) {
+                case AI_BOOL:
+                    Write<bool>(&chunk, *((bool*) value));
+                    break;
+                case AI_INT32:
+                    Write<int32_t>(&chunk, *((int32_t*) value));
+                    break;
+                case AI_UINT64:
+                    Write<uint64_t>(&chunk, *((uint64_t*) value));
+                    break;
+                case AI_FLOAT:
+                    Write<float>(&chunk, *((float*) value));
+                    break;
+                case AI_DOUBLE:
+                    Write<double>(&chunk, *((double*) value));
+                    break;
+                case AI_AISTRING:
+                    Write<aiString>(&chunk, *((aiString*) value));
+                    break;
+                case AI_AIVECTOR3D:
+                    Write<aiVector3D>(&chunk, *((aiVector3D*) value));
+                    break;
+#ifdef SWIG
+                case FORCE_32BIT:
+#endif // SWIG
+                default:
+                    break;
+            }
+        }
+    }
+
+    // -----------------------------------------------------------------------------------
+    void WriteBinaryTexture(IOStream * container, const aiTexture* tex)
+    {
+        AssbinChunkWriter chunk( container, ASSBIN_CHUNK_AITEXTURE );
+
+        Write<unsigned int>(&chunk,tex->mWidth);
+        Write<unsigned int>(&chunk,tex->mHeight);
+        chunk.Write( tex->achFormatHint, sizeof(char), 4 );
+
+        if(!shortened) {
+            if (!tex->mHeight) {
+                chunk.Write(tex->pcData,1,tex->mWidth);
+            }
+            else {
+                chunk.Write(tex->pcData,1,tex->mWidth*tex->mHeight*4);
+            }
+        }
+
+    }
+
+    // -----------------------------------------------------------------------------------
+    void WriteBinaryBone(IOStream * container, const aiBone* b)
+    {
+        AssbinChunkWriter chunk( container, ASSBIN_CHUNK_AIBONE );
+
+        Write<aiString>(&chunk,b->mName);
+        Write<unsigned int>(&chunk,b->mNumWeights);
+        Write<aiMatrix4x4>(&chunk,b->mOffsetMatrix);
+
+        // for the moment we write dumb min/max values for the bones, too.
+        // maybe I'll add a better, hash-like solution later
+        if (shortened) {
+            WriteBounds(&chunk,b->mWeights,b->mNumWeights);
+        } // else write as usual
+        else WriteArray<aiVertexWeight>(&chunk,b->mWeights,b->mNumWeights);
+    }
+
+    // -----------------------------------------------------------------------------------
+    void WriteBinaryMesh(IOStream * container, const aiMesh* mesh)
+    {
+        AssbinChunkWriter chunk( container, ASSBIN_CHUNK_AIMESH );
+
+        Write<unsigned int>(&chunk,mesh->mPrimitiveTypes);
+        Write<unsigned int>(&chunk,mesh->mNumVertices);
+        Write<unsigned int>(&chunk,mesh->mNumFaces);
+        Write<unsigned int>(&chunk,mesh->mNumBones);
+        Write<unsigned int>(&chunk,mesh->mMaterialIndex);
+
+        // first of all, write bits for all existent vertex components
+        unsigned int c = 0;
+        if (mesh->mVertices) {
+            c |= ASSBIN_MESH_HAS_POSITIONS;
+        }
+        if (mesh->mNormals) {
+            c |= ASSBIN_MESH_HAS_NORMALS;
+        }
+        if (mesh->mTangents && mesh->mBitangents) {
+            c |= ASSBIN_MESH_HAS_TANGENTS_AND_BITANGENTS;
+        }
+        for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_TEXTURECOORDS;++n) {
+            if (!mesh->mTextureCoords[n]) {
+                break;
+            }
+            c |= ASSBIN_MESH_HAS_TEXCOORD(n);
+        }
+        for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_COLOR_SETS;++n) {
+            if (!mesh->mColors[n]) {
+                break;
+            }
+            c |= ASSBIN_MESH_HAS_COLOR(n);
+        }
+        Write<unsigned int>(&chunk,c);
+
+        aiVector3D minVec, maxVec;
+        if (mesh->mVertices) {
+            if (shortened) {
+                WriteBounds(&chunk,mesh->mVertices,mesh->mNumVertices);
+            } // else write as usual
+            else WriteArray<aiVector3D>(&chunk,mesh->mVertices,mesh->mNumVertices);
+        }
+        if (mesh->mNormals) {
+            if (shortened) {
+                WriteBounds(&chunk,mesh->mNormals,mesh->mNumVertices);
+            } // else write as usual
+            else WriteArray<aiVector3D>(&chunk,mesh->mNormals,mesh->mNumVertices);
+        }
+        if (mesh->mTangents && mesh->mBitangents) {
+            if (shortened) {
+                WriteBounds(&chunk,mesh->mTangents,mesh->mNumVertices);
+                WriteBounds(&chunk,mesh->mBitangents,mesh->mNumVertices);
+            } // else write as usual
+            else {
+                WriteArray<aiVector3D>(&chunk,mesh->mTangents,mesh->mNumVertices);
+                WriteArray<aiVector3D>(&chunk,mesh->mBitangents,mesh->mNumVertices);
+            }
+        }
+        for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_COLOR_SETS;++n) {
+            if (!mesh->mColors[n])
+                break;
+
+            if (shortened) {
+                WriteBounds(&chunk,mesh->mColors[n],mesh->mNumVertices);
+            } // else write as usual
+            else WriteArray<aiColor4D>(&chunk,mesh->mColors[n],mesh->mNumVertices);
+        }
+        for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_TEXTURECOORDS;++n) {
+            if (!mesh->mTextureCoords[n])
+                break;
+
+            // write number of UV components
+            Write<unsigned int>(&chunk,mesh->mNumUVComponents[n]);
+
+            if (shortened) {
+                WriteBounds(&chunk,mesh->mTextureCoords[n],mesh->mNumVertices);
+            } // else write as usual
+            else WriteArray<aiVector3D>(&chunk,mesh->mTextureCoords[n],mesh->mNumVertices);
+        }
+
+        // write faces. There are no floating-point calculations involved
+        // in these, so we can write a simple hash over the face data
+        // to the dump file. We generate a single 32 Bit hash for 512 faces
+        // using Assimp's standard hashing function.
+        if (shortened) {
+            unsigned int processed = 0;
+            for (unsigned int job;(job = std::min(mesh->mNumFaces-processed,512u));processed += job) {
+
+                uint32_t hash = 0;
+                for (unsigned int a = 0; a < job;++a) {
+
+                    const aiFace& f = mesh->mFaces[processed+a];
+                    uint32_t tmp = f.mNumIndices;
+                    hash = SuperFastHash(reinterpret_cast<const char*>(&tmp),sizeof tmp,hash);
+                    for (unsigned int i = 0; i < f.mNumIndices; ++i) {
+                        static_assert(AI_MAX_VERTICES <= 0xffffffff, "AI_MAX_VERTICES <= 0xffffffff");
+                        tmp = static_cast<uint32_t>( f.mIndices[i] );
+                        hash = SuperFastHash(reinterpret_cast<const char*>(&tmp),sizeof tmp,hash);
+                    }
+                }
+                Write<unsigned int>(&chunk,hash);
+            }
+        }
+        else // else write as usual
+        {
+            // if there are less than 2^16 vertices, we can simply use 16 bit integers ...
+            for (unsigned int i = 0; i < mesh->mNumFaces;++i) {
+                const aiFace& f = mesh->mFaces[i];
+
+                static_assert(AI_MAX_FACE_INDICES <= 0xffff, "AI_MAX_FACE_INDICES <= 0xffff");
+                Write<uint16_t>(&chunk,f.mNumIndices);
+
+                for (unsigned int a = 0; a < f.mNumIndices;++a) {
+                    if (mesh->mNumVertices < (1u<<16)) {
+                        Write<uint16_t>(&chunk,f.mIndices[a]);
+                    }
+                    else Write<unsigned int>(&chunk,f.mIndices[a]);
+                }
+            }
+        }
+
+        // write bones
+        if (mesh->mNumBones) {
+            for (unsigned int a = 0; a < mesh->mNumBones;++a) {
+                const aiBone* b = mesh->mBones[a];
+                WriteBinaryBone(&chunk,b);
+            }
+        }
+    }
+
+    // -----------------------------------------------------------------------------------
+    void WriteBinaryMaterialProperty(IOStream * container, const aiMaterialProperty* prop)
+    {
+        AssbinChunkWriter chunk( container, ASSBIN_CHUNK_AIMATERIALPROPERTY );
+
+        Write<aiString>(&chunk,prop->mKey);
+        Write<unsigned int>(&chunk,prop->mSemantic);
+        Write<unsigned int>(&chunk,prop->mIndex);
+
+        Write<unsigned int>(&chunk,prop->mDataLength);
+        Write<unsigned int>(&chunk,(unsigned int)prop->mType);
+        chunk.Write(prop->mData,1,prop->mDataLength);
+    }
+
+    // -----------------------------------------------------------------------------------
+    void WriteBinaryMaterial(IOStream * container, const aiMaterial* mat)
+    {
+        AssbinChunkWriter chunk( container, ASSBIN_CHUNK_AIMATERIAL);
+
+        Write<unsigned int>(&chunk,mat->mNumProperties);
+        for (unsigned int i = 0; i < mat->mNumProperties;++i) {
+            WriteBinaryMaterialProperty( &chunk, mat->mProperties[i]);
+        }
+    }
+
+    // -----------------------------------------------------------------------------------
+    void WriteBinaryNodeAnim(IOStream * container, const aiNodeAnim* nd)
+    {
+        AssbinChunkWriter chunk( container, ASSBIN_CHUNK_AINODEANIM );
+
+        Write<aiString>(&chunk,nd->mNodeName);
+        Write<unsigned int>(&chunk,nd->mNumPositionKeys);
+        Write<unsigned int>(&chunk,nd->mNumRotationKeys);
+        Write<unsigned int>(&chunk,nd->mNumScalingKeys);
+        Write<unsigned int>(&chunk,nd->mPreState);
+        Write<unsigned int>(&chunk,nd->mPostState);
+
+        if (nd->mPositionKeys) {
+            if (shortened) {
+                WriteBounds(&chunk,nd->mPositionKeys,nd->mNumPositionKeys);
+
+            } // else write as usual
+            else WriteArray<aiVectorKey>(&chunk,nd->mPositionKeys,nd->mNumPositionKeys);
+        }
+        if (nd->mRotationKeys) {
+            if (shortened) {
+                WriteBounds(&chunk,nd->mRotationKeys,nd->mNumRotationKeys);
+
+            } // else write as usual
+            else WriteArray<aiQuatKey>(&chunk,nd->mRotationKeys,nd->mNumRotationKeys);
+        }
+        if (nd->mScalingKeys) {
+            if (shortened) {
+                WriteBounds(&chunk,nd->mScalingKeys,nd->mNumScalingKeys);
+
+            } // else write as usual
+            else WriteArray<aiVectorKey>(&chunk,nd->mScalingKeys,nd->mNumScalingKeys);
+        }
+    }
+
+
+    // -----------------------------------------------------------------------------------
+    void WriteBinaryAnim( IOStream * container, const aiAnimation* anim )
+    {
+        AssbinChunkWriter chunk( container, ASSBIN_CHUNK_AIANIMATION );
+
+        Write<aiString>(&chunk,anim->mName);
+        Write<double>(&chunk,anim->mDuration);
+        Write<double>(&chunk,anim->mTicksPerSecond);
+        Write<unsigned int>(&chunk,anim->mNumChannels);
+
+        for (unsigned int a = 0; a < anim->mNumChannels;++a) {
+            const aiNodeAnim* nd = anim->mChannels[a];
+            WriteBinaryNodeAnim(&chunk,nd);
+        }
+    }
+
+    // -----------------------------------------------------------------------------------
+    void WriteBinaryLight( IOStream * container, const aiLight* l )
+    {
+        AssbinChunkWriter chunk( container, ASSBIN_CHUNK_AILIGHT );
+
+        Write<aiString>(&chunk,l->mName);
+        Write<unsigned int>(&chunk,l->mType);
+
+        if (l->mType != aiLightSource_DIRECTIONAL) {
+            Write<float>(&chunk,l->mAttenuationConstant);
+            Write<float>(&chunk,l->mAttenuationLinear);
+            Write<float>(&chunk,l->mAttenuationQuadratic);
+        }
+
+        Write<aiColor3D>(&chunk,l->mColorDiffuse);
+        Write<aiColor3D>(&chunk,l->mColorSpecular);
+        Write<aiColor3D>(&chunk,l->mColorAmbient);
+
+        if (l->mType == aiLightSource_SPOT) {
+            Write<float>(&chunk,l->mAngleInnerCone);
+            Write<float>(&chunk,l->mAngleOuterCone);
+        }
+
+    }
+
+    // -----------------------------------------------------------------------------------
+    void WriteBinaryCamera( IOStream * container, const aiCamera* cam )
+    {
+        AssbinChunkWriter chunk( container, ASSBIN_CHUNK_AICAMERA );
+
+        Write<aiString>(&chunk,cam->mName);
+        Write<aiVector3D>(&chunk,cam->mPosition);
+        Write<aiVector3D>(&chunk,cam->mLookAt);
+        Write<aiVector3D>(&chunk,cam->mUp);
+        Write<float>(&chunk,cam->mHorizontalFOV);
+        Write<float>(&chunk,cam->mClipPlaneNear);
+        Write<float>(&chunk,cam->mClipPlaneFar);
+        Write<float>(&chunk,cam->mAspect);
+    }
+
+    // -----------------------------------------------------------------------------------
+    void WriteBinaryScene( IOStream * container, const aiScene* scene)
+    {
+        AssbinChunkWriter chunk( container, ASSBIN_CHUNK_AISCENE );
+
+        // basic scene information
+        Write<unsigned int>(&chunk,scene->mFlags);
+        Write<unsigned int>(&chunk,scene->mNumMeshes);
+        Write<unsigned int>(&chunk,scene->mNumMaterials);
+        Write<unsigned int>(&chunk,scene->mNumAnimations);
+        Write<unsigned int>(&chunk,scene->mNumTextures);
+        Write<unsigned int>(&chunk,scene->mNumLights);
+        Write<unsigned int>(&chunk,scene->mNumCameras);
+
+        // write node graph
+        WriteBinaryNode( &chunk, scene->mRootNode );
+
+        // write all meshes
+        for (unsigned int i = 0; i < scene->mNumMeshes;++i) {
+            const aiMesh* mesh = scene->mMeshes[i];
+            WriteBinaryMesh( &chunk,mesh);
+        }
+
+        // write materials
+        for (unsigned int i = 0; i< scene->mNumMaterials; ++i) {
+            const aiMaterial* mat = scene->mMaterials[i];
+            WriteBinaryMaterial(&chunk,mat);
+        }
+
+        // write all animations
+        for (unsigned int i = 0; i < scene->mNumAnimations;++i) {
+            const aiAnimation* anim = scene->mAnimations[i];
+            WriteBinaryAnim(&chunk,anim);
+        }
+
+
+        // write all textures
+        for (unsigned int i = 0; i < scene->mNumTextures;++i) {
+            const aiTexture* mesh = scene->mTextures[i];
+            WriteBinaryTexture(&chunk,mesh);
+        }
+
+        // write lights
+        for (unsigned int i = 0; i < scene->mNumLights;++i) {
+            const aiLight* l = scene->mLights[i];
+            WriteBinaryLight(&chunk,l);
+        }
+
+        // write cameras
+        for (unsigned int i = 0; i < scene->mNumCameras;++i) {
+            const aiCamera* cam = scene->mCameras[i];
+            WriteBinaryCamera(&chunk,cam);
+        }
+
+    }
+
+public:
+    AssbinExport()
+        : shortened(false), compressed(false) // temporary settings until properties are introduced for exporters
+    {
+    }
+
+    // -----------------------------------------------------------------------------------
+    // Write a binary model dump
+    void WriteBinaryDump(const char* pFile, IOSystem* pIOSystem, const aiScene* pScene)
+    {
+        IOStream * out = pIOSystem->Open( pFile, "wb" );
+        if (!out) return;
+
+        time_t tt = time(NULL);
+#if _WIN32
+        tm* p     = gmtime(&tt);
+#else
+        struct tm now;
+        tm* p = gmtime_r(&tt, &now);
+#endif
+
+        // header
+        char s[64];
+        memset( s, 0, 64 );
+#if _MSC_VER >= 1400
+        sprintf_s(s,"ASSIMP.binary-dump.%s",asctime(p));
+#else
+        ai_snprintf(s,64,"ASSIMP.binary-dump.%s",asctime(p));
+#endif
+        out->Write( s, 44, 1 );
+        // == 44 bytes
+
+        Write<unsigned int>( out, ASSBIN_VERSION_MAJOR );
+        Write<unsigned int>( out, ASSBIN_VERSION_MINOR );
+        Write<unsigned int>( out, aiGetVersionRevision() );
+        Write<unsigned int>( out, aiGetCompileFlags() );
+        Write<uint16_t>( out, shortened );
+        Write<uint16_t>( out, compressed );
+        // ==  20 bytes
+
+        char buff[256];
+        strncpy(buff,pFile,256);
+        out->Write(buff,sizeof(char),256);
+
+        char cmd[] = "\0";
+        strncpy(buff,cmd,128);
+        out->Write(buff,sizeof(char),128);
+
+        // leave 64 bytes free for future extensions
+        memset(buff,0xcd,64);
+        out->Write(buff,sizeof(char),64);
+        // == 435 bytes
+
+        // ==== total header size: 512 bytes
+        ai_assert( out->Tell() == ASSBIN_HEADER_LENGTH );
+
+        // Up to here the data is uncompressed. For compressed files, the rest
+        // is compressed using standard DEFLATE from zlib.
+        if (compressed)
+        {
+            AssbinChunkWriter uncompressedStream( NULL, 0 );
+            WriteBinaryScene( &uncompressedStream, pScene );
+
+            uLongf uncompressedSize = static_cast<uLongf>(uncompressedStream.Tell());
+            uLongf compressedSize = (uLongf)compressBound(uncompressedSize);
+            uint8_t* compressedBuffer = new uint8_t[ compressedSize ];
+
+            int res = compress2( compressedBuffer, &compressedSize, (const Bytef*)uncompressedStream.GetBufferPointer(), uncompressedSize, 9 );
+            if(res != Z_OK)
+            {
+                delete [] compressedBuffer;
+                pIOSystem->Close(out);
+                throw DeadlyExportError("Compression failed.");
+            }
+
+            out->Write( &uncompressedSize, sizeof(uint32_t), 1 );
+            out->Write( compressedBuffer, sizeof(char), compressedSize );
+
+            delete[] compressedBuffer;
+        }
+        else
+        {
+            WriteBinaryScene( out, pScene );
+        }
+
+        pIOSystem->Close( out );
+    }
+};
+
+void ExportSceneAssbin(const char* pFile, IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/) {
+    AssbinExport exporter;
+    exporter.WriteBinaryDump( pFile, pIOSystem, pScene );
+}
+} // end of namespace Assimp
+
+#endif // ASSIMP_BUILD_NO_ASSBIN_EXPORTER
+#endif // ASSIMP_BUILD_NO_EXPORT

+ 58 - 0
Engine/lib/assimp/code/AssbinExporter.h

@@ -0,0 +1,58 @@
+/*
+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 AssbinExporter.h
+ * ASSBIN Exporter Main Header
+ */
+#ifndef AI_ASSBINEXPORTER_H_INC
+#define AI_ASSBINEXPORTER_H_INC
+
+#include <assimp/defs.h>
+
+// nothing really needed here - reserved for future use like properties
+namespace Assimp {
+
+void ASSIMP_API ExportSceneAssbin(const char* pFile, IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/);
+
+}
+
+#endif // AI_ASSBINEXPORTER_H_INC

+ 733 - 0
Engine/lib/assimp/code/AssbinLoader.cpp

@@ -0,0 +1,733 @@
+/*
+---------------------------------------------------------------------------
+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  AssbinLoader.cpp
+ *  @brief Implementation of the .assbin importer class
+ *
+ *  see assbin_chunks.h
+ */
+
+#ifndef ASSIMP_BUILD_NO_ASSBIN_IMPORTER
+
+// internal headers
+#include "AssbinLoader.h"
+#include "assbin_chunks.h"
+#include <assimp/MemoryIOWrapper.h>
+#include <assimp/mesh.h>
+#include <assimp/anim.h>
+#include <assimp/scene.h>
+#include <assimp/importerdesc.h>
+#include <memory>
+
+#ifdef ASSIMP_BUILD_NO_OWN_ZLIB
+#   include <zlib.h>
+#else
+#   include <contrib/zlib/zlib.h>
+#endif
+
+using namespace Assimp;
+
+static const aiImporterDesc desc = {
+    ".assbin Importer",
+    "Gargaj / Conspiracy",
+    "",
+    "",
+    aiImporterFlags_SupportBinaryFlavour | aiImporterFlags_SupportCompressedFlavour,
+    0,
+    0,
+    0,
+    0,
+    "assbin"
+};
+
+// -----------------------------------------------------------------------------------
+const aiImporterDesc* AssbinImporter::GetInfo() const {
+    return &desc;
+}
+
+// -----------------------------------------------------------------------------------
+bool AssbinImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool /*checkSig*/ ) const {
+    IOStream * in = pIOHandler->Open(pFile);
+    if (nullptr == in) {
+        return false;
+    }
+
+    char s[32];
+    in->Read( s, sizeof(char), 32 );
+
+    pIOHandler->Close(in);
+
+    return strncmp( s, "ASSIMP.binary-dump.", 19 ) == 0;
+}
+
+// -----------------------------------------------------------------------------------
+template <typename T>
+T Read(IOStream * stream) {
+    T t;
+    size_t res = stream->Read( &t, sizeof(T), 1 );
+    if(res != 1)
+        throw DeadlyImportError("Unexpected EOF");
+    return t;
+}
+
+// -----------------------------------------------------------------------------------
+template <>
+aiVector3D Read<aiVector3D>(IOStream * stream) {
+    aiVector3D v;
+    v.x = Read<float>(stream);
+    v.y = Read<float>(stream);
+    v.z = Read<float>(stream);
+    return v;
+}
+
+// -----------------------------------------------------------------------------------
+template <>
+aiColor4D Read<aiColor4D>(IOStream * stream) {
+    aiColor4D c;
+    c.r = Read<float>(stream);
+    c.g = Read<float>(stream);
+    c.b = Read<float>(stream);
+    c.a = Read<float>(stream);
+    return c;
+}
+
+// -----------------------------------------------------------------------------------
+template <>
+aiQuaternion Read<aiQuaternion>(IOStream * stream) {
+    aiQuaternion v;
+    v.w = Read<float>(stream);
+    v.x = Read<float>(stream);
+    v.y = Read<float>(stream);
+    v.z = Read<float>(stream);
+    return v;
+}
+
+// -----------------------------------------------------------------------------------
+template <>
+aiString Read<aiString>(IOStream * stream) {
+    aiString s;
+    stream->Read(&s.length,4,1);
+    if(s.length)
+        stream->Read(s.data,s.length,1);
+    s.data[s.length] = 0;
+    return s;
+}
+
+// -----------------------------------------------------------------------------------
+template <>
+aiVertexWeight Read<aiVertexWeight>(IOStream * stream) {
+    aiVertexWeight w;
+    w.mVertexId = Read<unsigned int>(stream);
+    w.mWeight = Read<float>(stream);
+    return w;
+}
+
+// -----------------------------------------------------------------------------------
+template <>
+aiMatrix4x4 Read<aiMatrix4x4>(IOStream * stream) {
+    aiMatrix4x4 m;
+    for (unsigned int i = 0; i < 4;++i) {
+        for (unsigned int i2 = 0; i2 < 4;++i2) {
+            m[i][i2] = Read<float>(stream);
+        }
+    }
+    return m;
+}
+
+// -----------------------------------------------------------------------------------
+template <>
+aiVectorKey Read<aiVectorKey>(IOStream * stream) {
+    aiVectorKey v;
+    v.mTime = Read<double>(stream);
+    v.mValue = Read<aiVector3D>(stream);
+    return v;
+}
+
+// -----------------------------------------------------------------------------------
+template <>
+aiQuatKey Read<aiQuatKey>(IOStream * stream) {
+    aiQuatKey v;
+    v.mTime = Read<double>(stream);
+    v.mValue = Read<aiQuaternion>(stream);
+    return v;
+}
+
+// -----------------------------------------------------------------------------------
+template <typename T>
+void ReadArray( IOStream *stream, T * out, unsigned int size) {
+    ai_assert( nullptr != stream );
+    ai_assert( nullptr != out );
+
+    for (unsigned int i=0; i<size; i++) {
+        out[i] = Read<T>(stream);
+    }
+}
+
+// -----------------------------------------------------------------------------------
+template <typename T>
+void ReadBounds( IOStream * stream, T* /*p*/, unsigned int n ) {
+    // not sure what to do here, the data isn't really useful.
+    stream->Seek( sizeof(T) * n, aiOrigin_CUR );
+}
+
+// -----------------------------------------------------------------------------------
+void AssbinImporter::ReadBinaryNode( IOStream * stream, aiNode** onode, aiNode* parent ) {
+    if(Read<uint32_t>(stream) != ASSBIN_CHUNK_AINODE)
+        throw DeadlyImportError("Magic chunk identifiers are wrong!");
+    /*uint32_t size =*/ Read<uint32_t>(stream);
+
+    std::unique_ptr<aiNode> node(new aiNode());
+
+    node->mName = Read<aiString>(stream);
+    node->mTransformation = Read<aiMatrix4x4>(stream);
+    unsigned numChildren = Read<unsigned int>(stream);
+    unsigned numMeshes = Read<unsigned int>(stream);
+	unsigned int nb_metadata = Read<unsigned int>(stream);
+
+    if(parent) {
+        node->mParent = parent;
+    }
+
+    if (numMeshes)
+    {
+        node->mMeshes = new unsigned int[numMeshes];
+        for (unsigned int i = 0; i < numMeshes; ++i) {
+            node->mMeshes[i] = Read<unsigned int>(stream);
+            node->mNumMeshes++;
+        }
+    }
+
+    if (numChildren) {
+        node->mChildren = new aiNode*[numChildren];
+        for (unsigned int i = 0; i < numChildren; ++i) {
+            ReadBinaryNode( stream, &node->mChildren[i], node.get() );
+            node->mNumChildren++;
+        }
+    }
+
+    if ( nb_metadata > 0 ) {
+        node->mMetaData = aiMetadata::Alloc(nb_metadata);
+        for (unsigned int i = 0; i < nb_metadata; ++i) {
+            node->mMetaData->mKeys[i] = Read<aiString>(stream);
+            node->mMetaData->mValues[i].mType = (aiMetadataType) Read<uint16_t>(stream);
+            void* data = nullptr;
+
+            switch (node->mMetaData->mValues[i].mType) {
+                case AI_BOOL:
+                    data = new bool(Read<bool>(stream));
+                    break;
+                case AI_INT32:
+                    data = new int32_t(Read<int32_t>(stream));
+                    break;
+                case AI_UINT64:
+                    data = new uint64_t(Read<uint64_t>(stream));
+                    break;
+                case AI_FLOAT:
+                    data = new float(Read<float>(stream));
+                    break;
+                case AI_DOUBLE:
+                    data = new double(Read<double>(stream));
+                    break;
+                case AI_AISTRING:
+                    data = new aiString(Read<aiString>(stream));
+                    break;
+                case AI_AIVECTOR3D:
+                    data = new aiVector3D(Read<aiVector3D>(stream));
+                    break;
+#ifndef SWIG
+                case FORCE_32BIT:
+#endif // SWIG
+                default:
+                    break;
+            }
+
+			node->mMetaData->mValues[i].mData = data;
+		}
+	}
+    *onode = node.release();
+}
+
+// -----------------------------------------------------------------------------------
+void AssbinImporter::ReadBinaryBone( IOStream * stream, aiBone* b ) {
+    if(Read<uint32_t>(stream) != ASSBIN_CHUNK_AIBONE)
+        throw DeadlyImportError("Magic chunk identifiers are wrong!");
+    /*uint32_t size =*/ Read<uint32_t>(stream);
+
+    b->mName = Read<aiString>(stream);
+    b->mNumWeights = Read<unsigned int>(stream);
+    b->mOffsetMatrix = Read<aiMatrix4x4>(stream);
+
+    // for the moment we write dumb min/max values for the bones, too.
+    // maybe I'll add a better, hash-like solution later
+    if (shortened) {
+        ReadBounds(stream,b->mWeights,b->mNumWeights);
+    } else {
+        // else write as usual
+        b->mWeights = new aiVertexWeight[b->mNumWeights];
+        ReadArray<aiVertexWeight>(stream,b->mWeights,b->mNumWeights);
+    }
+}
+
+// -----------------------------------------------------------------------------------
+static bool fitsIntoUI16(unsigned int mNumVertices) {
+    return ( mNumVertices < (1u<<16) );
+}
+// -----------------------------------------------------------------------------------
+void AssbinImporter::ReadBinaryMesh( IOStream * stream, aiMesh* mesh ) {
+    if(Read<uint32_t>(stream) != ASSBIN_CHUNK_AIMESH)
+        throw DeadlyImportError("Magic chunk identifiers are wrong!");
+    /*uint32_t size =*/ Read<uint32_t>(stream);
+
+    mesh->mPrimitiveTypes = Read<unsigned int>(stream);
+    mesh->mNumVertices = Read<unsigned int>(stream);
+    mesh->mNumFaces = Read<unsigned int>(stream);
+    mesh->mNumBones = Read<unsigned int>(stream);
+    mesh->mMaterialIndex = Read<unsigned int>(stream);
+
+    // first of all, write bits for all existent vertex components
+    unsigned int c = Read<unsigned int>(stream);
+
+    if (c & ASSBIN_MESH_HAS_POSITIONS) {
+        if (shortened) {
+            ReadBounds(stream,mesh->mVertices,mesh->mNumVertices);
+        }  else {
+            // else write as usual
+            mesh->mVertices = new aiVector3D[mesh->mNumVertices];
+            ReadArray<aiVector3D>(stream,mesh->mVertices,mesh->mNumVertices);
+        }
+    }
+    if (c & ASSBIN_MESH_HAS_NORMALS) {
+        if (shortened) {
+            ReadBounds(stream,mesh->mNormals,mesh->mNumVertices);
+        }  else {
+            // else write as usual
+            mesh->mNormals = new aiVector3D[mesh->mNumVertices];
+            ReadArray<aiVector3D>(stream,mesh->mNormals,mesh->mNumVertices);
+        }
+    }
+    if (c & ASSBIN_MESH_HAS_TANGENTS_AND_BITANGENTS) {
+        if (shortened) {
+            ReadBounds(stream,mesh->mTangents,mesh->mNumVertices);
+            ReadBounds(stream,mesh->mBitangents,mesh->mNumVertices);
+        }  else {
+            // else write as usual
+            mesh->mTangents = new aiVector3D[mesh->mNumVertices];
+            ReadArray<aiVector3D>(stream,mesh->mTangents,mesh->mNumVertices);
+            mesh->mBitangents = new aiVector3D[mesh->mNumVertices];
+            ReadArray<aiVector3D>(stream,mesh->mBitangents,mesh->mNumVertices);
+        }
+    }
+    for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_COLOR_SETS;++n) {
+        if (!(c & ASSBIN_MESH_HAS_COLOR(n))) {
+            break;
+        }
+
+        if (shortened) {
+            ReadBounds(stream,mesh->mColors[n],mesh->mNumVertices);
+        }  else {
+            // else write as usual
+            mesh->mColors[n] = new aiColor4D[mesh->mNumVertices];
+            ReadArray<aiColor4D>(stream,mesh->mColors[n],mesh->mNumVertices);
+        }
+    }
+    for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_TEXTURECOORDS;++n) {
+        if (!(c & ASSBIN_MESH_HAS_TEXCOORD(n))) {
+            break;
+        }
+
+        // write number of UV components
+        mesh->mNumUVComponents[n] = Read<unsigned int>(stream);
+
+        if (shortened) {
+            ReadBounds(stream,mesh->mTextureCoords[n],mesh->mNumVertices);
+        }  else {
+            // else write as usual
+            mesh->mTextureCoords[n] = new aiVector3D[mesh->mNumVertices];
+            ReadArray<aiVector3D>(stream,mesh->mTextureCoords[n],mesh->mNumVertices);
+        }
+    }
+
+    // write faces. There are no floating-point calculations involved
+    // in these, so we can write a simple hash over the face data
+    // to the dump file. We generate a single 32 Bit hash for 512 faces
+    // using Assimp's standard hashing function.
+    if (shortened) {
+        Read<unsigned int>(stream);
+    } else  {
+        // else write as usual
+        // if there are less than 2^16 vertices, we can simply use 16 bit integers ...
+        mesh->mFaces = new aiFace[mesh->mNumFaces];
+        for (unsigned int i = 0; i < mesh->mNumFaces;++i) {
+            aiFace& f = mesh->mFaces[i];
+
+            static_assert(AI_MAX_FACE_INDICES <= 0xffff, "AI_MAX_FACE_INDICES <= 0xffff");
+            f.mNumIndices = Read<uint16_t>(stream);
+            f.mIndices = new unsigned int[f.mNumIndices];
+
+            for (unsigned int a = 0; a < f.mNumIndices;++a) {
+                // Check if unsigned  short ( 16 bit  ) are big enought for the indices
+                if ( fitsIntoUI16( mesh->mNumVertices ) ) {
+                    f.mIndices[a] = Read<uint16_t>(stream);
+                } else {
+                    f.mIndices[a] = Read<unsigned int>(stream);
+                }
+            }
+        }
+    }
+
+    // write bones
+    if (mesh->mNumBones) {
+        mesh->mBones = new C_STRUCT aiBone*[mesh->mNumBones];
+        for (unsigned int a = 0; a < mesh->mNumBones;++a) {
+            mesh->mBones[a] = new aiBone();
+            ReadBinaryBone(stream,mesh->mBones[a]);
+        }
+    }
+}
+
+// -----------------------------------------------------------------------------------
+void AssbinImporter::ReadBinaryMaterialProperty(IOStream * stream, aiMaterialProperty* prop) {
+    if(Read<uint32_t>(stream) != ASSBIN_CHUNK_AIMATERIALPROPERTY)
+        throw DeadlyImportError("Magic chunk identifiers are wrong!");
+    /*uint32_t size =*/ Read<uint32_t>(stream);
+
+    prop->mKey = Read<aiString>(stream);
+    prop->mSemantic = Read<unsigned int>(stream);
+    prop->mIndex = Read<unsigned int>(stream);
+
+    prop->mDataLength = Read<unsigned int>(stream);
+    prop->mType = (aiPropertyTypeInfo)Read<unsigned int>(stream);
+    prop->mData = new char [ prop->mDataLength ];
+    stream->Read(prop->mData,1,prop->mDataLength);
+}
+
+// -----------------------------------------------------------------------------------
+void AssbinImporter::ReadBinaryMaterial(IOStream * stream, aiMaterial* mat) {
+    if(Read<uint32_t>(stream) != ASSBIN_CHUNK_AIMATERIAL)
+        throw DeadlyImportError("Magic chunk identifiers are wrong!");
+    /*uint32_t size =*/ Read<uint32_t>(stream);
+
+    mat->mNumAllocated = mat->mNumProperties = Read<unsigned int>(stream);
+    if (mat->mNumProperties)
+    {
+        if (mat->mProperties)
+        {
+            delete[] mat->mProperties;
+        }
+        mat->mProperties = new aiMaterialProperty*[mat->mNumProperties];
+        for (unsigned int i = 0; i < mat->mNumProperties;++i) {
+            mat->mProperties[i] = new aiMaterialProperty();
+            ReadBinaryMaterialProperty( stream, mat->mProperties[i]);
+        }
+    }
+}
+
+// -----------------------------------------------------------------------------------
+void AssbinImporter::ReadBinaryNodeAnim(IOStream * stream, aiNodeAnim* nd) {
+    if(Read<uint32_t>(stream) != ASSBIN_CHUNK_AINODEANIM)
+        throw DeadlyImportError("Magic chunk identifiers are wrong!");
+    /*uint32_t size =*/ Read<uint32_t>(stream);
+
+    nd->mNodeName = Read<aiString>(stream);
+    nd->mNumPositionKeys = Read<unsigned int>(stream);
+    nd->mNumRotationKeys = Read<unsigned int>(stream);
+    nd->mNumScalingKeys = Read<unsigned int>(stream);
+    nd->mPreState = (aiAnimBehaviour)Read<unsigned int>(stream);
+    nd->mPostState = (aiAnimBehaviour)Read<unsigned int>(stream);
+
+    if (nd->mNumPositionKeys) {
+        if (shortened) {
+            ReadBounds(stream,nd->mPositionKeys,nd->mNumPositionKeys);
+
+        } // else write as usual
+        else {
+            nd->mPositionKeys = new aiVectorKey[nd->mNumPositionKeys];
+            ReadArray<aiVectorKey>(stream,nd->mPositionKeys,nd->mNumPositionKeys);
+        }
+    }
+    if (nd->mNumRotationKeys) {
+        if (shortened) {
+            ReadBounds(stream,nd->mRotationKeys,nd->mNumRotationKeys);
+
+        }  else {
+            // else write as usual
+            nd->mRotationKeys = new aiQuatKey[nd->mNumRotationKeys];
+            ReadArray<aiQuatKey>(stream,nd->mRotationKeys,nd->mNumRotationKeys);
+        }
+    }
+    if (nd->mNumScalingKeys) {
+        if (shortened) {
+            ReadBounds(stream,nd->mScalingKeys,nd->mNumScalingKeys);
+
+        }  else {
+            // else write as usual
+            nd->mScalingKeys = new aiVectorKey[nd->mNumScalingKeys];
+            ReadArray<aiVectorKey>(stream,nd->mScalingKeys,nd->mNumScalingKeys);
+        }
+    }
+}
+
+// -----------------------------------------------------------------------------------
+void AssbinImporter::ReadBinaryAnim( IOStream * stream, aiAnimation* anim ) {
+    if(Read<uint32_t>(stream) != ASSBIN_CHUNK_AIANIMATION)
+        throw DeadlyImportError("Magic chunk identifiers are wrong!");
+    /*uint32_t size =*/ Read<uint32_t>(stream);
+
+    anim->mName = Read<aiString> (stream);
+    anim->mDuration = Read<double> (stream);
+    anim->mTicksPerSecond = Read<double> (stream);
+    anim->mNumChannels = Read<unsigned int>(stream);
+
+    if (anim->mNumChannels) {
+        anim->mChannels = new aiNodeAnim*[ anim->mNumChannels ];
+        for (unsigned int a = 0; a < anim->mNumChannels;++a) {
+            anim->mChannels[a] = new aiNodeAnim();
+            ReadBinaryNodeAnim(stream,anim->mChannels[a]);
+        }
+    }
+}
+
+// -----------------------------------------------------------------------------------
+void AssbinImporter::ReadBinaryTexture(IOStream * stream, aiTexture* tex) {
+    if(Read<uint32_t>(stream) != ASSBIN_CHUNK_AITEXTURE)
+        throw DeadlyImportError("Magic chunk identifiers are wrong!");
+    /*uint32_t size =*/ Read<uint32_t>(stream);
+
+    tex->mWidth = Read<unsigned int>(stream);
+    tex->mHeight = Read<unsigned int>(stream);
+    stream->Read( tex->achFormatHint, sizeof(char), 4 );
+
+    if(!shortened) {
+        if (!tex->mHeight) {
+            tex->pcData = new aiTexel[ tex->mWidth ];
+            stream->Read(tex->pcData,1,tex->mWidth);
+        } else {
+            tex->pcData = new aiTexel[ tex->mWidth*tex->mHeight ];
+            stream->Read(tex->pcData,1,tex->mWidth*tex->mHeight*4);
+        }
+    }
+}
+
+// -----------------------------------------------------------------------------------
+void AssbinImporter::ReadBinaryLight( IOStream * stream, aiLight* l ) {
+    if(Read<uint32_t>(stream) != ASSBIN_CHUNK_AILIGHT)
+        throw DeadlyImportError("Magic chunk identifiers are wrong!");
+    /*uint32_t size =*/ Read<uint32_t>(stream);
+
+    l->mName = Read<aiString>(stream);
+    l->mType = (aiLightSourceType)Read<unsigned int>(stream);
+
+    if (l->mType != aiLightSource_DIRECTIONAL) {
+        l->mAttenuationConstant = Read<float>(stream);
+        l->mAttenuationLinear = Read<float>(stream);
+        l->mAttenuationQuadratic = Read<float>(stream);
+    }
+
+    l->mColorDiffuse = Read<aiColor3D>(stream);
+    l->mColorSpecular = Read<aiColor3D>(stream);
+    l->mColorAmbient = Read<aiColor3D>(stream);
+
+    if (l->mType == aiLightSource_SPOT) {
+        l->mAngleInnerCone = Read<float>(stream);
+        l->mAngleOuterCone = Read<float>(stream);
+    }
+}
+
+// -----------------------------------------------------------------------------------
+void AssbinImporter::ReadBinaryCamera( IOStream * stream, aiCamera* cam ) {
+    if(Read<uint32_t>(stream) != ASSBIN_CHUNK_AICAMERA)
+        throw DeadlyImportError("Magic chunk identifiers are wrong!");
+    /*uint32_t size =*/ Read<uint32_t>(stream);
+
+    cam->mName = Read<aiString>(stream);
+    cam->mPosition = Read<aiVector3D>(stream);
+    cam->mLookAt = Read<aiVector3D>(stream);
+    cam->mUp = Read<aiVector3D>(stream);
+    cam->mHorizontalFOV = Read<float>(stream);
+    cam->mClipPlaneNear = Read<float>(stream);
+    cam->mClipPlaneFar = Read<float>(stream);
+    cam->mAspect = Read<float>(stream);
+}
+
+// -----------------------------------------------------------------------------------
+void AssbinImporter::ReadBinaryScene( IOStream * stream, aiScene* scene ) {
+    if(Read<uint32_t>(stream) != ASSBIN_CHUNK_AISCENE)
+        throw DeadlyImportError("Magic chunk identifiers are wrong!");
+    /*uint32_t size =*/ Read<uint32_t>(stream);
+
+    scene->mFlags         = Read<unsigned int>(stream);
+    scene->mNumMeshes     = Read<unsigned int>(stream);
+    scene->mNumMaterials  = Read<unsigned int>(stream);
+    scene->mNumAnimations = Read<unsigned int>(stream);
+    scene->mNumTextures   = Read<unsigned int>(stream);
+    scene->mNumLights     = Read<unsigned int>(stream);
+    scene->mNumCameras    = Read<unsigned int>(stream);
+
+    // Read node graph
+    //scene->mRootNode = new aiNode[1];
+    ReadBinaryNode( stream, &scene->mRootNode, (aiNode*)NULL );
+
+    // Read all meshes
+    if (scene->mNumMeshes) {
+        scene->mMeshes = new aiMesh*[scene->mNumMeshes];
+        memset(scene->mMeshes, 0, scene->mNumMeshes*sizeof(aiMesh*));
+        for (unsigned int i = 0; i < scene->mNumMeshes;++i) {
+            scene->mMeshes[i] = new aiMesh();
+            ReadBinaryMesh( stream,scene->mMeshes[i]);
+        }
+    }
+
+    // Read materials
+    if (scene->mNumMaterials) {
+        scene->mMaterials = new aiMaterial*[scene->mNumMaterials];
+        memset(scene->mMaterials, 0, scene->mNumMaterials*sizeof(aiMaterial*));
+        for (unsigned int i = 0; i< scene->mNumMaterials; ++i) {
+            scene->mMaterials[i] = new aiMaterial();
+            ReadBinaryMaterial(stream,scene->mMaterials[i]);
+        }
+    }
+
+    // Read all animations
+    if (scene->mNumAnimations) {
+        scene->mAnimations = new aiAnimation*[scene->mNumAnimations];
+        memset(scene->mAnimations, 0, scene->mNumAnimations*sizeof(aiAnimation*));
+        for (unsigned int i = 0; i < scene->mNumAnimations;++i) {
+            scene->mAnimations[i] = new aiAnimation();
+            ReadBinaryAnim(stream,scene->mAnimations[i]);
+        }
+    }
+
+    // Read all textures
+    if (scene->mNumTextures) {
+        scene->mTextures = new aiTexture*[scene->mNumTextures];
+        memset(scene->mTextures, 0, scene->mNumTextures*sizeof(aiTexture*));
+        for (unsigned int i = 0; i < scene->mNumTextures;++i) {
+            scene->mTextures[i] = new aiTexture();
+            ReadBinaryTexture(stream,scene->mTextures[i]);
+        }
+    }
+
+    // Read lights
+    if (scene->mNumLights) {
+        scene->mLights = new aiLight*[scene->mNumLights];
+        memset(scene->mLights, 0, scene->mNumLights*sizeof(aiLight*));
+        for (unsigned int i = 0; i < scene->mNumLights;++i) {
+            scene->mLights[i] = new aiLight();
+            ReadBinaryLight(stream,scene->mLights[i]);
+        }
+    }
+
+    // Read cameras
+    if (scene->mNumCameras) {
+        scene->mCameras = new aiCamera*[scene->mNumCameras];
+        memset(scene->mCameras, 0, scene->mNumCameras*sizeof(aiCamera*));
+        for (unsigned int i = 0; i < scene->mNumCameras;++i) {
+            scene->mCameras[i] = new aiCamera();
+            ReadBinaryCamera(stream,scene->mCameras[i]);
+        }
+    }
+
+}
+
+// -----------------------------------------------------------------------------------
+void AssbinImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler ) {
+    IOStream * stream = pIOHandler->Open(pFile,"rb");
+    if (nullptr == stream) {
+        return;
+    }
+
+    // signature
+    stream->Seek( 44, aiOrigin_CUR );
+
+    unsigned int versionMajor = Read<unsigned int>(stream);
+    unsigned int versionMinor = Read<unsigned int>(stream);
+    if (versionMinor != ASSBIN_VERSION_MINOR || versionMajor != ASSBIN_VERSION_MAJOR) {
+        throw DeadlyImportError( "Invalid version, data format not compatible!" );
+    }
+
+    /*unsigned int versionRevision =*/ Read<unsigned int>(stream);
+    /*unsigned int compileFlags =*/ Read<unsigned int>(stream);
+
+    shortened = Read<uint16_t>(stream) > 0;
+    compressed = Read<uint16_t>(stream) > 0;
+
+    if (shortened)
+        throw DeadlyImportError( "Shortened binaries are not supported!" );
+
+    stream->Seek( 256, aiOrigin_CUR ); // original filename
+    stream->Seek( 128, aiOrigin_CUR ); // options
+    stream->Seek( 64, aiOrigin_CUR ); // padding
+
+    if (compressed) {
+        uLongf uncompressedSize = Read<uint32_t>(stream);
+        uLongf compressedSize = static_cast<uLongf>(stream->FileSize() - stream->Tell());
+
+        unsigned char * compressedData = new unsigned char[ compressedSize ];
+        size_t len = stream->Read( compressedData, 1, compressedSize );
+        ai_assert(len == compressedSize);
+
+        unsigned char * uncompressedData = new unsigned char[ uncompressedSize ];
+
+        int res = uncompress( uncompressedData, &uncompressedSize, compressedData, len );
+        if(res != Z_OK)
+        {
+            delete [] uncompressedData;
+            delete [] compressedData;
+            pIOHandler->Close(stream);
+            throw DeadlyImportError("Zlib decompression failed.");
+        }
+
+        MemoryIOStream io( uncompressedData, uncompressedSize );
+
+        ReadBinaryScene(&io,pScene);
+
+        delete[] uncompressedData;
+        delete[] compressedData;
+    } else {
+        ReadBinaryScene(stream,pScene);
+    }
+
+    pIOHandler->Close(stream);
+}
+
+#endif // !! ASSIMP_BUILD_NO_ASSBIN_IMPORTER

+ 106 - 0
Engine/lib/assimp/code/AssbinLoader.h

@@ -0,0 +1,106 @@
+
+/*
+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  AssbinLoader.h
+ *  @brief .assbin File format loader
+ */
+#ifndef AI_ASSBINIMPORTER_H_INC
+#define AI_ASSBINIMPORTER_H_INC
+
+#include <assimp/BaseImporter.h>
+
+struct aiMesh;
+struct aiNode;
+struct aiBone;
+struct aiMaterial;
+struct aiMaterialProperty;
+struct aiNodeAnim;
+struct aiAnimation;
+struct aiTexture;
+struct aiLight;
+struct aiCamera;
+
+#ifndef ASSIMP_BUILD_NO_ASSBIN_IMPORTER
+
+namespace Assimp    {
+
+// ---------------------------------------------------------------------------------
+/** Importer class for 3D Studio r3 and r4 3DS files
+ */
+class AssbinImporter : public BaseImporter
+{
+private:
+    bool shortened;
+    bool compressed;
+
+public:
+    virtual bool CanRead(
+        const std::string& pFile,
+        IOSystem* pIOHandler,
+        bool checkSig
+    ) const;
+    virtual const aiImporterDesc* GetInfo() const;
+    virtual void InternReadFile(
+    const std::string& pFile,
+        aiScene* pScene,
+        IOSystem* pIOHandler
+    );
+    void ReadHeader();
+    void ReadBinaryScene( IOStream * stream, aiScene* pScene );
+    void ReadBinaryNode( IOStream * stream, aiNode** mRootNode, aiNode* parent );
+    void ReadBinaryMesh( IOStream * stream, aiMesh* mesh );
+    void ReadBinaryBone( IOStream * stream, aiBone* bone );
+    void ReadBinaryMaterial(IOStream * stream, aiMaterial* mat);
+    void ReadBinaryMaterialProperty(IOStream * stream, aiMaterialProperty* prop);
+    void ReadBinaryNodeAnim(IOStream * stream, aiNodeAnim* nd);
+    void ReadBinaryAnim( IOStream * stream, aiAnimation* anim );
+    void ReadBinaryTexture(IOStream * stream, aiTexture* tex);
+    void ReadBinaryLight( IOStream * stream, aiLight* l );
+    void ReadBinaryCamera( IOStream * stream, aiCamera* cam );
+};
+
+} // end of namespace Assimp
+
+#endif // !! ASSIMP_BUILD_NO_ASSBIN_IMPORTER
+
+#endif // AI_ASSBINIMPORTER_H_INC

+ 695 - 0
Engine/lib/assimp/code/Assimp.cpp

@@ -0,0 +1,695 @@
+/*
+---------------------------------------------------------------------------
+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  Assimp.cpp
+ *  @brief Implementation of the Plain-C API
+ */
+
+#include <assimp/cimport.h>
+#include <assimp/LogStream.hpp>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/Importer.hpp>
+#include <assimp/importerdesc.h>
+#include <assimp/scene.h>
+#include <assimp/GenericProperty.h>
+#include <assimp/Exceptional.h>
+#include <assimp/BaseImporter.h>
+
+#include "CInterfaceIOWrapper.h"
+#include "Importer.h"
+#include "ScenePrivate.h"
+
+#include <list>
+
+// ------------------------------------------------------------------------------------------------
+#ifndef ASSIMP_BUILD_SINGLETHREADED
+#   include <thread>
+#   include <mutex>
+#endif
+// ------------------------------------------------------------------------------------------------
+using namespace Assimp;
+
+namespace Assimp {
+    // underlying structure for aiPropertyStore
+    typedef BatchLoader::PropertyMap PropertyMap;
+
+    /** Stores the LogStream objects for all active C log streams */
+    struct mpred {
+        bool operator  () (const aiLogStream& s0, const aiLogStream& s1) const  {
+            return s0.callback<s1.callback&&s0.user<s1.user;
+        }
+    };
+    typedef std::map<aiLogStream, Assimp::LogStream*, mpred> LogStreamMap;
+
+    /** Stores the LogStream objects allocated by #aiGetPredefinedLogStream */
+    typedef std::list<Assimp::LogStream*> PredefLogStreamMap;
+
+    /** Local storage of all active log streams */
+    static LogStreamMap gActiveLogStreams;
+
+    /** Local storage of LogStreams allocated by #aiGetPredefinedLogStream */
+    static PredefLogStreamMap gPredefinedStreams;
+
+    /** Error message of the last failed import process */
+    static std::string gLastErrorString;
+
+    /** Verbose logging active or not? */
+    static aiBool gVerboseLogging = false;
+
+    /** will return all registered importers. */
+    void GetImporterInstanceList(std::vector< BaseImporter* >& out);
+
+    /** will delete all registered importers. */
+    void DeleteImporterInstanceList(std::vector< BaseImporter* >& out);
+} // namespace assimp
+
+
+#ifndef ASSIMP_BUILD_SINGLETHREADED
+/** Global mutex to manage the access to the log-stream map */
+static std::mutex gLogStreamMutex;
+#endif
+
+// ------------------------------------------------------------------------------------------------
+// Custom LogStream implementation for the C-API
+class LogToCallbackRedirector : public LogStream {
+public:
+    explicit LogToCallbackRedirector(const aiLogStream& s)
+    : stream (s)    {
+        ai_assert(NULL != s.callback);
+    }
+
+    ~LogToCallbackRedirector()  {
+#ifndef ASSIMP_BUILD_SINGLETHREADED
+        std::lock_guard<std::mutex> lock(gLogStreamMutex);
+#endif
+        // (HACK) Check whether the 'stream.user' pointer points to a
+        // custom LogStream allocated by #aiGetPredefinedLogStream.
+        // In this case, we need to delete it, too. Of course, this
+        // might cause strange problems, but the chance is quite low.
+
+        PredefLogStreamMap::iterator it = std::find(gPredefinedStreams.begin(),
+            gPredefinedStreams.end(), (Assimp::LogStream*)stream.user);
+
+        if (it != gPredefinedStreams.end()) {
+            delete *it;
+            gPredefinedStreams.erase(it);
+        }
+    }
+
+    /** @copydoc LogStream::write */
+    void write(const char* message) {
+        stream.callback(message,stream.user);
+    }
+
+private:
+    aiLogStream stream;
+};
+
+// ------------------------------------------------------------------------------------------------
+void ReportSceneNotFoundError() {
+    ASSIMP_LOG_ERROR("Unable to find the Assimp::Importer for this aiScene. "
+        "The C-API does not accept scenes produced by the C++ API and vice versa");
+
+    ai_assert(false);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the given file and returns its content.
+const aiScene* aiImportFile( const char* pFile, unsigned int pFlags) {
+    return aiImportFileEx(pFile,pFlags,NULL);
+}
+
+// ------------------------------------------------------------------------------------------------
+const aiScene* aiImportFileEx( const char* pFile, unsigned int pFlags,  aiFileIO* pFS) {
+    return aiImportFileExWithProperties(pFile, pFlags, pFS, NULL);
+}
+
+// ------------------------------------------------------------------------------------------------
+const aiScene* aiImportFileExWithProperties( const char* pFile, unsigned int pFlags, 
+        aiFileIO* pFS, const aiPropertyStore* props) {
+    ai_assert(NULL != pFile);
+
+    const aiScene* scene = NULL;
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+
+    // create an Importer for this file
+    Assimp::Importer* imp = new Assimp::Importer();
+
+    // copy properties
+    if(props) {
+        const PropertyMap* pp = reinterpret_cast<const PropertyMap*>(props);
+        ImporterPimpl* pimpl = imp->Pimpl();
+        pimpl->mIntProperties = pp->ints;
+        pimpl->mFloatProperties = pp->floats;
+        pimpl->mStringProperties = pp->strings;
+        pimpl->mMatrixProperties = pp->matrices;
+    }
+    // setup a custom IO system if necessary
+    if (pFS) {
+        imp->SetIOHandler( new CIOSystemWrapper (pFS) );
+    }
+
+    // and have it read the file
+    scene = imp->ReadFile( pFile, pFlags);
+
+    // if succeeded, store the importer in the scene and keep it alive
+    if( scene)  {
+        ScenePrivateData* priv = const_cast<ScenePrivateData*>( ScenePriv(scene) );
+        priv->mOrigImporter = imp;
+    } else {
+        // if failed, extract error code and destroy the import
+        gLastErrorString = imp->GetErrorString();
+        delete imp;
+    }
+
+    // return imported data. If the import failed the pointer is NULL anyways
+    ASSIMP_END_EXCEPTION_REGION(const aiScene*);
+    
+    return scene;
+}
+
+// ------------------------------------------------------------------------------------------------
+const aiScene* aiImportFileFromMemory(
+    const char* pBuffer,
+    unsigned int pLength,
+    unsigned int pFlags,
+    const char* pHint)
+{
+    return aiImportFileFromMemoryWithProperties(pBuffer, pLength, pFlags, pHint, NULL);
+}
+
+// ------------------------------------------------------------------------------------------------
+const aiScene* aiImportFileFromMemoryWithProperties(
+    const char* pBuffer,
+    unsigned int pLength,
+    unsigned int pFlags,
+    const char* pHint,
+    const aiPropertyStore* props)
+{
+    ai_assert( NULL != pBuffer );
+    ai_assert( 0 != pLength );
+
+    const aiScene* scene = NULL;
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+
+    // create an Importer for this file
+    Assimp::Importer* imp = new Assimp::Importer();
+
+    // copy properties
+    if(props) {
+        const PropertyMap* pp = reinterpret_cast<const PropertyMap*>(props);
+        ImporterPimpl* pimpl = imp->Pimpl();
+        pimpl->mIntProperties = pp->ints;
+        pimpl->mFloatProperties = pp->floats;
+        pimpl->mStringProperties = pp->strings;
+        pimpl->mMatrixProperties = pp->matrices;
+    }
+
+    // and have it read the file from the memory buffer
+    scene = imp->ReadFileFromMemory( pBuffer, pLength, pFlags,pHint);
+
+    // if succeeded, store the importer in the scene and keep it alive
+    if( scene)  {
+         ScenePrivateData* priv = const_cast<ScenePrivateData*>( ScenePriv(scene) );
+         priv->mOrigImporter = imp;
+    }
+    else    {
+        // if failed, extract error code and destroy the import
+        gLastErrorString = imp->GetErrorString();
+        delete imp;
+    }
+    // return imported data. If the import failed the pointer is NULL anyways
+    ASSIMP_END_EXCEPTION_REGION(const aiScene*);
+    return scene;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Releases all resources associated with the given import process.
+void aiReleaseImport( const aiScene* pScene)
+{
+    if (!pScene) {
+        return;
+    }
+
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+
+    // find the importer associated with this data
+    const ScenePrivateData* priv = ScenePriv(pScene);
+    if( !priv || !priv->mOrigImporter)  {
+        delete pScene;
+    }
+    else {
+        // deleting the Importer also deletes the scene
+        // Note: the reason that this is not written as 'delete priv->mOrigImporter'
+        // is a suspected bug in gcc 4.4+ (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=52339)
+        Importer* importer = priv->mOrigImporter;
+        delete importer;
+    }
+
+    ASSIMP_END_EXCEPTION_REGION(void);
+}
+
+// ------------------------------------------------------------------------------------------------
+ASSIMP_API const aiScene* aiApplyPostProcessing(const aiScene* pScene,
+    unsigned int pFlags)
+{
+    const aiScene* sc = NULL;
+
+
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+
+    // find the importer associated with this data
+    const ScenePrivateData* priv = ScenePriv(pScene);
+    if( !priv || !priv->mOrigImporter)  {
+        ReportSceneNotFoundError();
+        return NULL;
+    }
+
+    sc = priv->mOrigImporter->ApplyPostProcessing(pFlags);
+
+    if (!sc) {
+        aiReleaseImport(pScene);
+        return NULL;
+    }
+
+    ASSIMP_END_EXCEPTION_REGION(const aiScene*);
+    return sc;
+}
+
+// ------------------------------------------------------------------------------------------------
+ASSIMP_API const aiScene *aiApplyCustomizedPostProcessing( const aiScene *scene,
+                                                           BaseProcess* process,
+                                                           bool requestValidation ) {
+    const aiScene* sc( NULL );
+
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+
+    // find the importer associated with this data
+    const ScenePrivateData* priv = ScenePriv( scene );
+    if ( NULL == priv || NULL == priv->mOrigImporter ) {
+        ReportSceneNotFoundError();
+        return NULL;
+    }
+
+    sc = priv->mOrigImporter->ApplyCustomizedPostProcessing( process, requestValidation );
+
+    if ( !sc ) {
+        aiReleaseImport( scene );
+        return NULL;
+    }
+
+    ASSIMP_END_EXCEPTION_REGION( const aiScene* );
+
+    return sc;
+}
+
+// ------------------------------------------------------------------------------------------------
+void CallbackToLogRedirector (const char* msg, char* dt)
+{
+    ai_assert( NULL != msg );
+    ai_assert( NULL != dt );
+    LogStream* s = (LogStream*)dt;
+
+    s->write(msg);
+}
+
+// ------------------------------------------------------------------------------------------------
+ASSIMP_API aiLogStream aiGetPredefinedLogStream(aiDefaultLogStream pStream,const char* file)
+{
+    aiLogStream sout;
+
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+    LogStream* stream = LogStream::createDefaultStream(pStream,file);
+    if (!stream) {
+        sout.callback = NULL;
+        sout.user = NULL;
+    }
+    else {
+        sout.callback = &CallbackToLogRedirector;
+        sout.user = (char*)stream;
+    }
+    gPredefinedStreams.push_back(stream);
+    ASSIMP_END_EXCEPTION_REGION(aiLogStream);
+    return sout;
+}
+
+// ------------------------------------------------------------------------------------------------
+ASSIMP_API void aiAttachLogStream( const aiLogStream* stream )
+{
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+
+#ifndef ASSIMP_BUILD_SINGLETHREADED
+    std::lock_guard<std::mutex> lock(gLogStreamMutex);
+#endif
+
+    LogStream* lg = new LogToCallbackRedirector(*stream);
+    gActiveLogStreams[*stream] = lg;
+
+    if (DefaultLogger::isNullLogger()) {
+        DefaultLogger::create(NULL,(gVerboseLogging == AI_TRUE ? Logger::VERBOSE : Logger::NORMAL));
+    }
+    DefaultLogger::get()->attachStream(lg);
+    ASSIMP_END_EXCEPTION_REGION(void);
+}
+
+// ------------------------------------------------------------------------------------------------
+ASSIMP_API aiReturn aiDetachLogStream( const aiLogStream* stream)
+{
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+
+#ifndef ASSIMP_BUILD_SINGLETHREADED
+    std::lock_guard<std::mutex> lock(gLogStreamMutex);
+#endif
+    // find the log-stream associated with this data
+    LogStreamMap::iterator it = gActiveLogStreams.find( *stream);
+    // it should be there... else the user is playing fools with us
+    if( it == gActiveLogStreams.end())  {
+        return AI_FAILURE;
+    }
+    DefaultLogger::get()->detatchStream( it->second );
+    delete it->second;
+
+    gActiveLogStreams.erase( it);
+
+    if (gActiveLogStreams.empty()) {
+        DefaultLogger::kill();
+    }
+    ASSIMP_END_EXCEPTION_REGION(aiReturn);
+    return AI_SUCCESS;
+}
+
+// ------------------------------------------------------------------------------------------------
+ASSIMP_API void aiDetachAllLogStreams(void)
+{
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+#ifndef ASSIMP_BUILD_SINGLETHREADED
+    std::lock_guard<std::mutex> lock(gLogStreamMutex);
+#endif
+    Logger *logger( DefaultLogger::get() );
+    if ( NULL == logger ) {
+        return;
+    }
+
+    for (LogStreamMap::iterator it = gActiveLogStreams.begin(); it != gActiveLogStreams.end(); ++it) {
+        logger->detatchStream( it->second );
+        delete it->second;
+    }
+    gActiveLogStreams.clear();
+    DefaultLogger::kill();
+
+    ASSIMP_END_EXCEPTION_REGION(void);
+}
+
+// ------------------------------------------------------------------------------------------------
+ASSIMP_API void aiEnableVerboseLogging(aiBool d)
+{
+    if (!DefaultLogger::isNullLogger()) {
+        DefaultLogger::get()->setLogSeverity((d == AI_TRUE ? Logger::VERBOSE : Logger::NORMAL));
+    }
+    gVerboseLogging = d;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Returns the error text of the last failed import process.
+const char* aiGetErrorString()
+{
+    return gLastErrorString.c_str();
+}
+
+// -----------------------------------------------------------------------------------------------
+// Return the description of a importer given its index
+const aiImporterDesc* aiGetImportFormatDescription( size_t pIndex)
+{
+    return Importer().GetImporterInfo(pIndex);
+}
+
+// -----------------------------------------------------------------------------------------------
+// Return the number of importers
+size_t aiGetImportFormatCount(void)
+{
+    return Importer().GetImporterCount();
+}
+
+// ------------------------------------------------------------------------------------------------
+// Returns the error text of the last failed import process.
+aiBool aiIsExtensionSupported(const char* szExtension)
+{
+    ai_assert(NULL != szExtension);
+    aiBool candoit=AI_FALSE;
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+
+    // FIXME: no need to create a temporary Importer instance just for that ..
+    Assimp::Importer tmp;
+    candoit = tmp.IsExtensionSupported(std::string(szExtension)) ? AI_TRUE : AI_FALSE;
+
+    ASSIMP_END_EXCEPTION_REGION(aiBool);
+    return candoit;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Get a list of all file extensions supported by ASSIMP
+void aiGetExtensionList(aiString* szOut)
+{
+    ai_assert(NULL != szOut);
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+
+    // FIXME: no need to create a temporary Importer instance just for that ..
+    Assimp::Importer tmp;
+    tmp.GetExtensionList(*szOut);
+
+    ASSIMP_END_EXCEPTION_REGION(void);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Get the memory requirements for a particular import.
+void aiGetMemoryRequirements(const C_STRUCT aiScene* pIn,
+    C_STRUCT aiMemoryInfo* in)
+{
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+
+    // find the importer associated with this data
+    const ScenePrivateData* priv = ScenePriv(pIn);
+    if( !priv || !priv->mOrigImporter)  {
+        ReportSceneNotFoundError();
+        return;
+    }
+
+    return priv->mOrigImporter->GetMemoryRequirements(*in);
+    ASSIMP_END_EXCEPTION_REGION(void);
+}
+
+// ------------------------------------------------------------------------------------------------
+ASSIMP_API aiPropertyStore* aiCreatePropertyStore(void)
+{
+    return reinterpret_cast<aiPropertyStore*>( new PropertyMap() );
+}
+
+// ------------------------------------------------------------------------------------------------
+ASSIMP_API void aiReleasePropertyStore(aiPropertyStore* p)
+{
+    delete reinterpret_cast<PropertyMap*>(p);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Importer::SetPropertyInteger
+ASSIMP_API void aiSetImportPropertyInteger(aiPropertyStore* p, const char* szName, int value)
+{
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+    PropertyMap* pp = reinterpret_cast<PropertyMap*>(p);
+    SetGenericProperty<int>(pp->ints,szName,value);
+    ASSIMP_END_EXCEPTION_REGION(void);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Importer::SetPropertyFloat
+ASSIMP_API void aiSetImportPropertyFloat(aiPropertyStore* p, const char* szName, ai_real value)
+{
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+    PropertyMap* pp = reinterpret_cast<PropertyMap*>(p);
+    SetGenericProperty<ai_real>(pp->floats,szName,value);
+    ASSIMP_END_EXCEPTION_REGION(void);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Importer::SetPropertyString
+ASSIMP_API void aiSetImportPropertyString(aiPropertyStore* p, const char* szName,
+    const C_STRUCT aiString* st)
+{
+    if (!st) {
+        return;
+    }
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+    PropertyMap* pp = reinterpret_cast<PropertyMap*>(p);
+    SetGenericProperty<std::string>(pp->strings,szName,std::string(st->C_Str()));
+    ASSIMP_END_EXCEPTION_REGION(void);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Importer::SetPropertyMatrix
+ASSIMP_API void aiSetImportPropertyMatrix(aiPropertyStore* p, const char* szName,
+    const C_STRUCT aiMatrix4x4* mat)
+{
+    if (!mat) {
+        return;
+    }
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+    PropertyMap* pp = reinterpret_cast<PropertyMap*>(p);
+    SetGenericProperty<aiMatrix4x4>(pp->matrices,szName,*mat);
+    ASSIMP_END_EXCEPTION_REGION(void);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Rotation matrix to quaternion
+ASSIMP_API void aiCreateQuaternionFromMatrix(aiQuaternion* quat,const aiMatrix3x3* mat)
+{
+    ai_assert( NULL != quat );
+    ai_assert( NULL != mat );
+    *quat = aiQuaternion(*mat);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Matrix decomposition
+ASSIMP_API void aiDecomposeMatrix(const aiMatrix4x4* mat,aiVector3D* scaling,
+    aiQuaternion* rotation,
+    aiVector3D* position)
+{
+    ai_assert( NULL != rotation );
+    ai_assert( NULL != position );
+    ai_assert( NULL != scaling );
+    ai_assert( NULL != mat );
+    mat->Decompose(*scaling,*rotation,*position);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Matrix transpose
+ASSIMP_API void aiTransposeMatrix3(aiMatrix3x3* mat)
+{
+    ai_assert(NULL != mat);
+    mat->Transpose();
+}
+
+// ------------------------------------------------------------------------------------------------
+ASSIMP_API void aiTransposeMatrix4(aiMatrix4x4* mat)
+{
+    ai_assert(NULL != mat);
+    mat->Transpose();
+}
+
+// ------------------------------------------------------------------------------------------------
+// Vector transformation
+ASSIMP_API void aiTransformVecByMatrix3(aiVector3D* vec,
+    const aiMatrix3x3* mat)
+{
+    ai_assert( NULL != mat );
+    ai_assert( NULL != vec);
+    *vec *= (*mat);
+}
+
+// ------------------------------------------------------------------------------------------------
+ASSIMP_API void aiTransformVecByMatrix4(aiVector3D* vec,
+    const aiMatrix4x4* mat)
+{
+    ai_assert( NULL != mat );
+    ai_assert( NULL != vec );
+
+    *vec *= (*mat);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Matrix multiplication
+ASSIMP_API void aiMultiplyMatrix4(
+    aiMatrix4x4* dst,
+    const aiMatrix4x4* src)
+{
+    ai_assert( NULL != dst );
+    ai_assert( NULL != src );
+    *dst = (*dst) * (*src);
+}
+
+// ------------------------------------------------------------------------------------------------
+ASSIMP_API void aiMultiplyMatrix3(
+    aiMatrix3x3* dst,
+    const aiMatrix3x3* src)
+{
+    ai_assert( NULL != dst );
+    ai_assert( NULL != src );
+    *dst = (*dst) * (*src);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Matrix identity
+ASSIMP_API void aiIdentityMatrix3(
+    aiMatrix3x3* mat)
+{
+    ai_assert(NULL != mat);
+    *mat = aiMatrix3x3();
+}
+
+// ------------------------------------------------------------------------------------------------
+ASSIMP_API void aiIdentityMatrix4(
+    aiMatrix4x4* mat)
+{
+    ai_assert(NULL != mat);
+    *mat = aiMatrix4x4();
+}
+
+// ------------------------------------------------------------------------------------------------
+ASSIMP_API C_STRUCT const aiImporterDesc* aiGetImporterDesc( const char *extension ) {
+    if( NULL == extension ) {
+        return NULL;
+    }
+    const aiImporterDesc *desc( NULL );
+    std::vector< BaseImporter* > out;
+    GetImporterInstanceList( out );
+    for( size_t i = 0; i < out.size(); ++i ) {
+        if( 0 == strncmp( out[ i ]->GetInfo()->mFileExtensions, extension, strlen( extension ) ) ) {
+            desc = out[ i ]->GetInfo();
+            break;
+        }
+    }
+
+    DeleteImporterInstanceList(out);
+
+    return desc;
+}
+
+// ------------------------------------------------------------------------------------------------

+ 156 - 0
Engine/lib/assimp/code/AssimpCExport.cpp

@@ -0,0 +1,156 @@
+/*
+---------------------------------------------------------------------------
+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 AssimpCExport.cpp
+Assimp C export interface. See Exporter.cpp for some notes.
+*/
+
+#ifndef ASSIMP_BUILD_NO_EXPORT
+
+#include "CInterfaceIOWrapper.h"
+#include <assimp/SceneCombiner.h>
+#include "ScenePrivate.h"
+#include <assimp/Exporter.hpp>
+
+using namespace Assimp;
+
+// ------------------------------------------------------------------------------------------------
+ASSIMP_API size_t aiGetExportFormatCount(void)
+{
+    return Exporter().GetExportFormatCount();
+}
+
+// ------------------------------------------------------------------------------------------------
+ASSIMP_API const aiExportFormatDesc* aiGetExportFormatDescription( size_t index)
+{
+    // Note: this is valid as the index always pertains to a built-in exporter,
+    // for which the returned structure is guaranteed to be of static storage duration.
+    Exporter exporter;
+    const aiExportFormatDesc* orig( exporter.GetExportFormatDescription( index ) );
+    if (NULL == orig) {
+        return NULL;
+    }
+
+    aiExportFormatDesc *desc = new aiExportFormatDesc;
+    desc->description = new char[ strlen( orig->description ) + 1 ]();
+    ::strncpy( (char*) desc->description, orig->description, strlen( orig->description ) );
+    desc->fileExtension = new char[ strlen( orig->fileExtension ) + 1 ]();
+    ::strncpy( ( char* ) desc->fileExtension, orig->fileExtension, strlen( orig->fileExtension ) );
+    desc->id = new char[ strlen( orig->id ) + 1 ]();
+    ::strncpy( ( char* ) desc->id, orig->id, strlen( orig->id ) );
+
+    return desc;
+}
+
+// ------------------------------------------------------------------------------------------------
+ASSIMP_API void aiReleaseExportFormatDescription( const aiExportFormatDesc *desc ) {
+    if (NULL == desc) {
+        return;
+    }
+
+    delete [] desc->description;
+    delete [] desc->fileExtension;
+    delete [] desc->id;
+    delete desc;
+}
+
+// ------------------------------------------------------------------------------------------------
+ASSIMP_API void aiCopyScene(const aiScene* pIn, aiScene** pOut)
+{
+    if (!pOut || !pIn) {
+        return;
+    }
+
+    SceneCombiner::CopyScene(pOut,pIn,true);
+    ScenePriv(*pOut)->mIsCopy = true;
+}
+
+
+// ------------------------------------------------------------------------------------------------
+ASSIMP_API void aiFreeScene(const C_STRUCT aiScene* pIn)
+{
+    // note: aiReleaseImport() is also able to delete scene copies, but in addition
+    // it also handles scenes with import metadata.
+    delete pIn;
+}
+
+
+// ------------------------------------------------------------------------------------------------
+ASSIMP_API aiReturn aiExportScene( const aiScene* pScene, const char* pFormatId, const char* pFileName, unsigned int pPreprocessing )
+{
+    return ::aiExportSceneEx(pScene,pFormatId,pFileName,NULL,pPreprocessing);
+}
+
+
+// ------------------------------------------------------------------------------------------------
+ASSIMP_API aiReturn aiExportSceneEx( const aiScene* pScene, const char* pFormatId, const char* pFileName, aiFileIO* pIO, unsigned int pPreprocessing )
+{
+    Exporter exp;
+
+    if (pIO) {
+        exp.SetIOHandler(new CIOSystemWrapper(pIO));
+    }
+    return exp.Export(pScene,pFormatId,pFileName,pPreprocessing);
+}
+
+
+// ------------------------------------------------------------------------------------------------
+ASSIMP_API const C_STRUCT aiExportDataBlob* aiExportSceneToBlob( const aiScene* pScene, const char* pFormatId, unsigned int pPreprocessing  )
+{
+    Exporter exp;
+    if (!exp.ExportToBlob(pScene,pFormatId,pPreprocessing)) {
+        return NULL;
+    }
+    const aiExportDataBlob* blob = exp.GetOrphanedBlob();
+    ai_assert(blob);
+
+    return blob;
+}
+
+// ------------------------------------------------------------------------------------------------
+ASSIMP_API C_STRUCT void aiReleaseExportBlob( const aiExportDataBlob* pData )
+{
+    delete pData;
+}
+
+#endif // !ASSIMP_BUILD_NO_EXPORT

+ 655 - 0
Engine/lib/assimp/code/AssxmlExporter.cpp

@@ -0,0 +1,655 @@
+/*
+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  AssxmlExporter.cpp
+ *  ASSXML exporter main code
+ */
+
+#ifndef ASSIMP_BUILD_NO_EXPORT
+#ifndef ASSIMP_BUILD_NO_ASSXML_EXPORTER
+
+#include <stdarg.h>
+#include <assimp/version.h>
+#include "ProcessHelper.h"
+#include <assimp/IOStream.hpp>
+#include <assimp/IOSystem.hpp>
+#include <assimp/Exporter.hpp>
+
+#ifdef ASSIMP_BUILD_NO_OWN_ZLIB
+#   include <zlib.h>
+#else
+#   include <contrib/zlib/zlib.h>
+#endif
+
+#include <time.h>
+#include <stdio.h>
+
+using namespace Assimp;
+
+namespace Assimp    {
+
+namespace AssxmlExport  {
+
+// -----------------------------------------------------------------------------------
+static int ioprintf( IOStream * io, const char *format, ... ) {
+	using namespace std;
+    if ( nullptr == io ) {
+        return -1;
+    }
+
+    static const int Size = 4096;
+    char sz[ Size ];
+    ::memset( sz, '\0', Size );
+    va_list va;
+    va_start( va, format );
+    const unsigned int nSize = vsnprintf( sz, Size-1, format, va );
+    ai_assert( nSize < Size );
+    va_end( va );
+
+    io->Write( sz, sizeof(char), nSize );
+
+    return nSize;
+}
+
+// -----------------------------------------------------------------------------------
+// Convert a name to standard XML format
+static void ConvertName(aiString& out, const aiString& in) {
+    out.length = 0;
+    for (unsigned int i = 0; i < in.length; ++i)  {
+        switch (in.data[i]) {
+            case '<':
+                out.Append("&lt;");break;
+            case '>':
+                out.Append("&gt;");break;
+            case '&':
+                out.Append("&amp;");break;
+            case '\"':
+                out.Append("&quot;");break;
+            case '\'':
+                out.Append("&apos;");break;
+            default:
+                out.data[out.length++] = in.data[i];
+        }
+    }
+    out.data[out.length] = 0;
+}
+
+// -----------------------------------------------------------------------------------
+// Write a single node as text dump
+static void WriteNode(const aiNode* node, IOStream * io, unsigned int depth) {
+    char prefix[512];
+    for (unsigned int i = 0; i < depth;++i)
+        prefix[i] = '\t';
+    prefix[depth] = '\0';
+
+    const aiMatrix4x4& m = node->mTransformation;
+
+    aiString name;
+    ConvertName(name,node->mName);
+    ioprintf(io,"%s<Node name=\"%s\"> \n"
+        "%s\t<Matrix4> \n"
+        "%s\t\t%0 6f %0 6f %0 6f %0 6f\n"
+        "%s\t\t%0 6f %0 6f %0 6f %0 6f\n"
+        "%s\t\t%0 6f %0 6f %0 6f %0 6f\n"
+        "%s\t\t%0 6f %0 6f %0 6f %0 6f\n"
+        "%s\t</Matrix4> \n",
+        prefix,name.data,prefix,
+        prefix,m.a1,m.a2,m.a3,m.a4,
+        prefix,m.b1,m.b2,m.b3,m.b4,
+        prefix,m.c1,m.c2,m.c3,m.c4,
+        prefix,m.d1,m.d2,m.d3,m.d4,prefix);
+
+    if (node->mNumMeshes) {
+        ioprintf(io, "%s\t<MeshRefs num=\"%i\">\n%s\t",
+            prefix,node->mNumMeshes,prefix);
+
+        for (unsigned int i = 0; i < node->mNumMeshes;++i) {
+            ioprintf(io,"%i ",node->mMeshes[i]);
+        }
+        ioprintf(io,"\n%s\t</MeshRefs>\n",prefix);
+    }
+
+    if (node->mNumChildren) {
+        ioprintf(io,"%s\t<NodeList num=\"%i\">\n",
+            prefix,node->mNumChildren);
+
+        for (unsigned int i = 0; i < node->mNumChildren;++i) {
+            WriteNode(node->mChildren[i],io,depth+2);
+        }
+        ioprintf(io,"%s\t</NodeList>\n",prefix);
+    }
+    ioprintf(io,"%s</Node>\n",prefix);
+}
+
+
+// -----------------------------------------------------------------------------------
+// Some chuncks of text will need to be encoded for XML
+// http://stackoverflow.com/questions/5665231/most-efficient-way-to-escape-xml-html-in-c-string#5665377
+static std::string encodeXML(const std::string& data) {
+    std::string buffer;
+    buffer.reserve(data.size());
+    for(size_t pos = 0; pos != data.size(); ++pos) {
+            switch(data[pos]) {
+                    case '&':  buffer.append("&amp;");              break;
+                    case '\"': buffer.append("&quot;");             break;
+                    case '\'': buffer.append("&apos;");             break;
+                    case '<':  buffer.append("&lt;");                   break;
+                    case '>':  buffer.append("&gt;");                   break;
+                    default:   buffer.append(&data[pos], 1);    break;
+            }
+    }
+    return buffer;
+}
+
+// -----------------------------------------------------------------------------------
+// Write a text model dump
+static
+void WriteDump(const aiScene* scene, IOStream* io, bool shortened) {
+    time_t tt = ::time( NULL );
+#if _WIN32
+    tm* p = gmtime(&tt);
+#else
+    struct tm now;
+    tm* p = gmtime_r(&tt, &now);
+#endif
+    ai_assert(nullptr != p);
+
+    // write header
+    std::string header(
+        "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+        "<ASSIMP format_id=\"1\">\n\n"
+        "<!-- XML Model dump produced by assimp dump\n"
+        "  Library version: %i.%i.%i\n"
+        "  %s\n"
+        "-->"
+        " \n\n"
+        "<Scene flags=\"%d\" postprocessing=\"%i\">\n"
+    );
+
+    const unsigned int majorVersion( aiGetVersionMajor() );
+    const unsigned int minorVersion( aiGetVersionMinor() );
+    const unsigned int rev( aiGetVersionRevision() );
+    const char *curtime( asctime( p ) );
+    ioprintf( io, header.c_str(), majorVersion, minorVersion, rev, curtime, scene->mFlags, 0 );
+
+    // write the node graph
+    WriteNode(scene->mRootNode, io, 0);
+
+#if 0
+        // write cameras
+    for (unsigned int i = 0; i < scene->mNumCameras;++i) {
+        aiCamera* cam  = scene->mCameras[i];
+        ConvertName(name,cam->mName);
+
+        // camera header
+        ioprintf(io,"\t<Camera parent=\"%s\">\n"
+            "\t\t<Vector3 name=\"up\"        > %0 8f %0 8f %0 8f </Vector3>\n"
+            "\t\t<Vector3 name=\"lookat\"    > %0 8f %0 8f %0 8f </Vector3>\n"
+            "\t\t<Vector3 name=\"pos\"       > %0 8f %0 8f %0 8f </Vector3>\n"
+            "\t\t<Float   name=\"fov\"       > %f </Float>\n"
+            "\t\t<Float   name=\"aspect\"    > %f </Float>\n"
+            "\t\t<Float   name=\"near_clip\" > %f </Float>\n"
+            "\t\t<Float   name=\"far_clip\"  > %f </Float>\n"
+            "\t</Camera>\n",
+            name.data,
+            cam->mUp.x,cam->mUp.y,cam->mUp.z,
+            cam->mLookAt.x,cam->mLookAt.y,cam->mLookAt.z,
+            cam->mPosition.x,cam->mPosition.y,cam->mPosition.z,
+            cam->mHorizontalFOV,cam->mAspect,cam->mClipPlaneNear,cam->mClipPlaneFar,i);
+    }
+
+    // write lights
+    for (unsigned int i = 0; i < scene->mNumLights;++i) {
+        aiLight* l  = scene->mLights[i];
+        ConvertName(name,l->mName);
+
+        // light header
+        ioprintf(io,"\t<Light parent=\"%s\"> type=\"%s\"\n"
+            "\t\t<Vector3 name=\"diffuse\"   > %0 8f %0 8f %0 8f </Vector3>\n"
+            "\t\t<Vector3 name=\"specular\"  > %0 8f %0 8f %0 8f </Vector3>\n"
+            "\t\t<Vector3 name=\"ambient\"   > %0 8f %0 8f %0 8f </Vector3>\n",
+            name.data,
+            (l->mType == aiLightSource_DIRECTIONAL ? "directional" :
+            (l->mType == aiLightSource_POINT ? "point" : "spot" )),
+            l->mColorDiffuse.r, l->mColorDiffuse.g, l->mColorDiffuse.b,
+            l->mColorSpecular.r,l->mColorSpecular.g,l->mColorSpecular.b,
+            l->mColorAmbient.r, l->mColorAmbient.g, l->mColorAmbient.b);
+
+        if (l->mType != aiLightSource_DIRECTIONAL) {
+            ioprintf(io,
+                "\t\t<Vector3 name=\"pos\"       > %0 8f %0 8f %0 8f </Vector3>\n"
+                "\t\t<Float   name=\"atten_cst\" > %f </Float>\n"
+                "\t\t<Float   name=\"atten_lin\" > %f </Float>\n"
+                "\t\t<Float   name=\"atten_sqr\" > %f </Float>\n",
+                l->mPosition.x,l->mPosition.y,l->mPosition.z,
+                l->mAttenuationConstant,l->mAttenuationLinear,l->mAttenuationQuadratic);
+        }
+
+        if (l->mType != aiLightSource_POINT) {
+            ioprintf(io,
+                "\t\t<Vector3 name=\"lookat\"    > %0 8f %0 8f %0 8f </Vector3>\n",
+                l->mDirection.x,l->mDirection.y,l->mDirection.z);
+        }
+
+        if (l->mType == aiLightSource_SPOT) {
+            ioprintf(io,
+                "\t\t<Float   name=\"cone_out\" > %f </Float>\n"
+                "\t\t<Float   name=\"cone_inn\" > %f </Float>\n",
+                l->mAngleOuterCone,l->mAngleInnerCone);
+        }
+        ioprintf(io,"\t</Light>\n");
+    }
+#endif
+    aiString name;
+
+    // write textures
+    if (scene->mNumTextures) {
+        ioprintf(io,"<TextureList num=\"%i\">\n",scene->mNumTextures);
+        for (unsigned int i = 0; i < scene->mNumTextures;++i) {
+            aiTexture* tex  = scene->mTextures[i];
+            bool compressed = (tex->mHeight == 0);
+
+            // mesh header
+            ioprintf(io,"\t<Texture width=\"%i\" height=\"%i\" compressed=\"%s\"> \n",
+                (compressed ? -1 : tex->mWidth),(compressed ? -1 : tex->mHeight),
+                (compressed ? "true" : "false"));
+
+            if (compressed) {
+                ioprintf(io,"\t\t<Data length=\"%i\"> \n",tex->mWidth);
+
+                if (!shortened) {
+                    for (unsigned int n = 0; n < tex->mWidth;++n) {
+                        ioprintf(io,"\t\t\t%2x",reinterpret_cast<uint8_t*>(tex->pcData)[n]);
+                        if (n && !(n % 50)) {
+                            ioprintf(io,"\n");
+                        }
+                    }
+                }
+            }
+            else if (!shortened){
+                ioprintf(io,"\t\t<Data length=\"%i\"> \n",tex->mWidth*tex->mHeight*4);
+
+                // const unsigned int width = (unsigned int)std::log10((double)std::max(tex->mHeight,tex->mWidth))+1;
+                for (unsigned int y = 0; y < tex->mHeight;++y) {
+                    for (unsigned int x = 0; x < tex->mWidth;++x) {
+                        aiTexel* tx = tex->pcData + y*tex->mWidth+x;
+                        unsigned int r = tx->r,g=tx->g,b=tx->b,a=tx->a;
+                        ioprintf(io,"\t\t\t%2x %2x %2x %2x",r,g,b,a);
+
+                        // group by four for readability
+                        if ( 0 == ( x + y*tex->mWidth ) % 4 ) {
+                            ioprintf( io, "\n" );
+                        }
+                    }
+                }
+            }
+            ioprintf(io,"\t\t</Data>\n\t</Texture>\n");
+        }
+        ioprintf(io,"</TextureList>\n");
+    }
+
+    // write materials
+    if (scene->mNumMaterials) {
+        ioprintf(io,"<MaterialList num=\"%i\">\n",scene->mNumMaterials);
+        for (unsigned int i = 0; i< scene->mNumMaterials; ++i) {
+            const aiMaterial* mat = scene->mMaterials[i];
+
+            ioprintf(io,"\t<Material>\n");
+            ioprintf(io,"\t\t<MatPropertyList  num=\"%i\">\n",mat->mNumProperties);
+            for (unsigned int n = 0; n < mat->mNumProperties;++n) {
+
+                const aiMaterialProperty* prop = mat->mProperties[n];
+                const char* sz = "";
+                if (prop->mType == aiPTI_Float) {
+                    sz = "float";
+                }
+                else if (prop->mType == aiPTI_Integer) {
+                    sz = "integer";
+                }
+                else if (prop->mType == aiPTI_String) {
+                    sz = "string";
+                }
+                else if (prop->mType == aiPTI_Buffer) {
+                    sz = "binary_buffer";
+                }
+
+                ioprintf(io,"\t\t\t<MatProperty key=\"%s\" \n\t\t\ttype=\"%s\" tex_usage=\"%s\" tex_index=\"%i\"",
+                    prop->mKey.data, sz,
+                    ::TextureTypeToString((aiTextureType)prop->mSemantic),prop->mIndex);
+
+                if (prop->mType == aiPTI_Float) {
+                    ioprintf(io," size=\"%i\">\n\t\t\t\t",
+                        static_cast<int>(prop->mDataLength/sizeof(float)));
+
+                    for (unsigned int p = 0; p < prop->mDataLength/sizeof(float);++p) {
+                        ioprintf(io,"%f ",*((float*)(prop->mData+p*sizeof(float))));
+                    }
+                }
+                else if (prop->mType == aiPTI_Integer) {
+                    ioprintf(io," size=\"%i\">\n\t\t\t\t",
+                        static_cast<int>(prop->mDataLength/sizeof(int)));
+
+                    for (unsigned int p = 0; p < prop->mDataLength/sizeof(int);++p) {
+                        ioprintf(io,"%i ",*((int*)(prop->mData+p*sizeof(int))));
+                    }
+                }
+                else if (prop->mType == aiPTI_Buffer) {
+                    ioprintf(io," size=\"%i\">\n\t\t\t\t",
+                        static_cast<int>(prop->mDataLength));
+
+                    for (unsigned int p = 0; p < prop->mDataLength;++p) {
+                        ioprintf(io,"%2x ",prop->mData[p]);
+                        if (p && 0 == p%30) {
+                            ioprintf(io,"\n\t\t\t\t");
+                        }
+                    }
+                }
+                else if (prop->mType == aiPTI_String) {
+                    ioprintf(io,">\n\t\t\t\t\"%s\"",encodeXML(prop->mData+4).c_str() /* skip length */);
+                }
+                ioprintf(io,"\n\t\t\t</MatProperty>\n");
+            }
+            ioprintf(io,"\t\t</MatPropertyList>\n");
+            ioprintf(io,"\t</Material>\n");
+        }
+        ioprintf(io,"</MaterialList>\n");
+    }
+
+    // write animations
+    if (scene->mNumAnimations) {
+        ioprintf(io,"<AnimationList num=\"%i\">\n",scene->mNumAnimations);
+        for (unsigned int i = 0; i < scene->mNumAnimations;++i) {
+            aiAnimation* anim = scene->mAnimations[i];
+
+            // anim header
+            ConvertName(name,anim->mName);
+            ioprintf(io,"\t<Animation name=\"%s\" duration=\"%e\" tick_cnt=\"%e\">\n",
+                name.data, anim->mDuration, anim->mTicksPerSecond);
+
+            // write bone animation channels
+            if (anim->mNumChannels) {
+                ioprintf(io,"\t\t<NodeAnimList num=\"%i\">\n",anim->mNumChannels);
+                for (unsigned int n = 0; n < anim->mNumChannels;++n) {
+                    aiNodeAnim* nd = anim->mChannels[n];
+
+                    // node anim header
+                    ConvertName(name,nd->mNodeName);
+                    ioprintf(io,"\t\t\t<NodeAnim node=\"%s\">\n",name.data);
+
+                    if (!shortened) {
+                        // write position keys
+                        if (nd->mNumPositionKeys) {
+                            ioprintf(io,"\t\t\t\t<PositionKeyList num=\"%i\">\n",nd->mNumPositionKeys);
+                            for (unsigned int a = 0; a < nd->mNumPositionKeys;++a) {
+                                aiVectorKey* vc = nd->mPositionKeys+a;
+                                ioprintf(io,"\t\t\t\t\t<PositionKey time=\"%e\">\n"
+                                    "\t\t\t\t\t\t%0 8f %0 8f %0 8f\n\t\t\t\t\t</PositionKey>\n",
+                                    vc->mTime,vc->mValue.x,vc->mValue.y,vc->mValue.z);
+                            }
+                            ioprintf(io,"\t\t\t\t</PositionKeyList>\n");
+                        }
+
+                        // write scaling keys
+                        if (nd->mNumScalingKeys) {
+                            ioprintf(io,"\t\t\t\t<ScalingKeyList num=\"%i\">\n",nd->mNumScalingKeys);
+                            for (unsigned int a = 0; a < nd->mNumScalingKeys;++a) {
+                                aiVectorKey* vc = nd->mScalingKeys+a;
+                                ioprintf(io,"\t\t\t\t\t<ScalingKey time=\"%e\">\n"
+                                    "\t\t\t\t\t\t%0 8f %0 8f %0 8f\n\t\t\t\t\t</ScalingKey>\n",
+                                    vc->mTime,vc->mValue.x,vc->mValue.y,vc->mValue.z);
+                            }
+                            ioprintf(io,"\t\t\t\t</ScalingKeyList>\n");
+                        }
+
+                        // write rotation keys
+                        if (nd->mNumRotationKeys) {
+                            ioprintf(io,"\t\t\t\t<RotationKeyList num=\"%i\">\n",nd->mNumRotationKeys);
+                            for (unsigned int a = 0; a < nd->mNumRotationKeys;++a) {
+                                aiQuatKey* vc = nd->mRotationKeys+a;
+                                ioprintf(io,"\t\t\t\t\t<RotationKey time=\"%e\">\n"
+                                    "\t\t\t\t\t\t%0 8f %0 8f %0 8f %0 8f\n\t\t\t\t\t</RotationKey>\n",
+                                    vc->mTime,vc->mValue.x,vc->mValue.y,vc->mValue.z,vc->mValue.w);
+                            }
+                            ioprintf(io,"\t\t\t\t</RotationKeyList>\n");
+                        }
+                    }
+                    ioprintf(io,"\t\t\t</NodeAnim>\n");
+                }
+                ioprintf(io,"\t\t</NodeAnimList>\n");
+            }
+            ioprintf(io,"\t</Animation>\n");
+        }
+        ioprintf(io,"</AnimationList>\n");
+    }
+
+    // write meshes
+    if (scene->mNumMeshes) {
+        ioprintf(io,"<MeshList num=\"%i\">\n",scene->mNumMeshes);
+        for (unsigned int i = 0; i < scene->mNumMeshes;++i) {
+            aiMesh* mesh = scene->mMeshes[i];
+            // const unsigned int width = (unsigned int)std::log10((double)mesh->mNumVertices)+1;
+
+            // mesh header
+            ioprintf(io,"\t<Mesh types=\"%s %s %s %s\" material_index=\"%i\">\n",
+                (mesh->mPrimitiveTypes & aiPrimitiveType_POINT    ? "points"    : ""),
+                (mesh->mPrimitiveTypes & aiPrimitiveType_LINE     ? "lines"     : ""),
+                (mesh->mPrimitiveTypes & aiPrimitiveType_TRIANGLE ? "triangles" : ""),
+                (mesh->mPrimitiveTypes & aiPrimitiveType_POLYGON  ? "polygons"  : ""),
+                mesh->mMaterialIndex);
+
+            // bones
+            if (mesh->mNumBones) {
+                ioprintf(io,"\t\t<BoneList num=\"%i\">\n",mesh->mNumBones);
+
+                for (unsigned int n = 0; n < mesh->mNumBones;++n) {
+                    aiBone* bone = mesh->mBones[n];
+
+                    ConvertName(name,bone->mName);
+                    // bone header
+                    ioprintf(io,"\t\t\t<Bone name=\"%s\">\n"
+                        "\t\t\t\t<Matrix4> \n"
+                        "\t\t\t\t\t%0 6f %0 6f %0 6f %0 6f\n"
+                        "\t\t\t\t\t%0 6f %0 6f %0 6f %0 6f\n"
+                        "\t\t\t\t\t%0 6f %0 6f %0 6f %0 6f\n"
+                        "\t\t\t\t\t%0 6f %0 6f %0 6f %0 6f\n"
+                        "\t\t\t\t</Matrix4> \n",
+                        name.data,
+                        bone->mOffsetMatrix.a1,bone->mOffsetMatrix.a2,bone->mOffsetMatrix.a3,bone->mOffsetMatrix.a4,
+                        bone->mOffsetMatrix.b1,bone->mOffsetMatrix.b2,bone->mOffsetMatrix.b3,bone->mOffsetMatrix.b4,
+                        bone->mOffsetMatrix.c1,bone->mOffsetMatrix.c2,bone->mOffsetMatrix.c3,bone->mOffsetMatrix.c4,
+                        bone->mOffsetMatrix.d1,bone->mOffsetMatrix.d2,bone->mOffsetMatrix.d3,bone->mOffsetMatrix.d4);
+
+                    if (!shortened && bone->mNumWeights) {
+                        ioprintf(io,"\t\t\t\t<WeightList num=\"%i\">\n",bone->mNumWeights);
+
+                        // bone weights
+                        for (unsigned int a = 0; a < bone->mNumWeights;++a) {
+                            aiVertexWeight* wght = bone->mWeights+a;
+
+                            ioprintf(io,"\t\t\t\t\t<Weight index=\"%i\">\n\t\t\t\t\t\t%f\n\t\t\t\t\t</Weight>\n",
+                                wght->mVertexId,wght->mWeight);
+                        }
+                        ioprintf(io,"\t\t\t\t</WeightList>\n");
+                    }
+                    ioprintf(io,"\t\t\t</Bone>\n");
+                }
+                ioprintf(io,"\t\t</BoneList>\n");
+            }
+
+            // faces
+            if (!shortened && mesh->mNumFaces) {
+                ioprintf(io,"\t\t<FaceList num=\"%i\">\n",mesh->mNumFaces);
+                for (unsigned int n = 0; n < mesh->mNumFaces; ++n) {
+                    aiFace& f = mesh->mFaces[n];
+                    ioprintf(io,"\t\t\t<Face num=\"%i\">\n"
+                        "\t\t\t\t",f.mNumIndices);
+
+                    for (unsigned int j = 0; j < f.mNumIndices;++j)
+                        ioprintf(io,"%i ",f.mIndices[j]);
+
+                    ioprintf(io,"\n\t\t\t</Face>\n");
+                }
+                ioprintf(io,"\t\t</FaceList>\n");
+            }
+
+            // vertex positions
+            if (mesh->HasPositions()) {
+                ioprintf(io,"\t\t<Positions num=\"%i\" set=\"0\" num_components=\"3\"> \n",mesh->mNumVertices);
+                if (!shortened) {
+                    for (unsigned int n = 0; n < mesh->mNumVertices; ++n) {
+                        ioprintf(io,"\t\t%0 8f %0 8f %0 8f\n",
+                            mesh->mVertices[n].x,
+                            mesh->mVertices[n].y,
+                            mesh->mVertices[n].z);
+                    }
+                }
+                ioprintf(io,"\t\t</Positions>\n");
+            }
+
+            // vertex normals
+            if (mesh->HasNormals()) {
+                ioprintf(io,"\t\t<Normals num=\"%i\" set=\"0\" num_components=\"3\"> \n",mesh->mNumVertices);
+                if (!shortened) {
+                    for (unsigned int n = 0; n < mesh->mNumVertices; ++n) {
+                        ioprintf(io,"\t\t%0 8f %0 8f %0 8f\n",
+                            mesh->mNormals[n].x,
+                            mesh->mNormals[n].y,
+                            mesh->mNormals[n].z);
+                    }
+                }
+                else {
+                }
+                ioprintf(io,"\t\t</Normals>\n");
+            }
+
+            // vertex tangents and bitangents
+            if (mesh->HasTangentsAndBitangents()) {
+                ioprintf(io,"\t\t<Tangents num=\"%i\" set=\"0\" num_components=\"3\"> \n",mesh->mNumVertices);
+                if (!shortened) {
+                    for (unsigned int n = 0; n < mesh->mNumVertices; ++n) {
+                        ioprintf(io,"\t\t%0 8f %0 8f %0 8f\n",
+                            mesh->mTangents[n].x,
+                            mesh->mTangents[n].y,
+                            mesh->mTangents[n].z);
+                    }
+                }
+                ioprintf(io,"\t\t</Tangents>\n");
+
+                ioprintf(io,"\t\t<Bitangents num=\"%i\" set=\"0\" num_components=\"3\"> \n",mesh->mNumVertices);
+                if (!shortened) {
+                    for (unsigned int n = 0; n < mesh->mNumVertices; ++n) {
+                        ioprintf(io,"\t\t%0 8f %0 8f %0 8f\n",
+                            mesh->mBitangents[n].x,
+                            mesh->mBitangents[n].y,
+                            mesh->mBitangents[n].z);
+                    }
+                }
+                ioprintf(io,"\t\t</Bitangents>\n");
+            }
+
+            // texture coordinates
+            for (unsigned int a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) {
+                if (!mesh->mTextureCoords[a])
+                    break;
+
+                ioprintf(io,"\t\t<TextureCoords num=\"%i\" set=\"%i\" num_components=\"%i\"> \n",mesh->mNumVertices,
+                    a,mesh->mNumUVComponents[a]);
+
+                if (!shortened) {
+                    if (mesh->mNumUVComponents[a] == 3) {
+                        for (unsigned int n = 0; n < mesh->mNumVertices; ++n) {
+                            ioprintf(io,"\t\t%0 8f %0 8f %0 8f\n",
+                                mesh->mTextureCoords[a][n].x,
+                                mesh->mTextureCoords[a][n].y,
+                                mesh->mTextureCoords[a][n].z);
+                        }
+                    }
+                    else {
+                        for (unsigned int n = 0; n < mesh->mNumVertices; ++n) {
+                            ioprintf(io,"\t\t%0 8f %0 8f\n",
+                                mesh->mTextureCoords[a][n].x,
+                                mesh->mTextureCoords[a][n].y);
+                        }
+                    }
+                }
+                ioprintf(io,"\t\t</TextureCoords>\n");
+            }
+
+            // vertex colors
+            for (unsigned int a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a) {
+                if (!mesh->mColors[a])
+                    break;
+                ioprintf(io,"\t\t<Colors num=\"%i\" set=\"%i\" num_components=\"4\"> \n",mesh->mNumVertices,a);
+                if (!shortened) {
+                    for (unsigned int n = 0; n < mesh->mNumVertices; ++n) {
+                        ioprintf(io,"\t\t%0 8f %0 8f %0 8f %0 8f\n",
+                            mesh->mColors[a][n].r,
+                            mesh->mColors[a][n].g,
+                            mesh->mColors[a][n].b,
+                            mesh->mColors[a][n].a);
+                    }
+                }
+                ioprintf(io,"\t\t</Colors>\n");
+            }
+            ioprintf(io,"\t</Mesh>\n");
+        }
+        ioprintf(io,"</MeshList>\n");
+    }
+    ioprintf(io,"</Scene>\n</ASSIMP>");
+}
+
+} // end of namespace AssxmlExport
+
+void ExportSceneAssxml(const char* pFile, IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/)
+{
+    IOStream * out = pIOSystem->Open( pFile, "wt" );
+    if (!out) return;
+
+    bool shortened = false;
+    AssxmlExport::WriteDump( pScene, out, shortened );
+
+    pIOSystem->Close( out );
+}
+
+} // end of namespace Assimp
+
+#endif // ASSIMP_BUILD_NO_ASSXML_EXPORTER
+#endif // ASSIMP_BUILD_NO_EXPORT

+ 51 - 0
Engine/lib/assimp/code/AssxmlExporter.h

@@ -0,0 +1,51 @@
+/*
+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 AssxmlExporter.h
+ * ASSXML Exporter Main Header
+ */
+#ifndef AI_ASSXMLEXPORTER_H_INC
+#define AI_ASSXMLEXPORTER_H_INC
+
+// nothing really needed here - reserved for future use like properties
+
+#endif

+ 738 - 0
Engine/lib/assimp/code/B3DImporter.cpp

@@ -0,0 +1,738 @@
+/*
+---------------------------------------------------------------------------
+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  B3DImporter.cpp
+ *  @brief Implementation of the b3d importer class
+ */
+
+
+#ifndef ASSIMP_BUILD_NO_B3D_IMPORTER
+
+// internal headers
+#include "B3DImporter.h"
+#include "TextureTransform.h"
+#include "ConvertToLHProcess.h"
+#include <assimp/StringUtils.h>
+#include <memory>
+#include <assimp/IOSystem.hpp>
+#include <assimp/anim.h>
+#include <assimp/scene.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/importerdesc.h>
+
+using namespace Assimp;
+using namespace std;
+
+static const aiImporterDesc desc = {
+    "BlitzBasic 3D Importer",
+    "",
+    "",
+    "http://www.blitzbasic.com/",
+    aiImporterFlags_SupportBinaryFlavour,
+    0,
+    0,
+    0,
+    0,
+    "b3d"
+};
+
+// (fixme, Aramis) quick workaround to get rid of all those signed to unsigned warnings
+#ifdef _MSC_VER
+#	pragma warning (disable: 4018)
+#endif
+
+//#define DEBUG_B3D
+
+template<typename T>
+void DeleteAllBarePointers(std::vector<T>& x)
+{
+    for(auto p : x)
+    {
+        delete p;
+    }
+}
+
+B3DImporter::~B3DImporter()
+{
+}
+
+// ------------------------------------------------------------------------------------------------
+bool B3DImporter::CanRead( const std::string& pFile, IOSystem* /*pIOHandler*/, bool /*checkSig*/) const{
+
+    size_t pos=pFile.find_last_of( '.' );
+    if( pos==string::npos ) return false;
+
+    string ext=pFile.substr( pos+1 );
+    if( ext.size()!=3 ) return false;
+
+    return (ext[0]=='b' || ext[0]=='B') && (ext[1]=='3') && (ext[2]=='d' || ext[2]=='D');
+}
+
+// ------------------------------------------------------------------------------------------------
+// Loader meta information
+const aiImporterDesc* B3DImporter::GetInfo () const
+{
+    return &desc;
+}
+
+#ifdef DEBUG_B3D
+    extern "C"{ void _stdcall AllocConsole(); }
+#endif
+// ------------------------------------------------------------------------------------------------
+void B3DImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler){
+
+#ifdef DEBUG_B3D
+    AllocConsole();
+    freopen( "conin$","r",stdin );
+    freopen( "conout$","w",stdout );
+    freopen( "conout$","w",stderr );
+    cout<<"Hello world from the B3DImporter!"<<endl;
+#endif
+
+    std::unique_ptr<IOStream> file( pIOHandler->Open( pFile));
+
+    // Check whether we can read from the file
+    if( file.get() == NULL)
+        throw DeadlyImportError( "Failed to open B3D file " + pFile + ".");
+
+    // check whether the .b3d file is large enough to contain
+    // at least one chunk.
+    size_t fileSize = file->FileSize();
+    if( fileSize<8 ) throw DeadlyImportError( "B3D File is too small.");
+
+    _pos=0;
+    _buf.resize( fileSize );
+    file->Read( &_buf[0],1,fileSize );
+    _stack.clear();
+
+    ReadBB3D( pScene );
+}
+
+// ------------------------------------------------------------------------------------------------
+AI_WONT_RETURN void B3DImporter::Oops(){
+    throw DeadlyImportError( "B3D Importer - INTERNAL ERROR" );
+}
+
+// ------------------------------------------------------------------------------------------------
+AI_WONT_RETURN void B3DImporter::Fail( string str ){
+#ifdef DEBUG_B3D
+    cout<<"Error in B3D file data: "<<str<<endl;
+#endif
+    throw DeadlyImportError( "B3D Importer - error in B3D file data: "+str );
+}
+
+// ------------------------------------------------------------------------------------------------
+int B3DImporter::ReadByte(){
+    if( _pos<_buf.size() ) return _buf[_pos++];
+    Fail( "EOF" );
+    return 0;
+}
+
+// ------------------------------------------------------------------------------------------------
+int B3DImporter::ReadInt(){
+    if( _pos+4<=_buf.size() ){
+        int n;
+        memcpy(&n, &_buf[_pos], 4);
+        _pos+=4;
+        return n;
+    }
+    Fail( "EOF" );
+    return 0;
+}
+
+// ------------------------------------------------------------------------------------------------
+float B3DImporter::ReadFloat(){
+    if( _pos+4<=_buf.size() ){
+        float n;
+        memcpy(&n, &_buf[_pos], 4);
+        _pos+=4;
+        return n;
+    }
+    Fail( "EOF" );
+    return 0.0f;
+}
+
+// ------------------------------------------------------------------------------------------------
+aiVector2D B3DImporter::ReadVec2(){
+    float x=ReadFloat();
+    float y=ReadFloat();
+    return aiVector2D( x,y );
+}
+
+// ------------------------------------------------------------------------------------------------
+aiVector3D B3DImporter::ReadVec3(){
+    float x=ReadFloat();
+    float y=ReadFloat();
+    float z=ReadFloat();
+    return aiVector3D( x,y,z );
+}
+
+// ------------------------------------------------------------------------------------------------
+aiQuaternion B3DImporter::ReadQuat(){
+    // (aramis_acg) Fix to adapt the loader to changed quat orientation
+    float w=-ReadFloat();
+    float x=ReadFloat();
+    float y=ReadFloat();
+    float z=ReadFloat();
+    return aiQuaternion( w,x,y,z );
+}
+
+// ------------------------------------------------------------------------------------------------
+string B3DImporter::ReadString(){
+    string str;
+    while( _pos<_buf.size() ){
+        char c=(char)ReadByte();
+        if( !c ) return str;
+        str+=c;
+    }
+    Fail( "EOF" );
+    return string();
+}
+
+// ------------------------------------------------------------------------------------------------
+string B3DImporter::ReadChunk(){
+    string tag;
+    for( int i=0;i<4;++i ){
+        tag+=char( ReadByte() );
+    }
+#ifdef DEBUG_B3D
+//	cout<<"ReadChunk:"<<tag<<endl;
+#endif
+    unsigned sz=(unsigned)ReadInt();
+    _stack.push_back( _pos+sz );
+    return tag;
+}
+
+// ------------------------------------------------------------------------------------------------
+void B3DImporter::ExitChunk(){
+    _pos=_stack.back();
+    _stack.pop_back();
+}
+
+// ------------------------------------------------------------------------------------------------
+unsigned B3DImporter::ChunkSize(){
+    return _stack.back()-_pos;
+}
+// ------------------------------------------------------------------------------------------------
+
+template<class T>
+T *B3DImporter::to_array( const vector<T> &v ){
+    if( v.empty() ) {
+        return 0;
+    }
+    T *p=new T[ v.size() ];
+    for( size_t i=0;i<v.size();++i ){
+        p[i]=v[i];
+    }
+    return p;
+}
+
+
+// ------------------------------------------------------------------------------------------------
+template<class T>
+T **unique_to_array( vector<std::unique_ptr<T> > &v ){
+    if( v.empty() ) {
+        return 0;
+    }
+    T **p = new T*[ v.size() ];
+    for( size_t i = 0; i < v.size(); ++i ){
+        p[i] = v[i].release();
+    }
+    return p;
+}
+
+
+// ------------------------------------------------------------------------------------------------
+void B3DImporter::ReadTEXS(){
+    while( ChunkSize() ){
+        string name=ReadString();
+        /*int flags=*/ReadInt();
+        /*int blend=*/ReadInt();
+        /*aiVector2D pos=*/ReadVec2();
+        /*aiVector2D scale=*/ReadVec2();
+        /*float rot=*/ReadFloat();
+
+        _textures.push_back( name );
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void B3DImporter::ReadBRUS(){
+    int n_texs=ReadInt();
+    if( n_texs<0 || n_texs>8 ){
+        Fail( "Bad texture count" );
+    }
+    while( ChunkSize() ){
+        string name=ReadString();
+        aiVector3D color=ReadVec3();
+        float alpha=ReadFloat();
+        float shiny=ReadFloat();
+        /*int blend=**/ReadInt();
+        int fx=ReadInt();
+
+        std::unique_ptr<aiMaterial> mat(new aiMaterial);
+
+        // Name
+        aiString ainame( name );
+        mat->AddProperty( &ainame,AI_MATKEY_NAME );
+
+        // Diffuse color
+        mat->AddProperty( &color,1,AI_MATKEY_COLOR_DIFFUSE );
+
+        // Opacity
+        mat->AddProperty( &alpha,1,AI_MATKEY_OPACITY );
+
+        // Specular color
+        aiColor3D speccolor( shiny,shiny,shiny );
+        mat->AddProperty( &speccolor,1,AI_MATKEY_COLOR_SPECULAR );
+
+        // Specular power
+        float specpow=shiny*128;
+        mat->AddProperty( &specpow,1,AI_MATKEY_SHININESS );
+
+        // Double sided
+        if( fx & 0x10 ){
+            int i=1;
+            mat->AddProperty( &i,1,AI_MATKEY_TWOSIDED );
+        }
+
+        //Textures
+        for( int i=0;i<n_texs;++i ){
+            int texid=ReadInt();
+            if( texid<-1 || (texid>=0 && texid>=static_cast<int>(_textures.size())) ){
+                Fail( "Bad texture id" );
+            }
+            if( i==0 && texid>=0 ){
+                aiString texname( _textures[texid] );
+                mat->AddProperty( &texname,AI_MATKEY_TEXTURE_DIFFUSE(0) );
+            }
+        }
+        _materials.emplace_back( std::move(mat) );
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void B3DImporter::ReadVRTS(){
+    _vflags=ReadInt();
+    _tcsets=ReadInt();
+    _tcsize=ReadInt();
+    if( _tcsets<0 || _tcsets>4 || _tcsize<0 || _tcsize>4 ){
+        Fail( "Bad texcoord data" );
+    }
+
+    int sz=12+(_vflags&1?12:0)+(_vflags&2?16:0)+(_tcsets*_tcsize*4);
+    int n_verts=ChunkSize()/sz;
+
+    int v0=static_cast<int>(_vertices.size());
+    _vertices.resize( v0+n_verts );
+
+    for( int i=0;i<n_verts;++i ){
+        Vertex &v=_vertices[v0+i];
+
+        memset( v.bones,0,sizeof(v.bones) );
+        memset( v.weights,0,sizeof(v.weights) );
+
+        v.vertex=ReadVec3();
+
+        if( _vflags & 1 ) v.normal=ReadVec3();
+
+        if( _vflags & 2 ) ReadQuat();	//skip v 4bytes...
+
+        for( int i=0;i<_tcsets;++i ){
+            float t[4]={0,0,0,0};
+            for( int j=0;j<_tcsize;++j ){
+                t[j]=ReadFloat();
+            }
+            t[1]=1-t[1];
+            if( !i ) v.texcoords=aiVector3D( t[0],t[1],t[2] );
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void B3DImporter::ReadTRIS( int v0 ){
+    int matid=ReadInt();
+    if( matid==-1 ){
+        matid=0;
+    }else if( matid<0 || matid>=(int)_materials.size() ){
+#ifdef DEBUG_B3D
+        cout<<"material id="<<matid<<endl;
+#endif
+        Fail( "Bad material id" );
+    }
+
+    std::unique_ptr<aiMesh> mesh(new aiMesh);
+
+    mesh->mMaterialIndex=matid;
+    mesh->mNumFaces=0;
+    mesh->mPrimitiveTypes=aiPrimitiveType_TRIANGLE;
+
+    int n_tris=ChunkSize()/12;
+    aiFace *face=mesh->mFaces=new aiFace[n_tris];
+
+    for( int i=0;i<n_tris;++i ){
+        int i0=ReadInt()+v0;
+        int i1=ReadInt()+v0;
+        int i2=ReadInt()+v0;
+        if( i0<0 || i0>=(int)_vertices.size() || i1<0 || i1>=(int)_vertices.size() || i2<0 || i2>=(int)_vertices.size() ){
+#ifdef DEBUG_B3D
+            cout<<"Bad triangle index: i0="<<i0<<", i1="<<i1<<", i2="<<i2<<endl;
+#endif
+            Fail( "Bad triangle index" );
+            continue;
+        }
+        face->mNumIndices=3;
+        face->mIndices=new unsigned[3];
+        face->mIndices[0]=i0;
+        face->mIndices[1]=i1;
+        face->mIndices[2]=i2;
+        ++mesh->mNumFaces;
+        ++face;
+    }
+
+    _meshes.emplace_back( std::move(mesh) );
+}
+
+// ------------------------------------------------------------------------------------------------
+void B3DImporter::ReadMESH(){
+    /*int matid=*/ReadInt();
+
+    int v0= static_cast<int>(_vertices.size());
+
+    while( ChunkSize() ){
+        string t=ReadChunk();
+        if( t=="VRTS" ){
+            ReadVRTS();
+        }else if( t=="TRIS" ){
+            ReadTRIS( v0 );
+        }
+        ExitChunk();
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void B3DImporter::ReadBONE( int id ){
+    while( ChunkSize() ){
+        int vertex=ReadInt();
+        float weight=ReadFloat();
+        if( vertex<0 || vertex>=(int)_vertices.size() ){
+            Fail( "Bad vertex index" );
+        }
+
+        Vertex &v=_vertices[vertex];
+        int i;
+        for( i=0;i<4;++i ){
+            if( !v.weights[i] ){
+                v.bones[i]=id;
+                v.weights[i]=weight;
+                break;
+            }
+        }
+#ifdef DEBUG_B3D
+        if( i==4 ){
+            cout<<"Too many bone weights"<<endl;
+        }
+#endif
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void B3DImporter::ReadKEYS( aiNodeAnim *nodeAnim ){
+    vector<aiVectorKey> trans,scale;
+    vector<aiQuatKey> rot;
+    int flags=ReadInt();
+    while( ChunkSize() ){
+        int frame=ReadInt();
+        if( flags & 1 ){
+            trans.push_back( aiVectorKey( frame,ReadVec3() ) );
+        }
+        if( flags & 2 ){
+            scale.push_back( aiVectorKey( frame,ReadVec3() ) );
+        }
+        if( flags & 4 ){
+            rot.push_back( aiQuatKey( frame,ReadQuat() ) );
+        }
+    }
+
+    if( flags & 1 ){
+        nodeAnim->mNumPositionKeys=static_cast<unsigned int>(trans.size());
+        nodeAnim->mPositionKeys=to_array( trans );
+    }
+
+    if( flags & 2 ){
+        nodeAnim->mNumScalingKeys=static_cast<unsigned int>(scale.size());
+        nodeAnim->mScalingKeys=to_array( scale );
+    }
+
+    if( flags & 4 ){
+        nodeAnim->mNumRotationKeys=static_cast<unsigned int>(rot.size());
+        nodeAnim->mRotationKeys=to_array( rot );
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void B3DImporter::ReadANIM(){
+    /*int flags=*/ReadInt();
+    int frames=ReadInt();
+    float fps=ReadFloat();
+
+    std::unique_ptr<aiAnimation> anim(new aiAnimation);
+
+    anim->mDuration=frames;
+    anim->mTicksPerSecond=fps;
+    _animations.emplace_back( std::move(anim) );
+}
+
+// ------------------------------------------------------------------------------------------------
+aiNode *B3DImporter::ReadNODE( aiNode *parent ){
+
+    string name=ReadString();
+    aiVector3D t=ReadVec3();
+    aiVector3D s=ReadVec3();
+    aiQuaternion r=ReadQuat();
+
+    aiMatrix4x4 trans,scale,rot;
+
+    aiMatrix4x4::Translation( t,trans );
+    aiMatrix4x4::Scaling( s,scale );
+    rot=aiMatrix4x4( r.GetMatrix() );
+
+    aiMatrix4x4 tform=trans * rot * scale;
+
+    int nodeid=static_cast<int>(_nodes.size());
+
+    aiNode *node=new aiNode( name );
+    _nodes.push_back( node );
+
+    node->mParent=parent;
+    node->mTransformation=tform;
+
+    std::unique_ptr<aiNodeAnim> nodeAnim;
+    vector<unsigned> meshes;
+    vector<aiNode*> children;
+
+    while( ChunkSize() ){
+        string t=ReadChunk();
+        if( t=="MESH" ){
+            unsigned int n= static_cast<unsigned int>(_meshes.size());
+            ReadMESH();
+            for( unsigned int i=n;i<static_cast<unsigned int>(_meshes.size());++i ){
+                meshes.push_back( i );
+            }
+        }else if( t=="BONE" ){
+            ReadBONE( nodeid );
+        }else if( t=="ANIM" ){
+            ReadANIM();
+        }else if( t=="KEYS" ){
+            if( !nodeAnim ){
+                nodeAnim.reset(new aiNodeAnim);
+                nodeAnim->mNodeName=node->mName;
+            }
+            ReadKEYS( nodeAnim.get() );
+        }else if( t=="NODE" ){
+            aiNode *child=ReadNODE( node );
+            children.push_back( child );
+        }
+        ExitChunk();
+    }
+
+    if (nodeAnim) {
+        _nodeAnims.emplace_back( std::move(nodeAnim) );
+    }
+
+    node->mNumMeshes= static_cast<unsigned int>(meshes.size());
+    node->mMeshes=to_array( meshes );
+
+    node->mNumChildren=static_cast<unsigned int>(children.size());
+    node->mChildren=to_array( children );
+
+    return node;
+}
+
+// ------------------------------------------------------------------------------------------------
+void B3DImporter::ReadBB3D( aiScene *scene ){
+
+    _textures.clear();
+
+    _materials.clear();
+
+    _vertices.clear();
+
+    _meshes.clear();
+
+    DeleteAllBarePointers(_nodes);
+    _nodes.clear();
+
+    _nodeAnims.clear();
+
+    _animations.clear();
+
+    string t=ReadChunk();
+    if( t=="BB3D" ){
+        int version=ReadInt();
+
+        if (!DefaultLogger::isNullLogger()) {
+            char dmp[128];
+            ai_snprintf(dmp, 128, "B3D file format version: %i",version);
+            ASSIMP_LOG_INFO(dmp);
+        }
+
+        while( ChunkSize() ){
+            string t=ReadChunk();
+            if( t=="TEXS" ){
+                ReadTEXS();
+            }else if( t=="BRUS" ){
+                ReadBRUS();
+            }else if( t=="NODE" ){
+                ReadNODE( 0 );
+            }
+            ExitChunk();
+        }
+    }
+    ExitChunk();
+
+    if( !_nodes.size() ) Fail( "No nodes" );
+
+    if( !_meshes.size() ) Fail( "No meshes" );
+
+    //Fix nodes/meshes/bones
+    for(size_t i=0;i<_nodes.size();++i ){
+        aiNode *node=_nodes[i];
+
+        for( size_t j=0;j<node->mNumMeshes;++j ){
+            aiMesh *mesh = _meshes[node->mMeshes[j]].get();
+
+            int n_tris=mesh->mNumFaces;
+            int n_verts=mesh->mNumVertices=n_tris * 3;
+
+            aiVector3D *mv=mesh->mVertices=new aiVector3D[ n_verts ],*mn=0,*mc=0;
+            if( _vflags & 1 ) mn=mesh->mNormals=new aiVector3D[ n_verts ];
+            if( _tcsets ) mc=mesh->mTextureCoords[0]=new aiVector3D[ n_verts ];
+
+            aiFace *face=mesh->mFaces;
+
+            vector< vector<aiVertexWeight> > vweights( _nodes.size() );
+
+            for( int i=0;i<n_verts;i+=3 ){
+                for( int j=0;j<3;++j ){
+                    Vertex &v=_vertices[face->mIndices[j]];
+
+                    *mv++=v.vertex;
+                    if( mn ) *mn++=v.normal;
+                    if( mc ) *mc++=v.texcoords;
+
+                    face->mIndices[j]=i+j;
+
+                    for( int k=0;k<4;++k ){
+                        if( !v.weights[k] ) break;
+
+                        int bone=v.bones[k];
+                        float weight=v.weights[k];
+
+                        vweights[bone].push_back( aiVertexWeight(i+j,weight) );
+                    }
+                }
+                ++face;
+            }
+
+            vector<aiBone*> bones;
+            for(size_t i=0;i<vweights.size();++i ){
+                vector<aiVertexWeight> &weights=vweights[i];
+                if( !weights.size() ) continue;
+
+                aiBone *bone=new aiBone;
+                bones.push_back( bone );
+
+                aiNode *bnode=_nodes[i];
+
+                bone->mName=bnode->mName;
+                bone->mNumWeights= static_cast<unsigned int>(weights.size());
+                bone->mWeights=to_array( weights );
+
+                aiMatrix4x4 mat=bnode->mTransformation;
+                while( bnode->mParent ){
+                    bnode=bnode->mParent;
+                    mat=bnode->mTransformation * mat;
+                }
+                bone->mOffsetMatrix=mat.Inverse();
+            }
+            mesh->mNumBones= static_cast<unsigned int>(bones.size());
+            mesh->mBones=to_array( bones );
+        }
+    }
+
+    //nodes
+    scene->mRootNode=_nodes[0];
+    _nodes.clear();  // node ownership now belongs to scene
+
+    //material
+    if( !_materials.size() ){
+        _materials.emplace_back( std::unique_ptr<aiMaterial>(new aiMaterial) );
+    }
+    scene->mNumMaterials= static_cast<unsigned int>(_materials.size());
+    scene->mMaterials = unique_to_array( _materials );
+
+    //meshes
+    scene->mNumMeshes= static_cast<unsigned int>(_meshes.size());
+    scene->mMeshes = unique_to_array( _meshes );
+
+    //animations
+    if( _animations.size()==1 && _nodeAnims.size() ){
+
+        aiAnimation *anim = _animations.back().get();
+        anim->mNumChannels=static_cast<unsigned int>(_nodeAnims.size());
+        anim->mChannels = unique_to_array( _nodeAnims );
+
+        scene->mNumAnimations=static_cast<unsigned int>(_animations.size());
+        scene->mAnimations=unique_to_array( _animations );
+    }
+
+    // convert to RH
+    MakeLeftHandedProcess makeleft;
+    makeleft.Execute( scene );
+
+    FlipWindingOrderProcess flip;
+    flip.Execute( scene );
+}
+
+#endif // !! ASSIMP_BUILD_NO_B3D_IMPORTER

+ 134 - 0
Engine/lib/assimp/code/B3DImporter.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.
+
+----------------------------------------------------------------------
+*/
+
+/** @file Definition of the .b3d importer class. */
+
+#ifndef AI_B3DIMPORTER_H_INC
+#define AI_B3DIMPORTER_H_INC
+
+#include <assimp/types.h>
+#include <assimp/mesh.h>
+#include <assimp/material.h>
+#include <assimp/BaseImporter.h>
+
+#include <memory>
+#include <vector>
+
+struct aiNodeAnim;
+struct aiNode;
+struct aiAnimation;
+
+namespace Assimp{
+
+class B3DImporter : public BaseImporter{
+public:
+    B3DImporter() = default;
+    virtual ~B3DImporter();
+
+    virtual bool CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const;
+
+protected:
+
+    virtual const aiImporterDesc* GetInfo () const;
+    virtual void InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler);
+
+private:
+
+    int ReadByte();
+    int ReadInt();
+    float ReadFloat();
+    aiVector2D ReadVec2();
+    aiVector3D ReadVec3();
+    aiQuaternion ReadQuat();
+    std::string ReadString();
+    std::string ReadChunk();
+    void ExitChunk();
+    unsigned ChunkSize();
+
+    template<class T>
+    T *to_array( const std::vector<T> &v );
+
+    struct Vertex{
+        aiVector3D vertex;
+        aiVector3D normal;
+        aiVector3D texcoords;
+        unsigned char bones[4];
+        float weights[4];
+    };
+
+    AI_WONT_RETURN void Oops() AI_WONT_RETURN_SUFFIX;
+    AI_WONT_RETURN void Fail( std::string str ) AI_WONT_RETURN_SUFFIX;
+
+    void ReadTEXS();
+    void ReadBRUS();
+
+    void ReadVRTS();
+    void ReadTRIS( int v0 );
+    void ReadMESH();
+    void ReadBONE( int id );
+    void ReadKEYS( aiNodeAnim *nodeAnim );
+    void ReadANIM();
+
+    aiNode *ReadNODE( aiNode *parent );
+
+    void ReadBB3D( aiScene *scene );
+
+    unsigned _pos;
+//  unsigned _size;
+    std::vector<unsigned char> _buf;
+    std::vector<unsigned> _stack;
+
+    std::vector<std::string> _textures;
+    std::vector<std::unique_ptr<aiMaterial> > _materials;
+
+    int _vflags,_tcsets,_tcsize;
+    std::vector<Vertex> _vertices;
+
+    std::vector<aiNode*> _nodes;
+    std::vector<std::unique_ptr<aiMesh> > _meshes;
+    std::vector<std::unique_ptr<aiNodeAnim> > _nodeAnims;
+    std::vector<std::unique_ptr<aiAnimation> > _animations;
+};
+
+}
+
+#endif

+ 578 - 0
Engine/lib/assimp/code/BVHLoader.cpp

@@ -0,0 +1,578 @@
+/** Implementation of the BVH loader */
+/*
+---------------------------------------------------------------------------
+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_BVH_IMPORTER
+
+#include "BVHLoader.h"
+#include <assimp/fast_atof.h>
+#include <assimp/SkeletonMeshBuilder.h>
+#include <assimp/Importer.hpp>
+#include <memory>
+#include <assimp/TinyFormatter.h>
+#include <assimp/IOSystem.hpp>
+#include <assimp/scene.h>
+#include <assimp/importerdesc.h>
+#include <map>
+
+using namespace Assimp;
+using namespace Assimp::Formatter;
+
+static const aiImporterDesc desc = {
+    "BVH Importer (MoCap)",
+    "",
+    "",
+    "",
+    aiImporterFlags_SupportTextFlavour,
+    0,
+    0,
+    0,
+    0,
+    "bvh"
+};
+
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by Importer
+BVHLoader::BVHLoader()
+    : mLine(),
+    mAnimTickDuration(),
+    mAnimNumFrames(),
+    noSkeletonMesh()
+{}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+BVHLoader::~BVHLoader()
+{}
+
+// ------------------------------------------------------------------------------------------------
+// Returns whether the class can handle the format of the given file.
+bool BVHLoader::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool cs) const
+{
+    // check file extension
+    const std::string extension = GetExtension(pFile);
+
+    if( extension == "bvh")
+        return true;
+
+    if ((!extension.length() || cs) && pIOHandler) {
+        const char* tokens[] = {"HIERARCHY"};
+        return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1);
+    }
+    return false;
+}
+
+// ------------------------------------------------------------------------------------------------
+void BVHLoader::SetupProperties(const Importer* pImp)
+{
+    noSkeletonMesh = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_NO_SKELETON_MESHES,0) != 0;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Loader meta information
+const aiImporterDesc* BVHLoader::GetInfo () const
+{
+    return &desc;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Imports the given file into the given scene structure.
+void BVHLoader::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler)
+{
+    mFileName = pFile;
+
+    // read file into memory
+    std::unique_ptr<IOStream> file( pIOHandler->Open( pFile));
+    if( file.get() == NULL)
+        throw DeadlyImportError( "Failed to open file " + pFile + ".");
+
+    size_t fileSize = file->FileSize();
+    if( fileSize == 0)
+        throw DeadlyImportError( "File is too small.");
+
+    mBuffer.resize( fileSize);
+    file->Read( &mBuffer.front(), 1, fileSize);
+
+    // start reading
+    mReader = mBuffer.begin();
+    mLine = 1;
+    ReadStructure( pScene);
+
+    if (!noSkeletonMesh) {
+        // build a dummy mesh for the skeleton so that we see something at least
+        SkeletonMeshBuilder meshBuilder( pScene);
+    }
+
+    // construct an animation from all the motion data we read
+    CreateAnimation( pScene);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the file
+void BVHLoader::ReadStructure( aiScene* pScene)
+{
+    // first comes hierarchy
+    std::string header = GetNextToken();
+    if( header != "HIERARCHY")
+        ThrowException( "Expected header string \"HIERARCHY\".");
+    ReadHierarchy( pScene);
+
+    // then comes the motion data
+    std::string motion = GetNextToken();
+    if( motion != "MOTION")
+        ThrowException( "Expected beginning of motion data \"MOTION\".");
+    ReadMotion( pScene);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the hierarchy
+void BVHLoader::ReadHierarchy( aiScene* pScene)
+{
+    std::string root = GetNextToken();
+    if( root != "ROOT")
+        ThrowException( "Expected root node \"ROOT\".");
+
+    // Go read the hierarchy from here
+    pScene->mRootNode = ReadNode();
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a node and recursively its childs and returns the created node;
+aiNode* BVHLoader::ReadNode()
+{
+    // first token is name
+    std::string nodeName = GetNextToken();
+    if( nodeName.empty() || nodeName == "{")
+        ThrowException( format() << "Expected node name, but found \"" << nodeName << "\"." );
+
+    // then an opening brace should follow
+    std::string openBrace = GetNextToken();
+    if( openBrace != "{")
+        ThrowException( format() << "Expected opening brace \"{\", but found \"" << openBrace << "\"." );
+
+    // Create a node
+    aiNode* node = new aiNode( nodeName);
+    std::vector<aiNode*> childNodes;
+
+    // and create an bone entry for it
+    mNodes.push_back( Node( node));
+    Node& internNode = mNodes.back();
+
+    // now read the node's contents
+    std::string siteToken;
+    while( 1)
+    {
+        std::string token = GetNextToken();
+
+        // node offset to parent node
+        if( token == "OFFSET")
+            ReadNodeOffset( node);
+        else if( token == "CHANNELS")
+            ReadNodeChannels( internNode);
+        else if( token == "JOINT")
+        {
+            // child node follows
+            aiNode* child = ReadNode();
+            child->mParent = node;
+            childNodes.push_back( child);
+        }
+        else if( token == "End")
+        {
+            // The real symbol is "End Site". Second part comes in a separate token
+            siteToken.clear();
+            siteToken = GetNextToken();
+            if( siteToken != "Site")
+                ThrowException( format() << "Expected \"End Site\" keyword, but found \"" << token << " " << siteToken << "\"." );
+
+            aiNode* child = ReadEndSite( nodeName);
+            child->mParent = node;
+            childNodes.push_back( child);
+        }
+        else if( token == "}")
+        {
+            // we're done with that part of the hierarchy
+            break;
+        } else
+        {
+            // everything else is a parse error
+            ThrowException( format() << "Unknown keyword \"" << token << "\"." );
+        }
+    }
+
+    // add the child nodes if there are any
+    if( childNodes.size() > 0)
+    {
+        node->mNumChildren = static_cast<unsigned int>(childNodes.size());
+        node->mChildren = new aiNode*[node->mNumChildren];
+        std::copy( childNodes.begin(), childNodes.end(), node->mChildren);
+    }
+
+    // and return the sub-hierarchy we built here
+    return node;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads an end node and returns the created node.
+aiNode* BVHLoader::ReadEndSite( const std::string& pParentName)
+{
+    // check opening brace
+    std::string openBrace = GetNextToken();
+    if( openBrace != "{")
+        ThrowException( format() << "Expected opening brace \"{\", but found \"" << openBrace << "\".");
+
+    // Create a node
+    aiNode* node = new aiNode( "EndSite_" + pParentName);
+
+    // now read the node's contents. Only possible entry is "OFFSET"
+    std::string token;
+    while( 1) {
+        token.clear();
+        token = GetNextToken();
+
+        // end node's offset
+        if( token == "OFFSET") {
+            ReadNodeOffset( node);
+        } else if( token == "}") {
+            // we're done with the end node
+            break;
+        } else {
+            // everything else is a parse error
+            ThrowException( format() << "Unknown keyword \"" << token << "\"." );
+        }
+    }
+
+    // and return the sub-hierarchy we built here
+    return node;
+}
+// ------------------------------------------------------------------------------------------------
+// Reads a node offset for the given node
+void BVHLoader::ReadNodeOffset( aiNode* pNode)
+{
+    // Offset consists of three floats to read
+    aiVector3D offset;
+    offset.x = GetNextTokenAsFloat();
+    offset.y = GetNextTokenAsFloat();
+    offset.z = GetNextTokenAsFloat();
+
+    // build a transformation matrix from it
+    pNode->mTransformation = aiMatrix4x4( 1.0f, 0.0f, 0.0f, offset.x,
+                                          0.0f, 1.0f, 0.0f, offset.y,
+                                          0.0f, 0.0f, 1.0f, offset.z,
+                                          0.0f, 0.0f, 0.0f, 1.0f);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the animation channels for the given node
+void BVHLoader::ReadNodeChannels( BVHLoader::Node& pNode)
+{
+    // number of channels. Use the float reader because we're lazy
+    float numChannelsFloat = GetNextTokenAsFloat();
+    unsigned int numChannels = (unsigned int) numChannelsFloat;
+
+    for( unsigned int a = 0; a < numChannels; a++)
+    {
+        std::string channelToken = GetNextToken();
+
+        if( channelToken == "Xposition")
+            pNode.mChannels.push_back( Channel_PositionX);
+        else if( channelToken == "Yposition")
+            pNode.mChannels.push_back( Channel_PositionY);
+        else if( channelToken == "Zposition")
+            pNode.mChannels.push_back( Channel_PositionZ);
+        else if( channelToken == "Xrotation")
+            pNode.mChannels.push_back( Channel_RotationX);
+        else if( channelToken == "Yrotation")
+            pNode.mChannels.push_back( Channel_RotationY);
+        else if( channelToken == "Zrotation")
+            pNode.mChannels.push_back( Channel_RotationZ);
+        else
+            ThrowException( format() << "Invalid channel specifier \"" << channelToken << "\"." );
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the motion data
+void BVHLoader::ReadMotion( aiScene* /*pScene*/)
+{
+    // Read number of frames
+    std::string tokenFrames = GetNextToken();
+    if( tokenFrames != "Frames:")
+        ThrowException( format() << "Expected frame count \"Frames:\", but found \"" << tokenFrames << "\".");
+
+    float numFramesFloat = GetNextTokenAsFloat();
+    mAnimNumFrames = (unsigned int) numFramesFloat;
+
+    // Read frame duration
+    std::string tokenDuration1 = GetNextToken();
+    std::string tokenDuration2 = GetNextToken();
+    if( tokenDuration1 != "Frame" || tokenDuration2 != "Time:")
+        ThrowException( format() << "Expected frame duration \"Frame Time:\", but found \"" << tokenDuration1 << " " << tokenDuration2 << "\"." );
+
+    mAnimTickDuration = GetNextTokenAsFloat();
+
+    // resize value vectors for each node
+    for( std::vector<Node>::iterator it = mNodes.begin(); it != mNodes.end(); ++it)
+        it->mChannelValues.reserve( it->mChannels.size() * mAnimNumFrames);
+
+    // now read all the data and store it in the corresponding node's value vector
+    for( unsigned int frame = 0; frame < mAnimNumFrames; ++frame)
+    {
+        // on each line read the values for all nodes
+        for( std::vector<Node>::iterator it = mNodes.begin(); it != mNodes.end(); ++it)
+        {
+            // get as many values as the node has channels
+            for( unsigned int c = 0; c < it->mChannels.size(); ++c)
+                it->mChannelValues.push_back( GetNextTokenAsFloat());
+        }
+
+        // after one frame worth of values for all nodes there should be a newline, but we better don't rely on it
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Retrieves the next token
+std::string BVHLoader::GetNextToken()
+{
+    // skip any preceding whitespace
+    while( mReader != mBuffer.end())
+    {
+        if( !isspace( *mReader))
+            break;
+
+        // count lines
+        if( *mReader == '\n')
+            mLine++;
+
+        ++mReader;
+    }
+
+    // collect all chars till the next whitespace. BVH is easy in respect to that.
+    std::string token;
+    while( mReader != mBuffer.end())
+    {
+        if( isspace( *mReader))
+            break;
+
+        token.push_back( *mReader);
+        ++mReader;
+
+        // little extra logic to make sure braces are counted correctly
+        if( token == "{" || token == "}")
+            break;
+    }
+
+    // empty token means end of file, which is just fine
+    return token;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the next token as a float
+float BVHLoader::GetNextTokenAsFloat()
+{
+    std::string token = GetNextToken();
+    if( token.empty())
+        ThrowException( "Unexpected end of file while trying to read a float");
+
+    // check if the float is valid by testing if the atof() function consumed every char of the token
+    const char* ctoken = token.c_str();
+    float result = 0.0f;
+    ctoken = fast_atoreal_move<float>( ctoken, result);
+
+    if( ctoken != token.c_str() + token.length())
+        ThrowException( format() << "Expected a floating point number, but found \"" << token << "\"." );
+
+    return result;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Aborts the file reading with an exception
+AI_WONT_RETURN void BVHLoader::ThrowException( const std::string& pError)
+{
+    throw DeadlyImportError( format() << mFileName << ":" << mLine << " - " << pError);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Constructs an animation for the motion data and stores it in the given scene
+void BVHLoader::CreateAnimation( aiScene* pScene)
+{
+    // create the animation
+    pScene->mNumAnimations = 1;
+    pScene->mAnimations = new aiAnimation*[1];
+    aiAnimation* anim = new aiAnimation;
+    pScene->mAnimations[0] = anim;
+
+    // put down the basic parameters
+    anim->mName.Set( "Motion");
+    anim->mTicksPerSecond = 1.0 / double( mAnimTickDuration);
+    anim->mDuration = double( mAnimNumFrames - 1);
+
+    // now generate the tracks for all nodes
+    anim->mNumChannels = static_cast<unsigned int>(mNodes.size());
+    anim->mChannels = new aiNodeAnim*[anim->mNumChannels];
+
+    // FIX: set the array elements to NULL to ensure proper deletion if an exception is thrown
+    for (unsigned int i = 0; i < anim->mNumChannels;++i)
+        anim->mChannels[i] = NULL;
+
+    for( unsigned int a = 0; a < anim->mNumChannels; a++)
+    {
+        const Node& node = mNodes[a];
+        const std::string nodeName = std::string( node.mNode->mName.data );
+        aiNodeAnim* nodeAnim = new aiNodeAnim;
+        anim->mChannels[a] = nodeAnim;
+        nodeAnim->mNodeName.Set( nodeName);
+		std::map<BVHLoader::ChannelType, int> channelMap;
+
+		//Build map of channels 
+		for (unsigned int channel = 0; channel < node.mChannels.size(); ++channel)
+		{
+			channelMap[node.mChannels[channel]] = channel;
+		}
+
+        // translational part, if given
+        if( node.mChannels.size() == 6)
+        {
+            nodeAnim->mNumPositionKeys = mAnimNumFrames;
+            nodeAnim->mPositionKeys = new aiVectorKey[mAnimNumFrames];
+            aiVectorKey* poskey = nodeAnim->mPositionKeys;
+            for( unsigned int fr = 0; fr < mAnimNumFrames; ++fr)
+            {
+                poskey->mTime = double( fr);
+
+                // Now compute all translations 
+                for(BVHLoader::ChannelType channel = Channel_PositionX; channel <= Channel_PositionZ; channel = (BVHLoader::ChannelType)(channel +1))
+                {
+					//Find channel in node
+					std::map<BVHLoader::ChannelType, int>::iterator mapIter = channelMap.find(channel);
+
+					if (mapIter == channelMap.end())
+						throw DeadlyImportError("Missing position channel in node " + nodeName);
+					else {
+						int channelIdx = mapIter->second;
+						switch (channel) {
+						    case Channel_PositionX: 
+                                poskey->mValue.x = node.mChannelValues[fr * node.mChannels.size() + channelIdx]; 
+                                break;
+						    case Channel_PositionY: 
+                                poskey->mValue.y = node.mChannelValues[fr * node.mChannels.size() + channelIdx]; 
+                                break;
+						    case Channel_PositionZ: 
+                                poskey->mValue.z = node.mChannelValues[fr * node.mChannels.size() + channelIdx]; 
+                                break;
+                                
+                            default:
+                                break;
+						}
+
+					}
+                }
+                ++poskey;
+            }
+        } else
+        {
+            // if no translation part is given, put a default sequence
+            aiVector3D nodePos( node.mNode->mTransformation.a4, node.mNode->mTransformation.b4, node.mNode->mTransformation.c4);
+            nodeAnim->mNumPositionKeys = 1;
+            nodeAnim->mPositionKeys = new aiVectorKey[1];
+            nodeAnim->mPositionKeys[0].mTime = 0.0;
+            nodeAnim->mPositionKeys[0].mValue = nodePos;
+        }
+
+        // rotation part. Always present. First find value offsets
+        {
+
+            // Then create the number of rotation keys
+            nodeAnim->mNumRotationKeys = mAnimNumFrames;
+            nodeAnim->mRotationKeys = new aiQuatKey[mAnimNumFrames];
+            aiQuatKey* rotkey = nodeAnim->mRotationKeys;
+            for( unsigned int fr = 0; fr < mAnimNumFrames; ++fr)
+            {
+                aiMatrix4x4 temp;
+                aiMatrix3x3 rotMatrix;
+				for (BVHLoader::ChannelType channel = Channel_RotationX; channel <= Channel_RotationZ; channel = (BVHLoader::ChannelType)(channel + 1))
+				{
+					//Find channel in node
+					std::map<BVHLoader::ChannelType, int>::iterator mapIter = channelMap.find(channel);
+
+					if (mapIter == channelMap.end())
+						throw DeadlyImportError("Missing rotation channel in node " + nodeName);
+					else {
+						int channelIdx = mapIter->second;
+						// translate ZXY euler angels into a quaternion
+						const float angle = node.mChannelValues[fr * node.mChannels.size() + channelIdx] * float(AI_MATH_PI) / 180.0f;
+
+						// Compute rotation transformations in the right order
+						switch (channel)
+						{
+							case Channel_RotationX: 
+                                aiMatrix4x4::RotationX(angle, temp); rotMatrix *= aiMatrix3x3(temp); 
+                                break;
+							case Channel_RotationY: 
+                                aiMatrix4x4::RotationY(angle, temp); rotMatrix *= aiMatrix3x3(temp);  
+                                break;
+							case Channel_RotationZ: aiMatrix4x4::RotationZ(angle, temp); rotMatrix *= aiMatrix3x3(temp); 
+                                break;
+                            default:
+                                break;
+						}
+					}
+                }
+
+                rotkey->mTime = double( fr);
+                rotkey->mValue = aiQuaternion( rotMatrix);
+                ++rotkey;
+            }
+        }
+
+        // scaling part. Always just a default track
+        {
+            nodeAnim->mNumScalingKeys = 1;
+            nodeAnim->mScalingKeys = new aiVectorKey[1];
+            nodeAnim->mScalingKeys[0].mTime = 0.0;
+            nodeAnim->mScalingKeys[0].mValue.Set( 1.0f, 1.0f, 1.0f);
+        }
+    }
+}
+
+#endif // !! ASSIMP_BUILD_NO_BVH_IMPORTER

+ 176 - 0
Engine/lib/assimp/code/BVHLoader.h

@@ -0,0 +1,176 @@
+/** Defines the BHV motion capturing loader class */
+
+/*
+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 BVHLoader.h
+ *  @brief Biovision BVH import
+ */
+
+#ifndef AI_BVHLOADER_H_INC
+#define AI_BVHLOADER_H_INC
+
+#include <assimp/BaseImporter.h>
+
+struct aiNode;
+
+namespace Assimp
+{
+
+// --------------------------------------------------------------------------------
+/** Loader class to read Motion Capturing data from a .bvh file.
+ *
+ * This format only contains a hierarchy of joints and a series of keyframes for
+ * the hierarchy. It contains no actual mesh data, but we generate a dummy mesh
+ * inside the loader just to be able to see something.
+*/
+class BVHLoader : public BaseImporter
+{
+
+    /** Possible animation channels for which the motion data holds the values */
+    enum ChannelType
+    {
+        Channel_PositionX,
+        Channel_PositionY,
+        Channel_PositionZ,
+        Channel_RotationX,
+        Channel_RotationY,
+        Channel_RotationZ
+    };
+
+    /** Collected list of node. Will be bones of the dummy mesh some day, addressed by their array index */
+    struct Node
+    {
+        const aiNode* mNode;
+        std::vector<ChannelType> mChannels;
+        std::vector<float> mChannelValues; // motion data values for that node. Of size NumChannels * NumFrames
+
+        Node()
+        : mNode(nullptr)
+        { }
+
+        explicit Node( const aiNode* pNode) : mNode( pNode) { }
+    };
+
+public:
+
+    BVHLoader();
+    ~BVHLoader();
+
+public:
+    /** Returns whether the class can handle the format of the given file.
+     * See BaseImporter::CanRead() for details. */
+    bool CanRead( const std::string& pFile, IOSystem* pIOHandler, bool cs) const;
+
+    void SetupProperties(const Importer* pImp);
+    const aiImporterDesc* GetInfo () const;
+
+protected:
+
+
+    /** Imports the given file into the given scene structure.
+     * See BaseImporter::InternReadFile() for details
+     */
+    void InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler);
+
+protected:
+    /** Reads the file */
+    void ReadStructure( aiScene* pScene);
+
+    /** Reads the hierarchy */
+    void ReadHierarchy( aiScene* pScene);
+
+    /** Reads a node and recursively its childs and returns the created node. */
+    aiNode* ReadNode();
+
+    /** Reads an end node and returns the created node. */
+    aiNode* ReadEndSite( const std::string& pParentName);
+
+    /** Reads a node offset for the given node */
+    void ReadNodeOffset( aiNode* pNode);
+
+    /** Reads the animation channels into the given node */
+    void ReadNodeChannels( BVHLoader::Node& pNode);
+
+    /** Reads the motion data */
+    void ReadMotion( aiScene* pScene);
+
+    /** Retrieves the next token */
+    std::string GetNextToken();
+
+    /** Reads the next token as a float */
+    float GetNextTokenAsFloat();
+
+    /** Aborts the file reading with an exception */
+    AI_WONT_RETURN void ThrowException( const std::string& pError) AI_WONT_RETURN_SUFFIX;
+
+    /** Constructs an animation for the motion data and stores it in the given scene */
+    void CreateAnimation( aiScene* pScene);
+
+protected:
+    /** Filename, for a verbose error message */
+    std::string mFileName;
+
+    /** Buffer to hold the loaded file */
+    std::vector<char> mBuffer;
+
+    /** Next char to read from the buffer */
+    std::vector<char>::const_iterator mReader;
+
+    /** Current line, for error messages */
+    unsigned int mLine;
+
+    /** Collected list of nodes. Will be bones of the dummy mesh some day, addressed by their array index.
+    * Also contain the motion data for the node's channels
+    */
+    std::vector<Node> mNodes;
+
+    /** basic Animation parameters */
+    float mAnimTickDuration;
+    unsigned int mAnimNumFrames;
+
+    bool noSkeletonMesh;
+};
+
+} // end of namespace Assimp
+
+#endif // AI_BVHLOADER_H_INC

+ 616 - 0
Engine/lib/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 %%%");
+    }
+}

+ 361 - 0
Engine/lib/assimp/code/BaseImporter.h

@@ -0,0 +1,361 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2017, 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 Definition of the base class for all importer worker classes. */
+#ifndef INCLUDED_AI_BASEIMPORTER_H
+#define INCLUDED_AI_BASEIMPORTER_H
+
+#include "Exceptional.h"
+
+#include <vector>
+#include <set>
+#include <assimp/types.h>
+#include <assimp/ProgressHandler.hpp>
+
+struct aiScene;
+struct aiImporterDesc;
+
+namespace Assimp    {
+
+class Importer;
+class IOSystem;
+class BaseProcess;
+class SharedPostProcessInfo;
+class IOStream;
+
+// utility to do char4 to uint32 in a portable manner
+#define AI_MAKE_MAGIC(string) ((uint32_t)((string[0] << 24) + \
+    (string[1] << 16) + (string[2] << 8) + string[3]))
+
+
+// ---------------------------------------------------------------------------
+/** FOR IMPORTER PLUGINS ONLY: The BaseImporter defines a common interface
+ *  for all importer worker classes.
+ *
+ * The interface defines two functions: CanRead() is used to check if the
+ * importer can handle the format of the given file. If an implementation of
+ * this function returns true, the importer then calls ReadFile() which
+ * imports the given file. ReadFile is not overridable, it just calls
+ * InternReadFile() and catches any ImportErrorException that might occur.
+ */
+class ASSIMP_API BaseImporter
+{
+    friend class Importer;
+
+public:
+
+    /** Constructor to be privately used by #Importer */
+    BaseImporter();
+
+    /** Destructor, private as well */
+    virtual ~BaseImporter();
+
+public:
+    // -------------------------------------------------------------------
+    /** Returns whether the class can handle the format of the given file.
+     *
+     * The implementation should be as quick as possible. A check for
+     * the file extension is enough. If no suitable loader is found with
+     * this strategy, CanRead() is called again, the 'checkSig' parameter
+     * set to true this time. Now the implementation is expected to
+     * perform a full check of the file structure, possibly searching the
+     * first bytes of the file for magic identifiers or keywords.
+     *
+     * @param pFile Path and file name of the file to be examined.
+     * @param pIOHandler The IO handler to use for accessing any file.
+     * @param checkSig Set to true if this method is called a second time.
+     *   This time, the implementation may take more time to examine the
+     *   contents of the file to be loaded for magic bytes, keywords, etc
+     *   to be able to load files with unknown/not existent file extensions.
+     * @return true if the class can read this file, false if not.
+     */
+    virtual bool CanRead(
+        const std::string& pFile,
+        IOSystem* pIOHandler,
+        bool checkSig
+        ) const = 0;
+
+    // -------------------------------------------------------------------
+    /** Imports the given file and returns the imported data.
+     * If the import succeeds, ownership of the data is transferred to
+     * the caller. If the import fails, NULL is returned. The function
+     * takes care that any partially constructed data is destroyed
+     * beforehand.
+     *
+     * @param pImp #Importer object hosting this loader.
+     * @param pFile Path of the file to be imported.
+     * @param pIOHandler IO-Handler used to open this and possible other files.
+     * @return The imported data or NULL if failed. If it failed a
+     * human-readable error description can be retrieved by calling
+     * GetErrorText()
+     *
+     * @note This function is not intended to be overridden. Implement
+     * InternReadFile() to do the import. If an exception is thrown somewhere
+     * in InternReadFile(), this function will catch it and transform it into
+     *  a suitable response to the caller.
+     */
+    aiScene* ReadFile(
+        const Importer* pImp,
+        const std::string& pFile,
+        IOSystem* pIOHandler
+        );
+
+    // -------------------------------------------------------------------
+    /** Returns the error description of the last error that occurred.
+     * @return A description of the last error that occurred. An empty
+     * string if there was no error.
+     */
+    const std::string& GetErrorText() const {
+        return m_ErrorText;
+    }
+
+    // -------------------------------------------------------------------
+    /** Called prior to ReadFile().
+     * The function is a request to the importer to update its configuration
+     * basing on the Importer's configuration property list.
+     * @param pImp Importer instance
+     */
+    virtual void SetupProperties(
+        const Importer* pImp
+        );
+
+    // -------------------------------------------------------------------
+    /** Called by #Importer::GetImporterInfo to get a description of
+     *  some loader features. Importers must provide this information. */
+    virtual const aiImporterDesc* GetInfo() const = 0;
+
+    // -------------------------------------------------------------------
+    /** Called by #Importer::GetExtensionList for each loaded importer.
+     *  Take the extension list contained in the structure returned by
+     *  #GetInfo and insert all file extensions into the given set.
+     *  @param extension set to collect file extensions in*/
+    void GetExtensionList(std::set<std::string>& extensions);
+
+protected:
+
+    // -------------------------------------------------------------------
+    /** Imports the given file into the given scene structure. The
+     * function is expected to throw an ImportErrorException if there is
+     * an error. If it terminates normally, the data in aiScene is
+     * expected to be correct. Override this function to implement the
+     * actual importing.
+     * <br>
+     *  The output scene must meet the following requirements:<br>
+     * <ul>
+     * <li>At least a root node must be there, even if its only purpose
+     *     is to reference one mesh.</li>
+     * <li>aiMesh::mPrimitiveTypes may be 0. The types of primitives
+     *   in the mesh are determined automatically in this case.</li>
+     * <li>the vertex data is stored in a pseudo-indexed "verbose" format.
+     *   In fact this means that every vertex that is referenced by
+     *   a face is unique. Or the other way round: a vertex index may
+     *   not occur twice in a single aiMesh.</li>
+     * <li>aiAnimation::mDuration may be -1. Assimp determines the length
+     *   of the animation automatically in this case as the length of
+     *   the longest animation channel.</li>
+     * <li>aiMesh::mBitangents may be NULL if tangents and normals are
+     *   given. In this case bitangents are computed as the cross product
+     *   between normal and tangent.</li>
+     * <li>There needn't be a material. If none is there a default material
+     *   is generated. However, it is recommended practice for loaders
+     *   to generate a default material for yourself that matches the
+     *   default material setting for the file format better than Assimp's
+     *   generic default material. Note that default materials *should*
+     *   be named AI_DEFAULT_MATERIAL_NAME if they're just color-shaded
+     *   or AI_DEFAULT_TEXTURED_MATERIAL_NAME if they define a (dummy)
+     *   texture. </li>
+     * </ul>
+     * If the AI_SCENE_FLAGS_INCOMPLETE-Flag is <b>not</b> set:<ul>
+     * <li> at least one mesh must be there</li>
+     * <li> there may be no meshes with 0 vertices or faces</li>
+     * </ul>
+     * This won't be checked (except by the validation step): Assimp will
+     * crash if one of the conditions is not met!
+     *
+     * @param pFile Path of the file to be imported.
+     * @param pScene The scene object to hold the imported data.
+     * NULL is not a valid parameter.
+     * @param pIOHandler The IO handler to use for any file access.
+     * NULL is not a valid parameter. */
+    virtual void InternReadFile(
+        const std::string& pFile,
+        aiScene* pScene,
+        IOSystem* pIOHandler
+        ) = 0;
+
+public: // static utilities
+
+    // -------------------------------------------------------------------
+    /** A utility for CanRead().
+     *
+     *  The function searches the header of a file for a specific token
+     *  and returns true if this token is found. This works for text
+     *  files only. There is a rudimentary handling of UNICODE files.
+     *  The comparison is case independent.
+     *
+     *  @param pIOSystem IO System to work with
+     *  @param file File name of the file
+     *  @param tokens List of tokens to search for
+     *  @param numTokens Size of the token array
+     *  @param searchBytes Number of bytes to be searched for the tokens.
+     */
+    static bool SearchFileHeaderForToken(
+        IOSystem* pIOSystem,
+        const std::string&  file,
+        const char** tokens,
+        unsigned int numTokens,
+        unsigned int searchBytes = 200,
+        bool tokensSol = false);
+
+    // -------------------------------------------------------------------
+    /** @brief Check whether a file has a specific file extension
+     *  @param pFile Input file
+     *  @param ext0 Extension to check for. Lowercase characters only, no dot!
+     *  @param ext1 Optional second extension
+     *  @param ext2 Optional third extension
+     *  @note Case-insensitive
+     */
+    static bool SimpleExtensionCheck (
+        const std::string& pFile,
+        const char* ext0,
+        const char* ext1 = NULL,
+        const char* ext2 = NULL);
+
+    // -------------------------------------------------------------------
+    /** @brief Extract file extension from a string
+     *  @param pFile Input file
+     *  @return Extension without trailing dot, all lowercase
+     */
+    static std::string GetExtension (
+        const std::string& pFile);
+
+    // -------------------------------------------------------------------
+    /** @brief Check whether a file starts with one or more magic tokens
+     *  @param pFile Input file
+     *  @param pIOHandler IO system to be used
+     *  @param magic n magic tokens
+     *  @params num Size of magic
+     *  @param offset Offset from file start where tokens are located
+     *  @param Size of one token, in bytes. Maximally 16 bytes.
+     *  @return true if one of the given tokens was found
+     *
+     *  @note For convenience, the check is also performed for the
+     *  byte-swapped variant of all tokens (big endian). Only for
+     *  tokens of size 2,4.
+     */
+    static bool CheckMagicToken(
+        IOSystem* pIOHandler,
+        const std::string& pFile,
+        const void* magic,
+        unsigned int num,
+        unsigned int offset = 0,
+        unsigned int size   = 4);
+
+    // -------------------------------------------------------------------
+    /** An utility for all text file loaders. It converts a file to our
+     *   UTF8 character set. Errors are reported, but ignored.
+     *
+     *  @param data File buffer to be converted to UTF8 data. The buffer
+     *  is resized as appropriate. */
+    static void ConvertToUTF8(
+        std::vector<char>& data);
+
+    // -------------------------------------------------------------------
+    /** An utility for all text file loaders. It converts a file from our
+     *   UTF8 character set back to ISO-8859-1. Errors are reported, but ignored.
+     *
+     *  @param data File buffer to be converted from UTF8 to ISO-8859-1. The buffer
+     *  is resized as appropriate. */
+    static void ConvertUTF8toISO8859_1(
+        std::string& data);
+
+    // -------------------------------------------------------------------
+    /// @brief  Enum to define, if empty files are ok or not.
+    enum TextFileMode { 
+        ALLOW_EMPTY,
+        FORBID_EMPTY 
+    };
+
+    // -------------------------------------------------------------------
+    /** Utility for text file loaders which copies the contents of the
+     *  file into a memory buffer and converts it to our UTF8
+     *  representation.
+     *  @param stream Stream to read from.
+     *  @param data Output buffer to be resized and filled with the
+     *   converted text file data. The buffer is terminated with
+     *   a binary 0.
+     *  @param mode Whether it is OK to load empty text files. */
+    static void TextFileToBuffer(
+        IOStream* stream,
+        std::vector<char>& data,
+        TextFileMode mode = FORBID_EMPTY);
+
+    // -------------------------------------------------------------------
+    /** Utility function to move a std::vector into a aiScene array
+    *  @param vec The vector to be moved
+    *  @param out The output pointer to the allocated array.
+    *  @param numOut The output count of elements copied. */
+    template<typename T>
+    AI_FORCE_INLINE
+    static void CopyVector(
+        std::vector<T>& vec,
+        T*& out,
+        unsigned int& outLength)
+    {
+        outLength = unsigned(vec.size());
+        if (outLength) {
+            out = new T[outLength];
+            std::swap_ranges(vec.begin(), vec.end(), out);
+        }
+    }
+
+protected:
+    /// Error description in case there was one.
+    std::string m_ErrorText;
+    /// Currently set progress handler.
+    ProgressHandler* m_progress;
+};
+
+
+
+} // end of namespace Assimp
+
+#endif // AI_BASEIMPORTER_H_INC

+ 107 - 0
Engine/lib/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
Engine/lib/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
Engine/lib/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);
+        }
+    }
+
+}

+ 123 - 0
Engine/lib/assimp/code/Bitmap.h

@@ -0,0 +1,123 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2017, 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.h
+ *  @brief Defines bitmap format helper for textures
+ *
+ * Used for file formats which embed their textures into the model file.
+ */
+
+#ifndef AI_BITMAP_H_INC
+#define AI_BITMAP_H_INC
+
+#include <stdint.h>
+#include <cstddef>
+
+struct aiTexture;
+
+namespace Assimp {
+
+class IOStream;
+
+class Bitmap {
+protected:
+
+    struct Header {
+        uint16_t type;
+        uint32_t size;
+        uint16_t reserved1;
+        uint16_t reserved2;
+        uint32_t offset;
+
+        // We define the struct size because sizeof(Header) might return a wrong result because of structure padding.
+        // Moreover, we must use this ugly and error prone syntax because Visual Studio neither support constexpr or sizeof(name_of_field).
+        static const std::size_t header_size =
+            sizeof(uint16_t) + // type
+            sizeof(uint32_t) + // size
+            sizeof(uint16_t) + // reserved1
+            sizeof(uint16_t) + // reserved2
+            sizeof(uint32_t);  // offset
+    };
+
+    struct DIB {
+        uint32_t size;
+        int32_t width;
+        int32_t height;
+        uint16_t planes;
+        uint16_t bits_per_pixel;
+        uint32_t compression;
+        uint32_t image_size;
+        int32_t x_resolution;
+        int32_t y_resolution;
+        uint32_t nb_colors;
+        uint32_t nb_important_colors;
+
+        // We define the struct size because sizeof(DIB) might return a wrong result because of structure padding.
+        // Moreover, we must use this ugly and error prone syntax because Visual Studio neither support constexpr or sizeof(name_of_field).
+        static const std::size_t dib_size =
+            sizeof(uint32_t) + // size
+            sizeof(int32_t) +  // width
+            sizeof(int32_t) +  // height
+            sizeof(uint16_t) + // planes
+            sizeof(uint16_t) + // bits_per_pixel
+            sizeof(uint32_t) + // compression
+            sizeof(uint32_t) + // image_size
+            sizeof(int32_t) +  // x_resolution
+            sizeof(int32_t) +  // y_resolution
+            sizeof(uint32_t) + // nb_colors
+            sizeof(uint32_t);  // nb_important_colors
+    };
+
+    static const std::size_t mBytesPerPixel = 4;
+
+public:
+    static void Save(aiTexture* texture, IOStream* file);
+
+protected:
+    static void WriteHeader(Header& header, IOStream* file);
+    static void WriteDIB(DIB& dib, IOStream* file);
+    static void WriteData(aiTexture* texture, IOStream* file);
+};
+
+}
+
+#endif // AI_BITMAP_H_INC

+ 206 - 0
Engine/lib/assimp/code/BlenderBMesh.cpp

@@ -0,0 +1,206 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2013, 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  BlenderBMesh.cpp
+ *  @brief Conversion of Blender's new BMesh stuff
+ */
+
+
+#ifndef ASSIMP_BUILD_NO_BLEND_IMPORTER
+
+#include "BlenderDNA.h"
+#include "BlenderScene.h"
+#include "BlenderBMesh.h"
+#include "BlenderTessellator.h"
+
+namespace Assimp
+{
+    template< > const char* LogFunctions< BlenderBMeshConverter >::Prefix()
+    {
+        static auto prefix = "BLEND_BMESH: ";
+        return prefix;
+    }
+}
+
+using namespace Assimp;
+using namespace Assimp::Blender;
+using namespace Assimp::Formatter;
+
+// ------------------------------------------------------------------------------------------------
+BlenderBMeshConverter::BlenderBMeshConverter( const Mesh* mesh ):
+    BMesh( mesh ),
+    triMesh( NULL )
+{
+}
+
+// ------------------------------------------------------------------------------------------------
+BlenderBMeshConverter::~BlenderBMeshConverter( )
+{
+    DestroyTriMesh( );
+}
+
+// ------------------------------------------------------------------------------------------------
+bool BlenderBMeshConverter::ContainsBMesh( ) const
+{
+    // TODO - Should probably do some additional verification here
+    return BMesh->totpoly && BMesh->totloop && BMesh->totvert;
+}
+
+// ------------------------------------------------------------------------------------------------
+const Mesh* BlenderBMeshConverter::TriangulateBMesh( )
+{
+    AssertValidMesh( );
+    AssertValidSizes( );
+    PrepareTriMesh( );
+
+    for ( int i = 0; i < BMesh->totpoly; ++i )
+    {
+        const MPoly& poly = BMesh->mpoly[ i ];
+        ConvertPolyToFaces( poly );
+    }
+
+    return triMesh;
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderBMeshConverter::AssertValidMesh( )
+{
+    if ( !ContainsBMesh( ) )
+    {
+        ThrowException( "BlenderBMeshConverter requires a BMesh with \"polygons\" - please call BlenderBMeshConverter::ContainsBMesh to check this first" );
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderBMeshConverter::AssertValidSizes( )
+{
+    if ( BMesh->totpoly != static_cast<int>( BMesh->mpoly.size( ) ) )
+    {
+        ThrowException( "BMesh poly array has incorrect size" );
+    }
+    if ( BMesh->totloop != static_cast<int>( BMesh->mloop.size( ) ) )
+    {
+        ThrowException( "BMesh loop array has incorrect size" );
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderBMeshConverter::PrepareTriMesh( )
+{
+    if ( triMesh )
+    {
+        DestroyTriMesh( );
+    }
+
+    triMesh = new Mesh( *BMesh );
+    triMesh->totface = 0;
+    triMesh->mface.clear( );
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderBMeshConverter::DestroyTriMesh( )
+{
+    delete triMesh;
+    triMesh = NULL;
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderBMeshConverter::ConvertPolyToFaces( const MPoly& poly )
+{
+    const MLoop* polyLoop = &BMesh->mloop[ poly.loopstart ];
+
+    if ( poly.totloop == 3 || poly.totloop == 4 )
+    {
+        AddFace( polyLoop[ 0 ].v, polyLoop[ 1 ].v, polyLoop[ 2 ].v, poly.totloop == 4 ? polyLoop[ 3 ].v : 0 );
+
+        // UVs are optional, so only convert when present.
+        if ( BMesh->mloopuv.size() )
+        {
+            if ( (poly.loopstart + poly.totloop ) > static_cast<int>( BMesh->mloopuv.size() ) )
+            {
+                ThrowException( "BMesh uv loop array has incorrect size" );
+            }
+            const MLoopUV* loopUV = &BMesh->mloopuv[ poly.loopstart ];
+            AddTFace( loopUV[ 0 ].uv, loopUV[ 1 ].uv, loopUV[ 2 ].uv, poly.totloop == 4 ? loopUV[ 3 ].uv : 0 );
+        }
+    }
+    else if ( poly.totloop > 4 )
+    {
+#if ASSIMP_BLEND_WITH_GLU_TESSELLATE
+        BlenderTessellatorGL tessGL( *this );
+        tessGL.Tessellate( polyLoop, poly.totloop, triMesh->mvert );
+#elif ASSIMP_BLEND_WITH_POLY_2_TRI
+        BlenderTessellatorP2T tessP2T( *this );
+        tessP2T.Tessellate( polyLoop, poly.totloop, triMesh->mvert );
+#endif
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderBMeshConverter::AddFace( int v1, int v2, int v3, int v4 )
+{
+    MFace face;
+    face.v1 = v1;
+    face.v2 = v2;
+    face.v3 = v3;
+    face.v4 = v4;
+    // TODO - Work out how materials work
+    face.mat_nr = 0;
+    triMesh->mface.push_back( face );
+    triMesh->totface = static_cast<int>(triMesh->mface.size( ));
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderBMeshConverter::AddTFace( const float* uv1, const float *uv2, const float *uv3, const float* uv4 )
+{
+    MTFace mtface;
+    memcpy( &mtface.uv[ 0 ], uv1, sizeof(float) * 2 );
+    memcpy( &mtface.uv[ 1 ], uv2, sizeof(float) * 2 );
+    memcpy( &mtface.uv[ 2 ], uv3, sizeof(float) * 2 );
+
+    if ( uv4 )
+    {
+        memcpy( &mtface.uv[ 3 ], uv4, sizeof(float) * 2 );
+    }
+
+    triMesh->mtface.push_back( mtface );
+}
+
+#endif // ASSIMP_BUILD_NO_BLEND_IMPORTER

+ 94 - 0
Engine/lib/assimp/code/BlenderBMesh.h

@@ -0,0 +1,94 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2013, 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  BlenderBMesh.h
+ *  @brief Conversion of Blender's new BMesh stuff
+ */
+#ifndef INCLUDED_AI_BLEND_BMESH_H
+#define INCLUDED_AI_BLEND_BMESH_H
+
+#include <assimp/LogAux.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;
+    }
+
+    // BlenderScene.h
+    namespace Blender
+    {
+        struct Mesh;
+        struct MPoly;
+        struct MLoop;
+    }
+
+    class BlenderBMeshConverter: public LogFunctions< BlenderBMeshConverter >
+    {
+    public:
+        BlenderBMeshConverter( const Blender::Mesh* mesh );
+        ~BlenderBMeshConverter( );
+
+        bool ContainsBMesh( ) const;
+
+        const Blender::Mesh* TriangulateBMesh( );
+
+    private:
+        void AssertValidMesh( );
+        void AssertValidSizes( );
+        void PrepareTriMesh( );
+        void DestroyTriMesh( );
+        void ConvertPolyToFaces( const Blender::MPoly& poly );
+        void AddFace( int v1, int v2, int v3, int v4 = 0 );
+        void AddTFace( const float* uv1, const float* uv2, const float *uv3, const float* uv4 = 0 );
+
+        const Blender::Mesh* BMesh;
+        Blender::Mesh* triMesh;
+
+        friend class BlenderTessellatorGL;
+        friend class BlenderTessellatorP2T;
+    };
+
+} // end of namespace Assimp
+
+#endif // INCLUDED_AI_BLEND_BMESH_H

+ 189 - 0
Engine/lib/assimp/code/BlenderCustomData.cpp

@@ -0,0 +1,189 @@
+#include "BlenderCustomData.h"
+#include "BlenderDNA.h"
+#include <array>
+#include <functional>
+
+namespace Assimp {
+    namespace Blender {
+        /**
+        *   @brief  read/convert of Structure array to memory
+        */
+        template<typename T>
+        bool read(const Structure &s, T *p, const size_t cnt, const FileDatabase &db) {
+            for (size_t i = 0; i < cnt; ++i) {
+                T read;
+                s.Convert(read, db);
+                *p = read;
+                p++;
+            }
+            return true;
+        }
+
+        /**
+        *   @brief  pointer to function read memory for n CustomData types
+        */
+        typedef bool        (*PRead)(ElemBase *pOut, const size_t cnt, const FileDatabase &db);
+        typedef ElemBase *  (*PCreate)(const size_t cnt);
+        typedef void(*PDestroy)(ElemBase *);
+
+#define IMPL_STRUCT_READ(ty)                                                    \
+        bool read##ty(ElemBase *v, const size_t cnt, const FileDatabase &db) {  \
+        ty *ptr = dynamic_cast<ty*>(v);                                         \
+        if (nullptr == ptr) {                                                   \
+            return false;                                                       \
+        }                                                                       \
+        return read<ty>(db.dna[#ty], ptr, cnt, db);                             \
+        }
+
+#define IMPL_STRUCT_CREATE(ty)                                                  \
+        ElemBase *create##ty(const size_t cnt) {                                \
+            return new ty[cnt];                                                 \
+        }
+
+#define IMPL_STRUCT_DESTROY(ty)                                                 \
+        void destroy##ty(ElemBase *pE) {                                        \
+            ty *p = dynamic_cast<ty *>(pE);                                     \
+            delete[]p;                                                          \
+        }
+
+        /**
+        *   @brief  helper macro to define Structure functions
+        */
+#define IMPL_STRUCT(ty)                                                         \
+        IMPL_STRUCT_READ(ty)                                                    \
+        IMPL_STRUCT_CREATE(ty)                                                  \
+        IMPL_STRUCT_DESTROY(ty)
+
+        // supported structures for CustomData
+        IMPL_STRUCT(MVert)
+        IMPL_STRUCT(MEdge)
+        IMPL_STRUCT(MFace)
+        IMPL_STRUCT(MTFace)
+        IMPL_STRUCT(MTexPoly)
+        IMPL_STRUCT(MLoopUV)
+        IMPL_STRUCT(MLoopCol)
+        IMPL_STRUCT(MPoly)
+        IMPL_STRUCT(MLoop)
+
+        /**
+        *   @brief  describes the size of data and the read function to be used for single CustomerData.type
+        */
+        struct CustomDataTypeDescription {
+            PRead Read;                         ///< function to read one CustomData type element
+            PCreate Create;                       ///< function to allocate n type elements
+            PDestroy Destroy;
+
+            CustomDataTypeDescription(PRead read, PCreate create, PDestroy destroy)
+                : Read(read)
+                , Create(create)
+                , Destroy(destroy)
+            {}
+        };
+
+
+        /**
+        *   @brief  helper macro to define Structure type specific CustomDataTypeDescription
+        *   @note   IMPL_STRUCT_READ for same ty must be used earlier to implement the typespecific read function
+        */
+#define DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(ty)           \
+        CustomDataTypeDescription{&read##ty, &create##ty, &destroy##ty}
+
+        /**
+        *   @brief  helper macro to define CustomDataTypeDescription for UNSUPPORTED type
+        */
+#define DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION          \
+        CustomDataTypeDescription{nullptr, nullptr, nullptr}
+
+        /**
+        *   @brief  descriptors for data pointed to from CustomDataLayer.data
+        *   @note   some of the CustomData uses already well defined Structures
+        *           other (like CD_ORCO, ...) uses arrays of rawtypes or even arrays of Structures
+        *           use a special readfunction for that cases
+        */
+        std::array<CustomDataTypeDescription, CD_NUMTYPES> customDataTypeDescriptions = { {
+            DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MVert),
+            DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
+            DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
+            DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MEdge),
+            DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MFace),
+            DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MTFace),
+            DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
+            DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
+            DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
+            DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
+
+            DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
+            DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
+            DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
+            DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
+            DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
+            DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MTexPoly),
+            DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MLoopUV),
+            DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MLoopCol),
+            DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
+            DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
+
+            DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
+            DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
+            DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
+            DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
+            DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
+            DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MPoly),
+            DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MLoop),
+            DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
+            DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
+            DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
+
+            DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
+            DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
+            DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
+            DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
+            DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
+            DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
+            DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
+            DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
+            DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
+            DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
+
+            DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
+            DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION
+        }};
+
+
+        bool isValidCustomDataType(const int cdtype) {
+            return cdtype >= 0 && cdtype < CD_NUMTYPES;
+        }
+
+        bool readCustomData(std::shared_ptr<ElemBase> &out, const int cdtype, const size_t cnt, const FileDatabase &db) {
+            if (!isValidCustomDataType(cdtype)) {
+                throw Error((Formatter::format(), "CustomData.type ", cdtype, " out of index"));
+            }
+
+            const CustomDataTypeDescription cdtd = customDataTypeDescriptions[cdtype];
+            if (cdtd.Read && cdtd.Create && cdtd.Destroy && cnt > 0) {
+                // allocate cnt elements and parse them from file
+                out.reset(cdtd.Create(cnt), cdtd.Destroy);
+                return cdtd.Read(out.get(), cnt, db);
+            }
+            return false;
+        }
+
+        std::shared_ptr<CustomDataLayer> getCustomDataLayer(const CustomData &customdata, const CustomDataType cdtype, const std::string &name) {
+            for (auto it = customdata.layers.begin(); it != customdata.layers.end(); ++it) {
+                if (it->get()->type == cdtype && name == it->get()->name) {
+                    return *it;
+                }
+            }
+            return nullptr;
+        }
+
+        const ElemBase * getCustomDataLayerData(const CustomData &customdata, const CustomDataType cdtype, const std::string &name)
+        {
+            const std::shared_ptr<CustomDataLayer> pLayer = getCustomDataLayer(customdata, cdtype, name);
+            if (pLayer && pLayer->data) {
+                return pLayer->data.get();
+            }
+            return nullptr;
+        }
+    }
+}

+ 89 - 0
Engine/lib/assimp/code/BlenderCustomData.h

@@ -0,0 +1,89 @@
+#pragma once
+
+#include "BlenderDNA.h"
+#include "BlenderScene.h"
+#include <memory>
+
+namespace Assimp {
+    namespace Blender {
+        /* CustomData.type from Blender (2.79b) */
+        enum CustomDataType {
+            CD_AUTO_FROM_NAME = -1,
+            CD_MVERT = 0,
+#ifdef DNA_DEPRECATED
+            CD_MSTICKY = 1,  /* DEPRECATED */
+#endif
+            CD_MDEFORMVERT = 2,
+            CD_MEDGE = 3,
+            CD_MFACE = 4,
+            CD_MTFACE = 5,
+            CD_MCOL = 6,
+            CD_ORIGINDEX = 7,
+            CD_NORMAL = 8,
+            /*	CD_POLYINDEX        = 9, */
+            CD_PROP_FLT = 10,
+            CD_PROP_INT = 11,
+            CD_PROP_STR = 12,
+            CD_ORIGSPACE = 13,  /* for modifier stack face location mapping */
+            CD_ORCO = 14,
+            CD_MTEXPOLY = 15,
+            CD_MLOOPUV = 16,
+            CD_MLOOPCOL = 17,
+            CD_TANGENT = 18,
+            CD_MDISPS = 19,
+            CD_PREVIEW_MCOL = 20,  /* for displaying weightpaint colors */
+            /*	CD_ID_MCOL          = 21, */
+            CD_TEXTURE_MLOOPCOL = 22,
+            CD_CLOTH_ORCO = 23,
+            CD_RECAST = 24,
+
+            /* BMESH ONLY START */
+            CD_MPOLY = 25,
+            CD_MLOOP = 26,
+            CD_SHAPE_KEYINDEX = 27,
+            CD_SHAPEKEY = 28,
+            CD_BWEIGHT = 29,
+            CD_CREASE = 30,
+            CD_ORIGSPACE_MLOOP = 31,
+            CD_PREVIEW_MLOOPCOL = 32,
+            CD_BM_ELEM_PYPTR = 33,
+            /* BMESH ONLY END */
+
+            CD_PAINT_MASK = 34,
+            CD_GRID_PAINT_MASK = 35,
+            CD_MVERT_SKIN = 36,
+            CD_FREESTYLE_EDGE = 37,
+            CD_FREESTYLE_FACE = 38,
+            CD_MLOOPTANGENT = 39,
+            CD_TESSLOOPNORMAL = 40,
+            CD_CUSTOMLOOPNORMAL = 41,
+
+            CD_NUMTYPES = 42
+        };
+
+        /**
+        *   @brief  check if given cdtype is valid (ie >= 0 and < CD_NUMTYPES)
+        *   @param[in]  cdtype to check
+        *   @return true when valid
+        */
+        bool isValidCustomDataType(const int cdtype);
+
+        /**
+        *   @brief  returns CustomDataLayer ptr for given cdtype and name
+        *   @param[in]  customdata CustomData to search for wanted layer
+        *   @param[in]  cdtype to search for
+        *   @param[in]  name to search for
+        *   @return CustomDataLayer * or nullptr if not found
+        */
+        std::shared_ptr<CustomDataLayer> getCustomDataLayer(const CustomData &customdata, CustomDataType cdtype, const std::string &name);
+
+        /**
+        *   @brief  returns CustomDataLayer data ptr for given cdtype and name
+        *   @param[in]  customdata CustomData to search for wanted layer
+        *   @param[in]  cdtype to search for
+        *   @param[in]  name to search for
+        *   @return * to struct data or nullptr if not found
+        */
+        const ElemBase * getCustomDataLayerData(const CustomData &customdata, CustomDataType cdtype, const std::string &name);
+    }
+}

+ 375 - 0
Engine/lib/assimp/code/BlenderDNA.cpp

@@ -0,0 +1,375 @@
+/*
+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  BlenderDNA.cpp
+ *  @brief Implementation of the Blender `DNA`, that is its own
+ *    serialized set of data structures.
+ */
+
+
+#ifndef ASSIMP_BUILD_NO_BLEND_IMPORTER
+#include "BlenderDNA.h"
+#include <assimp/StreamReader.h>
+#include <assimp/fast_atof.h>
+#include <assimp/TinyFormatter.h>
+
+using namespace Assimp;
+using namespace Assimp::Blender;
+using namespace Assimp::Formatter;
+
+static bool match4(StreamReaderAny& stream, const char* string) {
+    ai_assert( nullptr != string );
+    char tmp[4];
+    tmp[ 0 ] = ( stream ).GetI1();
+    tmp[ 1 ] = ( stream ).GetI1();
+    tmp[ 2 ] = ( stream ).GetI1();
+    tmp[ 3 ] = ( stream ).GetI1();
+    return (tmp[0]==string[0] && tmp[1]==string[1] && tmp[2]==string[2] && tmp[3]==string[3]);
+}
+
+struct Type {
+    size_t size;
+    std::string name;
+};
+
+// ------------------------------------------------------------------------------------------------
+void DNAParser::Parse ()
+{
+    StreamReaderAny& stream = *db.reader.get();
+    DNA& dna = db.dna;
+
+    if(!match4(stream,"SDNA")) {
+        throw DeadlyImportError("BlenderDNA: Expected SDNA chunk");
+    }
+
+    // name dictionary
+    if(!match4(stream,"NAME")) {
+        throw DeadlyImportError("BlenderDNA: Expected NAME field");
+    }
+
+    std::vector<std::string> names (stream.GetI4());
+    for(std::string& s : names) {
+        while (char c = stream.GetI1()) {
+            s += c;
+        }
+    }
+
+    // type dictionary
+    for (;stream.GetCurrentPos() & 0x3; stream.GetI1());
+    if(!match4(stream,"TYPE")) {
+        throw DeadlyImportError("BlenderDNA: Expected TYPE field");
+    }
+
+    std::vector<Type> types (stream.GetI4());
+    for(Type& s : types) {
+        while (char c = stream.GetI1()) {
+            s.name += c;
+        }
+    }
+
+    // type length dictionary
+    for (;stream.GetCurrentPos() & 0x3; stream.GetI1());
+    if(!match4(stream,"TLEN")) {
+        throw DeadlyImportError("BlenderDNA: Expected TLEN field");
+    }
+
+    for(Type& s : types) {
+        s.size = stream.GetI2();
+    }
+
+    // structures dictionary
+    for (;stream.GetCurrentPos() & 0x3; stream.GetI1());
+    if(!match4(stream,"STRC")) {
+        throw DeadlyImportError("BlenderDNA: Expected STRC field");
+    }
+
+    size_t end = stream.GetI4(), fields = 0;
+
+    dna.structures.reserve(end);
+    for(size_t i = 0; i != end; ++i) {
+
+        uint16_t n = stream.GetI2();
+        if (n >= types.size()) {
+            throw DeadlyImportError((format(),
+                "BlenderDNA: Invalid type index in structure name" ,n,
+                " (there are only ", types.size(), " entries)"
+            ));
+        }
+
+        // maintain separate indexes
+        dna.indices[types[n].name] = dna.structures.size();
+
+        dna.structures.push_back(Structure());
+        Structure& s = dna.structures.back();
+        s.name  = types[n].name;
+        //s.index = dna.structures.size()-1;
+
+        n = stream.GetI2();
+        s.fields.reserve(n);
+
+        size_t offset = 0;
+        for (size_t m = 0; m < n; ++m, ++fields) {
+
+            uint16_t j = stream.GetI2();
+            if (j >= types.size()) {
+                throw DeadlyImportError((format(),
+                    "BlenderDNA: Invalid type index in structure field ", j,
+                    " (there are only ", types.size(), " entries)"
+                ));
+            }
+            s.fields.push_back(Field());
+            Field& f = s.fields.back();
+            f.offset = offset;
+
+            f.type = types[j].name;
+            f.size = types[j].size;
+
+            j = stream.GetI2();
+            if (j >= names.size()) {
+                throw DeadlyImportError((format(),
+                    "BlenderDNA: Invalid name index in structure field ", j,
+                    " (there are only ", names.size(), " entries)"
+                ));
+            }
+
+            f.name = names[j];
+            f.flags = 0u;
+
+            // pointers always specify the size of the pointee instead of their own.
+            // The pointer asterisk remains a property of the lookup name.
+            if (f.name[0] == '*') {
+                f.size = db.i64bit ? 8 : 4;
+                f.flags |= FieldFlag_Pointer;
+            }
+
+            // arrays, however, specify the size of a single element so we
+            // need to parse the (possibly multi-dimensional) array declaration
+            // in order to obtain the actual size of the array in the file.
+            // Also we need to alter the lookup name to include no array
+            // brackets anymore or size fixup won't work (if our size does
+            // not match the size read from the DNA).
+            if (*f.name.rbegin() == ']') {
+                const std::string::size_type rb = f.name.find('[');
+                if (rb == std::string::npos) {
+                    throw DeadlyImportError((format(),
+                        "BlenderDNA: Encountered invalid array declaration ",
+                        f.name
+                    ));
+                }
+
+                f.flags |= FieldFlag_Array;
+                DNA::ExtractArraySize(f.name,f.array_sizes);
+                f.name = f.name.substr(0,rb);
+
+                f.size *= f.array_sizes[0] * f.array_sizes[1];
+            }
+
+            // maintain separate indexes
+            s.indices[f.name] = s.fields.size()-1;
+            offset += f.size;
+        }
+        s.size = offset;
+    }
+
+    ASSIMP_LOG_DEBUG_F( "BlenderDNA: Got ", dna.structures.size()," structures with totally ",fields," fields");
+
+#ifdef ASSIMP_BUILD_BLENDER_DEBUG
+    dna.DumpToFile();
+#endif
+
+    dna.AddPrimitiveStructures();
+    dna.RegisterConverters();
+}
+
+
+#ifdef ASSIMP_BUILD_BLENDER_DEBUG
+
+#include <fstream>
+// ------------------------------------------------------------------------------------------------
+void DNA :: DumpToFile()
+{
+    // we don't bother using the VFS here for this is only for debugging.
+    // (and all your bases are belong to us).
+
+    std::ofstream f("dna.txt");
+    if (f.fail()) {
+        ASSIMP_LOG_ERROR("Could not dump dna to dna.txt");
+        return;
+    }
+    f << "Field format: type name offset size" << "\n";
+    f << "Structure format: name size" << "\n";
+
+    for(const Structure& s : structures) {
+        f << s.name << " " << s.size << "\n\n";
+        for(const Field& ff : s.fields) {
+            f << "\t" << ff.type << " " << ff.name << " " << ff.offset << " " << ff.size << "\n";
+        }
+        f << "\n";
+    }
+    f << std::flush;
+
+    ASSIMP_LOG_INFO("BlenderDNA: Dumped dna to dna.txt");
+}
+#endif
+
+// ------------------------------------------------------------------------------------------------
+/*static*/ void  DNA :: ExtractArraySize(
+    const std::string& out,
+    size_t array_sizes[2]
+)
+{
+    array_sizes[0] = array_sizes[1] = 1;
+    std::string::size_type pos = out.find('[');
+    if (pos++ == std::string::npos) {
+        return;
+    }
+    array_sizes[0] = strtoul10(&out[pos]);
+
+    pos = out.find('[',pos);
+    if (pos++ == std::string::npos) {
+        return;
+    }
+    array_sizes[1] = strtoul10(&out[pos]);
+}
+
+// ------------------------------------------------------------------------------------------------
+std::shared_ptr< ElemBase > DNA :: ConvertBlobToStructure(
+    const Structure& structure,
+    const FileDatabase& db
+) const
+{
+    std::map<std::string, FactoryPair >::const_iterator it = converters.find(structure.name);
+    if (it == converters.end()) {
+        return std::shared_ptr< ElemBase >();
+    }
+
+    std::shared_ptr< ElemBase > ret = (structure.*((*it).second.first))();
+    (structure.*((*it).second.second))(ret,db);
+
+    return ret;
+}
+
+// ------------------------------------------------------------------------------------------------
+DNA::FactoryPair DNA :: GetBlobToStructureConverter(
+    const Structure& structure,
+    const FileDatabase& /*db*/
+) const
+{
+    std::map<std::string,  FactoryPair>::const_iterator it = converters.find(structure.name);
+    return it == converters.end() ? FactoryPair() : (*it).second;
+}
+
+// basing on http://www.blender.org/development/architecture/notes-on-sdna/
+// ------------------------------------------------------------------------------------------------
+void DNA :: AddPrimitiveStructures()
+{
+    // NOTE: these are just dummies. Their presence enforces
+    // Structure::Convert<target_type> to be called on these
+    // empty structures. These converters are special
+    // overloads which scan the name of the structure and
+    // perform the required data type conversion if one
+    // of these special names is found in the structure
+    // in question.
+
+    indices["int"] = structures.size();
+    structures.push_back( Structure() );
+    structures.back().name = "int";
+    structures.back().size = 4;
+
+    indices["short"] = structures.size();
+    structures.push_back( Structure() );
+    structures.back().name = "short";
+    structures.back().size = 2;
+
+
+    indices["char"] = structures.size();
+    structures.push_back( Structure() );
+    structures.back().name = "char";
+    structures.back().size = 1;
+
+
+    indices["float"] = structures.size();
+    structures.push_back( Structure() );
+    structures.back().name = "float";
+    structures.back().size = 4;
+
+
+    indices["double"] = structures.size();
+    structures.push_back( Structure() );
+    structures.back().name = "double";
+    structures.back().size = 8;
+
+    // no long, seemingly.
+}
+
+// ------------------------------------------------------------------------------------------------
+void SectionParser :: Next()
+{
+    stream.SetCurrentPos(current.start + current.size);
+
+    const char tmp[] = {
+        (const char)stream.GetI1(),
+        (const char)stream.GetI1(),
+        (const char)stream.GetI1(),
+        (const char)stream.GetI1()
+    };
+    current.id = std::string(tmp,tmp[3]?4:tmp[2]?3:tmp[1]?2:1);
+
+    current.size = stream.GetI4();
+    current.address.val = ptr64 ? stream.GetU8() : stream.GetU4();
+
+    current.dna_index = stream.GetI4();
+    current.num = stream.GetI4();
+
+    current.start = stream.GetCurrentPos();
+    if (stream.GetRemainingSizeToLimit() < current.size) {
+        throw DeadlyImportError("BLEND: invalid size of file block");
+    }
+
+#ifdef ASSIMP_BUILD_BLENDER_DEBUG
+    ASSIMP_LOG_DEBUG(current.id);
+#endif
+}
+
+
+
+#endif

+ 844 - 0
Engine/lib/assimp/code/BlenderDNA.h

@@ -0,0 +1,844 @@
+/*
+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  BlenderDNA.h
+ *  @brief Blender `DNA` (file format specification embedded in
+ *    blend file itself) loader.
+ */
+#ifndef INCLUDED_AI_BLEND_DNA_H
+#define INCLUDED_AI_BLEND_DNA_H
+
+#include <assimp/BaseImporter.h>
+#include <assimp/StreamReader.h>
+#include <assimp/DefaultLogger.hpp>
+#include <stdint.h>
+#include <memory>
+#include <map>
+
+// enable verbose log output. really verbose, so be careful.
+#ifdef ASSIMP_BUILD_DEBUG
+#   define ASSIMP_BUILD_BLENDER_DEBUG
+#endif
+
+// #define ASSIMP_BUILD_BLENDER_NO_STATS
+
+namespace Assimp    {
+
+template <bool,bool> class StreamReader;
+typedef StreamReader<true,true> StreamReaderAny;
+
+namespace Blender {
+
+class  FileDatabase;
+struct FileBlockHead;
+
+template <template <typename> class TOUT>
+class ObjectCache;
+
+// -------------------------------------------------------------------------------
+/** Exception class used by the blender loader to selectively catch exceptions
+ *  thrown in its own code (DeadlyImportErrors thrown in general utility
+ *  functions are untouched then). If such an exception is not caught by
+ *  the loader itself, it will still be caught by Assimp due to its
+ *  ancestry. */
+// -------------------------------------------------------------------------------
+struct Error : DeadlyImportError {
+    Error (const std::string& s)
+    : DeadlyImportError(s) {
+        // empty
+    }
+};
+
+// -------------------------------------------------------------------------------
+/** The only purpose of this structure is to feed a virtual dtor into its
+ *  descendents. It serves as base class for all data structure fields. */
+// -------------------------------------------------------------------------------
+struct ElemBase {
+    ElemBase()
+    : dna_type(nullptr)
+    {
+        // empty
+    }
+
+    virtual ~ElemBase() {
+        // empty
+    }
+
+    /** Type name of the element. The type
+     * string points is the `c_str` of the `name` attribute of the
+     * corresponding `Structure`, that is, it is only valid as long
+     * as the DNA is not modified. The dna_type is only set if the
+     * data type is not static, i.e. a std::shared_ptr<ElemBase>
+     * in the scene description would have its type resolved
+     * at runtime, so this member is always set. */
+    const char* dna_type;
+};
+
+// -------------------------------------------------------------------------------
+/** Represents a generic pointer to a memory location, which can be either 32
+ *  or 64 bits. These pointers are loaded from the BLEND file and finally
+ *  fixed to point to the real, converted representation of the objects
+ *  they used to point to.*/
+// -------------------------------------------------------------------------------
+struct Pointer {
+    Pointer()
+    : val() {
+        // empty
+    }
+    uint64_t val;
+};
+
+// -------------------------------------------------------------------------------
+/** Represents a generic offset within a BLEND file */
+// -------------------------------------------------------------------------------
+struct FileOffset {
+    FileOffset()
+    : val() {
+        // empty
+    }
+    uint64_t val;
+};
+
+// -------------------------------------------------------------------------------
+/** Dummy derivate of std::vector to be able to use it in templates simultaenously
+ *  with std::shared_ptr, which takes only one template argument
+ *  while std::vector takes three. Also we need to provide some special member
+ *  functions of shared_ptr */
+// -------------------------------------------------------------------------------
+template <typename T>
+class vector : public std::vector<T> {
+public:
+    using std::vector<T>::resize;
+    using std::vector<T>::empty;
+
+    void reset() {
+        resize(0);
+    }
+
+    operator bool () const {
+        return !empty();
+    }
+};
+
+// -------------------------------------------------------------------------------
+/** Mixed flags for use in #Field */
+// -------------------------------------------------------------------------------
+enum FieldFlags {
+    FieldFlag_Pointer = 0x1,
+    FieldFlag_Array   = 0x2
+};
+
+// -------------------------------------------------------------------------------
+/** Represents a single member of a data structure in a BLEND file */
+// -------------------------------------------------------------------------------
+struct Field {
+    std::string name;
+    std::string type;
+
+    size_t size;
+    size_t offset;
+
+    /** Size of each array dimension. For flat arrays,
+     *  the second dimension is set to 1. */
+    size_t array_sizes[2];
+
+    /** Any of the #FieldFlags enumerated values */
+    unsigned int flags;
+};
+
+// -------------------------------------------------------------------------------
+/** Range of possible behaviours for fields absend in the input file. Some are
+ *  mission critical so we need them, while others can silently be default
+ *  initialized and no animations are harmed. */
+// -------------------------------------------------------------------------------
+enum ErrorPolicy {
+    /** Substitute default value and ignore */
+    ErrorPolicy_Igno,
+    /** Substitute default value and write to log */
+    ErrorPolicy_Warn,
+    /** Substitute a massive error message and crash the whole matrix. Its time for another zion */
+    ErrorPolicy_Fail
+};
+
+#ifdef ASSIMP_BUILD_BLENDER_DEBUG
+#   define ErrorPolicy_Igno ErrorPolicy_Warn
+#endif
+
+// -------------------------------------------------------------------------------
+/** Represents a data structure in a BLEND file. A Structure defines n fields
+ *  and their locations and encodings the input stream. Usually, every
+ *  Structure instance pertains to one equally-named data structure in the
+ *  BlenderScene.h header. This class defines various utilities to map a
+ *  binary `blob` read from the file to such a structure instance with
+ *  meaningful contents. */
+// -------------------------------------------------------------------------------
+class Structure {
+    template <template <typename> class> friend class ObjectCache;
+
+public:
+    Structure()
+    : cache_idx(static_cast<size_t>(-1) ){
+        // empty
+    }
+
+public:
+
+    // publicly accessible members
+    std::string name;
+    vector< Field > fields;
+    std::map<std::string, size_t> indices;
+
+    size_t size;
+
+public:
+
+    // --------------------------------------------------------
+    /** Access a field of the structure by its canonical name. The pointer version
+     *  returns NULL on failure while the reference version raises an import error. */
+    inline const Field& operator [] (const std::string& ss) const;
+    inline const Field* Get (const std::string& ss) const;
+
+    // --------------------------------------------------------
+    /** Access a field of the structure by its index */
+    inline const Field& operator [] (const size_t i) const;
+
+    // --------------------------------------------------------
+    inline bool operator== (const Structure& other) const {
+        return name == other.name; // name is meant to be an unique identifier
+    }
+
+    // --------------------------------------------------------
+    inline bool operator!= (const Structure& other) const {
+        return name != other.name;
+    }
+
+public:
+
+    // --------------------------------------------------------
+    /** Try to read an instance of the structure from the stream
+     *  and attempt to convert to `T`. This is done by
+     *  an appropriate specialization. If none is available,
+     *  a compiler complain is the result.
+     *  @param dest Destination value to be written
+     *  @param db File database, including input stream. */
+    template <typename T> void Convert (T& dest, const FileDatabase& db) const;
+
+    // --------------------------------------------------------
+    // generic converter
+    template <typename T>
+    void Convert(std::shared_ptr<ElemBase> in,const FileDatabase& db) const;
+
+    // --------------------------------------------------------
+    // generic allocator
+    template <typename T> std::shared_ptr<ElemBase> Allocate() const;
+
+
+
+    // --------------------------------------------------------
+    // field parsing for 1d arrays
+    template <int error_policy, typename T, size_t M>
+    void ReadFieldArray(T (& out)[M], const char* name,
+        const FileDatabase& db) const;
+
+    // --------------------------------------------------------
+    // field parsing for 2d arrays
+    template <int error_policy, typename T, size_t M, size_t N>
+    void ReadFieldArray2(T (& out)[M][N], const char* name,
+        const FileDatabase& db) const;
+
+    // --------------------------------------------------------
+    // field parsing for pointer or dynamic array types
+    // (std::shared_ptr)
+    // The return value indicates whether the data was already cached.
+    template <int error_policy, template <typename> class TOUT, typename T>
+    bool ReadFieldPtr(TOUT<T>& out, const char* name,
+        const FileDatabase& db,
+        bool non_recursive = false) const;
+
+    // --------------------------------------------------------
+    // field parsing for static arrays of pointer or dynamic
+    // array types (std::shared_ptr[])
+    // The return value indicates whether the data was already cached.
+    template <int error_policy, template <typename> class TOUT, typename T, size_t N>
+    bool ReadFieldPtr(TOUT<T> (&out)[N], const char* name,
+        const FileDatabase& db) const;
+
+    // --------------------------------------------------------
+    // field parsing for `normal` values
+    // The return value indicates whether the data was already cached.
+    template <int error_policy, typename T>
+    void ReadField(T& out, const char* name,
+        const FileDatabase& db) const;
+
+    // --------------------------------------------------------
+    /**
+    *   @brief  field parsing for dynamic vectors
+    *   @param[in]  out vector of struct to be filled
+    *   @param[in]  name of field
+    *   @param[in]  db to access the file, dna, ...
+    *   @return true when read was successful
+    */
+    template <int error_policy, template <typename> class TOUT, typename T>
+    bool ReadFieldPtrVector(vector<TOUT<T>>&out, const char* name, const FileDatabase& db) const;
+
+    /**
+    *   @brief  parses raw customdata
+    *   @param[in]  out shared_ptr to be filled
+    *   @param[in]  cdtype customdata type to read
+    *   @param[in]  name of field ptr
+    *   @param[in]  db to access the file, dna, ...
+    *   @return true when read was successful
+    */
+    template <int error_policy>
+    bool ReadCustomDataPtr(std::shared_ptr<ElemBase>&out, int cdtype, const char* name, const FileDatabase& db) const;
+
+private:
+
+    // --------------------------------------------------------
+    template <template <typename> class TOUT, typename T>
+    bool ResolvePointer(TOUT<T>& out, const Pointer & ptrval,
+        const FileDatabase& db, const Field& f,
+        bool non_recursive = false) const;
+
+    // --------------------------------------------------------
+    template <template <typename> class TOUT, typename T>
+    bool ResolvePointer(vector< TOUT<T> >& out, const Pointer & ptrval,
+        const FileDatabase& db, const Field& f, bool) const;
+
+    // --------------------------------------------------------
+    bool ResolvePointer( std::shared_ptr< FileOffset >& out, const Pointer & ptrval,
+        const FileDatabase& db, const Field& f, bool) const;
+
+    // --------------------------------------------------------
+    inline const FileBlockHead* LocateFileBlockForAddress(
+        const Pointer & ptrval,
+        const FileDatabase& db) const;
+
+private:
+
+    // ------------------------------------------------------------------------------
+    template <typename T> T* _allocate(std::shared_ptr<T>& out, size_t& s) const {
+        out = std::shared_ptr<T>(new T());
+        s = 1;
+        return out.get();
+    }
+
+    template <typename T> T* _allocate(vector<T>& out, size_t& s) const {
+        out.resize(s);
+        return s ? &out.front() : NULL;
+    }
+
+    // --------------------------------------------------------
+    template <int error_policy>
+    struct _defaultInitializer {
+
+        template <typename T, unsigned int N>
+        void operator ()(T (& out)[N], const char* = NULL) {
+            for (unsigned int i = 0; i < N; ++i) {
+                out[i] = T();
+            }
+        }
+
+        template <typename T, unsigned int N, unsigned int M>
+        void operator ()(T (& out)[N][M], const char* = NULL) {
+            for (unsigned int i = 0; i < N; ++i) {
+                for (unsigned int j = 0; j < M; ++j) {
+                    out[i][j] = T();
+                }
+            }
+        }
+
+        template <typename T>
+        void operator ()(T& out, const char* = NULL) {
+            out = T();
+        }
+    };
+
+private:
+
+    mutable size_t cache_idx;
+};
+
+// --------------------------------------------------------
+template <>  struct Structure :: _defaultInitializer<ErrorPolicy_Warn> {
+
+    template <typename T>
+    void operator ()(T& out, const char* reason = "<add reason>") {
+        ASSIMP_LOG_WARN(reason);
+
+        // ... and let the show go on
+        _defaultInitializer<0 /*ErrorPolicy_Igno*/>()(out);
+    }
+};
+
+template <> struct Structure :: _defaultInitializer<ErrorPolicy_Fail> {
+
+    template <typename T>
+    void operator ()(T& /*out*/,const char* = "") {
+        // obviously, it is crucial that _DefaultInitializer is used
+        // only from within a catch clause.
+        throw;
+    }
+};
+
+// -------------------------------------------------------------------------------------------------------
+template <> inline bool Structure :: ResolvePointer<std::shared_ptr,ElemBase>(std::shared_ptr<ElemBase>& out,
+    const Pointer & ptrval,
+    const FileDatabase& db,
+    const Field& f,
+    bool
+    ) const;
+
+
+// -------------------------------------------------------------------------------
+/** Represents the full data structure information for a single BLEND file.
+ *  This data is extracted from the DNA1 chunk in the file.
+ *  #DNAParser does the reading and represents currently the only place where
+ *  DNA is altered.*/
+// -------------------------------------------------------------------------------
+class DNA
+{
+public:
+
+    typedef void (Structure::*ConvertProcPtr) (
+        std::shared_ptr<ElemBase> in,
+        const FileDatabase&
+    ) const;
+
+    typedef std::shared_ptr<ElemBase> (
+        Structure::*AllocProcPtr) () const;
+
+    typedef std::pair< AllocProcPtr, ConvertProcPtr > FactoryPair;
+
+public:
+
+    std::map<std::string, FactoryPair > converters;
+    vector<Structure > structures;
+    std::map<std::string, size_t> indices;
+
+public:
+
+    // --------------------------------------------------------
+    /** Access a structure by its canonical name, the pointer version returns NULL on failure
+      * while the reference version raises an error. */
+    inline const Structure& operator [] (const std::string& ss) const;
+    inline const Structure* Get (const std::string& ss) const;
+
+    // --------------------------------------------------------
+    /** Access a structure by its index */
+    inline const Structure& operator [] (const size_t i) const;
+
+public:
+
+    // --------------------------------------------------------
+    /** Add structure definitions for all the primitive types,
+     *  i.e. integer, short, char, float */
+    void AddPrimitiveStructures();
+
+    // --------------------------------------------------------
+    /** Fill the @c converters member with converters for all
+     *  known data types. The implementation of this method is
+     *  in BlenderScene.cpp and is machine-generated.
+     *  Converters are used to quickly handle objects whose
+     *  exact data type is a runtime-property and not yet
+     *  known at compile time (consier Object::data).*/
+    void RegisterConverters();
+
+
+    // --------------------------------------------------------
+    /** Take an input blob from the stream, interpret it according to
+     *  a its structure name and convert it to the intermediate
+     *  representation.
+     *  @param structure Destination structure definition
+     *  @param db File database.
+     *  @return A null pointer if no appropriate converter is available.*/
+    std::shared_ptr< ElemBase > ConvertBlobToStructure(
+        const Structure& structure,
+        const FileDatabase& db
+        ) const;
+
+    // --------------------------------------------------------
+    /** Find a suitable conversion function for a given Structure.
+     *  Such a converter function takes a blob from the input
+     *  stream, reads as much as it needs, and builds up a
+     *  complete object in intermediate representation.
+     *  @param structure Destination structure definition
+     *  @param db File database.
+     *  @return A null pointer in .first if no appropriate converter is available.*/
+    FactoryPair GetBlobToStructureConverter(
+        const Structure& structure,
+        const FileDatabase& db
+        ) const;
+
+
+#ifdef ASSIMP_BUILD_BLENDER_DEBUG
+    // --------------------------------------------------------
+    /** Dump the DNA to a text file. This is for debugging purposes.
+     *  The output file is `dna.txt` in the current working folder*/
+    void DumpToFile();
+#endif
+
+    // --------------------------------------------------------
+    /** Extract array dimensions from a C array declaration, such
+     *  as `...[4][6]`. Returned string would be `...[][]`.
+     *  @param out
+     *  @param array_sizes Receive maximally two array dimensions,
+     *    the second element is set to 1 if the array is flat.
+     *    Both are set to 1 if the input is not an array.
+     *  @throw DeadlyImportError if more than 2 dimensions are
+     *    encountered. */
+    static void ExtractArraySize(
+        const std::string& out,
+        size_t array_sizes[2]
+    );
+};
+
+// special converters for primitive types
+template <> inline void Structure :: Convert<int>       (int& dest,const FileDatabase& db) const;
+template <> inline void Structure :: Convert<short>     (short& dest,const FileDatabase& db) const;
+template <> inline void Structure :: Convert<char>      (char& dest,const FileDatabase& db) const;
+template <> inline void Structure :: Convert<float>     (float& dest,const FileDatabase& db) const;
+template <> inline void Structure :: Convert<double>    (double& dest,const FileDatabase& db) const;
+template <> inline void Structure :: Convert<Pointer>   (Pointer& dest,const FileDatabase& db) const;
+
+// -------------------------------------------------------------------------------
+/** Describes a master file block header. Each master file sections holds n
+ *  elements of a certain SDNA structure (or otherwise unspecified data). */
+// -------------------------------------------------------------------------------
+struct FileBlockHead
+{
+    // points right after the header of the file block
+    StreamReaderAny::pos start;
+
+    std::string id;
+    size_t size;
+
+    // original memory address of the data
+    Pointer address;
+
+    // index into DNA
+    unsigned int dna_index;
+
+    // number of structure instances to follow
+    size_t num;
+
+
+
+    // file blocks are sorted by address to quickly locate specific memory addresses
+    bool operator < (const FileBlockHead& o) const {
+        return address.val < o.address.val;
+    }
+
+    // for std::upper_bound
+    operator const Pointer& () const {
+        return address;
+    }
+};
+
+// for std::upper_bound
+inline bool operator< (const Pointer& a, const Pointer& b) {
+    return a.val < b.val;
+}
+
+// -------------------------------------------------------------------------------
+/** Utility to read all master file blocks in turn. */
+// -------------------------------------------------------------------------------
+class SectionParser
+{
+public:
+
+    // --------------------------------------------------------
+    /** @param stream Inout stream, must point to the
+     *  first section in the file. Call Next() once
+     *  to have it read.
+     *  @param ptr64 Pointer size in file is 64 bits? */
+    SectionParser(StreamReaderAny& stream,bool ptr64)
+        : stream(stream)
+        , ptr64(ptr64)
+    {
+        current.size = current.start = 0;
+    }
+
+public:
+
+    // --------------------------------------------------------
+    const FileBlockHead& GetCurrent() const {
+        return current;
+    }
+
+
+public:
+
+    // --------------------------------------------------------
+    /** Advance to the next section.
+     *  @throw DeadlyImportError if the last chunk was passed. */
+    void Next();
+
+public:
+
+    FileBlockHead current;
+    StreamReaderAny& stream;
+    bool ptr64;
+};
+
+
+#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
+// -------------------------------------------------------------------------------
+/** Import statistics, i.e. number of file blocks read*/
+// -------------------------------------------------------------------------------
+class Statistics {
+
+public:
+
+    Statistics ()
+        : fields_read       ()
+        , pointers_resolved ()
+        , cache_hits        ()
+//      , blocks_read       ()
+        , cached_objects    ()
+    {}
+
+public:
+
+    /** total number of fields we read */
+    unsigned int fields_read;
+
+    /** total number of resolved pointers */
+    unsigned int pointers_resolved;
+
+    /** number of pointers resolved from the cache */
+    unsigned int cache_hits;
+
+    /** number of blocks (from  FileDatabase::entries)
+      we did actually read from. */
+    // unsigned int blocks_read;
+
+    /** objects in FileData::cache */
+    unsigned int cached_objects;
+};
+#endif
+
+// -------------------------------------------------------------------------------
+/** The object cache - all objects addressed by pointers are added here. This
+ *  avoids circular references and avoids object duplication. */
+// -------------------------------------------------------------------------------
+template <template <typename> class TOUT>
+class ObjectCache
+{
+public:
+
+    typedef std::map< Pointer, TOUT<ElemBase> > StructureCache;
+
+public:
+
+    ObjectCache(const FileDatabase& db)
+        : db(db)
+    {
+        // currently there are only ~400 structure records per blend file.
+        // we read only a small part of them and don't cache objects
+        // which we don't need, so this should suffice.
+        caches.reserve(64);
+    }
+
+public:
+
+    // --------------------------------------------------------
+    /** Check whether a specific item is in the cache.
+     *  @param s Data type of the item
+     *  @param out Output pointer. Unchanged if the
+     *   cache doesn't know the item yet.
+     *  @param ptr Item address to look for. */
+    template <typename T> void get (
+        const Structure& s,
+        TOUT<T>& out,
+        const Pointer& ptr) const;
+
+    // --------------------------------------------------------
+    /** Add an item to the cache after the item has
+     * been fully read. Do not insert anything that
+     * may be faulty or might cause the loading
+     * to abort.
+     *  @param s Data type of the item
+     *  @param out Item to insert into the cache
+     *  @param ptr address (cache key) of the item. */
+    template <typename T> void set
+        (const Structure& s,
+        const TOUT<T>& out,
+        const Pointer& ptr);
+
+private:
+
+    mutable vector<StructureCache> caches;
+    const FileDatabase& db;
+};
+
+// -------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------
+template <> class ObjectCache<Blender::vector>
+{
+public:
+
+    ObjectCache(const FileDatabase&) {}
+
+    template <typename T> void get(const Structure&, vector<T>&, const Pointer&) {}
+    template <typename T> void set(const Structure&, const vector<T>&, const Pointer&) {}
+};
+
+#ifdef _MSC_VER
+#   pragma warning(disable:4355)
+#endif
+
+// -------------------------------------------------------------------------------
+/** Memory representation of a full BLEND file and all its dependencies. The
+ *  output aiScene is constructed from an instance of this data structure. */
+// -------------------------------------------------------------------------------
+class FileDatabase
+{
+    template <template <typename> class TOUT> friend class ObjectCache;
+
+public:
+    FileDatabase()
+        : _cacheArrays(*this)
+        , _cache(*this)
+        , next_cache_idx()
+    {}
+
+public:
+    // publicly accessible fields
+    bool i64bit;
+    bool little;
+
+    DNA dna;
+    std::shared_ptr< StreamReaderAny > reader;
+    vector< FileBlockHead > entries;
+
+public:
+
+    Statistics& stats() const {
+        return _stats;
+    }
+
+    // For all our templates to work on both shared_ptr's and vector's
+    // using the same code, a dummy cache for arrays is provided. Actually,
+    // arrays of objects are never cached because we can't easily
+    // ensure their proper destruction.
+    template <typename T>
+    ObjectCache<std::shared_ptr>& cache(std::shared_ptr<T>& /*in*/) const {
+        return _cache;
+    }
+
+    template <typename T>
+    ObjectCache<vector>& cache(vector<T>& /*in*/) const {
+        return _cacheArrays;
+    }
+
+private:
+
+
+#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
+    mutable Statistics _stats;
+#endif
+
+    mutable ObjectCache<vector> _cacheArrays;
+    mutable ObjectCache<std::shared_ptr> _cache;
+
+    mutable size_t next_cache_idx;
+};
+
+#ifdef _MSC_VER
+#   pragma warning(default:4355)
+#endif
+
+// -------------------------------------------------------------------------------
+/** Factory to extract a #DNA from the DNA1 file block in a BLEND file. */
+// -------------------------------------------------------------------------------
+class DNAParser
+{
+
+public:
+
+    /** Bind the parser to a empty DNA and an input stream */
+    DNAParser(FileDatabase& db)
+        : db(db)
+    {}
+
+public:
+
+    // --------------------------------------------------------
+    /** Locate the DNA in the file and parse it. The input
+     *  stream is expected to point to the beginning of the DN1
+     *  chunk at the time this method is called and is
+     *  undefined afterwards.
+     *  @throw DeadlyImportError if the DNA cannot be read.
+     *  @note The position of the stream pointer is undefined
+     *    afterwards.*/
+    void Parse ();
+
+public:
+
+    /** Obtain a reference to the extracted DNA information */
+    const Blender::DNA& GetDNA() const {
+        return db.dna;
+    }
+
+private:
+
+    FileDatabase& db;
+};
+
+/**
+*   @brief  read CustomData's data to ptr to mem
+*   @param[out] out memory ptr to set
+*   @param[in]  cdtype  to read
+*   @param[in]  cnt cnt of elements to read
+*   @param[in]  db to read elements from
+*   @return true when ok
+*/
+bool readCustomData(std::shared_ptr<ElemBase> &out, int cdtype, size_t cnt, const FileDatabase &db);
+
+
+    } // end Blend
+} // end Assimp
+
+#include "BlenderDNA.inl"
+
+#endif

+ 856 - 0
Engine/lib/assimp/code/BlenderDNA.inl

@@ -0,0 +1,856 @@
+/*
+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  BlenderDNA.inl
+ *  @brief Blender `DNA` (file format specification embedded in
+ *    blend file itself) loader.
+ */
+#ifndef INCLUDED_AI_BLEND_DNA_INL
+#define INCLUDED_AI_BLEND_DNA_INL
+
+#include <memory>
+#include <assimp/TinyFormatter.h>
+
+namespace Assimp {
+namespace Blender {
+
+//--------------------------------------------------------------------------------
+const Field& Structure :: operator [] (const std::string& ss) const
+{
+    std::map<std::string, size_t>::const_iterator it = indices.find(ss);
+    if (it == indices.end()) {
+        throw Error((Formatter::format(),
+            "BlendDNA: Did not find a field named `",ss,"` in structure `",name,"`"
+            ));
+    }
+
+    return fields[(*it).second];
+}
+
+//--------------------------------------------------------------------------------
+const Field* Structure :: Get (const std::string& ss) const
+{
+    std::map<std::string, size_t>::const_iterator it = indices.find(ss);
+    return it == indices.end() ? NULL : &fields[(*it).second];
+}
+
+//--------------------------------------------------------------------------------
+const Field& Structure :: operator [] (const size_t i) const
+{
+    if (i >= fields.size()) {
+        throw Error((Formatter::format(),
+            "BlendDNA: There is no field with index `",i,"` in structure `",name,"`"
+            ));
+    }
+
+    return fields[i];
+}
+
+//--------------------------------------------------------------------------------
+template <typename T> std::shared_ptr<ElemBase> Structure :: Allocate() const
+{
+    return std::shared_ptr<T>(new T());
+}
+
+//--------------------------------------------------------------------------------
+template <typename T> void Structure :: Convert(
+    std::shared_ptr<ElemBase> in,
+    const FileDatabase& db) const
+{
+    Convert<T> (*static_cast<T*> ( in.get() ),db);
+}
+
+//--------------------------------------------------------------------------------
+template <int error_policy, typename T, size_t M>
+void Structure :: ReadFieldArray(T (& out)[M], const char* name, const FileDatabase& db) const
+{
+    const StreamReaderAny::pos old = db.reader->GetCurrentPos();
+    try {
+        const Field& f = (*this)[name];
+        const Structure& s = db.dna[f.type];
+
+        // is the input actually an array?
+        if (!(f.flags & FieldFlag_Array)) {
+            throw Error((Formatter::format(),"Field `",name,"` of structure `",
+                this->name,"` ought to be an array of size ",M
+                ));
+        }
+
+        db.reader->IncPtr(f.offset);
+
+        // size conversions are always allowed, regardless of error_policy
+        unsigned int i = 0;
+        for(; i < std::min(f.array_sizes[0],M); ++i) {
+            s.Convert(out[i],db);
+        }
+        for(; i < M; ++i) {
+            _defaultInitializer<ErrorPolicy_Igno>()(out[i]);
+        }
+    }
+    catch (const Error& e) {
+        _defaultInitializer<error_policy>()(out,e.what());
+    }
+
+    // and recover the previous stream position
+    db.reader->SetCurrentPos(old);
+
+#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
+    ++db.stats().fields_read;
+#endif
+}
+
+//--------------------------------------------------------------------------------
+template <int error_policy, typename T, size_t M, size_t N>
+void Structure :: ReadFieldArray2(T (& out)[M][N], const char* name, const FileDatabase& db) const
+{
+    const StreamReaderAny::pos old = db.reader->GetCurrentPos();
+    try {
+        const Field& f = (*this)[name];
+        const Structure& s = db.dna[f.type];
+
+        // is the input actually an array?
+        if (!(f.flags & FieldFlag_Array)) {
+            throw Error((Formatter::format(),"Field `",name,"` of structure `",
+                this->name,"` ought to be an array of size ",M,"*",N
+                ));
+        }
+
+        db.reader->IncPtr(f.offset);
+
+        // size conversions are always allowed, regardless of error_policy
+        unsigned int i = 0;
+        for(; i < std::min(f.array_sizes[0],M); ++i) {
+            unsigned int j = 0;
+            for(; j < std::min(f.array_sizes[1],N); ++j) {
+                s.Convert(out[i][j],db);
+            }
+            for(; j < N; ++j) {
+                _defaultInitializer<ErrorPolicy_Igno>()(out[i][j]);
+            }
+        }
+        for(; i < M; ++i) {
+            _defaultInitializer<ErrorPolicy_Igno>()(out[i]);
+        }
+    }
+    catch (const Error& e) {
+        _defaultInitializer<error_policy>()(out,e.what());
+    }
+
+    // and recover the previous stream position
+    db.reader->SetCurrentPos(old);
+
+#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
+    ++db.stats().fields_read;
+#endif
+}
+
+//--------------------------------------------------------------------------------
+template <int error_policy, template <typename> class TOUT, typename T>
+bool Structure :: ReadFieldPtr(TOUT<T>& out, const char* name, const FileDatabase& db,
+    bool non_recursive /*= false*/) const
+{
+    const StreamReaderAny::pos old = db.reader->GetCurrentPos();
+    Pointer ptrval;
+    const Field* f;
+    try {
+        f = &(*this)[name];
+
+        // sanity check, should never happen if the genblenddna script is right
+        if (!(f->flags & FieldFlag_Pointer)) {
+            throw Error((Formatter::format(),"Field `",name,"` of structure `",
+                this->name,"` ought to be a pointer"));
+        }
+
+        db.reader->IncPtr(f->offset);
+        Convert(ptrval,db);
+        // actually it is meaningless on which Structure the Convert is called
+        // because the `Pointer` argument triggers a special implementation.
+    }
+    catch (const Error& e) {
+        _defaultInitializer<error_policy>()(out,e.what());
+
+        out.reset();
+        return false;
+    }
+
+    // resolve the pointer and load the corresponding structure
+    const bool res = ResolvePointer(out,ptrval,db,*f, non_recursive);
+
+    if(!non_recursive) {
+        // and recover the previous stream position
+        db.reader->SetCurrentPos(old);
+    }
+
+#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
+    ++db.stats().fields_read;
+#endif
+
+    return res;
+}
+
+//--------------------------------------------------------------------------------
+template <int error_policy, template <typename> class TOUT, typename T, size_t N>
+bool Structure :: ReadFieldPtr(TOUT<T> (&out)[N], const char* name,
+    const FileDatabase& db) const
+{
+    // XXX see if we can reduce this to call to the 'normal' ReadFieldPtr
+    const StreamReaderAny::pos old = db.reader->GetCurrentPos();
+    Pointer ptrval[N];
+    const Field* f;
+    try {
+        f = &(*this)[name];
+
+        // sanity check, should never happen if the genblenddna script is right
+        if ((FieldFlag_Pointer|FieldFlag_Pointer) != (f->flags & (FieldFlag_Pointer|FieldFlag_Pointer))) {
+            throw Error((Formatter::format(),"Field `",name,"` of structure `",
+                this->name,"` ought to be a pointer AND an array"));
+        }
+
+        db.reader->IncPtr(f->offset);
+
+        size_t i = 0;
+        for(; i < std::min(f->array_sizes[0],N); ++i) {
+            Convert(ptrval[i],db);
+        }
+        for(; i < N; ++i) {
+            _defaultInitializer<ErrorPolicy_Igno>()(ptrval[i]);
+        }
+
+        // actually it is meaningless on which Structure the Convert is called
+        // because the `Pointer` argument triggers a special implementation.
+    }
+    catch (const Error& e) {
+        _defaultInitializer<error_policy>()(out,e.what());
+        for(size_t i = 0; i < N; ++i) {
+            out[i].reset();
+        }
+        return false;
+    }
+
+    bool res = true;
+    for(size_t i = 0; i < N; ++i) {
+        // resolve the pointer and load the corresponding structure
+        res = ResolvePointer(out[i],ptrval[i],db,*f) && res;
+    }
+
+    // and recover the previous stream position
+    db.reader->SetCurrentPos(old);
+
+#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
+    ++db.stats().fields_read;
+#endif
+    return res;
+}
+
+//--------------------------------------------------------------------------------
+template <int error_policy, typename T>
+void Structure :: ReadField(T& out, const char* name, const FileDatabase& db) const
+{
+    const StreamReaderAny::pos old = db.reader->GetCurrentPos();
+    try {
+        const Field& f = (*this)[name];
+        // find the structure definition pertaining to this field
+        const Structure& s = db.dna[f.type];
+
+        db.reader->IncPtr(f.offset);
+        s.Convert(out,db);
+    }
+    catch (const Error& e) {
+        _defaultInitializer<error_policy>()(out,e.what());
+    }
+
+    // and recover the previous stream position
+    db.reader->SetCurrentPos(old);
+
+#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
+    ++db.stats().fields_read;
+#endif
+}
+
+
+//--------------------------------------------------------------------------------
+// field parsing for raw untyped data (like CustomDataLayer.data)
+template <int error_policy>
+bool Structure::ReadCustomDataPtr(std::shared_ptr<ElemBase>&out, int cdtype, const char* name, const FileDatabase& db) const {
+
+	const StreamReaderAny::pos old = db.reader->GetCurrentPos();
+
+	Pointer ptrval;
+	const Field* f;
+	try	{
+		f = &(*this)[name];
+
+		// sanity check, should never happen if the genblenddna script is right
+		if (!(f->flags & FieldFlag_Pointer)) {
+			throw Error((Formatter::format(), "Field `", name, "` of structure `",
+				this->name, "` ought to be a pointer"));
+		}
+
+		db.reader->IncPtr(f->offset);
+		Convert(ptrval, db);
+		// actually it is meaningless on which Structure the Convert is called
+		// because the `Pointer` argument triggers a special implementation.
+	}
+	catch (const Error& e) {
+		_defaultInitializer<error_policy>()(out, e.what());
+		out.reset();
+	}
+
+	bool readOk = true;
+	if (ptrval.val)	{
+		// get block for ptr
+		const FileBlockHead* block = LocateFileBlockForAddress(ptrval, db);
+		db.reader->SetCurrentPos(block->start + static_cast<size_t>((ptrval.val - block->address.val)));
+		// read block->num instances of given type to out
+		readOk = readCustomData(out, cdtype, block->num, db);
+	}
+
+	// and recover the previous stream position
+	db.reader->SetCurrentPos(old);
+
+#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
+	++db.stats().fields_read;
+#endif
+
+	return readOk;
+}
+
+//--------------------------------------------------------------------------------
+template <int error_policy, template <typename> class TOUT, typename T>
+bool Structure::ReadFieldPtrVector(vector<TOUT<T>>&out, const char* name, const FileDatabase& db) const {
+	out.clear();
+
+	const StreamReaderAny::pos old = db.reader->GetCurrentPos();
+
+	Pointer ptrval;
+	const Field* f;
+	try	{
+		f = &(*this)[name];
+
+		// sanity check, should never happen if the genblenddna script is right
+		if (!(f->flags & FieldFlag_Pointer)) {
+			throw Error((Formatter::format(), "Field `", name, "` of structure `",
+				this->name, "` ought to be a pointer"));
+		}
+
+		db.reader->IncPtr(f->offset);
+		Convert(ptrval, db);
+		// actually it is meaningless on which Structure the Convert is called
+		// because the `Pointer` argument triggers a special implementation.
+	}
+	catch (const Error& e) {
+		_defaultInitializer<error_policy>()(out, e.what());
+		out.clear();
+		return false;
+	}
+
+
+	if (ptrval.val)	{
+		// find the file block the pointer is pointing to
+		const FileBlockHead* block = LocateFileBlockForAddress(ptrval, db);
+		db.reader->SetCurrentPos(block->start + static_cast<size_t>((ptrval.val - block->address.val)));
+		// FIXME: basically, this could cause problems with 64 bit pointers on 32 bit systems.
+		// I really ought to improve StreamReader to work with 64 bit indices exclusively.
+
+		const Structure& s = db.dna[f->type];
+		for (size_t i = 0; i < block->num; ++i)	{
+			TOUT<T> p(new T);
+			s.Convert(*p, db);
+			out.push_back(p);
+		}
+	}
+
+	db.reader->SetCurrentPos(old);
+
+#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
+	++db.stats().fields_read;
+#endif
+
+	return true;
+}
+
+
+//--------------------------------------------------------------------------------
+template <template <typename> class TOUT, typename T>
+bool Structure :: ResolvePointer(TOUT<T>& out, const Pointer & ptrval, const FileDatabase& db,
+    const Field& f,
+    bool non_recursive /*= false*/) const
+{
+    out.reset(); // ensure null pointers work
+    if (!ptrval.val) {
+        return false;
+    }
+    const Structure& s = db.dna[f.type];
+    // find the file block the pointer is pointing to
+    const FileBlockHead* block = LocateFileBlockForAddress(ptrval,db);
+
+    // also determine the target type from the block header
+    // and check if it matches the type which we expect.
+    const Structure& ss = db.dna[block->dna_index];
+    if (ss != s) {
+        throw Error((Formatter::format(),"Expected target to be of type `",s.name,
+            "` but seemingly it is a `",ss.name,"` instead"
+            ));
+    }
+
+    // try to retrieve the object from the cache
+    db.cache(out).get(s,out,ptrval);
+    if (out) {
+        return true;
+    }
+
+    // seek to this location, but save the previous stream pointer.
+    const StreamReaderAny::pos pold = db.reader->GetCurrentPos();
+    db.reader->SetCurrentPos(block->start+ static_cast<size_t>((ptrval.val - block->address.val) ));
+    // FIXME: basically, this could cause problems with 64 bit pointers on 32 bit systems.
+    // I really ought to improve StreamReader to work with 64 bit indices exclusively.
+
+    // continue conversion after allocating the required storage
+    size_t num = block->size / ss.size;
+    T* o = _allocate(out,num);
+
+    // cache the object before we convert it to avoid cyclic recursion.
+    db.cache(out).set(s,out,ptrval);
+
+    // if the non_recursive flag is set, we don't do anything but leave
+    // the cursor at the correct position to resolve the object.
+    if (!non_recursive) {
+        for (size_t i = 0; i < num; ++i,++o) {
+            s.Convert(*o,db);
+        }
+
+        db.reader->SetCurrentPos(pold);
+    }
+
+#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
+    if(out) {
+        ++db.stats().pointers_resolved;
+    }
+#endif
+    return false;
+}
+
+
+//--------------------------------------------------------------------------------
+inline bool Structure :: ResolvePointer( std::shared_ptr< FileOffset >& out, const Pointer & ptrval,
+    const FileDatabase& db,
+    const Field&,
+    bool) const
+{
+    // Currently used exclusively by PackedFile::data to represent
+    // a simple offset into the mapped BLEND file.
+    out.reset();
+    if (!ptrval.val) {
+        return false;
+    }
+
+    // find the file block the pointer is pointing to
+    const FileBlockHead* block = LocateFileBlockForAddress(ptrval,db);
+
+    out =  std::shared_ptr< FileOffset > (new FileOffset());
+    out->val = block->start+ static_cast<size_t>((ptrval.val - block->address.val) );
+    return false;
+}
+
+//--------------------------------------------------------------------------------
+template <template <typename> class TOUT, typename T>
+bool Structure :: ResolvePointer(vector< TOUT<T> >& out, const Pointer & ptrval,
+    const FileDatabase& db,
+    const Field& f,
+    bool) const
+{
+    // This is a function overload, not a template specialization. According to
+    // the partial ordering rules, it should be selected by the compiler
+    // for array-of-pointer inputs, i.e. Object::mats.
+
+    out.reset();
+    if (!ptrval.val) {
+        return false;
+    }
+
+    // find the file block the pointer is pointing to
+    const FileBlockHead* block = LocateFileBlockForAddress(ptrval,db);
+    const size_t num = block->size / (db.i64bit?8:4);
+
+    // keep the old stream position
+    const StreamReaderAny::pos pold = db.reader->GetCurrentPos();
+    db.reader->SetCurrentPos(block->start+ static_cast<size_t>((ptrval.val - block->address.val) ));
+
+    bool res = false;
+    // allocate raw storage for the array
+    out.resize(num);
+    for (size_t i = 0; i< num; ++i) {
+        Pointer val;
+        Convert(val,db);
+
+        // and resolve the pointees
+        res = ResolvePointer(out[i],val,db,f) && res;
+    }
+
+    db.reader->SetCurrentPos(pold);
+    return res;
+}
+
+//--------------------------------------------------------------------------------
+template <> bool Structure :: ResolvePointer<std::shared_ptr,ElemBase>(std::shared_ptr<ElemBase>& out,
+    const Pointer & ptrval,
+    const FileDatabase& db,
+    const Field&,
+    bool
+) const
+{
+    // Special case when the data type needs to be determined at runtime.
+    // Less secure than in the `strongly-typed` case.
+
+    out.reset();
+    if (!ptrval.val) {
+        return false;
+    }
+
+    // find the file block the pointer is pointing to
+    const FileBlockHead* block = LocateFileBlockForAddress(ptrval,db);
+
+    // determine the target type from the block header
+    const Structure& s = db.dna[block->dna_index];
+
+    // try to retrieve the object from the cache
+    db.cache(out).get(s,out,ptrval);
+    if (out) {
+        return true;
+    }
+
+    // seek to this location, but save the previous stream pointer.
+    const StreamReaderAny::pos pold = db.reader->GetCurrentPos();
+    db.reader->SetCurrentPos(block->start+ static_cast<size_t>((ptrval.val - block->address.val) ));
+    // FIXME: basically, this could cause problems with 64 bit pointers on 32 bit systems.
+    // I really ought to improve StreamReader to work with 64 bit indices exclusively.
+
+    // continue conversion after allocating the required storage
+    DNA::FactoryPair builders = db.dna.GetBlobToStructureConverter(s,db);
+    if (!builders.first) {
+        // this might happen if DNA::RegisterConverters hasn't been called so far
+        // or if the target type is not contained in `our` DNA.
+        out.reset();
+        ASSIMP_LOG_WARN_F( "Failed to find a converter for the `",s.name,"` structure" );
+        return false;
+    }
+
+    // allocate the object hull
+    out = (s.*builders.first)();
+
+    // cache the object immediately to prevent infinite recursion in a
+    // circular list with a single element (i.e. a self-referencing element).
+    db.cache(out).set(s,out,ptrval);
+
+    // and do the actual conversion
+    (s.*builders.second)(out,db);
+    db.reader->SetCurrentPos(pold);
+
+    // store a pointer to the name string of the actual type
+    // in the object itself. This allows the conversion code
+    // to perform additional type checking.
+    out->dna_type = s.name.c_str();
+
+
+#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
+    ++db.stats().pointers_resolved;
+#endif
+    return false;
+}
+
+//--------------------------------------------------------------------------------
+const FileBlockHead* Structure :: LocateFileBlockForAddress(const Pointer & ptrval, const FileDatabase& db) const
+{
+    // the file blocks appear in list sorted by
+    // with ascending base addresses so we can run a
+    // binary search to locate the pointer quickly.
+
+    // NOTE: Blender seems to distinguish between side-by-side
+    // data (stored in the same data block) and far pointers,
+    // which are only used for structures starting with an ID.
+    // We don't need to make this distinction, our algorithm
+    // works regardless where the data is stored.
+    vector<FileBlockHead>::const_iterator it = std::lower_bound(db.entries.begin(),db.entries.end(),ptrval);
+    if (it == db.entries.end()) {
+        // this is crucial, pointers may not be invalid.
+        // this is either a corrupted file or an attempted attack.
+        throw DeadlyImportError((Formatter::format(),"Failure resolving pointer 0x",
+            std::hex,ptrval.val,", no file block falls into this address range"
+            ));
+    }
+    if (ptrval.val >= (*it).address.val + (*it).size) {
+        throw DeadlyImportError((Formatter::format(),"Failure resolving pointer 0x",
+            std::hex,ptrval.val,", nearest file block starting at 0x",
+            (*it).address.val," ends at 0x",
+            (*it).address.val + (*it).size
+            ));
+    }
+    return &*it;
+}
+
+// ------------------------------------------------------------------------------------------------
+// NOTE: The MSVC debugger keeps showing up this annoying `a cast to a smaller data type has
+// caused a loss of data`-warning. Avoid this warning by a masking with an appropriate bitmask.
+
+template <typename T> struct signless;
+template <> struct signless<char> {typedef unsigned char type;};
+template <> struct signless<short> {typedef unsigned short type;};
+template <> struct signless<int> {typedef unsigned int type;};
+template <> struct signless<unsigned char> { typedef unsigned char type; };
+template <typename T>
+struct static_cast_silent {
+    template <typename V>
+    T operator()(V in) {
+        return static_cast<T>(in & static_cast<typename signless<T>::type>(-1));
+    }
+};
+
+template <> struct static_cast_silent<float> {
+    template <typename V> float  operator()(V in) {
+        return static_cast<float> (in);
+    }
+};
+
+template <> struct static_cast_silent<double> {
+    template <typename V> double operator()(V in) {
+        return static_cast<double>(in);
+    }
+};
+
+// ------------------------------------------------------------------------------------------------
+template <typename T> inline void ConvertDispatcher(T& out, const Structure& in,const FileDatabase& db)
+{
+    if (in.name == "int") {
+        out = static_cast_silent<T>()(db.reader->GetU4());
+    }
+    else if (in.name == "short") {
+        out = static_cast_silent<T>()(db.reader->GetU2());
+    }
+    else if (in.name == "char") {
+        out = static_cast_silent<T>()(db.reader->GetU1());
+    }
+    else if (in.name == "float") {
+        out = static_cast<T>(db.reader->GetF4());
+    }
+    else if (in.name == "double") {
+        out = static_cast<T>(db.reader->GetF8());
+    }
+    else {
+        throw DeadlyImportError("Unknown source for conversion to primitive data type: "+in.name);
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+template <> inline void Structure :: Convert<int>    (int& dest,const FileDatabase& db) const
+{
+    ConvertDispatcher(dest,*this,db);
+}
+
+// ------------------------------------------------------------------------------------------------
+template<> inline void Structure :: Convert<short>  (short& dest,const FileDatabase& db) const
+{
+    // automatic rescaling from short to float and vice versa (seems to be used by normals)
+    if (name == "float") {
+        float f = db.reader->GetF4();
+        if ( f > 1.0f )
+            f = 1.0f;
+        dest = static_cast<short>( f * 32767.f);
+        //db.reader->IncPtr(-4);
+        return;
+    }
+    else if (name == "double") {
+        dest = static_cast<short>(db.reader->GetF8() * 32767.);
+        //db.reader->IncPtr(-8);
+        return;
+    }
+    ConvertDispatcher(dest,*this,db);
+}
+
+// ------------------------------------------------------------------------------------------------
+template <> inline void Structure :: Convert<char>   (char& dest,const FileDatabase& db) const
+{
+    // automatic rescaling from char to float and vice versa (seems useful for RGB colors)
+    if (name == "float") {
+        dest = static_cast<char>(db.reader->GetF4() * 255.f);
+        return;
+    }
+    else if (name == "double") {
+        dest = static_cast<char>(db.reader->GetF8() * 255.f);
+        return;
+    }
+    ConvertDispatcher(dest,*this,db);
+}
+
+// ------------------------------------------------------------------------------------------------
+template <> inline void Structure::Convert<unsigned char>(unsigned char& dest, const FileDatabase& db) const
+{
+	// automatic rescaling from char to float and vice versa (seems useful for RGB colors)
+	if (name == "float") {
+		dest = static_cast<unsigned char>(db.reader->GetF4() * 255.f);
+		return;
+	}
+	else if (name == "double") {
+		dest = static_cast<unsigned char>(db.reader->GetF8() * 255.f);
+		return;
+	}
+	ConvertDispatcher(dest, *this, db);
+}
+
+
+// ------------------------------------------------------------------------------------------------
+template <> inline void Structure :: Convert<float>  (float& dest,const FileDatabase& db) const
+{
+    // automatic rescaling from char to float and vice versa (seems useful for RGB colors)
+    if (name == "char") {
+        dest = db.reader->GetI1() / 255.f;
+        return;
+    }
+    // automatic rescaling from short to float and vice versa (used by normals)
+    else if (name == "short") {
+        dest = db.reader->GetI2() / 32767.f;
+        return;
+    }
+    ConvertDispatcher(dest,*this,db);
+}
+
+// ------------------------------------------------------------------------------------------------
+template <> inline void Structure :: Convert<double> (double& dest,const FileDatabase& db) const
+{
+    if (name == "char") {
+        dest = db.reader->GetI1() / 255.;
+        return;
+    }
+    else if (name == "short") {
+        dest = db.reader->GetI2() / 32767.;
+        return;
+    }
+    ConvertDispatcher(dest,*this,db);
+}
+
+// ------------------------------------------------------------------------------------------------
+template <> inline void Structure :: Convert<Pointer> (Pointer& dest,const FileDatabase& db) const
+{
+    if (db.i64bit) {
+        dest.val = db.reader->GetU8();
+        //db.reader->IncPtr(-8);
+        return;
+    }
+    dest.val = db.reader->GetU4();
+    //db.reader->IncPtr(-4);
+}
+
+//--------------------------------------------------------------------------------
+const Structure& DNA :: operator [] (const std::string& ss) const
+{
+    std::map<std::string, size_t>::const_iterator it = indices.find(ss);
+    if (it == indices.end()) {
+        throw Error((Formatter::format(),
+            "BlendDNA: Did not find a structure named `",ss,"`"
+            ));
+    }
+
+    return structures[(*it).second];
+}
+
+//--------------------------------------------------------------------------------
+const Structure* DNA :: Get (const std::string& ss) const
+{
+    std::map<std::string, size_t>::const_iterator it = indices.find(ss);
+    return it == indices.end() ? NULL : &structures[(*it).second];
+}
+
+//--------------------------------------------------------------------------------
+const Structure& DNA :: operator [] (const size_t i) const
+{
+    if (i >= structures.size()) {
+        throw Error((Formatter::format(),
+            "BlendDNA: There is no structure with index `",i,"`"
+            ));
+    }
+
+    return structures[i];
+}
+
+//--------------------------------------------------------------------------------
+template <template <typename> class TOUT> template <typename T> void ObjectCache<TOUT> :: get (
+    const Structure& s,
+    TOUT<T>& out,
+    const Pointer& ptr
+) const {
+
+    if(s.cache_idx == static_cast<size_t>(-1)) {
+        s.cache_idx = db.next_cache_idx++;
+        caches.resize(db.next_cache_idx);
+        return;
+    }
+
+    typename StructureCache::const_iterator it = caches[s.cache_idx].find(ptr);
+    if (it != caches[s.cache_idx].end()) {
+        out = std::static_pointer_cast<T>( (*it).second );
+
+#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
+        ++db.stats().cache_hits;
+#endif
+    }
+    // otherwise, out remains untouched
+}
+
+
+//--------------------------------------------------------------------------------
+template <template <typename> class TOUT> template <typename T> void ObjectCache<TOUT> :: set (
+    const Structure& s,
+    const TOUT<T>& out,
+    const Pointer& ptr
+) {
+    if(s.cache_idx == static_cast<size_t>(-1)) {
+        s.cache_idx = db.next_cache_idx++;
+        caches.resize(db.next_cache_idx);
+    }
+    caches[s.cache_idx][ptr] = std::static_pointer_cast<ElemBase>( out );
+
+#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
+    ++db.stats().cached_objects;
+#endif
+}
+
+}}
+#endif

+ 206 - 0
Engine/lib/assimp/code/BlenderIntermediate.h

@@ -0,0 +1,206 @@
+/*
+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  BlenderIntermediate.h
+ *  @brief Internal utility structures for the BlenderLoader. It also serves
+ *    as master include file for the whole (internal) Blender subsystem.
+ */
+#ifndef INCLUDED_AI_BLEND_INTERMEDIATE_H
+#define INCLUDED_AI_BLEND_INTERMEDIATE_H
+
+#include "BlenderLoader.h"
+#include "BlenderDNA.h"
+#include "BlenderScene.h"
+#include <deque>
+#include <assimp/material.h>
+
+struct aiTexture;
+
+namespace Assimp {
+namespace Blender {
+
+    // --------------------------------------------------------------------
+    /** Mini smart-array to avoid pulling in even more boost stuff. usable with vector and deque */
+    // --------------------------------------------------------------------
+    template <template <typename,typename> class TCLASS, typename T>
+    struct TempArray    {
+        typedef TCLASS< T*,std::allocator<T*> > mywrap;
+
+        TempArray() {
+        }
+
+        ~TempArray () {
+            for(T* elem : arr) {
+                delete elem;
+            }
+        }
+
+        void dismiss() {
+            arr.clear();
+        }
+
+        mywrap* operator -> () {
+            return &arr;
+        }
+
+        operator mywrap& () {
+            return arr;
+        }
+
+        operator const mywrap& () const {
+            return arr;
+        }
+
+        mywrap& get () {
+            return arr;
+        }
+
+        const mywrap& get () const {
+            return arr;
+        }
+
+        T* operator[] (size_t idx) const {
+            return arr[idx];
+        }
+
+        T*& operator[] (size_t idx) {
+            return arr[idx];
+        }
+
+    private:
+        // no copy semantics
+        void operator= (const TempArray&)  {
+        }
+
+        TempArray(const TempArray& /*arr*/) {
+        }
+
+    private:
+        mywrap arr;
+    };
+
+#ifdef _MSC_VER
+#   pragma warning(disable:4351)
+#endif
+
+    // As counter-intuitive as it may seem, a comparator must return false for equal values.
+    // The C++ standard defines and expects this behavior: true if lhs < rhs, false otherwise.
+    struct ObjectCompare {
+        bool operator() (const Object* left, const Object* right) const {
+            return ::strncmp(left->id.name, right->id.name, strlen( left->id.name ) ) < 0;
+        }
+    };
+
+    // When keeping objects in sets, sort them by their name.
+    typedef std::set<const Object*, ObjectCompare> ObjectSet;
+
+    // --------------------------------------------------------------------
+    /** ConversionData acts as intermediate storage location for
+     *  the various ConvertXXX routines in BlenderImporter.*/
+    // --------------------------------------------------------------------
+    struct ConversionData
+    {
+        ConversionData(const FileDatabase& db)
+            : sentinel_cnt()
+            , next_texture()
+            , db(db)
+        {}
+
+        // As counter-intuitive as it may seem, a comparator must return false for equal values.
+        // The C++ standard defines and expects this behavior: true if lhs < rhs, false otherwise.
+        struct ObjectCompare {
+            bool operator() (const Object* left, const Object* right) const {
+                return ::strncmp( left->id.name, right->id.name, strlen( left->id.name ) ) < 0;
+            }
+        };
+
+        ObjectSet objects;
+
+        TempArray <std::vector, aiMesh> meshes;
+        TempArray <std::vector, aiCamera> cameras;
+        TempArray <std::vector, aiLight> lights;
+        TempArray <std::vector, aiMaterial> materials;
+        TempArray <std::vector, aiTexture> textures;
+
+        // set of all materials referenced by at least one mesh in the scene
+        std::deque< std::shared_ptr< Material > > materials_raw;
+
+        // counter to name sentinel textures inserted as substitutes for procedural textures.
+        unsigned int sentinel_cnt;
+
+        // next texture ID for each texture type, respectively
+        unsigned int next_texture[aiTextureType_UNKNOWN+1];
+
+        // original file data
+        const FileDatabase& db;
+    };
+#ifdef _MSC_VER
+#   pragma warning(default:4351)
+#endif
+
+// ------------------------------------------------------------------------------------------------
+inline const char* GetTextureTypeDisplayString(Tex::Type t)
+{
+    switch (t)  {
+    case Tex::Type_CLOUDS       :  return  "Clouds";
+    case Tex::Type_WOOD         :  return  "Wood";
+    case Tex::Type_MARBLE       :  return  "Marble";
+    case Tex::Type_MAGIC        :  return  "Magic";
+    case Tex::Type_BLEND        :  return  "Blend";
+    case Tex::Type_STUCCI       :  return  "Stucci";
+    case Tex::Type_NOISE        :  return  "Noise";
+    case Tex::Type_PLUGIN       :  return  "Plugin";
+    case Tex::Type_MUSGRAVE     :  return  "Musgrave";
+    case Tex::Type_VORONOI      :  return  "Voronoi";
+    case Tex::Type_DISTNOISE    :  return  "DistortedNoise";
+    case Tex::Type_ENVMAP       :  return  "EnvMap";
+    case Tex::Type_IMAGE        :  return  "Image";
+    default:
+        break;
+    }
+    return "<Unknown>";
+}
+
+} // ! Blender
+} // ! Assimp
+
+#endif // ! INCLUDED_AI_BLEND_INTERMEDIATE_H

+ 1370 - 0
Engine/lib/assimp/code/BlenderLoader.cpp

@@ -0,0 +1,1370 @@
+
+/*
+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  BlenderLoader.cpp
+ *  @brief Implementation of the Blender3D importer class.
+ */
+
+
+//#define ASSIMP_BUILD_NO_COMPRESSED_BLEND
+// Uncomment this to disable support for (gzip)compressed .BLEND files
+
+#ifndef ASSIMP_BUILD_NO_BLEND_IMPORTER
+
+#include "BlenderIntermediate.h"
+#include "BlenderModifier.h"
+#include "BlenderBMesh.h"
+#include "BlenderCustomData.h"
+#include <assimp/StringUtils.h>
+#include <assimp/scene.h>
+#include <assimp/importerdesc.h>
+
+#include <assimp/StringComparison.h>
+#include <assimp/StreamReader.h>
+#include <assimp/MemoryIOWrapper.h>
+
+#include <cctype>
+
+
+// zlib is needed for compressed blend files
+#ifndef ASSIMP_BUILD_NO_COMPRESSED_BLEND
+#   ifdef ASSIMP_BUILD_NO_OWN_ZLIB
+#       include <zlib.h>
+#   else
+#       include "../contrib/zlib/zlib.h"
+#   endif
+#endif
+
+namespace Assimp {
+    template<> const char* LogFunctions<BlenderImporter>::Prefix()
+    {
+        static auto prefix = "BLEND: ";
+        return prefix;
+    }
+}
+
+using namespace Assimp;
+using namespace Assimp::Blender;
+using namespace Assimp::Formatter;
+
+static const aiImporterDesc blenderDesc = {
+    "Blender 3D Importer \nhttp://www.blender3d.org",
+    "",
+    "",
+    "No animation support yet",
+    aiImporterFlags_SupportBinaryFlavour,
+    0,
+    0,
+    2,
+    50,
+    "blend"
+};
+
+
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by Importer
+BlenderImporter::BlenderImporter()
+: modifier_cache(new BlenderModifierShowcase()) {
+    // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+BlenderImporter::~BlenderImporter()
+{
+    delete modifier_cache;
+}
+
+static const char* Tokens[] = { "BLENDER" };
+static const char* TokensForSearch[] = { "blender" };
+
+// ------------------------------------------------------------------------------------------------
+// Returns whether the class can handle the format of the given file.
+bool BlenderImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
+{
+    const std::string& extension = GetExtension(pFile);
+    if (extension == "blend") {
+        return true;
+    }
+
+    else if ((!extension.length() || checkSig) && pIOHandler)   {
+        // note: this won't catch compressed files
+        return SearchFileHeaderForToken(pIOHandler,pFile, TokensForSearch,1);
+    }
+    return false;
+}
+
+// ------------------------------------------------------------------------------------------------
+// List all extensions handled by this loader
+void BlenderImporter::GetExtensionList(std::set<std::string>& app)
+{
+    app.insert("blend");
+}
+
+// ------------------------------------------------------------------------------------------------
+// Loader registry entry
+const aiImporterDesc* BlenderImporter::GetInfo () const
+{
+    return &blenderDesc;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Setup configuration properties for the loader
+void BlenderImporter::SetupProperties(const Importer* /*pImp*/)
+{
+    // nothing to be done for the moment
+}
+
+
+// ------------------------------------------------------------------------------------------------
+// Imports the given file into the given scene structure.
+void BlenderImporter::InternReadFile( const std::string& pFile,
+    aiScene* pScene, IOSystem* pIOHandler)
+{
+#ifndef ASSIMP_BUILD_NO_COMPRESSED_BLEND
+    std::vector<Bytef> uncompressed;
+#endif
+
+
+    FileDatabase file;
+    std::shared_ptr<IOStream> stream(pIOHandler->Open(pFile,"rb"));
+    if (!stream) {
+        ThrowException("Could not open file for reading");
+    }
+
+    char magic[8] = {0};
+    stream->Read(magic,7,1);
+    if (strcmp(magic, Tokens[0] )) {
+        // Check for presence of the gzip header. If yes, assume it is a
+        // compressed blend file and try uncompressing it, else fail. This is to
+        // avoid uncompressing random files which our loader might end up with.
+#ifdef ASSIMP_BUILD_NO_COMPRESSED_BLEND
+        ThrowException("BLENDER magic bytes are missing, is this file compressed (Assimp was built without decompression support)?");
+#else
+
+        if (magic[0] != 0x1f || static_cast<uint8_t>(magic[1]) != 0x8b) {
+            ThrowException("BLENDER magic bytes are missing, couldn't find GZIP header either");
+        }
+
+        LogDebug("Found no BLENDER magic word but a GZIP header, might be a compressed file");
+        if (magic[2] != 8) {
+            ThrowException("Unsupported GZIP compression method");
+        }
+
+        // http://www.gzip.org/zlib/rfc-gzip.html#header-trailer
+        stream->Seek(0L,aiOrigin_SET);
+        std::shared_ptr<StreamReaderLE> reader = std::shared_ptr<StreamReaderLE>(new StreamReaderLE(stream));
+
+        // build a zlib stream
+        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
+        inflateInit2(&zstream, 16+MAX_WBITS);
+
+        zstream.next_in   = reinterpret_cast<Bytef*>( reader->GetPtr() );
+        zstream.avail_in  = reader->GetRemainingSize();
+
+        size_t total = 0l;
+
+        // TODO: be smarter about this, decompress directly into heap buffer
+        // and decompress the data .... do 1k chunks in the hope that we won't kill the stack
+#define MYBLOCK 1024
+        Bytef block[MYBLOCK];
+        int ret;
+        do {
+            zstream.avail_out = MYBLOCK;
+            zstream.next_out = block;
+            ret = inflate(&zstream, Z_NO_FLUSH);
+
+            if (ret != Z_STREAM_END && ret != Z_OK) {
+                ThrowException("Failure decompressing this file using gzip, seemingly it is NOT a compressed .BLEND file");
+            }
+            const size_t have = MYBLOCK - zstream.avail_out;
+            total += have;
+            uncompressed.resize(total);
+            memcpy(uncompressed.data() + total - have,block,have);
+        }
+        while (ret != Z_STREAM_END);
+
+        // terminate zlib
+        inflateEnd(&zstream);
+
+        // replace the input stream with a memory stream
+        stream.reset(new MemoryIOStream(reinterpret_cast<uint8_t*>(uncompressed.data()),total));
+
+        // .. and retry
+        stream->Read(magic,7,1);
+        if (strcmp(magic,"BLENDER")) {
+            ThrowException("Found no BLENDER magic word in decompressed GZIP file");
+        }
+#endif
+    }
+
+    file.i64bit = (stream->Read(magic,1,1),magic[0]=='-');
+    file.little = (stream->Read(magic,1,1),magic[0]=='v');
+
+    stream->Read(magic,3,1);
+    magic[3] = '\0';
+
+    LogInfo((format(),"Blender version is ",magic[0],".",magic+1,
+        " (64bit: ",file.i64bit?"true":"false",
+        ", little endian: ",file.little?"true":"false",")"
+    ));
+
+    ParseBlendFile(file,stream);
+
+    Scene scene;
+    ExtractScene(scene,file);
+
+    ConvertBlendFile(pScene,scene,file);
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderImporter::ParseBlendFile(FileDatabase& out, std::shared_ptr<IOStream> stream)
+{
+    out.reader = std::shared_ptr<StreamReaderAny>(new StreamReaderAny(stream,out.little));
+
+    DNAParser dna_reader(out);
+    const DNA* dna = NULL;
+
+    out.entries.reserve(128); { // even small BLEND files tend to consist of many file blocks
+        SectionParser parser(*out.reader.get(),out.i64bit);
+
+        // first parse the file in search for the DNA and insert all other sections into the database
+        while ((parser.Next(),1)) {
+            const FileBlockHead& head = parser.GetCurrent();
+
+            if (head.id == "ENDB") {
+                break; // only valid end of the file
+            }
+            else if (head.id == "DNA1") {
+                dna_reader.Parse();
+                dna = &dna_reader.GetDNA();
+                continue;
+            }
+
+            out.entries.push_back(head);
+        }
+    }
+    if (!dna) {
+        ThrowException("SDNA not found");
+    }
+
+    std::sort(out.entries.begin(),out.entries.end());
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderImporter::ExtractScene(Scene& out, const FileDatabase& file)
+{
+    const FileBlockHead* block = NULL;
+    std::map<std::string,size_t>::const_iterator it = file.dna.indices.find("Scene");
+    if (it == file.dna.indices.end()) {
+        ThrowException("There is no `Scene` structure record");
+    }
+
+    const Structure& ss = file.dna.structures[(*it).second];
+
+    // we need a scene somewhere to start with.
+    for(const FileBlockHead& bl :file.entries) {
+
+        // Fix: using the DNA index is more reliable to locate scenes
+        //if (bl.id == "SC") {
+
+        if (bl.dna_index == (*it).second) {
+            block = &bl;
+            break;
+        }
+    }
+
+    if (!block) {
+        ThrowException("There is not a single `Scene` record to load");
+    }
+
+    file.reader->SetCurrentPos(block->start);
+    ss.Convert(out,file);
+
+#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
+    ASSIMP_LOG_INFO_F(
+        "(Stats) Fields read: " ,file.stats().fields_read,
+        ", pointers resolved: " ,file.stats().pointers_resolved,
+        ", cache hits: "        ,file.stats().cache_hits,
+        ", cached objects: "    ,file.stats().cached_objects
+    );
+#endif
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderImporter::ConvertBlendFile(aiScene* out, const Scene& in,const FileDatabase& file)
+{
+    ConversionData conv(file);
+
+    // FIXME it must be possible to take the hierarchy directly from
+    // the file. This is terrible. Here, we're first looking for
+    // all objects which don't have parent objects at all -
+    std::deque<const Object*> no_parents;
+    for (std::shared_ptr<Base> cur = std::static_pointer_cast<Base> ( in.base.first ); cur; cur = cur->next) {
+        if (cur->object) {
+            if(!cur->object->parent) {
+                no_parents.push_back(cur->object.get());
+            } else {
+                conv.objects.insert( cur->object.get() );
+            }
+        }
+    }
+    for (std::shared_ptr<Base> cur = in.basact; cur; cur = cur->next) {
+        if (cur->object) {
+            if(cur->object->parent) {
+                conv.objects.insert(cur->object.get());
+            }
+        }
+    }
+
+    if (no_parents.empty()) {
+        ThrowException("Expected at least one object with no parent");
+    }
+
+    aiNode* root = out->mRootNode = new aiNode("<BlenderRoot>");
+
+    root->mNumChildren = static_cast<unsigned int>(no_parents.size());
+    root->mChildren = new aiNode*[root->mNumChildren]();
+    for (unsigned int i = 0; i < root->mNumChildren; ++i) {
+        root->mChildren[i] = ConvertNode(in, no_parents[i], conv, aiMatrix4x4());
+        root->mChildren[i]->mParent = root;
+    }
+
+    BuildMaterials(conv);
+
+    if (conv.meshes->size()) {
+        out->mMeshes = new aiMesh*[out->mNumMeshes = static_cast<unsigned int>( conv.meshes->size() )];
+        std::copy(conv.meshes->begin(),conv.meshes->end(),out->mMeshes);
+        conv.meshes.dismiss();
+    }
+
+    if (conv.lights->size()) {
+        out->mLights = new aiLight*[out->mNumLights = static_cast<unsigned int>( conv.lights->size() )];
+        std::copy(conv.lights->begin(),conv.lights->end(),out->mLights);
+        conv.lights.dismiss();
+    }
+
+    if (conv.cameras->size()) {
+        out->mCameras = new aiCamera*[out->mNumCameras = static_cast<unsigned int>( conv.cameras->size() )];
+        std::copy(conv.cameras->begin(),conv.cameras->end(),out->mCameras);
+        conv.cameras.dismiss();
+    }
+
+    if (conv.materials->size()) {
+        out->mMaterials = new aiMaterial*[out->mNumMaterials = static_cast<unsigned int>( conv.materials->size() )];
+        std::copy(conv.materials->begin(),conv.materials->end(),out->mMaterials);
+        conv.materials.dismiss();
+    }
+
+    if (conv.textures->size()) {
+        out->mTextures = new aiTexture*[out->mNumTextures = static_cast<unsigned int>( conv.textures->size() )];
+        std::copy(conv.textures->begin(),conv.textures->end(),out->mTextures);
+        conv.textures.dismiss();
+    }
+
+    // acknowledge that the scene might come out incomplete
+    // by Assimp's definition of `complete`: blender scenes
+    // can consist of thousands of cameras or lights with
+    // not a single mesh between them.
+    if (!out->mNumMeshes) {
+        out->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderImporter::ResolveImage(aiMaterial* out, const Material* mat, const MTex* tex, const Image* img, ConversionData& conv_data)
+{
+    (void)mat; (void)tex; (void)conv_data;
+    aiString name;
+
+    // check if the file contents are bundled with the BLEND file
+    if (img->packedfile) {
+        name.data[0] = '*';
+        name.length = 1+ ASSIMP_itoa10(name.data+1,static_cast<unsigned int>(MAXLEN-1), static_cast<int32_t>(conv_data.textures->size()));
+
+        conv_data.textures->push_back(new aiTexture());
+        aiTexture* tex = conv_data.textures->back();
+
+        // usually 'img->name' will be the original file name of the embedded textures,
+        // so we can extract the file extension from it.
+        const size_t nlen = strlen( img->name );
+        const char* s = img->name+nlen, *e = s;
+        while ( s >= img->name && *s != '.' ) {
+            --s;
+        }
+
+        tex->achFormatHint[0] = s+1>e ? '\0' : ::tolower( s[1] );
+        tex->achFormatHint[1] = s+2>e ? '\0' : ::tolower( s[2] );
+        tex->achFormatHint[2] = s+3>e ? '\0' : ::tolower( s[3] );
+        tex->achFormatHint[3] = '\0';
+
+        // tex->mHeight = 0;
+        tex->mWidth = img->packedfile->size;
+        uint8_t* ch = new uint8_t[tex->mWidth];
+
+        conv_data.db.reader->SetCurrentPos(static_cast<size_t>( img->packedfile->data->val));
+        conv_data.db.reader->CopyAndAdvance(ch,tex->mWidth);
+
+        tex->pcData = reinterpret_cast<aiTexel*>(ch);
+
+        LogInfo("Reading embedded texture, original file was "+std::string(img->name));
+    } else {
+        name = aiString( img->name );
+    }
+
+    aiTextureType texture_type = aiTextureType_UNKNOWN;
+    MTex::MapType map_type = tex->mapto;
+
+    if (map_type & MTex::MapType_COL)
+        texture_type = aiTextureType_DIFFUSE;
+    else if (map_type & MTex::MapType_NORM) {
+        if (tex->tex->imaflag & Tex::ImageFlags_NORMALMAP) {
+            texture_type = aiTextureType_NORMALS;
+        }
+        else {
+            texture_type = aiTextureType_HEIGHT;
+        }
+        out->AddProperty(&tex->norfac,1,AI_MATKEY_BUMPSCALING);
+    }
+    else if (map_type & MTex::MapType_COLSPEC)
+        texture_type = aiTextureType_SPECULAR;
+    else if (map_type & MTex::MapType_COLMIR)
+        texture_type = aiTextureType_REFLECTION;
+    //else if (map_type & MTex::MapType_REF)
+    else if (map_type & MTex::MapType_SPEC)
+        texture_type = aiTextureType_SHININESS;
+    else if (map_type & MTex::MapType_EMIT)
+        texture_type = aiTextureType_EMISSIVE;
+    //else if (map_type & MTex::MapType_ALPHA)
+    //else if (map_type & MTex::MapType_HAR)
+    //else if (map_type & MTex::MapType_RAYMIRR)
+    //else if (map_type & MTex::MapType_TRANSLU)
+    else if (map_type & MTex::MapType_AMB)
+        texture_type = aiTextureType_AMBIENT;
+    else if (map_type & MTex::MapType_DISPLACE)
+        texture_type = aiTextureType_DISPLACEMENT;
+    //else if (map_type & MTex::MapType_WARP)
+
+    out->AddProperty(&name,AI_MATKEY_TEXTURE(texture_type,
+        conv_data.next_texture[texture_type]++));
+
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderImporter::AddSentinelTexture(aiMaterial* out, const Material* mat, const MTex* tex, ConversionData& conv_data)
+{
+    (void)mat; (void)tex; (void)conv_data;
+
+    aiString name;
+    name.length = ai_snprintf(name.data, MAXLEN, "Procedural,num=%i,type=%s",conv_data.sentinel_cnt++,
+        GetTextureTypeDisplayString(tex->tex->type)
+    );
+    out->AddProperty(&name,AI_MATKEY_TEXTURE_DIFFUSE(
+        conv_data.next_texture[aiTextureType_DIFFUSE]++)
+    );
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderImporter::ResolveTexture(aiMaterial* out, const Material* mat, const MTex* tex, ConversionData& conv_data)
+{
+    const Tex* rtex = tex->tex.get();
+    if(!rtex || !rtex->type) {
+        return;
+    }
+
+    // We can't support most of the texture types because they're mostly procedural.
+    // These are substituted by a dummy texture.
+    const char* dispnam = "";
+    switch( rtex->type )
+    {
+            // these are listed in blender's UI
+        case Tex::Type_CLOUDS       :
+        case Tex::Type_WOOD         :
+        case Tex::Type_MARBLE       :
+        case Tex::Type_MAGIC        :
+        case Tex::Type_BLEND        :
+        case Tex::Type_STUCCI       :
+        case Tex::Type_NOISE        :
+        case Tex::Type_PLUGIN       :
+        case Tex::Type_MUSGRAVE     :
+        case Tex::Type_VORONOI      :
+        case Tex::Type_DISTNOISE    :
+        case Tex::Type_ENVMAP       :
+
+            // these do no appear in the UI, why?
+        case Tex::Type_POINTDENSITY :
+        case Tex::Type_VOXELDATA    :
+
+            LogWarn(std::string("Encountered a texture with an unsupported type: ")+dispnam);
+            AddSentinelTexture(out, mat, tex, conv_data);
+            break;
+
+        case Tex::Type_IMAGE        :
+            if (!rtex->ima) {
+                LogError("A texture claims to be an Image, but no image reference is given");
+                break;
+            }
+            ResolveImage(out, mat, tex, rtex->ima.get(),conv_data);
+            break;
+
+        default:
+            ai_assert(false);
+    };
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderImporter::BuildDefaultMaterial(Blender::ConversionData& conv_data)
+{
+    // add a default material if necessary
+    unsigned int index = static_cast<unsigned int>( -1 );
+    for( aiMesh* mesh : conv_data.meshes.get() ) {
+        if (mesh->mMaterialIndex == static_cast<unsigned int>( -1 )) {
+
+            if (index == static_cast<unsigned int>( -1 )) {
+                // Setup a default material.
+                std::shared_ptr<Material> p(new Material());
+                ai_assert(::strlen(AI_DEFAULT_MATERIAL_NAME) < sizeof(p->id.name)-2);
+                strcpy( p->id.name+2, AI_DEFAULT_MATERIAL_NAME );
+
+                // Note: MSVC11 does not zero-initialize Material here, although it should.
+                // Thus all relevant fields should be explicitly initialized. We cannot add
+                // a default constructor to Material since the DNA codegen does not support
+                // parsing it.
+                p->r = p->g = p->b = 0.6f;
+                p->specr = p->specg = p->specb = 0.6f;
+                p->ambr = p->ambg = p->ambb = 0.0f;
+                p->mirr = p->mirg = p->mirb = 0.0f;
+                p->emit = 0.f;
+                p->alpha = 0.f;
+                p->har = 0;
+
+                index = static_cast<unsigned int>( conv_data.materials_raw.size() );
+                conv_data.materials_raw.push_back(p);
+                LogInfo("Adding default material");
+            }
+            mesh->mMaterialIndex = index;
+        }
+    }
+}
+
+void BlenderImporter::AddBlendParams(aiMaterial* result, const Material* source)
+{
+    aiColor3D diffuseColor(source->r, source->g, source->b);
+    result->AddProperty(&diffuseColor, 1, "$mat.blend.diffuse.color", 0, 0);
+
+    float diffuseIntensity = source->ref;
+    result->AddProperty(&diffuseIntensity, 1, "$mat.blend.diffuse.intensity", 0, 0);
+
+    int diffuseShader = source->diff_shader;
+    result->AddProperty(&diffuseShader, 1, "$mat.blend.diffuse.shader", 0, 0);
+
+    int diffuseRamp = 0;
+    result->AddProperty(&diffuseRamp, 1, "$mat.blend.diffuse.ramp", 0, 0);
+
+
+    aiColor3D specularColor(source->specr, source->specg, source->specb);
+    result->AddProperty(&specularColor, 1, "$mat.blend.specular.color", 0, 0);
+
+    float specularIntensity = source->spec;
+    result->AddProperty(&specularIntensity, 1, "$mat.blend.specular.intensity", 0, 0);
+
+    int specularShader = source->spec_shader;
+    result->AddProperty(&specularShader, 1, "$mat.blend.specular.shader", 0, 0);
+
+    int specularRamp = 0;
+    result->AddProperty(&specularRamp, 1, "$mat.blend.specular.ramp", 0, 0);
+
+    int specularHardness = source->har;
+    result->AddProperty(&specularHardness, 1, "$mat.blend.specular.hardness", 0, 0);
+
+
+    int transparencyUse = source->mode & MA_TRANSPARENCY ? 1 : 0;
+    result->AddProperty(&transparencyUse, 1, "$mat.blend.transparency.use", 0, 0);
+
+    int transparencyMethod = source->mode & MA_RAYTRANSP ? 2 : (source->mode & MA_ZTRANSP ? 1 : 0);
+    result->AddProperty(&transparencyMethod, 1, "$mat.blend.transparency.method", 0, 0);
+
+    float transparencyAlpha = source->alpha;
+    result->AddProperty(&transparencyAlpha, 1, "$mat.blend.transparency.alpha", 0, 0);
+
+    float transparencySpecular = source->spectra;
+    result->AddProperty(&transparencySpecular, 1, "$mat.blend.transparency.specular", 0, 0);
+
+    float transparencyFresnel = source->fresnel_tra;
+    result->AddProperty(&transparencyFresnel, 1, "$mat.blend.transparency.fresnel", 0, 0);
+
+    float transparencyBlend = source->fresnel_tra_i;
+    result->AddProperty(&transparencyBlend, 1, "$mat.blend.transparency.blend", 0, 0);
+
+    float transparencyIor = source->ang;
+    result->AddProperty(&transparencyIor, 1, "$mat.blend.transparency.ior", 0, 0);
+
+    float transparencyFilter = source->filter;
+    result->AddProperty(&transparencyFilter, 1, "$mat.blend.transparency.filter", 0, 0);
+
+    float transparencyFalloff = source->tx_falloff;
+    result->AddProperty(&transparencyFalloff, 1, "$mat.blend.transparency.falloff", 0, 0);
+
+    float transparencyLimit = source->tx_limit;
+    result->AddProperty(&transparencyLimit, 1, "$mat.blend.transparency.limit", 0, 0);
+
+    int transparencyDepth = source->ray_depth_tra;
+    result->AddProperty(&transparencyDepth, 1, "$mat.blend.transparency.depth", 0, 0);
+
+    float transparencyGlossAmount = source->gloss_tra;
+    result->AddProperty(&transparencyGlossAmount, 1, "$mat.blend.transparency.glossAmount", 0, 0);
+
+    float transparencyGlossThreshold = source->adapt_thresh_tra;
+    result->AddProperty(&transparencyGlossThreshold, 1, "$mat.blend.transparency.glossThreshold", 0, 0);
+
+    int transparencyGlossSamples = source->samp_gloss_tra;
+    result->AddProperty(&transparencyGlossSamples, 1, "$mat.blend.transparency.glossSamples", 0, 0);
+
+
+    int mirrorUse = source->mode & MA_RAYMIRROR ? 1 : 0;
+    result->AddProperty(&mirrorUse, 1, "$mat.blend.mirror.use", 0, 0);
+
+    float mirrorReflectivity = source->ray_mirror;
+    result->AddProperty(&mirrorReflectivity, 1, "$mat.blend.mirror.reflectivity", 0, 0);
+
+    aiColor3D mirrorColor(source->mirr, source->mirg, source->mirb);
+    result->AddProperty(&mirrorColor, 1, "$mat.blend.mirror.color", 0, 0);
+
+    float mirrorFresnel = source->fresnel_mir;
+    result->AddProperty(&mirrorFresnel, 1, "$mat.blend.mirror.fresnel", 0, 0);
+
+    float mirrorBlend = source->fresnel_mir_i;
+    result->AddProperty(&mirrorBlend, 1, "$mat.blend.mirror.blend", 0, 0);
+
+    int mirrorDepth = source->ray_depth;
+    result->AddProperty(&mirrorDepth, 1, "$mat.blend.mirror.depth", 0, 0);
+
+    float mirrorMaxDist = source->dist_mir;
+    result->AddProperty(&mirrorMaxDist, 1, "$mat.blend.mirror.maxDist", 0, 0);
+
+    int mirrorFadeTo = source->fadeto_mir;
+    result->AddProperty(&mirrorFadeTo, 1, "$mat.blend.mirror.fadeTo", 0, 0);
+
+    float mirrorGlossAmount = source->gloss_mir;
+    result->AddProperty(&mirrorGlossAmount, 1, "$mat.blend.mirror.glossAmount", 0, 0);
+
+    float mirrorGlossThreshold = source->adapt_thresh_mir;
+    result->AddProperty(&mirrorGlossThreshold, 1, "$mat.blend.mirror.glossThreshold", 0, 0);
+
+    int mirrorGlossSamples = source->samp_gloss_mir;
+    result->AddProperty(&mirrorGlossSamples, 1, "$mat.blend.mirror.glossSamples", 0, 0);
+
+    float mirrorGlossAnisotropic = source->aniso_gloss_mir;
+    result->AddProperty(&mirrorGlossAnisotropic, 1, "$mat.blend.mirror.glossAnisotropic", 0, 0);
+}
+
+void BlenderImporter::BuildMaterials(ConversionData& conv_data)
+{
+    conv_data.materials->reserve(conv_data.materials_raw.size());
+
+    BuildDefaultMaterial(conv_data);
+
+    for(std::shared_ptr<Material> mat : conv_data.materials_raw) {
+
+        // reset per material global counters
+        for (size_t i = 0; i < sizeof(conv_data.next_texture)/sizeof(conv_data.next_texture[0]);++i) {
+            conv_data.next_texture[i] = 0 ;
+        }
+
+        aiMaterial* mout = new aiMaterial();
+        conv_data.materials->push_back(mout);
+        // For any new material field handled here, the default material above must be updated with an appropriate default value.
+
+        // set material name
+        aiString name = aiString(mat->id.name+2); // skip over the name prefix 'MA'
+        mout->AddProperty(&name,AI_MATKEY_NAME);
+
+        // basic material colors
+        aiColor3D col(mat->r,mat->g,mat->b);
+        if (mat->r || mat->g || mat->b ) {
+
+            // Usually, zero diffuse color means no diffuse color at all in the equation.
+            // So we omit this member to express this intent.
+            mout->AddProperty(&col,1,AI_MATKEY_COLOR_DIFFUSE);
+
+            if (mat->emit) {
+                aiColor3D emit_col(mat->emit * mat->r, mat->emit * mat->g, mat->emit * mat->b) ;
+                mout->AddProperty(&emit_col, 1, AI_MATKEY_COLOR_EMISSIVE) ;
+            }
+        }
+
+        col = aiColor3D(mat->specr,mat->specg,mat->specb);
+        mout->AddProperty(&col,1,AI_MATKEY_COLOR_SPECULAR);
+
+        // is hardness/shininess set?
+        if( mat->har ) {
+            const float har = mat->har;
+            mout->AddProperty(&har,1,AI_MATKEY_SHININESS);
+        }
+
+        col = aiColor3D(mat->ambr,mat->ambg,mat->ambb);
+        mout->AddProperty(&col,1,AI_MATKEY_COLOR_AMBIENT);
+
+        // is mirror enabled?
+        if( mat->mode & MA_RAYMIRROR ) {
+            const float ray_mirror = mat->ray_mirror;
+            mout->AddProperty(&ray_mirror,1,AI_MATKEY_REFLECTIVITY);
+        }
+
+        col = aiColor3D(mat->mirr,mat->mirg,mat->mirb);
+        mout->AddProperty(&col,1,AI_MATKEY_COLOR_REFLECTIVE);
+
+        for(size_t i = 0; i < sizeof(mat->mtex) / sizeof(mat->mtex[0]); ++i) {
+            if (!mat->mtex[i]) {
+                continue;
+            }
+
+            ResolveTexture(mout,mat.get(),mat->mtex[i].get(),conv_data);
+        }
+
+        AddBlendParams(mout, mat.get());
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderImporter::CheckActualType(const ElemBase* dt, const char* check)
+{
+    ai_assert(dt);
+    if (strcmp(dt->dna_type,check)) {
+        ThrowException((format(),
+            "Expected object at ",std::hex,dt," to be of type `",check,
+            "`, but it claims to be a `",dt->dna_type,"`instead"
+        ));
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderImporter::NotSupportedObjectType(const Object* obj, const char* type)
+{
+    LogWarn((format(), "Object `",obj->id.name,"` - type is unsupported: `",type, "`, skipping" ));
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderImporter::ConvertMesh(const Scene& /*in*/, const Object* /*obj*/, const Mesh* mesh,
+    ConversionData& conv_data, TempArray<std::vector,aiMesh>&  temp
+    )
+{
+    // TODO: Resolve various problems with BMesh triangulation before re-enabling.
+    //       See issues #400, #373, #318  #315 and #132.
+#if defined(TODO_FIX_BMESH_CONVERSION)
+    BlenderBMeshConverter BMeshConverter( mesh );
+    if ( BMeshConverter.ContainsBMesh( ) )
+    {
+        mesh = BMeshConverter.TriangulateBMesh( );
+    }
+#endif
+
+    typedef std::pair<const int,size_t> MyPair;
+    if ((!mesh->totface && !mesh->totloop) || !mesh->totvert) {
+        return;
+    }
+
+    // some sanity checks
+    if (static_cast<size_t> ( mesh->totface ) > mesh->mface.size() ){
+        ThrowException("Number of faces is larger than the corresponding array");
+    }
+
+    if (static_cast<size_t> ( mesh->totvert ) > mesh->mvert.size()) {
+        ThrowException("Number of vertices is larger than the corresponding array");
+    }
+
+    if (static_cast<size_t> ( mesh->totloop ) > mesh->mloop.size()) {
+        ThrowException("Number of vertices is larger than the corresponding array");
+    }
+
+    // collect per-submesh numbers
+    std::map<int,size_t> per_mat;
+    std::map<int,size_t> per_mat_verts;
+    for (int i = 0; i < mesh->totface; ++i) {
+
+        const MFace& mf = mesh->mface[i];
+        per_mat[ mf.mat_nr ]++;
+        per_mat_verts[ mf.mat_nr ] += mf.v4?4:3;
+    }
+
+    for (int i = 0; i < mesh->totpoly; ++i) {
+        const MPoly& mp = mesh->mpoly[i];
+        per_mat[ mp.mat_nr ]++;
+        per_mat_verts[ mp.mat_nr ] += mp.totloop;
+    }
+
+    // ... and allocate the corresponding meshes
+    const size_t old = temp->size();
+    temp->reserve(temp->size() + per_mat.size());
+
+    std::map<size_t,size_t> mat_num_to_mesh_idx;
+    for(MyPair& it : per_mat) {
+
+        mat_num_to_mesh_idx[it.first] = temp->size();
+        temp->push_back(new aiMesh());
+
+        aiMesh* out = temp->back();
+        out->mVertices = new aiVector3D[per_mat_verts[it.first]];
+        out->mNormals  = new aiVector3D[per_mat_verts[it.first]];
+
+        //out->mNumFaces = 0
+        //out->mNumVertices = 0
+        out->mFaces = new aiFace[it.second]();
+
+        // all sub-meshes created from this mesh are named equally. this allows
+        // curious users to recover the original adjacency.
+        out->mName = aiString(mesh->id.name+2);
+            // skip over the name prefix 'ME'
+
+        // resolve the material reference and add this material to the set of
+        // output materials. The (temporary) material index is the index
+        // of the material entry within the list of resolved materials.
+        if (mesh->mat) {
+
+            if (static_cast<size_t> ( it.first ) >= mesh->mat.size() ) {
+                ThrowException("Material index is out of range");
+            }
+
+            std::shared_ptr<Material> mat = mesh->mat[it.first];
+            const std::deque< std::shared_ptr<Material> >::iterator has = std::find(
+                    conv_data.materials_raw.begin(),
+                    conv_data.materials_raw.end(),mat
+            );
+
+            if (has != conv_data.materials_raw.end()) {
+                out->mMaterialIndex = static_cast<unsigned int>( std::distance(conv_data.materials_raw.begin(),has));
+            }
+            else {
+                out->mMaterialIndex = static_cast<unsigned int>( conv_data.materials_raw.size() );
+                conv_data.materials_raw.push_back(mat);
+            }
+        }
+        else out->mMaterialIndex = static_cast<unsigned int>( -1 );
+    }
+
+    for (int i = 0; i < mesh->totface; ++i) {
+
+        const MFace& mf = mesh->mface[i];
+
+        aiMesh* const out = temp[ mat_num_to_mesh_idx[ mf.mat_nr ] ];
+        aiFace& f = out->mFaces[out->mNumFaces++];
+
+        f.mIndices = new unsigned int[ f.mNumIndices = mf.v4?4:3 ];
+        aiVector3D* vo = out->mVertices + out->mNumVertices;
+        aiVector3D* vn = out->mNormals + out->mNumVertices;
+
+        // XXX we can't fold this easily, because we are restricted
+        // to the member names from the BLEND file (v1,v2,v3,v4)
+        // which are assigned by the genblenddna.py script and
+        // cannot be changed without breaking the entire
+        // import process.
+
+        if (mf.v1 >= mesh->totvert) {
+            ThrowException("Vertex index v1 out of range");
+        }
+        const MVert* v = &mesh->mvert[mf.v1];
+        vo->x = v->co[0];
+        vo->y = v->co[1];
+        vo->z = v->co[2];
+        vn->x = v->no[0];
+        vn->y = v->no[1];
+        vn->z = v->no[2];
+        f.mIndices[0] = out->mNumVertices++;
+        ++vo;
+        ++vn;
+
+        //  if (f.mNumIndices >= 2) {
+        if (mf.v2 >= mesh->totvert) {
+            ThrowException("Vertex index v2 out of range");
+        }
+        v = &mesh->mvert[mf.v2];
+        vo->x = v->co[0];
+        vo->y = v->co[1];
+        vo->z = v->co[2];
+        vn->x = v->no[0];
+        vn->y = v->no[1];
+        vn->z = v->no[2];
+        f.mIndices[1] = out->mNumVertices++;
+        ++vo;
+        ++vn;
+
+        if (mf.v3 >= mesh->totvert) {
+            ThrowException("Vertex index v3 out of range");
+        }
+        //  if (f.mNumIndices >= 3) {
+        v = &mesh->mvert[mf.v3];
+        vo->x = v->co[0];
+        vo->y = v->co[1];
+        vo->z = v->co[2];
+        vn->x = v->no[0];
+        vn->y = v->no[1];
+        vn->z = v->no[2];
+        f.mIndices[2] = out->mNumVertices++;
+        ++vo;
+        ++vn;
+
+        if (mf.v4 >= mesh->totvert) {
+            ThrowException("Vertex index v4 out of range");
+        }
+        //  if (f.mNumIndices >= 4) {
+        if (mf.v4) {
+            v = &mesh->mvert[mf.v4];
+            vo->x = v->co[0];
+            vo->y = v->co[1];
+            vo->z = v->co[2];
+            vn->x = v->no[0];
+            vn->y = v->no[1];
+            vn->z = v->no[2];
+            f.mIndices[3] = out->mNumVertices++;
+            ++vo;
+            ++vn;
+
+            out->mPrimitiveTypes |= aiPrimitiveType_POLYGON;
+        }
+        else out->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE;
+
+        //  }
+        //  }
+        //  }
+    }
+
+    for (int i = 0; i < mesh->totpoly; ++i) {
+
+        const MPoly& mf = mesh->mpoly[i];
+
+        aiMesh* const out = temp[ mat_num_to_mesh_idx[ mf.mat_nr ] ];
+        aiFace& f = out->mFaces[out->mNumFaces++];
+
+        f.mIndices = new unsigned int[ f.mNumIndices = mf.totloop ];
+        aiVector3D* vo = out->mVertices + out->mNumVertices;
+        aiVector3D* vn = out->mNormals + out->mNumVertices;
+
+        // XXX we can't fold this easily, because we are restricted
+        // to the member names from the BLEND file (v1,v2,v3,v4)
+        // which are assigned by the genblenddna.py script and
+        // cannot be changed without breaking the entire
+        // import process.
+        for (int j = 0;j < mf.totloop; ++j)
+        {
+            const MLoop& loop = mesh->mloop[mf.loopstart + j];
+
+            if (loop.v >= mesh->totvert) {
+                ThrowException("Vertex index out of range");
+            }
+
+            const MVert& v = mesh->mvert[loop.v];
+
+            vo->x = v.co[0];
+            vo->y = v.co[1];
+            vo->z = v.co[2];
+            vn->x = v.no[0];
+            vn->y = v.no[1];
+            vn->z = v.no[2];
+            f.mIndices[j] = out->mNumVertices++;
+
+            ++vo;
+            ++vn;
+
+        }
+        if (mf.totloop == 3)
+        {
+            out->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE;
+        }
+        else
+        {
+            out->mPrimitiveTypes |= aiPrimitiveType_POLYGON;
+        }
+    }
+
+    // TODO should we create the TextureUVMapping map in Convert<Material> to prevent redundant processing?
+
+    // create texture <-> uvname mapping for all materials
+    // key is texture number, value is data *
+    typedef std::map<uint32_t, const MLoopUV *> TextureUVMapping;
+    // key is material number, value is the TextureUVMapping for the material
+    typedef std::map<uint32_t, TextureUVMapping> MaterialTextureUVMappings;
+    MaterialTextureUVMappings matTexUvMappings;
+    const uint32_t maxMat = static_cast<const uint32_t>(mesh->mat.size());
+    for (uint32_t m = 0; m < maxMat; ++m) {
+        // get material by index
+        const std::shared_ptr<Material> pMat = mesh->mat[m];
+        TextureUVMapping texuv;
+        const uint32_t maxTex = sizeof(pMat->mtex) / sizeof(pMat->mtex[0]);
+        for (uint32_t t = 0; t < maxTex; ++t) {
+            if (pMat->mtex[t] && pMat->mtex[t]->uvname[0]) {
+                // get the CustomData layer for given uvname and correct type
+                const ElemBase *pLoop = getCustomDataLayerData(mesh->ldata, CD_MLOOPUV, pMat->mtex[t]->uvname);
+                if (pLoop) {
+                    texuv.insert(std::make_pair(t, dynamic_cast<const MLoopUV *>(pLoop)));
+                }
+            }
+        }
+        if (texuv.size()) {
+            matTexUvMappings.insert(std::make_pair(m, texuv));
+        }
+    }
+
+    // collect texture coordinates, they're stored in a separate per-face buffer
+    if (mesh->mtface || mesh->mloopuv) {
+        if (mesh->totface > static_cast<int> ( mesh->mtface.size())) {
+            ThrowException("Number of UV faces is larger than the corresponding UV face array (#1)");
+        }
+        for (std::vector<aiMesh*>::iterator it = temp->begin()+old; it != temp->end(); ++it) {
+            ai_assert((*it)->mNumVertices && (*it)->mNumFaces);
+            const auto itMatTexUvMapping = matTexUvMappings.find((*it)->mMaterialIndex);
+            if (itMatTexUvMapping == matTexUvMappings.end()) {
+                // default behaviour like before
+                (*it)->mTextureCoords[0] = new aiVector3D[(*it)->mNumVertices];
+            }
+            else {
+                // create texture coords for every mapped tex
+                for (uint32_t i = 0; i < itMatTexUvMapping->second.size(); ++i) {
+                    (*it)->mTextureCoords[i] = new aiVector3D[(*it)->mNumVertices];
+                }
+            }
+            (*it)->mNumFaces = (*it)->mNumVertices = 0;
+        }
+
+        for (int i = 0; i < mesh->totface; ++i) {
+            const MTFace* v = &mesh->mtface[i];
+
+            aiMesh* const out = temp[ mat_num_to_mesh_idx[ mesh->mface[i].mat_nr ] ];
+            const aiFace& f = out->mFaces[out->mNumFaces++];
+
+            aiVector3D* vo = &out->mTextureCoords[0][out->mNumVertices];
+            for (unsigned int i = 0; i < f.mNumIndices; ++i,++vo,++out->mNumVertices) {
+                vo->x = v->uv[i][0];
+                vo->y = v->uv[i][1];
+            }
+        }
+
+        for (int i = 0; i < mesh->totpoly; ++i) {
+            const MPoly& v = mesh->mpoly[i];
+            aiMesh* const out = temp[ mat_num_to_mesh_idx[ v.mat_nr ] ];
+            const aiFace& f = out->mFaces[out->mNumFaces++];
+
+            const auto itMatTexUvMapping = matTexUvMappings.find(v.mat_nr);
+            if (itMatTexUvMapping == matTexUvMappings.end()) {
+                // old behavior
+                aiVector3D* vo = &out->mTextureCoords[0][out->mNumVertices];
+                for (unsigned int j = 0; j < f.mNumIndices; ++j, ++vo, ++out->mNumVertices) {
+                    const MLoopUV& uv = mesh->mloopuv[v.loopstart + j];
+                    vo->x = uv.uv[0];
+                    vo->y = uv.uv[1];
+                }
+            }
+            else {
+                // create textureCoords for every mapped tex
+                for (uint32_t m = 0; m < itMatTexUvMapping->second.size(); ++m) {
+                    const MLoopUV *tm = itMatTexUvMapping->second[m];
+                    aiVector3D* vo = &out->mTextureCoords[m][out->mNumVertices];
+                    uint32_t j = 0;
+                    for (; j < f.mNumIndices; ++j, ++vo) {
+                        const MLoopUV& uv = tm[v.loopstart + j];
+                        vo->x = uv.uv[0];
+                        vo->y = uv.uv[1];
+                    }
+                    // only update written mNumVertices in last loop
+                    // TODO why must the numVertices be incremented here?
+                    if (m == itMatTexUvMapping->second.size() - 1) {
+                        out->mNumVertices += j;
+                    }
+                }
+            }
+        }
+    }
+
+    // collect texture coordinates, old-style (marked as deprecated in current blender sources)
+    if (mesh->tface) {
+        if (mesh->totface > static_cast<int> ( mesh->tface.size())) {
+            ThrowException("Number of faces is larger than the corresponding UV face array (#2)");
+        }
+        for (std::vector<aiMesh*>::iterator it = temp->begin()+old; it != temp->end(); ++it) {
+            ai_assert((*it)->mNumVertices && (*it)->mNumFaces);
+
+            (*it)->mTextureCoords[0] = new aiVector3D[(*it)->mNumVertices];
+            (*it)->mNumFaces = (*it)->mNumVertices = 0;
+        }
+
+        for (int i = 0; i < mesh->totface; ++i) {
+            const TFace* v = &mesh->tface[i];
+
+            aiMesh* const out = temp[ mat_num_to_mesh_idx[ mesh->mface[i].mat_nr ] ];
+            const aiFace& f = out->mFaces[out->mNumFaces++];
+
+            aiVector3D* vo = &out->mTextureCoords[0][out->mNumVertices];
+            for (unsigned int i = 0; i < f.mNumIndices; ++i,++vo,++out->mNumVertices) {
+                vo->x = v->uv[i][0];
+                vo->y = v->uv[i][1];
+            }
+        }
+    }
+
+    // collect vertex colors, stored separately as well
+    if (mesh->mcol || mesh->mloopcol) {
+        if (mesh->totface > static_cast<int> ( (mesh->mcol.size()/4)) ) {
+            ThrowException("Number of faces is larger than the corresponding color face array");
+        }
+        for (std::vector<aiMesh*>::iterator it = temp->begin()+old; it != temp->end(); ++it) {
+            ai_assert((*it)->mNumVertices && (*it)->mNumFaces);
+
+            (*it)->mColors[0] = new aiColor4D[(*it)->mNumVertices];
+            (*it)->mNumFaces = (*it)->mNumVertices = 0;
+        }
+
+        for (int i = 0; i < mesh->totface; ++i) {
+
+            aiMesh* const out = temp[ mat_num_to_mesh_idx[ mesh->mface[i].mat_nr ] ];
+            const aiFace& f = out->mFaces[out->mNumFaces++];
+
+            aiColor4D* vo = &out->mColors[0][out->mNumVertices];
+            for (unsigned int n = 0; n < f.mNumIndices; ++n, ++vo,++out->mNumVertices) {
+                const MCol* col = &mesh->mcol[(i<<2)+n];
+
+                vo->r = col->r;
+                vo->g = col->g;
+                vo->b = col->b;
+                vo->a = col->a;
+            }
+            for (unsigned int n = f.mNumIndices; n < 4; ++n);
+        }
+
+        for (int i = 0; i < mesh->totpoly; ++i) {
+            const MPoly& v = mesh->mpoly[i];
+            aiMesh* const out = temp[ mat_num_to_mesh_idx[ v.mat_nr ] ];
+            const aiFace& f = out->mFaces[out->mNumFaces++];
+
+            aiColor4D* vo = &out->mColors[0][out->mNumVertices];
+			const ai_real scaleZeroToOne = 1.f/255.f;
+            for (unsigned int j = 0; j < f.mNumIndices; ++j,++vo,++out->mNumVertices) {
+                const MLoopCol& col = mesh->mloopcol[v.loopstart + j];
+                vo->r = ai_real(col.r) * scaleZeroToOne;
+                vo->g = ai_real(col.g) * scaleZeroToOne;
+                vo->b = ai_real(col.b) * scaleZeroToOne;
+                vo->a = ai_real(col.a) * scaleZeroToOne;
+            }
+
+        }
+
+    }
+
+    return;
+}
+
+// ------------------------------------------------------------------------------------------------
+aiCamera* BlenderImporter::ConvertCamera(const Scene& /*in*/, const Object* obj, const Camera* cam, ConversionData& /*conv_data*/)
+{
+    std::unique_ptr<aiCamera> out(new aiCamera());
+    out->mName = obj->id.name+2;
+    out->mPosition = aiVector3D(0.f, 0.f, 0.f);
+    out->mUp = aiVector3D(0.f, 1.f, 0.f);
+    out->mLookAt = aiVector3D(0.f, 0.f, -1.f);
+    if (cam->sensor_x && cam->lens) {
+        out->mHorizontalFOV = 2.f * std::atan2(cam->sensor_x,  2.f * cam->lens);
+    }
+    out->mClipPlaneNear = cam->clipsta;
+    out->mClipPlaneFar = cam->clipend;
+
+    return out.release();
+}
+
+// ------------------------------------------------------------------------------------------------
+aiLight* BlenderImporter::ConvertLight(const Scene& /*in*/, const Object* obj, const Lamp* lamp, ConversionData& /*conv_data*/)
+{
+    std::unique_ptr<aiLight> out(new aiLight());
+    out->mName = obj->id.name+2;
+
+    switch (lamp->type)
+    {
+        case Lamp::Type_Local:
+            out->mType = aiLightSource_POINT;
+            break;
+        case Lamp::Type_Sun:
+            out->mType = aiLightSource_DIRECTIONAL;
+
+            // blender orients directional lights as facing toward -z
+            out->mDirection = aiVector3D(0.f, 0.f, -1.f);
+            out->mUp = aiVector3D(0.f, 1.f, 0.f);
+            break;
+
+        case Lamp::Type_Area:
+            out->mType = aiLightSource_AREA;
+
+            if (lamp->area_shape == 0) {
+                out->mSize = aiVector2D(lamp->area_size, lamp->area_size);
+            }
+            else {
+                out->mSize = aiVector2D(lamp->area_size, lamp->area_sizey);
+            }
+
+            // blender orients directional lights as facing toward -z
+            out->mDirection = aiVector3D(0.f, 0.f, -1.f);
+            out->mUp = aiVector3D(0.f, 1.f, 0.f);
+            break;
+
+        default:
+            break;
+    }
+
+    out->mColorAmbient = aiColor3D(lamp->r, lamp->g, lamp->b) * lamp->energy;
+    out->mColorSpecular = aiColor3D(lamp->r, lamp->g, lamp->b) * lamp->energy;
+    out->mColorDiffuse = aiColor3D(lamp->r, lamp->g, lamp->b) * lamp->energy;
+    return out.release();
+}
+
+// ------------------------------------------------------------------------------------------------
+aiNode* BlenderImporter::ConvertNode(const Scene& in, const Object* obj, ConversionData& conv_data, const aiMatrix4x4& parentTransform)
+{
+    std::deque<const Object*> children;
+    for(ObjectSet::iterator it = conv_data.objects.begin(); it != conv_data.objects.end() ;) {
+        const Object* object = *it;
+        if (object->parent == obj) {
+            children.push_back(object);
+
+            conv_data.objects.erase(it++);
+            continue;
+        }
+        ++it;
+    }
+
+    std::unique_ptr<aiNode> node(new aiNode(obj->id.name+2)); // skip over the name prefix 'OB'
+    if (obj->data) {
+        switch (obj->type)
+        {
+        case Object :: Type_EMPTY:
+            break; // do nothing
+
+
+            // supported object types
+        case Object :: Type_MESH: {
+            const size_t old = conv_data.meshes->size();
+
+            CheckActualType(obj->data.get(),"Mesh");
+            ConvertMesh(in,obj,static_cast<const Mesh*>(obj->data.get()),conv_data,conv_data.meshes);
+
+            if (conv_data.meshes->size() > old) {
+                node->mMeshes = new unsigned int[node->mNumMeshes = static_cast<unsigned int>(conv_data.meshes->size()-old)];
+                for (unsigned int i = 0; i < node->mNumMeshes; ++i) {
+                    node->mMeshes[i] = static_cast<unsigned int>(i + old);
+                }
+            }}
+            break;
+        case Object :: Type_LAMP: {
+            CheckActualType(obj->data.get(),"Lamp");
+            aiLight* mesh = ConvertLight(in,obj,static_cast<const Lamp*>(
+                obj->data.get()),conv_data);
+
+            if (mesh) {
+                conv_data.lights->push_back(mesh);
+            }}
+            break;
+        case Object :: Type_CAMERA: {
+            CheckActualType(obj->data.get(),"Camera");
+            aiCamera* mesh = ConvertCamera(in,obj,static_cast<const Camera*>(
+                obj->data.get()),conv_data);
+
+            if (mesh) {
+                conv_data.cameras->push_back(mesh);
+            }}
+            break;
+
+
+            // unsupported object types / log, but do not break
+        case Object :: Type_CURVE:
+            NotSupportedObjectType(obj,"Curve");
+            break;
+        case Object :: Type_SURF:
+            NotSupportedObjectType(obj,"Surface");
+            break;
+        case Object :: Type_FONT:
+            NotSupportedObjectType(obj,"Font");
+            break;
+        case Object :: Type_MBALL:
+            NotSupportedObjectType(obj,"MetaBall");
+            break;
+        case Object :: Type_WAVE:
+            NotSupportedObjectType(obj,"Wave");
+            break;
+        case Object :: Type_LATTICE:
+            NotSupportedObjectType(obj,"Lattice");
+            break;
+
+            // invalid or unknown type
+        default:
+            break;
+        }
+    }
+
+    for(unsigned int x = 0; x < 4; ++x) {
+        for(unsigned int y = 0; y < 4; ++y) {
+            node->mTransformation[y][x] = obj->obmat[x][y];
+        }
+    }
+
+    aiMatrix4x4 m = parentTransform;
+    m = m.Inverse();
+
+    node->mTransformation = m*node->mTransformation;
+
+    if (children.size()) {
+        node->mNumChildren = static_cast<unsigned int>(children.size());
+        aiNode** nd = node->mChildren = new aiNode*[node->mNumChildren]();
+        for (const Object* nobj :children) {
+            *nd = ConvertNode(in,nobj,conv_data,node->mTransformation * parentTransform);
+            (*nd++)->mParent = node.get();
+        }
+    }
+
+    // apply modifiers
+    modifier_cache->ApplyModifiers(*node,conv_data,in,*obj);
+
+    return node.release();
+}
+
+#endif // ASSIMP_BUILD_NO_BLEND_IMPORTER

+ 240 - 0
Engine/lib/assimp/code/BlenderLoader.h

@@ -0,0 +1,240 @@
+/*
+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  BlenderLoader.h
+ *  @brief Declaration of the Blender 3D (*.blend) importer class.
+ */
+#ifndef INCLUDED_AI_BLEND_LOADER_H
+#define INCLUDED_AI_BLEND_LOADER_H
+
+#include <assimp/BaseImporter.h>
+#include <assimp/LogAux.h>
+#include <memory>
+
+struct aiNode;
+struct aiMesh;
+struct aiLight;
+struct aiCamera;
+struct aiMaterial;
+
+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;
+    }
+
+    // BlenderDNA.h
+    namespace Blender {
+        class  FileDatabase;
+        struct ElemBase;
+    }
+
+    // BlenderScene.h
+    namespace Blender {
+        struct Scene;
+        struct Object;
+        struct Mesh;
+        struct Camera;
+        struct Lamp;
+        struct MTex;
+        struct Image;
+        struct Material;
+    }
+
+    // BlenderIntermediate.h
+    namespace Blender {
+        struct ConversionData;
+        template <template <typename,typename> class TCLASS, typename T> struct TempArray;
+    }
+
+    // BlenderModifier.h
+    namespace Blender {
+        class BlenderModifierShowcase;
+        class BlenderModifier;
+    }
+
+
+
+// -------------------------------------------------------------------------------------------
+/** Load blenders official binary format. The actual file structure (the `DNA` how they
+ *  call it is outsourced to BlenderDNA.cpp/BlenderDNA.h. This class only performs the
+ *  conversion from intermediate format to aiScene. */
+// -------------------------------------------------------------------------------------------
+class BlenderImporter : public BaseImporter, public LogFunctions<BlenderImporter>
+{
+public:
+    BlenderImporter();
+    ~BlenderImporter();
+
+public:
+
+    // --------------------
+    bool CanRead( const std::string& pFile,
+        IOSystem* pIOHandler,
+        bool checkSig
+    ) const;
+
+protected:
+
+    // --------------------
+    const aiImporterDesc* GetInfo () const;
+
+    // --------------------
+    void GetExtensionList(std::set<std::string>& app);
+
+    // --------------------
+    void SetupProperties(const Importer* pImp);
+
+    // --------------------
+    void InternReadFile( const std::string& pFile,
+        aiScene* pScene,
+        IOSystem* pIOHandler
+    );
+
+    // --------------------
+    void ParseBlendFile(Blender::FileDatabase& out,
+        std::shared_ptr<IOStream> stream
+    );
+
+    // --------------------
+    void ExtractScene(Blender::Scene& out,
+        const Blender::FileDatabase& file
+    );
+
+    // --------------------
+    void ConvertBlendFile(aiScene* out,
+        const Blender::Scene& in,
+        const Blender::FileDatabase& file
+    );
+
+private:
+
+    // --------------------
+    aiNode* ConvertNode(const Blender::Scene& in,
+        const Blender::Object* obj,
+        Blender::ConversionData& conv_info,
+        const aiMatrix4x4& parentTransform
+    );
+
+    // --------------------
+    void ConvertMesh(const Blender::Scene& in,
+        const Blender::Object* obj,
+        const Blender::Mesh* mesh,
+        Blender::ConversionData& conv_data,
+        Blender::TempArray<std::vector,aiMesh>& temp
+    );
+
+    // --------------------
+    aiLight* ConvertLight(const Blender::Scene& in,
+        const Blender::Object* obj,
+        const Blender::Lamp* mesh,
+        Blender::ConversionData& conv_data
+    );
+
+    // --------------------
+    aiCamera* ConvertCamera(const Blender::Scene& in,
+        const Blender::Object* obj,
+        const Blender::Camera* mesh,
+        Blender::ConversionData& conv_data
+    );
+
+    // --------------------
+    void BuildDefaultMaterial(
+        Blender::ConversionData& conv_data
+    );
+
+    void AddBlendParams(
+        aiMaterial* result,
+        const Blender::Material* source
+    );
+
+    void BuildMaterials(
+        Blender::ConversionData& conv_data
+    );
+
+    // --------------------
+    void ResolveTexture(
+        aiMaterial* out,
+        const Blender::Material* mat,
+        const Blender::MTex* tex,
+        Blender::ConversionData& conv_data
+    );
+
+    // --------------------
+    void ResolveImage(
+        aiMaterial* out,
+        const Blender::Material* mat,
+        const Blender::MTex* tex,
+        const Blender::Image* img,
+        Blender::ConversionData& conv_data
+    );
+
+    void AddSentinelTexture(
+        aiMaterial* out,
+        const Blender::Material* mat,
+        const Blender::MTex* tex,
+        Blender::ConversionData& conv_data
+    );
+
+private: // static stuff, mostly logging and error reporting.
+
+    // --------------------
+    static void CheckActualType(const Blender::ElemBase* dt,
+        const char* check
+    );
+
+    // --------------------
+    static void NotSupportedObjectType(const Blender::Object* obj,
+        const char* type
+    );
+
+
+private:
+
+    Blender::BlenderModifierShowcase* modifier_cache;
+
+}; // !class BlenderImporter
+
+} // end of namespace Assimp
+#endif // AI_UNREALIMPORTER_H_INC

+ 299 - 0
Engine/lib/assimp/code/BlenderModifier.cpp

@@ -0,0 +1,299 @@
+/*
+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  BlenderModifier.cpp
+ *  @brief Implementation of some blender modifiers (i.e subdivision, mirror).
+ */
+
+#ifndef ASSIMP_BUILD_NO_BLEND_IMPORTER
+
+#include "BlenderModifier.h"
+#include <assimp/SceneCombiner.h>
+#include <assimp/Subdivision.h>
+#include <assimp/scene.h>
+#include <memory>
+
+#include <functional>
+
+using namespace Assimp;
+using namespace Assimp::Blender;
+
+template <typename T> BlenderModifier* god() {
+    return new T();
+}
+
+// add all available modifiers here
+typedef BlenderModifier* (*fpCreateModifier)();
+static const fpCreateModifier creators[] = {
+        &god<BlenderModifier_Mirror>,
+        &god<BlenderModifier_Subdivision>,
+
+        NULL // sentinel
+};
+
+// ------------------------------------------------------------------------------------------------
+struct SharedModifierData : ElemBase
+{
+    ModifierData modifier;
+};
+
+// ------------------------------------------------------------------------------------------------
+void BlenderModifierShowcase::ApplyModifiers(aiNode& out, ConversionData& conv_data, const Scene& in, const Object& orig_object )
+{
+    size_t cnt = 0u, ful = 0u;
+
+    // NOTE: this cast is potentially unsafe by design, so we need to perform type checks before
+    // we're allowed to dereference the pointers without risking to crash. We might still be
+    // invoking UB btw - we're assuming that the ModifierData member of the respective modifier
+    // structures is at offset sizeof(vftable) with no padding.
+    const SharedModifierData* cur = static_cast<const SharedModifierData *> ( orig_object.modifiers.first.get() );
+    for (; cur; cur =  static_cast<const SharedModifierData *> ( cur->modifier.next.get() ), ++ful) {
+        ai_assert(cur->dna_type);
+
+        const Structure* s = conv_data.db.dna.Get( cur->dna_type );
+        if (!s) {
+            ASSIMP_LOG_WARN_F("BlendModifier: could not resolve DNA name: ",cur->dna_type);
+            continue;
+        }
+
+        // this is a common trait of all XXXMirrorData structures in BlenderDNA
+        const Field* f = s->Get("modifier");
+        if (!f || f->offset != 0) {
+            ASSIMP_LOG_WARN("BlendModifier: expected a `modifier` member at offset 0");
+            continue;
+        }
+
+        s = conv_data.db.dna.Get( f->type );
+        if (!s || s->name != "ModifierData") {
+            ASSIMP_LOG_WARN("BlendModifier: expected a ModifierData structure as first member");
+            continue;
+        }
+
+        // now, we can be sure that we should be fine to dereference *cur* as
+        // ModifierData (with the above note).
+        const ModifierData& dat = cur->modifier;
+
+        const fpCreateModifier* curgod = creators;
+        std::vector< BlenderModifier* >::iterator curmod = cached_modifiers->begin(), endmod = cached_modifiers->end();
+
+        for (;*curgod;++curgod,++curmod) { // allocate modifiers on the fly
+            if (curmod == endmod) {
+                cached_modifiers->push_back((*curgod)());
+
+                endmod = cached_modifiers->end();
+                curmod = endmod-1;
+            }
+
+            BlenderModifier* const modifier = *curmod;
+            if(modifier->IsActive(dat)) {
+                modifier->DoIt(out,conv_data,*static_cast<const ElemBase *>(cur),in,orig_object);
+                cnt++;
+
+                curgod = NULL;
+                break;
+            }
+        }
+        if (curgod) {
+            ASSIMP_LOG_WARN_F("Couldn't find a handler for modifier: ",dat.name);
+        }
+    }
+
+    // Even though we managed to resolve some or all of the modifiers on this
+    // object, we still can't say whether our modifier implementations were
+    // able to fully do their job.
+    if (ful) {
+        ASSIMP_LOG_DEBUG_F("BlendModifier: found handlers for ",cnt," of ",ful," modifiers on `",orig_object.id.name,
+            "`, check log messages above for errors");
+    }
+}
+
+
+
+// ------------------------------------------------------------------------------------------------
+bool BlenderModifier_Mirror :: IsActive (const ModifierData& modin)
+{
+    return modin.type == ModifierData::eModifierType_Mirror;
+}
+
+// ------------------------------------------------------------------------------------------------
+void  BlenderModifier_Mirror :: DoIt(aiNode& out, ConversionData& conv_data,  const ElemBase& orig_modifier,
+    const Scene& /*in*/,
+    const Object& orig_object )
+{
+    // hijacking the ABI, see the big note in BlenderModifierShowcase::ApplyModifiers()
+    const MirrorModifierData& mir = static_cast<const MirrorModifierData&>(orig_modifier);
+    ai_assert(mir.modifier.type == ModifierData::eModifierType_Mirror);
+
+    conv_data.meshes->reserve(conv_data.meshes->size() + out.mNumMeshes);
+
+    // XXX not entirely correct, mirroring on two axes results in 4 distinct objects in blender ...
+
+    // take all input meshes and clone them
+    for (unsigned int i = 0; i < out.mNumMeshes; ++i) {
+        aiMesh* mesh;
+        SceneCombiner::Copy(&mesh,conv_data.meshes[out.mMeshes[i]]);
+
+        const float xs = mir.flag & MirrorModifierData::Flags_AXIS_X ? -1.f : 1.f;
+        const float ys = mir.flag & MirrorModifierData::Flags_AXIS_Y ? -1.f : 1.f;
+        const float zs = mir.flag & MirrorModifierData::Flags_AXIS_Z ? -1.f : 1.f;
+
+        if (mir.mirror_ob) {
+            const aiVector3D center( mir.mirror_ob->obmat[3][0],mir.mirror_ob->obmat[3][1],mir.mirror_ob->obmat[3][2] );
+            for (unsigned int i = 0; i < mesh->mNumVertices; ++i) {
+                aiVector3D& v = mesh->mVertices[i];
+
+                v.x = center.x + xs*(center.x - v.x);
+                v.y = center.y + ys*(center.y - v.y);
+                v.z = center.z + zs*(center.z - v.z);
+            }
+        }
+        else {
+            for (unsigned int i = 0; i < mesh->mNumVertices; ++i) {
+                aiVector3D& v = mesh->mVertices[i];
+                v.x *= xs;v.y *= ys;v.z *= zs;
+            }
+        }
+
+        if (mesh->mNormals) {
+            for (unsigned int i = 0; i < mesh->mNumVertices; ++i) {
+                aiVector3D& v = mesh->mNormals[i];
+                v.x *= xs;v.y *= ys;v.z *= zs;
+            }
+        }
+
+        if (mesh->mTangents) {
+            for (unsigned int i = 0; i < mesh->mNumVertices; ++i) {
+                aiVector3D& v = mesh->mTangents[i];
+                v.x *= xs;v.y *= ys;v.z *= zs;
+            }
+        }
+
+        if (mesh->mBitangents) {
+            for (unsigned int i = 0; i < mesh->mNumVertices; ++i) {
+                aiVector3D& v = mesh->mBitangents[i];
+                v.x *= xs;v.y *= ys;v.z *= zs;
+            }
+        }
+
+        const float us = mir.flag & MirrorModifierData::Flags_MIRROR_U ? -1.f : 1.f;
+        const float vs = mir.flag & MirrorModifierData::Flags_MIRROR_V ? -1.f : 1.f;
+
+        for (unsigned int n = 0; mesh->HasTextureCoords(n); ++n) {
+            for (unsigned int i = 0; i < mesh->mNumVertices; ++i) {
+                aiVector3D& v = mesh->mTextureCoords[n][i];
+                v.x *= us;v.y *= vs;
+            }
+        }
+
+        // Only reverse the winding order if an odd number of axes were mirrored.
+        if (xs * ys * zs < 0) {
+            for( unsigned int i = 0; i < mesh->mNumFaces; i++) {
+                aiFace& face = mesh->mFaces[i];
+                for( unsigned int fi = 0; fi < face.mNumIndices / 2; ++fi)
+                    std::swap( face.mIndices[fi], face.mIndices[face.mNumIndices - 1 - fi]);
+            }
+        }
+
+        conv_data.meshes->push_back(mesh);
+    }
+    unsigned int* nind = new unsigned int[out.mNumMeshes*2];
+
+    std::copy(out.mMeshes,out.mMeshes+out.mNumMeshes,nind);
+    std::transform(out.mMeshes,out.mMeshes+out.mNumMeshes,nind+out.mNumMeshes,
+        [&out](unsigned int n) { return out.mNumMeshes + n; });
+
+    delete[] out.mMeshes;
+    out.mMeshes = nind;
+    out.mNumMeshes *= 2;
+
+    ASSIMP_LOG_INFO_F("BlendModifier: Applied the `Mirror` modifier to `",
+        orig_object.id.name,"`");
+}
+
+// ------------------------------------------------------------------------------------------------
+bool BlenderModifier_Subdivision :: IsActive (const ModifierData& modin)
+{
+    return modin.type == ModifierData::eModifierType_Subsurf;
+}
+
+// ------------------------------------------------------------------------------------------------
+void  BlenderModifier_Subdivision :: DoIt(aiNode& out, ConversionData& conv_data,  const ElemBase& orig_modifier,
+    const Scene& /*in*/,
+    const Object& orig_object )
+{
+    // hijacking the ABI, see the big note in BlenderModifierShowcase::ApplyModifiers()
+    const SubsurfModifierData& mir = static_cast<const SubsurfModifierData&>(orig_modifier);
+    ai_assert(mir.modifier.type == ModifierData::eModifierType_Subsurf);
+
+    Subdivider::Algorithm algo;
+    switch (mir.subdivType)
+    {
+    case SubsurfModifierData::TYPE_CatmullClarke:
+        algo = Subdivider::CATMULL_CLARKE;
+        break;
+
+    case SubsurfModifierData::TYPE_Simple:
+        ASSIMP_LOG_WARN("BlendModifier: The `SIMPLE` subdivision algorithm is not currently implemented, using Catmull-Clarke");
+        algo = Subdivider::CATMULL_CLARKE;
+        break;
+
+    default:
+        ASSIMP_LOG_WARN_F("BlendModifier: Unrecognized subdivision algorithm: ",mir.subdivType);
+        return;
+    };
+
+    std::unique_ptr<Subdivider> subd(Subdivider::Create(algo));
+    ai_assert(subd);
+    if ( conv_data.meshes->empty() ) {
+        return;
+    }
+    aiMesh** const meshes = &conv_data.meshes[conv_data.meshes->size() - out.mNumMeshes];
+    std::unique_ptr<aiMesh*[]> tempmeshes(new aiMesh*[out.mNumMeshes]());
+
+    subd->Subdivide(meshes,out.mNumMeshes,tempmeshes.get(),std::max( mir.renderLevels, mir.levels ),true);
+    std::copy(tempmeshes.get(),tempmeshes.get()+out.mNumMeshes,meshes);
+
+    ASSIMP_LOG_INFO_F("BlendModifier: Applied the `Subdivision` modifier to `",
+        orig_object.id.name,"`");
+}
+
+#endif // ASSIMP_BUILD_NO_BLEND_IMPORTER

+ 155 - 0
Engine/lib/assimp/code/BlenderModifier.h

@@ -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  BlenderModifier.h
+ *  @brief Declare dedicated helper classes to simulate some blender modifiers (i.e. mirror)
+ */
+#ifndef INCLUDED_AI_BLEND_MODIFIER_H
+#define INCLUDED_AI_BLEND_MODIFIER_H
+
+#include "BlenderIntermediate.h"
+
+namespace Assimp {
+namespace Blender {
+
+// -------------------------------------------------------------------------------------------
+/** 
+ *  Dummy base class for all blender modifiers. Modifiers are reused between imports, so
+ *  they should be stateless and not try to cache model data. 
+ */
+// -------------------------------------------------------------------------------------------
+class BlenderModifier {
+public:
+    /**
+     *  The class destructor, virtual.
+     */
+    virtual ~BlenderModifier() {
+        // empty
+    }
+
+    // --------------------
+    /** 
+     *  Check if *this* modifier is active, given a ModifierData& block.
+     */
+    virtual bool IsActive( const ModifierData& /*modin*/) {
+        return false;
+    }
+
+    // --------------------
+    /** 
+     *  Apply the modifier to a given output node. The original data used
+     *  to construct the node is given as well. Not called unless IsActive()
+     *  was called and gave positive response. 
+     */
+    virtual void DoIt(aiNode& /*out*/,
+        ConversionData& /*conv_data*/,
+        const ElemBase& orig_modifier,
+        const Scene& /*in*/,
+        const Object& /*orig_object*/
+    ) {
+        ASSIMP_LOG_INFO_F("This modifier is not supported, skipping: ",orig_modifier.dna_type );
+        return;
+    }
+};
+
+// -------------------------------------------------------------------------------------------
+/** 
+ *  Manage all known modifiers and instance and apply them if necessary 
+ */
+// -------------------------------------------------------------------------------------------
+class BlenderModifierShowcase {
+public:
+    // --------------------
+    /** Apply all requested modifiers provided we support them. */
+    void ApplyModifiers(aiNode& out,
+        ConversionData& conv_data,
+        const Scene& in,
+        const Object& orig_object
+    );
+
+private:
+    TempArray< std::vector,BlenderModifier > cached_modifiers;
+};
+
+// MODIFIERS /////////////////////////////////////////////////////////////////////////////////
+
+// -------------------------------------------------------------------------------------------
+/** 
+ *  Mirror modifier. Status: implemented. 
+ */
+// -------------------------------------------------------------------------------------------
+class BlenderModifier_Mirror : public BlenderModifier {
+public:
+    // --------------------
+    virtual bool IsActive( const ModifierData& modin);
+
+    // --------------------
+    virtual void DoIt(aiNode& out,
+        ConversionData& conv_data,
+        const ElemBase& orig_modifier,
+        const Scene& in,
+        const Object& orig_object
+    ) ;
+};
+
+// -------------------------------------------------------------------------------------------
+/** Subdivision modifier. Status: dummy. */
+// -------------------------------------------------------------------------------------------
+class BlenderModifier_Subdivision : public BlenderModifier {
+public:
+
+    // --------------------
+    virtual bool IsActive( const ModifierData& modin);
+
+    // --------------------
+    virtual void DoIt(aiNode& out,
+        ConversionData& conv_data,
+        const ElemBase& orig_modifier,
+        const Scene& in,
+        const Object& orig_object
+    ) ;
+};
+
+}
+}
+
+#endif // !INCLUDED_AI_BLEND_MODIFIER_H

+ 872 - 0
Engine/lib/assimp/code/BlenderScene.cpp

@@ -0,0 +1,872 @@
+/*
+Open Asset Import Library (ASSIMP)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2016, ASSIMP Development 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 Development 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  BlenderScene.cpp
+ *  @brief MACHINE GENERATED BY ./scripts/BlenderImporter/genblenddna.py
+ */
+
+#ifndef ASSIMP_BUILD_NO_BLEND_IMPORTER
+
+#include "BlenderScene.h"
+#include "BlenderSceneGen.h"
+#include "BlenderDNA.h"
+#include "BlenderCustomData.h"
+
+using namespace Assimp;
+using namespace Assimp::Blender;
+
+//--------------------------------------------------------------------------------
+template <> void Structure :: Convert<Object> (
+    Object& dest,
+    const FileDatabase& db
+    ) const
+{
+
+    ReadField<ErrorPolicy_Fail>(dest.id,"id",db);
+    int temp = 0;
+    ReadField<ErrorPolicy_Fail>(temp,"type",db);
+    dest.type = static_cast<Assimp::Blender::Object::Type>(temp);
+    ReadFieldArray2<ErrorPolicy_Warn>(dest.obmat,"obmat",db);
+    ReadFieldArray2<ErrorPolicy_Warn>(dest.parentinv,"parentinv",db);
+    ReadFieldArray<ErrorPolicy_Warn>(dest.parsubstr,"parsubstr",db);
+    {
+        std::shared_ptr<Object> parent;
+        ReadFieldPtr<ErrorPolicy_Warn>(parent,"*parent",db);
+        dest.parent = parent.get();
+    }
+    ReadFieldPtr<ErrorPolicy_Warn>(dest.track,"*track",db);
+    ReadFieldPtr<ErrorPolicy_Warn>(dest.proxy,"*proxy",db);
+    ReadFieldPtr<ErrorPolicy_Warn>(dest.proxy_from,"*proxy_from",db);
+    ReadFieldPtr<ErrorPolicy_Warn>(dest.proxy_group,"*proxy_group",db);
+    ReadFieldPtr<ErrorPolicy_Warn>(dest.dup_group,"*dup_group",db);
+    ReadFieldPtr<ErrorPolicy_Fail>(dest.data,"*data",db);
+    ReadField<ErrorPolicy_Igno>(dest.modifiers,"modifiers",db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure :: Convert<Group> (
+    Group& dest,
+    const FileDatabase& db
+    ) const
+{
+
+    ReadField<ErrorPolicy_Fail>(dest.id,"id",db);
+    ReadField<ErrorPolicy_Igno>(dest.layer,"layer",db);
+    ReadFieldPtr<ErrorPolicy_Igno>(dest.gobject,"*gobject",db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure :: Convert<MTex> (
+    MTex& dest,
+    const FileDatabase& db
+    ) const
+{
+
+    int temp_short = 0;
+    ReadField<ErrorPolicy_Igno>(temp_short,"mapto",db);
+    dest.mapto = static_cast<Assimp::Blender::MTex::MapType>(temp_short);
+    int temp = 0;
+    ReadField<ErrorPolicy_Igno>(temp,"blendtype",db);
+    dest.blendtype = static_cast<Assimp::Blender::MTex::BlendType>(temp);
+    ReadFieldPtr<ErrorPolicy_Igno>(dest.object,"*object",db);
+    ReadFieldPtr<ErrorPolicy_Igno>(dest.tex,"*tex",db);
+    ReadFieldArray<ErrorPolicy_Igno>(dest.uvname,"uvname",db);
+    ReadField<ErrorPolicy_Igno>(temp,"projx",db);
+    dest.projx = static_cast<Assimp::Blender::MTex::Projection>(temp);
+    ReadField<ErrorPolicy_Igno>(temp,"projy",db);
+    dest.projy = static_cast<Assimp::Blender::MTex::Projection>(temp);
+    ReadField<ErrorPolicy_Igno>(temp,"projz",db);
+    dest.projz = static_cast<Assimp::Blender::MTex::Projection>(temp);
+    ReadField<ErrorPolicy_Igno>(dest.mapping,"mapping",db);
+    ReadFieldArray<ErrorPolicy_Igno>(dest.ofs,"ofs",db);
+    ReadFieldArray<ErrorPolicy_Igno>(dest.size,"size",db);
+    ReadField<ErrorPolicy_Igno>(dest.rot,"rot",db);
+    ReadField<ErrorPolicy_Igno>(dest.texflag,"texflag",db);
+    ReadField<ErrorPolicy_Igno>(dest.colormodel,"colormodel",db);
+    ReadField<ErrorPolicy_Igno>(dest.pmapto,"pmapto",db);
+    ReadField<ErrorPolicy_Igno>(dest.pmaptoneg,"pmaptoneg",db);
+    ReadField<ErrorPolicy_Warn>(dest.r,"r",db);
+    ReadField<ErrorPolicy_Warn>(dest.g,"g",db);
+    ReadField<ErrorPolicy_Warn>(dest.b,"b",db);
+    ReadField<ErrorPolicy_Warn>(dest.k,"k",db);
+    ReadField<ErrorPolicy_Igno>(dest.colspecfac,"colspecfac",db);
+    ReadField<ErrorPolicy_Igno>(dest.mirrfac,"mirrfac",db);
+    ReadField<ErrorPolicy_Igno>(dest.alphafac,"alphafac",db);
+    ReadField<ErrorPolicy_Igno>(dest.difffac,"difffac",db);
+    ReadField<ErrorPolicy_Igno>(dest.specfac,"specfac",db);
+    ReadField<ErrorPolicy_Igno>(dest.emitfac,"emitfac",db);
+    ReadField<ErrorPolicy_Igno>(dest.hardfac,"hardfac",db);
+    ReadField<ErrorPolicy_Igno>(dest.norfac,"norfac",db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure :: Convert<TFace> (
+    TFace& dest,
+    const FileDatabase& db
+    ) const
+{
+
+    ReadFieldArray2<ErrorPolicy_Fail>(dest.uv,"uv",db);
+    ReadFieldArray<ErrorPolicy_Fail>(dest.col,"col",db);
+    ReadField<ErrorPolicy_Igno>(dest.flag,"flag",db);
+    ReadField<ErrorPolicy_Igno>(dest.mode,"mode",db);
+    ReadField<ErrorPolicy_Igno>(dest.tile,"tile",db);
+    ReadField<ErrorPolicy_Igno>(dest.unwrap,"unwrap",db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure :: Convert<SubsurfModifierData> (
+    SubsurfModifierData& dest,
+    const FileDatabase& db
+    ) const
+{
+
+    ReadField<ErrorPolicy_Fail>(dest.modifier,"modifier",db);
+    ReadField<ErrorPolicy_Warn>(dest.subdivType,"subdivType",db);
+    ReadField<ErrorPolicy_Fail>(dest.levels,"levels",db);
+    ReadField<ErrorPolicy_Igno>(dest.renderLevels,"renderLevels",db);
+    ReadField<ErrorPolicy_Igno>(dest.flags,"flags",db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure :: Convert<MFace> (
+    MFace& dest,
+    const FileDatabase& db
+    ) const
+{
+
+    ReadField<ErrorPolicy_Fail>(dest.v1,"v1",db);
+    ReadField<ErrorPolicy_Fail>(dest.v2,"v2",db);
+    ReadField<ErrorPolicy_Fail>(dest.v3,"v3",db);
+    ReadField<ErrorPolicy_Fail>(dest.v4,"v4",db);
+    ReadField<ErrorPolicy_Fail>(dest.mat_nr,"mat_nr",db);
+    ReadField<ErrorPolicy_Igno>(dest.flag,"flag",db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure :: Convert<Lamp> (
+    Lamp& dest,
+    const FileDatabase& db
+    ) const
+{
+
+    ReadField<ErrorPolicy_Fail>(dest.id,"id",db);
+    int temp = 0;
+    ReadField<ErrorPolicy_Fail>(temp,"type",db);
+    dest.type = static_cast<Assimp::Blender::Lamp::Type>(temp);
+    ReadField<ErrorPolicy_Igno>(dest.flags,"flag",db);
+    ReadField<ErrorPolicy_Igno>(dest.colormodel,"colormodel",db);
+    ReadField<ErrorPolicy_Igno>(dest.totex,"totex",db);
+    ReadField<ErrorPolicy_Warn>(dest.r,"r",db);
+    ReadField<ErrorPolicy_Warn>(dest.g,"g",db);
+    ReadField<ErrorPolicy_Warn>(dest.b,"b",db);
+    ReadField<ErrorPolicy_Warn>(dest.k,"k",db);
+    ReadField<ErrorPolicy_Igno>(dest.energy,"energy",db);
+    ReadField<ErrorPolicy_Igno>(dest.dist,"dist",db);
+    ReadField<ErrorPolicy_Igno>(dest.spotsize,"spotsize",db);
+    ReadField<ErrorPolicy_Igno>(dest.spotblend,"spotblend",db);
+    ReadField<ErrorPolicy_Igno>(dest.att1,"att1",db);
+    ReadField<ErrorPolicy_Igno>(dest.att2,"att2",db);
+    ReadField<ErrorPolicy_Igno>(temp,"falloff_type",db);
+    dest.falloff_type = static_cast<Assimp::Blender::Lamp::FalloffType>(temp);
+    ReadField<ErrorPolicy_Igno>(dest.sun_brightness,"sun_brightness",db);
+    ReadField<ErrorPolicy_Igno>(dest.area_size,"area_size",db);
+    ReadField<ErrorPolicy_Igno>(dest.area_sizey,"area_sizey",db);
+    ReadField<ErrorPolicy_Igno>(dest.area_sizez,"area_sizez",db);
+    ReadField<ErrorPolicy_Igno>(dest.area_shape,"area_shape",db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure :: Convert<MDeformWeight> (
+    MDeformWeight& dest,
+    const FileDatabase& db
+    ) const
+{
+
+    ReadField<ErrorPolicy_Fail>(dest.def_nr,"def_nr",db);
+    ReadField<ErrorPolicy_Fail>(dest.weight,"weight",db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure :: Convert<PackedFile> (
+    PackedFile& dest,
+    const FileDatabase& db
+    ) const
+{
+
+    ReadField<ErrorPolicy_Warn>(dest.size,"size",db);
+    ReadField<ErrorPolicy_Warn>(dest.seek,"seek",db);
+    ReadFieldPtr<ErrorPolicy_Warn>(dest.data,"*data",db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure :: Convert<Base> (
+    Base& dest,
+    const FileDatabase& db
+    ) const
+{
+    // note: as per https://github.com/assimp/assimp/issues/128,
+    // reading the Object linked list recursively is prone to stack overflow.
+    // This structure converter is therefore an hand-written exception that
+    // does it iteratively.
+
+    const int initial_pos = db.reader->GetCurrentPos();
+
+    std::pair<Base*, int> todo = std::make_pair(&dest, initial_pos);
+    for ( ;; ) {
+
+        Base& cur_dest = *todo.first;
+        db.reader->SetCurrentPos(todo.second);
+
+        // we know that this is a double-linked, circular list which we never
+        // traverse backwards, so don't bother resolving the back links.
+        cur_dest.prev = NULL;
+
+        ReadFieldPtr<ErrorPolicy_Warn>(cur_dest.object,"*object",db);
+
+        // the return value of ReadFieldPtr indicates whether the object
+        // was already cached. In this case, we don't need to resolve
+        // it again.
+        if(!ReadFieldPtr<ErrorPolicy_Warn>(cur_dest.next,"*next",db, true) && cur_dest.next) {
+            todo = std::make_pair(&*cur_dest.next, db.reader->GetCurrentPos());
+            continue;
+        }
+        break;
+    }
+
+    db.reader->SetCurrentPos(initial_pos + size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure :: Convert<MTFace> (
+    MTFace& dest,
+    const FileDatabase& db
+    ) const
+{
+
+    ReadFieldArray2<ErrorPolicy_Fail>(dest.uv,"uv",db);
+    ReadField<ErrorPolicy_Igno>(dest.flag,"flag",db);
+    ReadField<ErrorPolicy_Igno>(dest.mode,"mode",db);
+    ReadField<ErrorPolicy_Igno>(dest.tile,"tile",db);
+    ReadField<ErrorPolicy_Igno>(dest.unwrap,"unwrap",db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure :: Convert<Material> (
+    Material& dest,
+    const FileDatabase& db
+    ) const
+{
+    ReadField<ErrorPolicy_Fail>(dest.id,"id",db);
+    ReadField<ErrorPolicy_Warn>(dest.r,"r",db);
+    ReadField<ErrorPolicy_Warn>(dest.g,"g",db);
+    ReadField<ErrorPolicy_Warn>(dest.b,"b",db);
+    ReadField<ErrorPolicy_Warn>(dest.specr,"specr",db);
+    ReadField<ErrorPolicy_Warn>(dest.specg,"specg",db);
+    ReadField<ErrorPolicy_Warn>(dest.specb,"specb",db);
+    ReadField<ErrorPolicy_Igno>(dest.har,"har",db);
+    ReadField<ErrorPolicy_Warn>(dest.ambr,"ambr",db);
+    ReadField<ErrorPolicy_Warn>(dest.ambg,"ambg",db);
+    ReadField<ErrorPolicy_Warn>(dest.ambb,"ambb",db);
+    ReadField<ErrorPolicy_Igno>(dest.mirr,"mirr",db);
+    ReadField<ErrorPolicy_Igno>(dest.mirg,"mirg",db);
+    ReadField<ErrorPolicy_Igno>(dest.mirb,"mirb",db);
+    ReadField<ErrorPolicy_Warn>(dest.emit,"emit",db);
+    ReadField<ErrorPolicy_Igno>(dest.ray_mirror,"ray_mirror",db);
+    ReadField<ErrorPolicy_Warn>(dest.alpha,"alpha",db);
+    ReadField<ErrorPolicy_Igno>(dest.ref,"ref",db);
+    ReadField<ErrorPolicy_Igno>(dest.translucency,"translucency",db);
+    ReadField<ErrorPolicy_Igno>(dest.mode,"mode",db);
+    ReadField<ErrorPolicy_Igno>(dest.roughness,"roughness",db);
+    ReadField<ErrorPolicy_Igno>(dest.darkness,"darkness",db);
+    ReadField<ErrorPolicy_Igno>(dest.refrac,"refrac",db);
+    ReadFieldPtr<ErrorPolicy_Igno>(dest.group,"*group",db);
+    ReadField<ErrorPolicy_Warn>(dest.diff_shader,"diff_shader",db);
+    ReadField<ErrorPolicy_Warn>(dest.spec_shader,"spec_shader",db);
+    ReadFieldPtr<ErrorPolicy_Igno>(dest.mtex,"*mtex",db);
+
+
+    ReadField<ErrorPolicy_Igno>(dest.amb, "amb", db);
+    ReadField<ErrorPolicy_Igno>(dest.ang, "ang", db);
+    ReadField<ErrorPolicy_Igno>(dest.spectra, "spectra", db);
+    ReadField<ErrorPolicy_Igno>(dest.spec, "spec", db);
+    ReadField<ErrorPolicy_Igno>(dest.zoffs, "zoffs", db);
+    ReadField<ErrorPolicy_Igno>(dest.add, "add", db);
+    ReadField<ErrorPolicy_Igno>(dest.fresnel_mir, "fresnel_mir", db);
+    ReadField<ErrorPolicy_Igno>(dest.fresnel_mir_i, "fresnel_mir_i", db);
+    ReadField<ErrorPolicy_Igno>(dest.fresnel_tra, "fresnel_tra", db);
+    ReadField<ErrorPolicy_Igno>(dest.fresnel_tra_i, "fresnel_tra_i", db);
+    ReadField<ErrorPolicy_Igno>(dest.filter, "filter", db);
+    ReadField<ErrorPolicy_Igno>(dest.tx_limit, "tx_limit", db);
+    ReadField<ErrorPolicy_Igno>(dest.tx_falloff, "tx_falloff", db);
+    ReadField<ErrorPolicy_Igno>(dest.gloss_mir, "gloss_mir", db);
+    ReadField<ErrorPolicy_Igno>(dest.gloss_tra, "gloss_tra", db);
+    ReadField<ErrorPolicy_Igno>(dest.adapt_thresh_mir, "adapt_thresh_mir", db);
+    ReadField<ErrorPolicy_Igno>(dest.adapt_thresh_tra, "adapt_thresh_tra", db);
+    ReadField<ErrorPolicy_Igno>(dest.aniso_gloss_mir, "aniso_gloss_mir", db);
+    ReadField<ErrorPolicy_Igno>(dest.dist_mir, "dist_mir", db);
+    ReadField<ErrorPolicy_Igno>(dest.hasize, "hasize", db);
+    ReadField<ErrorPolicy_Igno>(dest.flaresize, "flaresize", db);
+    ReadField<ErrorPolicy_Igno>(dest.subsize, "subsize", db);
+    ReadField<ErrorPolicy_Igno>(dest.flareboost, "flareboost", db);
+    ReadField<ErrorPolicy_Igno>(dest.strand_sta, "strand_sta", db);
+    ReadField<ErrorPolicy_Igno>(dest.strand_end, "strand_end", db);
+    ReadField<ErrorPolicy_Igno>(dest.strand_ease, "strand_ease", db);
+    ReadField<ErrorPolicy_Igno>(dest.strand_surfnor, "strand_surfnor", db);
+    ReadField<ErrorPolicy_Igno>(dest.strand_min, "strand_min", db);
+    ReadField<ErrorPolicy_Igno>(dest.strand_widthfade, "strand_widthfade", db);
+    ReadField<ErrorPolicy_Igno>(dest.sbias, "sbias", db);
+    ReadField<ErrorPolicy_Igno>(dest.lbias, "lbias", db);
+    ReadField<ErrorPolicy_Igno>(dest.shad_alpha, "shad_alpha", db);
+    ReadField<ErrorPolicy_Igno>(dest.param, "param", db);
+    ReadField<ErrorPolicy_Igno>(dest.rms, "rms", db);
+    ReadField<ErrorPolicy_Igno>(dest.rampfac_col, "rampfac_col", db);
+    ReadField<ErrorPolicy_Igno>(dest.rampfac_spec, "rampfac_spec", db);
+    ReadField<ErrorPolicy_Igno>(dest.friction, "friction", db);
+    ReadField<ErrorPolicy_Igno>(dest.fh, "fh", db);
+    ReadField<ErrorPolicy_Igno>(dest.reflect, "reflect", db);
+    ReadField<ErrorPolicy_Igno>(dest.fhdist, "fhdist", db);
+    ReadField<ErrorPolicy_Igno>(dest.xyfrict, "xyfrict", db);
+    ReadField<ErrorPolicy_Igno>(dest.sss_radius, "sss_radius", db);
+    ReadField<ErrorPolicy_Igno>(dest.sss_col, "sss_col", db);
+    ReadField<ErrorPolicy_Igno>(dest.sss_error, "sss_error", db);
+    ReadField<ErrorPolicy_Igno>(dest.sss_scale, "sss_scale", db);
+    ReadField<ErrorPolicy_Igno>(dest.sss_ior, "sss_ior", db);
+    ReadField<ErrorPolicy_Igno>(dest.sss_colfac, "sss_colfac", db);
+    ReadField<ErrorPolicy_Igno>(dest.sss_texfac, "sss_texfac", db);
+    ReadField<ErrorPolicy_Igno>(dest.sss_front, "sss_front", db);
+    ReadField<ErrorPolicy_Igno>(dest.sss_back, "sss_back", db);
+
+    ReadField<ErrorPolicy_Igno>(dest.material_type, "material_type", db);
+    ReadField<ErrorPolicy_Igno>(dest.flag, "flag", db);
+    ReadField<ErrorPolicy_Igno>(dest.ray_depth, "ray_depth", db);
+    ReadField<ErrorPolicy_Igno>(dest.ray_depth_tra, "ray_depth_tra", db);
+    ReadField<ErrorPolicy_Igno>(dest.samp_gloss_mir, "samp_gloss_mir", db);
+    ReadField<ErrorPolicy_Igno>(dest.samp_gloss_tra, "samp_gloss_tra", db);
+    ReadField<ErrorPolicy_Igno>(dest.fadeto_mir, "fadeto_mir", db);
+    ReadField<ErrorPolicy_Igno>(dest.shade_flag, "shade_flag", db);
+    ReadField<ErrorPolicy_Igno>(dest.flarec, "flarec", db);
+    ReadField<ErrorPolicy_Igno>(dest.starc, "starc", db);
+    ReadField<ErrorPolicy_Igno>(dest.linec, "linec", db);
+    ReadField<ErrorPolicy_Igno>(dest.ringc, "ringc", db);
+    ReadField<ErrorPolicy_Igno>(dest.pr_lamp, "pr_lamp", db);
+    ReadField<ErrorPolicy_Igno>(dest.pr_texture, "pr_texture", db);
+    ReadField<ErrorPolicy_Igno>(dest.ml_flag, "ml_flag", db);
+    ReadField<ErrorPolicy_Igno>(dest.diff_shader, "diff_shader", db);
+    ReadField<ErrorPolicy_Igno>(dest.spec_shader, "spec_shader", db);
+    ReadField<ErrorPolicy_Igno>(dest.texco, "texco", db);
+    ReadField<ErrorPolicy_Igno>(dest.mapto, "mapto", db);
+    ReadField<ErrorPolicy_Igno>(dest.ramp_show, "ramp_show", db);
+    ReadField<ErrorPolicy_Igno>(dest.pad3, "pad3", db);
+    ReadField<ErrorPolicy_Igno>(dest.dynamode, "dynamode", db);
+    ReadField<ErrorPolicy_Igno>(dest.pad2, "pad2", db);
+    ReadField<ErrorPolicy_Igno>(dest.sss_flag, "sss_flag", db);
+    ReadField<ErrorPolicy_Igno>(dest.sss_preset, "sss_preset", db);
+    ReadField<ErrorPolicy_Igno>(dest.shadowonly_flag, "shadowonly_flag", db);
+    ReadField<ErrorPolicy_Igno>(dest.index, "index", db);
+    ReadField<ErrorPolicy_Igno>(dest.vcol_alpha, "vcol_alpha", db);
+    ReadField<ErrorPolicy_Igno>(dest.pad4, "pad4", db);
+
+    ReadField<ErrorPolicy_Igno>(dest.seed1, "seed1", db);
+    ReadField<ErrorPolicy_Igno>(dest.seed2, "seed2", db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure :: Convert<MTexPoly> (
+    MTexPoly& dest,
+    const FileDatabase& db
+    ) const
+{
+
+    {
+        std::shared_ptr<Image> tpage;
+        ReadFieldPtr<ErrorPolicy_Igno>(tpage,"*tpage",db);
+        dest.tpage = tpage.get();
+    }
+    ReadField<ErrorPolicy_Igno>(dest.flag,"flag",db);
+    ReadField<ErrorPolicy_Igno>(dest.transp,"transp",db);
+    ReadField<ErrorPolicy_Igno>(dest.mode,"mode",db);
+    ReadField<ErrorPolicy_Igno>(dest.tile,"tile",db);
+    ReadField<ErrorPolicy_Igno>(dest.pad,"pad",db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure :: Convert<Mesh> (
+    Mesh& dest,
+    const FileDatabase& db
+    ) const
+{
+
+    ReadField<ErrorPolicy_Fail>(dest.id,"id",db);
+    ReadField<ErrorPolicy_Fail>(dest.totface,"totface",db);
+    ReadField<ErrorPolicy_Fail>(dest.totedge,"totedge",db);
+    ReadField<ErrorPolicy_Fail>(dest.totvert,"totvert",db);
+    ReadField<ErrorPolicy_Igno>(dest.totloop,"totloop",db);
+    ReadField<ErrorPolicy_Igno>(dest.totpoly,"totpoly",db);
+    ReadField<ErrorPolicy_Igno>(dest.subdiv,"subdiv",db);
+    ReadField<ErrorPolicy_Igno>(dest.subdivr,"subdivr",db);
+    ReadField<ErrorPolicy_Igno>(dest.subsurftype,"subsurftype",db);
+    ReadField<ErrorPolicy_Igno>(dest.smoothresh,"smoothresh",db);
+    ReadFieldPtr<ErrorPolicy_Fail>(dest.mface,"*mface",db);
+    ReadFieldPtr<ErrorPolicy_Igno>(dest.mtface,"*mtface",db);
+    ReadFieldPtr<ErrorPolicy_Igno>(dest.tface,"*tface",db);
+    ReadFieldPtr<ErrorPolicy_Fail>(dest.mvert,"*mvert",db);
+    ReadFieldPtr<ErrorPolicy_Warn>(dest.medge,"*medge",db);
+    ReadFieldPtr<ErrorPolicy_Igno>(dest.mloop,"*mloop",db);
+    ReadFieldPtr<ErrorPolicy_Igno>(dest.mloopuv,"*mloopuv",db);
+    ReadFieldPtr<ErrorPolicy_Igno>(dest.mloopcol,"*mloopcol",db);
+    ReadFieldPtr<ErrorPolicy_Igno>(dest.mpoly,"*mpoly",db);
+    ReadFieldPtr<ErrorPolicy_Igno>(dest.mtpoly,"*mtpoly",db);
+    ReadFieldPtr<ErrorPolicy_Igno>(dest.dvert,"*dvert",db);
+    ReadFieldPtr<ErrorPolicy_Igno>(dest.mcol,"*mcol",db);
+    ReadFieldPtr<ErrorPolicy_Fail>(dest.mat,"**mat",db);
+
+    ReadField<ErrorPolicy_Igno>(dest.vdata, "vdata", db);
+    ReadField<ErrorPolicy_Igno>(dest.edata, "edata", db);
+    ReadField<ErrorPolicy_Igno>(dest.fdata, "fdata", db);
+    ReadField<ErrorPolicy_Igno>(dest.pdata, "pdata", db);
+    ReadField<ErrorPolicy_Warn>(dest.ldata, "ldata", db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure :: Convert<MDeformVert> (
+    MDeformVert& dest,
+    const FileDatabase& db
+    ) const
+{
+
+    ReadFieldPtr<ErrorPolicy_Warn>(dest.dw,"*dw",db);
+    ReadField<ErrorPolicy_Igno>(dest.totweight,"totweight",db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure :: Convert<World> (
+    World& dest,
+    const FileDatabase& db
+    ) const
+{
+
+    ReadField<ErrorPolicy_Fail>(dest.id,"id",db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure :: Convert<MLoopCol> (
+    MLoopCol& dest,
+    const FileDatabase& db
+    ) const
+{
+
+    ReadField<ErrorPolicy_Igno>(dest.r,"r",db);
+    ReadField<ErrorPolicy_Igno>(dest.g,"g",db);
+    ReadField<ErrorPolicy_Igno>(dest.b,"b",db);
+    ReadField<ErrorPolicy_Igno>(dest.a,"a",db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure :: Convert<MVert> (
+    MVert& dest,
+    const FileDatabase& db
+    ) const
+{
+
+    ReadFieldArray<ErrorPolicy_Fail>(dest.co,"co",db);
+    ReadFieldArray<ErrorPolicy_Fail>(dest.no,"no",db);
+    ReadField<ErrorPolicy_Igno>(dest.flag,"flag",db);
+    //ReadField<ErrorPolicy_Warn>(dest.mat_nr,"mat_nr",db);
+    ReadField<ErrorPolicy_Igno>(dest.bweight,"bweight",db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure :: Convert<MEdge> (
+    MEdge& dest,
+    const FileDatabase& db
+    ) const
+{
+
+    ReadField<ErrorPolicy_Fail>(dest.v1,"v1",db);
+    ReadField<ErrorPolicy_Fail>(dest.v2,"v2",db);
+    ReadField<ErrorPolicy_Igno>(dest.crease,"crease",db);
+    ReadField<ErrorPolicy_Igno>(dest.bweight,"bweight",db);
+    ReadField<ErrorPolicy_Igno>(dest.flag,"flag",db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure :: Convert<MLoopUV> (
+    MLoopUV& dest,
+    const FileDatabase& db
+    ) const
+{
+
+    ReadFieldArray<ErrorPolicy_Igno>(dest.uv,"uv",db);
+    ReadField<ErrorPolicy_Igno>(dest.flag,"flag",db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure :: Convert<GroupObject> (
+    GroupObject& dest,
+    const FileDatabase& db
+    ) const
+{
+
+    ReadFieldPtr<ErrorPolicy_Fail>(dest.prev,"*prev",db);
+    ReadFieldPtr<ErrorPolicy_Fail>(dest.next,"*next",db);
+    ReadFieldPtr<ErrorPolicy_Igno>(dest.ob,"*ob",db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure :: Convert<ListBase> (
+    ListBase& dest,
+    const FileDatabase& db
+    ) const
+{
+
+    ReadFieldPtr<ErrorPolicy_Igno>(dest.first,"*first",db);
+    ReadFieldPtr<ErrorPolicy_Igno>(dest.last,"*last",db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure :: Convert<MLoop> (
+    MLoop& dest,
+    const FileDatabase& db
+    ) const
+{
+
+    ReadField<ErrorPolicy_Igno>(dest.v,"v",db);
+    ReadField<ErrorPolicy_Igno>(dest.e,"e",db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure :: Convert<ModifierData> (
+    ModifierData& dest,
+    const FileDatabase& db
+    ) const
+{
+
+    ReadFieldPtr<ErrorPolicy_Warn>(dest.next,"*next",db);
+    ReadFieldPtr<ErrorPolicy_Warn>(dest.prev,"*prev",db);
+    ReadField<ErrorPolicy_Igno>(dest.type,"type",db);
+    ReadField<ErrorPolicy_Igno>(dest.mode,"mode",db);
+    ReadFieldArray<ErrorPolicy_Igno>(dest.name,"name",db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure :: Convert<ID> (
+    ID& dest,
+    const FileDatabase& db
+    ) const
+{
+
+    ReadFieldArray<ErrorPolicy_Warn>(dest.name,"name",db);
+    ReadField<ErrorPolicy_Igno>(dest.flag,"flag",db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure :: Convert<MCol> (
+    MCol& dest,
+    const FileDatabase& db
+    ) const
+{
+
+    ReadField<ErrorPolicy_Fail>(dest.r,"r",db);
+    ReadField<ErrorPolicy_Fail>(dest.g,"g",db);
+    ReadField<ErrorPolicy_Fail>(dest.b,"b",db);
+    ReadField<ErrorPolicy_Fail>(dest.a,"a",db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure :: Convert<MPoly> (
+    MPoly& dest,
+    const FileDatabase& db
+    ) const
+{
+
+    ReadField<ErrorPolicy_Igno>(dest.loopstart,"loopstart",db);
+    ReadField<ErrorPolicy_Igno>(dest.totloop,"totloop",db);
+    ReadField<ErrorPolicy_Igno>(dest.mat_nr,"mat_nr",db);
+    ReadField<ErrorPolicy_Igno>(dest.flag,"flag",db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure :: Convert<Scene> (
+    Scene& dest,
+    const FileDatabase& db
+    ) const
+{
+
+    ReadField<ErrorPolicy_Fail>(dest.id,"id",db);
+    ReadFieldPtr<ErrorPolicy_Warn>(dest.camera,"*camera",db);
+    ReadFieldPtr<ErrorPolicy_Warn>(dest.world,"*world",db);
+    ReadFieldPtr<ErrorPolicy_Warn>(dest.basact,"*basact",db);
+    ReadField<ErrorPolicy_Igno>(dest.base,"base",db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure :: Convert<Library> (
+    Library& dest,
+    const FileDatabase& db
+    ) const
+{
+
+    ReadField<ErrorPolicy_Fail>(dest.id,"id",db);
+    ReadFieldArray<ErrorPolicy_Warn>(dest.name,"name",db);
+    ReadFieldArray<ErrorPolicy_Fail>(dest.filename,"filename",db);
+    ReadFieldPtr<ErrorPolicy_Warn>(dest.parent,"*parent",db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure :: Convert<Tex> (
+    Tex& dest,
+    const FileDatabase& db
+    ) const
+{
+    short temp_short = 0;
+    ReadField<ErrorPolicy_Igno>(temp_short,"imaflag",db);
+    dest.imaflag = static_cast<Assimp::Blender::Tex::ImageFlags>(temp_short);
+    int temp = 0;
+    ReadField<ErrorPolicy_Fail>(temp,"type",db);
+    dest.type = static_cast<Assimp::Blender::Tex::Type>(temp);
+    ReadFieldPtr<ErrorPolicy_Warn>(dest.ima,"*ima",db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure :: Convert<Camera> (
+    Camera& dest,
+    const FileDatabase& db
+    ) const
+{
+
+    ReadField<ErrorPolicy_Fail>(dest.id,"id",db);
+    int temp = 0;
+    ReadField<ErrorPolicy_Warn>(temp,"type",db);
+    dest.type = static_cast<Assimp::Blender::Camera::Type>(temp);
+    ReadField<ErrorPolicy_Warn>(temp,"flag",db);
+    dest.flag = static_cast<Assimp::Blender::Camera::Type>(temp);
+    ReadField<ErrorPolicy_Warn>(dest.lens,"lens",db);
+    ReadField<ErrorPolicy_Warn>(dest.sensor_x,"sensor_x",db);
+    ReadField<ErrorPolicy_Igno>(dest.clipsta,"clipsta",db);
+    ReadField<ErrorPolicy_Igno>(dest.clipend,"clipend",db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure :: Convert<MirrorModifierData> (
+    MirrorModifierData& dest,
+    const FileDatabase& db
+    ) const
+{
+
+    ReadField<ErrorPolicy_Fail>(dest.modifier,"modifier",db);
+    ReadField<ErrorPolicy_Igno>(dest.axis,"axis",db);
+    ReadField<ErrorPolicy_Igno>(dest.flag,"flag",db);
+    ReadField<ErrorPolicy_Igno>(dest.tolerance,"tolerance",db);
+    ReadFieldPtr<ErrorPolicy_Igno>(dest.mirror_ob,"*mirror_ob",db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure :: Convert<Image> (
+    Image& dest,
+    const FileDatabase& db
+    ) const
+{
+
+    ReadField<ErrorPolicy_Fail>(dest.id,"id",db);
+    ReadFieldArray<ErrorPolicy_Warn>(dest.name,"name",db);
+    ReadField<ErrorPolicy_Igno>(dest.ok,"ok",db);
+    ReadField<ErrorPolicy_Igno>(dest.flag,"flag",db);
+    ReadField<ErrorPolicy_Igno>(dest.source,"source",db);
+    ReadField<ErrorPolicy_Igno>(dest.type,"type",db);
+    ReadField<ErrorPolicy_Igno>(dest.pad,"pad",db);
+    ReadField<ErrorPolicy_Igno>(dest.pad1,"pad1",db);
+    ReadField<ErrorPolicy_Igno>(dest.lastframe,"lastframe",db);
+    ReadField<ErrorPolicy_Igno>(dest.tpageflag,"tpageflag",db);
+    ReadField<ErrorPolicy_Igno>(dest.totbind,"totbind",db);
+    ReadField<ErrorPolicy_Igno>(dest.xrep,"xrep",db);
+    ReadField<ErrorPolicy_Igno>(dest.yrep,"yrep",db);
+    ReadField<ErrorPolicy_Igno>(dest.twsta,"twsta",db);
+    ReadField<ErrorPolicy_Igno>(dest.twend,"twend",db);
+    ReadFieldPtr<ErrorPolicy_Igno>(dest.packedfile,"*packedfile",db);
+    ReadField<ErrorPolicy_Igno>(dest.lastupdate,"lastupdate",db);
+    ReadField<ErrorPolicy_Igno>(dest.lastused,"lastused",db);
+    ReadField<ErrorPolicy_Igno>(dest.animspeed,"animspeed",db);
+    ReadField<ErrorPolicy_Igno>(dest.gen_x,"gen_x",db);
+    ReadField<ErrorPolicy_Igno>(dest.gen_y,"gen_y",db);
+    ReadField<ErrorPolicy_Igno>(dest.gen_type,"gen_type",db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure::Convert<CustomData>(
+    CustomData& dest,
+    const FileDatabase& db
+    ) const
+{
+    ReadFieldArray<ErrorPolicy_Warn>(dest.typemap, "typemap", db);
+    ReadField<ErrorPolicy_Warn>(dest.totlayer, "totlayer", db);
+    ReadField<ErrorPolicy_Warn>(dest.maxlayer, "maxlayer", db);
+    ReadField<ErrorPolicy_Warn>(dest.totsize, "totsize", db);
+    ReadFieldPtrVector<ErrorPolicy_Warn>(dest.layers, "*layers", db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+template <> void Structure::Convert<CustomDataLayer>(
+    CustomDataLayer& dest,
+    const FileDatabase& db
+    ) const
+{
+    ReadField<ErrorPolicy_Fail>(dest.type, "type", db);
+    ReadField<ErrorPolicy_Fail>(dest.offset, "offset", db);
+    ReadField<ErrorPolicy_Fail>(dest.flag, "flag", db);
+    ReadField<ErrorPolicy_Fail>(dest.active, "active", db);
+    ReadField<ErrorPolicy_Fail>(dest.active_rnd, "active_rnd", db);
+    ReadField<ErrorPolicy_Fail>(dest.active_clone, "active_clone", db);
+    ReadField<ErrorPolicy_Fail>(dest.active_mask, "active_mask", db);
+    ReadField<ErrorPolicy_Fail>(dest.uid, "uid", db);
+    ReadFieldArray<ErrorPolicy_Warn>(dest.name, "name", db);
+    ReadCustomDataPtr<ErrorPolicy_Fail>(dest.data, dest.type, "*data", db);
+
+    db.reader->IncPtr(size);
+}
+
+//--------------------------------------------------------------------------------
+void DNA::RegisterConverters() {
+
+    converters["Object"] = DNA::FactoryPair( &Structure::Allocate<Object>, &Structure::Convert<Object> );
+    converters["Group"] = DNA::FactoryPair( &Structure::Allocate<Group>, &Structure::Convert<Group> );
+    converters["MTex"] = DNA::FactoryPair( &Structure::Allocate<MTex>, &Structure::Convert<MTex> );
+    converters["TFace"] = DNA::FactoryPair( &Structure::Allocate<TFace>, &Structure::Convert<TFace> );
+    converters["SubsurfModifierData"] = DNA::FactoryPair( &Structure::Allocate<SubsurfModifierData>, &Structure::Convert<SubsurfModifierData> );
+    converters["MFace"] = DNA::FactoryPair( &Structure::Allocate<MFace>, &Structure::Convert<MFace> );
+    converters["Lamp"] = DNA::FactoryPair( &Structure::Allocate<Lamp>, &Structure::Convert<Lamp> );
+    converters["MDeformWeight"] = DNA::FactoryPair( &Structure::Allocate<MDeformWeight>, &Structure::Convert<MDeformWeight> );
+    converters["PackedFile"] = DNA::FactoryPair( &Structure::Allocate<PackedFile>, &Structure::Convert<PackedFile> );
+    converters["Base"] = DNA::FactoryPair( &Structure::Allocate<Base>, &Structure::Convert<Base> );
+    converters["MTFace"] = DNA::FactoryPair( &Structure::Allocate<MTFace>, &Structure::Convert<MTFace> );
+    converters["Material"] = DNA::FactoryPair( &Structure::Allocate<Material>, &Structure::Convert<Material> );
+    converters["MTexPoly"] = DNA::FactoryPair( &Structure::Allocate<MTexPoly>, &Structure::Convert<MTexPoly> );
+    converters["Mesh"] = DNA::FactoryPair( &Structure::Allocate<Mesh>, &Structure::Convert<Mesh> );
+    converters["MDeformVert"] = DNA::FactoryPair( &Structure::Allocate<MDeformVert>, &Structure::Convert<MDeformVert> );
+    converters["World"] = DNA::FactoryPair( &Structure::Allocate<World>, &Structure::Convert<World> );
+    converters["MLoopCol"] = DNA::FactoryPair( &Structure::Allocate<MLoopCol>, &Structure::Convert<MLoopCol> );
+    converters["MVert"] = DNA::FactoryPair( &Structure::Allocate<MVert>, &Structure::Convert<MVert> );
+    converters["MEdge"] = DNA::FactoryPair( &Structure::Allocate<MEdge>, &Structure::Convert<MEdge> );
+    converters["MLoopUV"] = DNA::FactoryPair( &Structure::Allocate<MLoopUV>, &Structure::Convert<MLoopUV> );
+    converters["GroupObject"] = DNA::FactoryPair( &Structure::Allocate<GroupObject>, &Structure::Convert<GroupObject> );
+    converters["ListBase"] = DNA::FactoryPair( &Structure::Allocate<ListBase>, &Structure::Convert<ListBase> );
+    converters["MLoop"] = DNA::FactoryPair( &Structure::Allocate<MLoop>, &Structure::Convert<MLoop> );
+    converters["ModifierData"] = DNA::FactoryPair( &Structure::Allocate<ModifierData>, &Structure::Convert<ModifierData> );
+    converters["ID"] = DNA::FactoryPair( &Structure::Allocate<ID>, &Structure::Convert<ID> );
+    converters["MCol"] = DNA::FactoryPair( &Structure::Allocate<MCol>, &Structure::Convert<MCol> );
+    converters["MPoly"] = DNA::FactoryPair( &Structure::Allocate<MPoly>, &Structure::Convert<MPoly> );
+    converters["Scene"] = DNA::FactoryPair( &Structure::Allocate<Scene>, &Structure::Convert<Scene> );
+    converters["Library"] = DNA::FactoryPair( &Structure::Allocate<Library>, &Structure::Convert<Library> );
+    converters["Tex"] = DNA::FactoryPair( &Structure::Allocate<Tex>, &Structure::Convert<Tex> );
+    converters["Camera"] = DNA::FactoryPair( &Structure::Allocate<Camera>, &Structure::Convert<Camera> );
+    converters["MirrorModifierData"] = DNA::FactoryPair( &Structure::Allocate<MirrorModifierData>, &Structure::Convert<MirrorModifierData> );
+    converters["Image"] = DNA::FactoryPair( &Structure::Allocate<Image>, &Structure::Convert<Image> );
+    converters["CustomData"] = DNA::FactoryPair(&Structure::Allocate<CustomData>, &Structure::Convert<CustomData>);
+    converters["CustomDataLayer"] = DNA::FactoryPair(&Structure::Allocate<CustomDataLayer>, &Structure::Convert<CustomDataLayer>);
+}
+
+#endif // ASSIMP_BUILD_NO_BLEND_IMPORTER
+

+ 979 - 0
Engine/lib/assimp/code/BlenderScene.h

@@ -0,0 +1,979 @@
+/*
+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  BlenderScene.h
+ *  @brief Intermediate representation of a BLEND scene.
+ */
+#ifndef INCLUDED_AI_BLEND_SCENE_H
+#define INCLUDED_AI_BLEND_SCENE_H
+
+#include "BlenderDNA.h"
+
+namespace Assimp    {
+namespace Blender {
+
+// Minor parts of this file are extracts from blender data structures,
+// declared in the ./source/blender/makesdna directory.
+// Stuff that is not used by Assimp is commented.
+
+
+// NOTE
+// this file serves as input data to the `./scripts/genblenddna.py`
+// script. This script generates the actual binding code to read a
+// blender file with a possibly different DNA into our structures.
+// Only `struct` declarations are considered and the following
+// rules must be obeyed in order for the script to work properly:
+//
+// * C++ style comments only
+//
+// * Structures may include the primitive types char, int, short,
+//   float, double. Signed specifiers are not allowed on
+//   integers. Enum types are allowed, but they must have been
+//   defined in this header.
+//
+// * Structures may aggregate other structures, unless not defined
+//   in this header.
+//
+// * Pointers to other structures or primitive types are allowed.
+//   No references or double pointers or arrays of pointers.
+//   A pointer to a T is normally written as std::shared_ptr, while a
+//   pointer to an array of elements is written as boost::
+//   shared_array. To avoid cyclic pointers, use raw pointers in
+//   one direction.
+//
+// * Arrays can have maximally two-dimensions. Any non-pointer
+//   type can form them.
+//
+// * Multiple fields can be declare in a single line (i.e `int a,b;`)
+//   provided they are neither pointers nor arrays.
+//
+// * One of WARN, FAIL can be appended to the declaration (
+//   prior to the semicolon to specify the error handling policy if
+//   this field is missing in the input DNA). If none of those
+//   is specified the default policy is to substitute a default
+//   value for the field.
+//
+
+// warn if field is missing, substitute default value
+#ifdef WARN
+#  undef WARN
+#endif
+#define WARN 
+
+// fail the import if the field does not exist
+#ifdef FAIL
+#  undef FAIL
+#endif
+#define FAIL 
+
+struct Object;
+struct MTex;
+struct Image;
+
+#include <memory>
+
+#define AI_BLEND_MESH_MAX_VERTS 2000000000L
+
+static const size_t MaxNameLen = 1024;
+
+// -------------------------------------------------------------------------------
+struct ID : ElemBase {
+    char name[ MaxNameLen ] WARN;
+    short flag;
+};
+
+// -------------------------------------------------------------------------------
+struct ListBase : ElemBase {
+    std::shared_ptr<ElemBase> first;
+    std::shared_ptr<ElemBase> last;
+};
+
+
+// -------------------------------------------------------------------------------
+struct PackedFile : ElemBase {
+     int size WARN;
+     int seek WARN;
+     std::shared_ptr< FileOffset > data WARN;
+};
+
+// -------------------------------------------------------------------------------
+struct GroupObject : ElemBase {
+    std::shared_ptr<GroupObject> prev,next FAIL;
+    std::shared_ptr<Object> ob;
+};
+
+// -------------------------------------------------------------------------------
+struct Group : ElemBase {
+    ID id FAIL;
+    int layer;
+
+    std::shared_ptr<GroupObject> gobject;
+};
+
+// -------------------------------------------------------------------------------
+struct World : ElemBase {
+    ID id FAIL;
+};
+
+// -------------------------------------------------------------------------------
+struct MVert : ElemBase {
+    float co[3] FAIL;
+    float no[3] FAIL;       // readed as short and divided through / 32767.f
+    char flag;
+    int mat_nr WARN;
+    int bweight;
+
+    MVert() : ElemBase()
+        , flag(0)
+        , mat_nr(0)
+        , bweight(0)
+    {}
+};
+
+// -------------------------------------------------------------------------------
+struct MEdge : ElemBase {
+      int v1, v2 FAIL;
+      char crease, bweight;
+      short flag;
+};
+
+// -------------------------------------------------------------------------------
+struct MLoop : ElemBase {
+    int v, e;
+};
+
+// -------------------------------------------------------------------------------
+struct MLoopUV : ElemBase {
+    float uv[2];
+    int flag;
+};
+
+// -------------------------------------------------------------------------------
+// Note that red and blue are not swapped, as with MCol
+struct MLoopCol : ElemBase {
+	unsigned char r, g, b, a;
+};
+
+// -------------------------------------------------------------------------------
+struct MPoly : ElemBase {
+    int loopstart;
+    int totloop;
+    short mat_nr;
+    char flag;
+};
+
+// -------------------------------------------------------------------------------
+struct MTexPoly : ElemBase {
+    Image* tpage;
+    char flag, transp;
+    short mode, tile, pad;
+};
+
+// -------------------------------------------------------------------------------
+struct MCol : ElemBase {
+    char r,g,b,a FAIL;
+};
+
+// -------------------------------------------------------------------------------
+struct MFace : ElemBase {
+    int v1,v2,v3,v4 FAIL;
+    int mat_nr FAIL;
+    char flag;
+};
+
+// -------------------------------------------------------------------------------
+struct TFace : ElemBase {
+    float uv[4][2] FAIL;
+    int col[4] FAIL;
+    char flag;
+    short mode;
+    short tile;
+    short unwrap;
+};
+
+// -------------------------------------------------------------------------------
+struct MTFace : ElemBase {
+	MTFace()
+	: flag(0)
+	, mode(0)
+	, tile(0)
+	, unwrap(0)
+	{
+	}
+
+    float uv[4][2] FAIL;
+    char flag;
+    short mode;
+    short tile;
+    short unwrap;
+
+    // std::shared_ptr<Image> tpage;
+};
+
+// -------------------------------------------------------------------------------
+struct MDeformWeight : ElemBase  {
+      int    def_nr FAIL;
+      float  weight FAIL;
+};
+
+// -------------------------------------------------------------------------------
+struct MDeformVert : ElemBase  {
+    vector<MDeformWeight> dw WARN;
+    int totweight;
+};
+
+// -------------------------------------------------------------------------------
+#define MA_RAYMIRROR    0x40000
+#define MA_TRANSPARENCY 0x10000
+#define MA_RAYTRANSP    0x20000
+#define MA_ZTRANSP      0x00040
+
+struct Material : ElemBase {
+    ID id FAIL;
+
+    float r,g,b WARN;
+    float specr,specg,specb WARN;
+    short har;
+    float ambr,ambg,ambb WARN;
+    float mirr,mirg,mirb;
+    float emit WARN;
+    float ray_mirror;
+    float alpha WARN;
+    float ref;
+    float translucency;
+    int mode;
+    float roughness;
+    float darkness;
+    float refrac;
+
+    float amb;
+    float ang;
+    float spectra;
+    float spec;
+    float zoffs;
+    float add;
+    float fresnel_mir;
+    float fresnel_mir_i;
+    float fresnel_tra;
+    float fresnel_tra_i;
+    float filter;
+    float tx_limit;
+    float tx_falloff;
+    float gloss_mir;
+    float gloss_tra;
+    float adapt_thresh_mir;
+    float adapt_thresh_tra;
+    float aniso_gloss_mir;
+    float dist_mir;
+    float hasize;
+    float flaresize;
+    float subsize;
+    float flareboost;
+    float strand_sta;
+    float strand_end;
+    float strand_ease;
+    float strand_surfnor;
+    float strand_min;
+    float strand_widthfade;
+    float sbias;
+    float lbias;
+    float shad_alpha;
+    float param;
+    float rms;
+    float rampfac_col;
+    float rampfac_spec;
+    float friction;
+    float fh;
+    float reflect;
+    float fhdist;
+    float xyfrict;
+    float sss_radius;
+    float sss_col;
+    float sss_error;
+    float sss_scale;
+    float sss_ior;
+    float sss_colfac;
+    float sss_texfac;
+    float sss_front;
+    float sss_back;
+
+    short material_type;
+    short flag;
+    short ray_depth;
+    short ray_depth_tra;
+    short samp_gloss_mir;
+    short samp_gloss_tra;
+    short fadeto_mir;
+    short shade_flag;
+    short flarec;
+    short starc;
+    short linec;
+    short ringc;
+    short pr_lamp;
+    short pr_texture;
+    short ml_flag;
+    short texco;
+    short mapto;
+    short ramp_show;
+    short pad3;
+    short dynamode;
+    short pad2;
+    short sss_flag;
+    short sss_preset;
+    short shadowonly_flag;
+    short index;
+    short vcol_alpha;
+    short pad4;
+
+    char seed1;
+    char seed2;
+
+    std::shared_ptr<Group> group;
+
+    short diff_shader WARN;
+    short spec_shader WARN;
+
+    std::shared_ptr<MTex> mtex[18];
+};
+
+/*
+CustomDataLayer 104
+
+    int type 0 4
+    int offset 4 4
+    int flag 8 4
+    int active 12 4
+    int active_rnd 16 4
+    int active_clone 20 4
+    int active_mask 24 4
+    int uid 28 4
+    char name 32 64
+    void *data 96 8
+*/
+struct CustomDataLayer : ElemBase {
+    int type;
+    int offset;
+    int flag;
+    int active;
+    int active_rnd;
+    int active_clone;
+    int active_mask;
+    int uid;
+    char name[64];
+    std::shared_ptr<ElemBase> data;     // must be converted to real type according type member
+
+    CustomDataLayer()
+        : ElemBase()
+        , type(0)
+        , offset(0)
+        , flag(0)
+        , active(0)
+        , active_rnd(0)
+        , active_clone(0)
+        , active_mask(0)
+        , uid(0)
+        , data(nullptr)
+    {
+        memset(name, 0, sizeof name);
+    }
+};
+
+/*
+CustomData 208
+
+    CustomDataLayer *layers 0 8
+    int typemap 8 168
+    int pad_i1 176 4
+    int totlayer 180 4
+    int maxlayer 184 4
+    int totsize 188 4
+    BLI_mempool *pool 192 8
+    CustomDataExternal *external 200 8
+*/
+struct CustomData : ElemBase {
+    vector<std::shared_ptr<struct CustomDataLayer> > layers;
+    int typemap[42];    // CD_NUMTYPES
+    int totlayer;
+    int maxlayer;
+    int totsize;
+    /*
+    std::shared_ptr<BLI_mempool> pool;
+    std::shared_ptr<CustomDataExternal> external;
+    */
+};
+
+// -------------------------------------------------------------------------------
+struct Mesh : ElemBase {
+    ID id FAIL;
+
+    int totface FAIL;
+    int totedge FAIL;
+    int totvert FAIL;
+    int totloop;
+    int totpoly;
+
+    short subdiv;
+    short subdivr;
+    short subsurftype;
+    short smoothresh;
+
+    vector<MFace> mface FAIL;
+    vector<MTFace> mtface;
+    vector<TFace> tface;
+    vector<MVert> mvert FAIL;
+    vector<MEdge> medge WARN;
+    vector<MLoop> mloop;
+    vector<MLoopUV> mloopuv;
+    vector<MLoopCol> mloopcol;
+    vector<MPoly> mpoly;
+    vector<MTexPoly> mtpoly;
+    vector<MDeformVert> dvert;
+    vector<MCol> mcol;
+
+    vector< std::shared_ptr<Material> > mat FAIL;
+
+    struct CustomData vdata;
+    struct CustomData edata;
+    struct CustomData fdata;
+    struct CustomData pdata;
+    struct CustomData ldata;
+};
+
+// -------------------------------------------------------------------------------
+struct Library : ElemBase {
+    ID id FAIL;
+
+    char name[240] WARN;
+    char filename[240] FAIL;
+    std::shared_ptr<Library> parent WARN;
+};
+
+// -------------------------------------------------------------------------------
+struct Camera : ElemBase {
+    enum Type {
+          Type_PERSP    =   0
+         ,Type_ORTHO    =   1
+    };
+
+    ID id FAIL;
+
+    Type type,flag WARN;
+    float lens WARN;
+    float sensor_x WARN;
+    float clipsta, clipend;
+};
+
+
+// -------------------------------------------------------------------------------
+struct Lamp : ElemBase {
+
+    enum FalloffType {
+         FalloffType_Constant   = 0x0
+        ,FalloffType_InvLinear  = 0x1
+        ,FalloffType_InvSquare  = 0x2
+        //,FalloffType_Curve    = 0x3
+        //,FalloffType_Sliders  = 0x4
+    };
+
+    enum Type {
+         Type_Local         = 0x0
+        ,Type_Sun           = 0x1
+        ,Type_Spot          = 0x2
+        ,Type_Hemi          = 0x3
+        ,Type_Area          = 0x4
+        //,Type_YFPhoton    = 0x5
+    };
+
+      ID id FAIL;
+      //AnimData *adt;
+
+      Type type FAIL;
+      short flags;
+
+      //int mode;
+
+      short colormodel, totex;
+      float r,g,b,k WARN;
+      //float shdwr, shdwg, shdwb;
+
+      float energy, dist, spotsize, spotblend;
+      //float haint;
+
+      float att1, att2;
+      //struct CurveMapping *curfalloff;
+      FalloffType falloff_type;
+
+      //float clipsta, clipend, shadspotsize;
+      //float bias, soft, compressthresh;
+      //short bufsize, samp, buffers, filtertype;
+      //char bufflag, buftype;
+
+      //short ray_samp, ray_sampy, ray_sampz;
+      //short ray_samp_type;
+      short area_shape;
+      float area_size, area_sizey, area_sizez;
+      //float adapt_thresh;
+      //short ray_samp_method;
+
+      //short texact, shadhalostep;
+
+      //short sun_effect_type;
+      //short skyblendtype;
+      //float horizon_brightness;
+      //float spread;
+      float sun_brightness;
+      //float sun_size;
+      //float backscattered_light;
+      //float sun_intensity;
+      //float atm_turbidity;
+      //float atm_inscattering_factor;
+      //float atm_extinction_factor;
+      //float atm_distance_factor;
+      //float skyblendfac;
+      //float sky_exposure;
+      //short sky_colorspace;
+
+      // int YF_numphotons, YF_numsearch;
+      // short YF_phdepth, YF_useqmc, YF_bufsize, YF_pad;
+      // float YF_causticblur, YF_ltradius;
+
+      // float YF_glowint, YF_glowofs;
+      // short YF_glowtype, YF_pad2;
+
+      //struct Ipo *ipo;
+      //struct MTex *mtex[18];
+      // short pr_texture;
+
+      //struct PreviewImage *preview;
+};
+
+// -------------------------------------------------------------------------------
+struct ModifierData : ElemBase  {
+    enum ModifierType {
+      eModifierType_None = 0,
+      eModifierType_Subsurf,
+      eModifierType_Lattice,
+      eModifierType_Curve,
+      eModifierType_Build,
+      eModifierType_Mirror,
+      eModifierType_Decimate,
+      eModifierType_Wave,
+      eModifierType_Armature,
+      eModifierType_Hook,
+      eModifierType_Softbody,
+      eModifierType_Boolean,
+      eModifierType_Array,
+      eModifierType_EdgeSplit,
+      eModifierType_Displace,
+      eModifierType_UVProject,
+      eModifierType_Smooth,
+      eModifierType_Cast,
+      eModifierType_MeshDeform,
+      eModifierType_ParticleSystem,
+      eModifierType_ParticleInstance,
+      eModifierType_Explode,
+      eModifierType_Cloth,
+      eModifierType_Collision,
+      eModifierType_Bevel,
+      eModifierType_Shrinkwrap,
+      eModifierType_Fluidsim,
+      eModifierType_Mask,
+      eModifierType_SimpleDeform,
+      eModifierType_Multires,
+      eModifierType_Surface,
+      eModifierType_Smoke,
+      eModifierType_ShapeKey
+    };
+
+    std::shared_ptr<ElemBase> next WARN;
+    std::shared_ptr<ElemBase> prev WARN;
+
+    int type, mode;
+    char name[32];
+};
+
+// -------------------------------------------------------------------------------
+struct SubsurfModifierData : ElemBase  {
+
+    enum Type {
+
+        TYPE_CatmullClarke = 0x0,
+        TYPE_Simple = 0x1
+    };
+
+    enum Flags {
+        // some omitted
+        FLAGS_SubsurfUV     =1<<3
+    };
+
+    ModifierData modifier FAIL;
+    short subdivType WARN;
+    short levels FAIL;
+    short renderLevels ;
+    short flags;
+};
+
+// -------------------------------------------------------------------------------
+struct MirrorModifierData : ElemBase {
+
+    enum Flags {
+        Flags_CLIPPING      =1<<0,
+        Flags_MIRROR_U      =1<<1,
+        Flags_MIRROR_V      =1<<2,
+        Flags_AXIS_X        =1<<3,
+        Flags_AXIS_Y        =1<<4,
+        Flags_AXIS_Z        =1<<5,
+        Flags_VGROUP        =1<<6
+    };
+
+    ModifierData modifier FAIL;
+
+    short axis, flag;
+    float tolerance;
+    std::shared_ptr<Object> mirror_ob;
+};
+
+// -------------------------------------------------------------------------------
+struct Object : ElemBase  {
+    ID id FAIL;
+
+    enum Type {
+         Type_EMPTY     =   0
+        ,Type_MESH      =   1
+        ,Type_CURVE     =   2
+        ,Type_SURF      =   3
+        ,Type_FONT      =   4
+        ,Type_MBALL     =   5
+
+        ,Type_LAMP      =   10
+        ,Type_CAMERA    =   11
+
+        ,Type_WAVE      =   21
+        ,Type_LATTICE   =   22
+    };
+
+    Type type FAIL;
+    float obmat[4][4] WARN;
+    float parentinv[4][4] WARN;
+    char parsubstr[32] WARN;
+
+    Object* parent WARN;
+    std::shared_ptr<Object> track WARN;
+
+    std::shared_ptr<Object> proxy,proxy_from,proxy_group WARN;
+    std::shared_ptr<Group> dup_group WARN;
+    std::shared_ptr<ElemBase> data FAIL;
+
+    ListBase modifiers;
+
+    Object()
+    : ElemBase()
+    , type( Type_EMPTY )
+    , parent( nullptr )
+    , track()
+    , proxy()
+    , proxy_from()
+    , data() {
+        // empty
+    }
+};
+
+
+// -------------------------------------------------------------------------------
+struct Base : ElemBase {
+    Base* prev WARN;
+    std::shared_ptr<Base> next WARN;
+    std::shared_ptr<Object> object WARN;
+
+    Base() 
+    : ElemBase()
+    , prev( nullptr )
+    , next()
+    , object() {
+        // empty
+        // empty
+    }
+};
+
+// -------------------------------------------------------------------------------
+struct Scene : ElemBase {
+    ID id FAIL;
+
+    std::shared_ptr<Object> camera WARN;
+    std::shared_ptr<World> world WARN;
+    std::shared_ptr<Base> basact WARN;
+
+    ListBase base;
+
+    Scene()
+    : ElemBase()
+    , camera()
+    , world()
+    , basact() {
+        // empty
+    }
+};
+
+// -------------------------------------------------------------------------------
+struct Image : ElemBase {
+    ID id FAIL;
+
+    char name[240] WARN;
+
+    //struct anim *anim;
+
+    short ok, flag;
+    short source, type, pad, pad1;
+    int lastframe;
+
+    short tpageflag, totbind;
+    short xrep, yrep;
+    short twsta, twend;
+    //unsigned int bindcode;
+    //unsigned int *repbind;
+
+    std::shared_ptr<PackedFile> packedfile;
+    //struct PreviewImage * preview;
+
+    float lastupdate;
+    int lastused;
+    short animspeed;
+
+    short gen_x, gen_y, gen_type;
+    
+    Image()
+    : ElemBase() {
+        // empty
+    }
+};
+
+// -------------------------------------------------------------------------------
+struct Tex : ElemBase {
+
+    // actually, the only texture type we support is Type_IMAGE
+    enum Type {
+         Type_CLOUDS        = 1
+        ,Type_WOOD          = 2
+        ,Type_MARBLE        = 3
+        ,Type_MAGIC         = 4
+        ,Type_BLEND         = 5
+        ,Type_STUCCI        = 6
+        ,Type_NOISE         = 7
+        ,Type_IMAGE         = 8
+        ,Type_PLUGIN        = 9
+        ,Type_ENVMAP        = 10
+        ,Type_MUSGRAVE      = 11
+        ,Type_VORONOI       = 12
+        ,Type_DISTNOISE     = 13
+        ,Type_POINTDENSITY  = 14
+        ,Type_VOXELDATA     = 15
+    };
+
+    enum ImageFlags {
+         ImageFlags_INTERPOL         = 1
+        ,ImageFlags_USEALPHA         = 2
+        ,ImageFlags_MIPMAP           = 4
+        ,ImageFlags_IMAROT           = 16
+        ,ImageFlags_CALCALPHA        = 32
+        ,ImageFlags_NORMALMAP        = 2048
+        ,ImageFlags_GAUSS_MIP        = 4096
+        ,ImageFlags_FILTER_MIN       = 8192
+        ,ImageFlags_DERIVATIVEMAP   = 16384
+    };
+
+    ID id FAIL;
+    // AnimData *adt;
+
+    //float noisesize, turbul;
+    //float bright, contrast, rfac, gfac, bfac;
+    //float filtersize;
+
+    //float mg_H, mg_lacunarity, mg_octaves, mg_offset, mg_gain;
+    //float dist_amount, ns_outscale;
+
+    //float vn_w1;
+    //float vn_w2;
+    //float vn_w3;
+    //float vn_w4;
+    //float vn_mexp;
+    //short vn_distm, vn_coltype;
+
+    //short noisedepth, noisetype;
+    //short noisebasis, noisebasis2;
+
+    //short flag;
+    ImageFlags imaflag;
+    Type type FAIL;
+    //short stype;
+
+    //float cropxmin, cropymin, cropxmax, cropymax;
+    //int texfilter;
+    //int afmax;
+    //short xrepeat, yrepeat;
+    //short extend;
+
+    //short fie_ima;
+    //int len;
+    //int frames, offset, sfra;
+
+    //float checkerdist, nabla;
+    //float norfac;
+
+    //ImageUser iuser;
+
+    //bNodeTree *nodetree;
+    //Ipo *ipo;
+    std::shared_ptr<Image> ima WARN;
+    //PluginTex *plugin;
+    //ColorBand *coba;
+    //EnvMap *env;
+    //PreviewImage * preview;
+    //PointDensity *pd;
+    //VoxelData *vd;
+
+    //char use_nodes;
+
+    Tex()
+    : ElemBase()
+    , imaflag( ImageFlags_INTERPOL )
+    , type( Type_CLOUDS )
+    , ima() {
+        // empty
+    }
+};
+
+// -------------------------------------------------------------------------------
+struct MTex : ElemBase {
+
+    enum Projection {
+         Proj_N = 0
+        ,Proj_X = 1
+        ,Proj_Y = 2
+        ,Proj_Z = 3
+    };
+
+    enum Flag {
+         Flag_RGBTOINT      = 0x1
+        ,Flag_STENCIL       = 0x2
+        ,Flag_NEGATIVE      = 0x4
+        ,Flag_ALPHAMIX      = 0x8
+        ,Flag_VIEWSPACE     = 0x10
+    };
+
+    enum BlendType {
+         BlendType_BLEND            = 0
+        ,BlendType_MUL              = 1
+        ,BlendType_ADD              = 2
+        ,BlendType_SUB              = 3
+        ,BlendType_DIV              = 4
+        ,BlendType_DARK             = 5
+        ,BlendType_DIFF             = 6
+        ,BlendType_LIGHT            = 7
+        ,BlendType_SCREEN           = 8
+        ,BlendType_OVERLAY          = 9
+        ,BlendType_BLEND_HUE        = 10
+        ,BlendType_BLEND_SAT        = 11
+        ,BlendType_BLEND_VAL        = 12
+        ,BlendType_BLEND_COLOR      = 13
+    };
+
+    enum MapType {
+         MapType_COL         = 1
+        ,MapType_NORM        = 2
+        ,MapType_COLSPEC     = 4
+        ,MapType_COLMIR      = 8
+        ,MapType_REF         = 16
+        ,MapType_SPEC        = 32
+        ,MapType_EMIT        = 64
+        ,MapType_ALPHA       = 128
+        ,MapType_HAR         = 256
+        ,MapType_RAYMIRR     = 512
+        ,MapType_TRANSLU     = 1024
+        ,MapType_AMB         = 2048
+        ,MapType_DISPLACE    = 4096
+        ,MapType_WARP        = 8192
+    };
+
+    // short texco, maptoneg;
+    MapType mapto;
+
+    BlendType blendtype;
+    std::shared_ptr<Object> object;
+    std::shared_ptr<Tex> tex;
+    char uvname[32];
+
+    Projection projx,projy,projz;
+    char mapping;
+    float ofs[3], size[3], rot;
+
+    int texflag;
+    short colormodel, pmapto, pmaptoneg;
+    //short normapspace, which_output;
+    //char brush_map_mode;
+    float r,g,b,k WARN;
+    //float def_var, rt;
+
+    //float colfac, varfac;
+
+    float norfac;
+    //float dispfac, warpfac;
+    float colspecfac, mirrfac, alphafac;
+    float difffac, specfac, emitfac, hardfac;
+    //float raymirrfac, translfac, ambfac;
+    //float colemitfac, colreflfac, coltransfac;
+    //float densfac, scatterfac, reflfac;
+
+    //float timefac, lengthfac, clumpfac;
+    //float kinkfac, roughfac, padensfac;
+    //float lifefac, sizefac, ivelfac, pvelfac;
+    //float shadowfac;
+    //float zenupfac, zendownfac, blendfac;
+
+    MTex()
+    : ElemBase() {
+        // empty
+    }
+};
+
+}
+}
+#endif

+ 266 - 0
Engine/lib/assimp/code/BlenderSceneGen.h

@@ -0,0 +1,266 @@
+/*
+Open Asset Import Library (ASSIMP)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2016, ASSIMP Development 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 Development 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  BlenderSceneGen.h
+ *  @brief MACHINE GENERATED BY ./scripts/BlenderImporter/genblenddna.py
+ */
+#ifndef INCLUDED_AI_BLEND_SCENEGEN_H
+#define INCLUDED_AI_BLEND_SCENEGEN_H
+
+#include "BlenderDNA.h"
+#include "BlenderScene.h"
+
+namespace Assimp    {
+namespace Blender {
+
+template <> void Structure :: Convert<Object> (
+    Object& dest,
+    const FileDatabase& db
+    ) const
+;
+
+template <> void Structure :: Convert<Group> (
+    Group& dest,
+    const FileDatabase& db
+    ) const
+;
+
+template <> void Structure :: Convert<MTex> (
+    MTex& dest,
+    const FileDatabase& db
+    ) const
+;
+
+template <> void Structure :: Convert<TFace> (
+    TFace& dest,
+    const FileDatabase& db
+    ) const
+;
+
+template <> void Structure :: Convert<SubsurfModifierData> (
+    SubsurfModifierData& dest,
+    const FileDatabase& db
+    ) const
+;
+
+template <> void Structure :: Convert<MFace> (
+    MFace& dest,
+    const FileDatabase& db
+    ) const
+;
+
+template <> void Structure :: Convert<Lamp> (
+    Lamp& dest,
+    const FileDatabase& db
+    ) const
+;
+
+template <> void Structure :: Convert<MDeformWeight> (
+    MDeformWeight& dest,
+    const FileDatabase& db
+    ) const
+;
+
+template <> void Structure :: Convert<PackedFile> (
+    PackedFile& dest,
+    const FileDatabase& db
+    ) const
+;
+
+template <> void Structure :: Convert<Base> (
+    Base& dest,
+    const FileDatabase& db
+    ) const
+;
+
+template <> void Structure :: Convert<MTFace> (
+    MTFace& dest,
+    const FileDatabase& db
+    ) const
+;
+
+template <> void Structure :: Convert<Material> (
+    Material& dest,
+    const FileDatabase& db
+    ) const
+;
+
+template <> void Structure :: Convert<MTexPoly> (
+    MTexPoly& dest,
+    const FileDatabase& db
+    ) const
+;
+
+template <> void Structure :: Convert<Mesh> (
+    Mesh& dest,
+    const FileDatabase& db
+    ) const
+;
+
+template <> void Structure :: Convert<MDeformVert> (
+    MDeformVert& dest,
+    const FileDatabase& db
+    ) const
+;
+
+template <> void Structure :: Convert<World> (
+    World& dest,
+    const FileDatabase& db
+    ) const
+;
+
+template <> void Structure :: Convert<MLoopCol> (
+    MLoopCol& dest,
+    const FileDatabase& db
+    ) const
+;
+
+template <> void Structure :: Convert<MVert> (
+    MVert& dest,
+    const FileDatabase& db
+    ) const
+;
+
+template <> void Structure :: Convert<MEdge> (
+    MEdge& dest,
+    const FileDatabase& db
+    ) const
+;
+
+template <> void Structure :: Convert<MLoopUV> (
+    MLoopUV& dest,
+    const FileDatabase& db
+    ) const
+;
+
+template <> void Structure :: Convert<GroupObject> (
+    GroupObject& dest,
+    const FileDatabase& db
+    ) const
+;
+
+template <> void Structure :: Convert<ListBase> (
+    ListBase& dest,
+    const FileDatabase& db
+    ) const
+;
+
+template <> void Structure :: Convert<MLoop> (
+    MLoop& dest,
+    const FileDatabase& db
+    ) const
+;
+
+template <> void Structure :: Convert<ModifierData> (
+    ModifierData& dest,
+    const FileDatabase& db
+    ) const
+;
+
+template <> void Structure :: Convert<ID> (
+    ID& dest,
+    const FileDatabase& db
+    ) const
+;
+
+template <> void Structure :: Convert<MCol> (
+    MCol& dest,
+    const FileDatabase& db
+    ) const
+;
+
+template <> void Structure :: Convert<MPoly> (
+    MPoly& dest,
+    const FileDatabase& db
+    ) const
+;
+
+template <> void Structure :: Convert<Scene> (
+    Scene& dest,
+    const FileDatabase& db
+    ) const
+;
+
+template <> void Structure :: Convert<Library> (
+    Library& dest,
+    const FileDatabase& db
+    ) const
+;
+
+template <> void Structure :: Convert<Tex> (
+    Tex& dest,
+    const FileDatabase& db
+    ) const
+;
+
+template <> void Structure :: Convert<Camera> (
+    Camera& dest,
+    const FileDatabase& db
+    ) const
+;
+
+template <> void Structure :: Convert<MirrorModifierData> (
+    MirrorModifierData& dest,
+    const FileDatabase& db
+    ) const
+;
+
+template <> void Structure :: Convert<Image> (
+    Image& dest,
+    const FileDatabase& db
+    ) const
+;
+
+template <> void Structure::Convert<CustomData>(
+    CustomData& dest,
+    const FileDatabase& db
+    ) const
+    ;
+
+template <> void Structure::Convert<CustomDataLayer>(
+    CustomDataLayer& dest,
+    const FileDatabase& db
+    ) const
+    ;
+
+    }
+}
+
+#endif

+ 526 - 0
Engine/lib/assimp/code/BlenderTessellator.cpp

@@ -0,0 +1,526 @@
+/*
+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  BlenderTessellator.cpp
+ *  @brief A simple tessellation wrapper
+ */
+
+
+#ifndef ASSIMP_BUILD_NO_BLEND_IMPORTER
+
+#include "BlenderDNA.h"
+#include "BlenderScene.h"
+#include "BlenderBMesh.h"
+#include "BlenderTessellator.h"
+
+#include <stddef.h>
+
+static const unsigned int BLEND_TESS_MAGIC = 0x83ed9ac3;
+
+#if ASSIMP_BLEND_WITH_GLU_TESSELLATE
+
+namspace Assimp
+{
+    template< > const char* LogFunctions< BlenderTessellatorGL >::Prefix()
+    {
+        static auto prefix = "BLEND_TESS_GL: ";
+        return prefix;
+    }
+}
+
+using namespace Assimp;
+using namespace Assimp::Blender;
+
+#ifndef CALLBACK
+#define CALLBACK
+#endif
+
+// ------------------------------------------------------------------------------------------------
+BlenderTessellatorGL::BlenderTessellatorGL( BlenderBMeshConverter& converter ):
+    converter( &converter )
+{
+}
+
+// ------------------------------------------------------------------------------------------------
+BlenderTessellatorGL::~BlenderTessellatorGL( )
+{
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderTessellatorGL::Tessellate( const MLoop* polyLoop, int vertexCount, const std::vector< MVert >& vertices )
+{
+    AssertVertexCount( vertexCount );
+
+    std::vector< VertexGL > polyLoopGL;
+    GenerateLoopVerts( polyLoopGL, polyLoop, vertexCount, vertices );
+
+    TessDataGL tessData;
+    Tesssellate( polyLoopGL, tessData );
+
+    TriangulateDrawCalls( tessData );
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderTessellatorGL::AssertVertexCount( int vertexCount )
+{
+    if ( vertexCount <= 4 )
+    {
+        ThrowException( "Expected more than 4 vertices for tessellation" );
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderTessellatorGL::GenerateLoopVerts( std::vector< VertexGL >& polyLoopGL, const MLoop* polyLoop, int vertexCount, const std::vector< MVert >& vertices )
+{
+    for ( int i = 0; i < vertexCount; ++i )
+    {
+        const MLoop& loopItem = polyLoop[ i ];
+        const MVert& vertex = vertices[ loopItem.v ];
+        polyLoopGL.push_back( VertexGL( vertex.co[ 0 ], vertex.co[ 1 ], vertex.co[ 2 ], loopItem.v, BLEND_TESS_MAGIC ) );
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderTessellatorGL::Tesssellate( std::vector< VertexGL >& polyLoopGL, TessDataGL& tessData )
+{
+    GLUtesselator* tessellator = gluNewTess( );
+    gluTessCallback( tessellator, GLU_TESS_BEGIN_DATA, reinterpret_cast< void ( CALLBACK * )( ) >( TessellateBegin ) );
+    gluTessCallback( tessellator, GLU_TESS_END_DATA, reinterpret_cast< void ( CALLBACK * )( ) >( TessellateEnd ) );
+    gluTessCallback( tessellator, GLU_TESS_VERTEX_DATA, reinterpret_cast< void ( CALLBACK * )( ) >( TessellateVertex ) );
+    gluTessCallback( tessellator, GLU_TESS_COMBINE_DATA, reinterpret_cast< void ( CALLBACK * )( ) >( TessellateCombine ) );
+    gluTessCallback( tessellator, GLU_TESS_EDGE_FLAG_DATA, reinterpret_cast< void ( CALLBACK * )( ) >( TessellateEdgeFlag ) );
+    gluTessCallback( tessellator, GLU_TESS_ERROR_DATA, reinterpret_cast< void ( CALLBACK * )( ) >( TessellateError ) );
+    gluTessProperty( tessellator, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NONZERO );
+
+    gluTessBeginPolygon( tessellator, &tessData );
+    gluTessBeginContour( tessellator );
+
+    for ( unsigned int i = 0; i < polyLoopGL.size( ); ++i )
+    {
+        gluTessVertex( tessellator, reinterpret_cast< GLdouble* >( &polyLoopGL[ i ] ), &polyLoopGL[ i ] );
+    }
+
+    gluTessEndContour( tessellator );
+    gluTessEndPolygon( tessellator );
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderTessellatorGL::TriangulateDrawCalls( const TessDataGL& tessData )
+{
+    // NOTE - Because we are supplying a callback to GLU_TESS_EDGE_FLAG_DATA we don't technically
+    //        need support for GL_TRIANGLE_STRIP and GL_TRIANGLE_FAN but we'll keep it here in case
+    //        GLU tessellate changes or tri-strips and fans are wanted.
+    //        See: http://www.opengl.org/sdk/docs/man2/xhtml/gluTessCallback.xml
+    for ( unsigned int i = 0; i < tessData.drawCalls.size( ); ++i )
+    {
+        const DrawCallGL& drawCallGL = tessData.drawCalls[ i ];
+        const VertexGL* vertices = &tessData.vertices[ drawCallGL.baseVertex ];
+        if ( drawCallGL.drawMode == GL_TRIANGLES )
+        {
+            MakeFacesFromTris( vertices, drawCallGL.vertexCount );
+        }
+        else if ( drawCallGL.drawMode == GL_TRIANGLE_STRIP )
+        {
+            MakeFacesFromTriStrip( vertices, drawCallGL.vertexCount );
+        }
+        else if ( drawCallGL.drawMode == GL_TRIANGLE_FAN )
+        {
+            MakeFacesFromTriFan( vertices, drawCallGL.vertexCount );
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderTessellatorGL::MakeFacesFromTris( const VertexGL* vertices, int vertexCount )
+{
+    const int triangleCount = vertexCount / 3;
+    for ( int i = 0; i < triangleCount; ++i )
+    {
+        int vertexBase = i * 3;
+        converter->AddFace( vertices[ vertexBase + 0 ].index, vertices[ vertexBase + 1 ].index, vertices[ vertexBase + 2 ].index );
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderTessellatorGL::MakeFacesFromTriStrip( const VertexGL* vertices, int vertexCount )
+{
+    const int triangleCount = vertexCount - 2;
+    for ( int i = 0; i < triangleCount; ++i )
+    {
+        int vertexBase = i;
+        converter->AddFace( vertices[ vertexBase + 0 ].index, vertices[ vertexBase + 1 ].index, vertices[ vertexBase + 2 ].index );
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderTessellatorGL::MakeFacesFromTriFan( const VertexGL* vertices, int vertexCount )
+{
+    const int triangleCount = vertexCount - 2;
+    for ( int i = 0; i < triangleCount; ++i )
+    {
+        int vertexBase = i;
+        converter->AddFace( vertices[ 0 ].index, vertices[ vertexBase + 1 ].index, vertices[ vertexBase + 2 ].index );
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderTessellatorGL::TessellateBegin( GLenum drawModeGL, void* userData )
+{
+    TessDataGL& tessData = *reinterpret_cast< TessDataGL* >( userData );
+    tessData.drawCalls.push_back( DrawCallGL( drawModeGL, tessData.vertices.size( ) ) );
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderTessellatorGL::TessellateEnd( void* )
+{
+    // Do nothing
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderTessellatorGL::TessellateVertex( const void* vtxData, void* userData )
+{
+    TessDataGL& tessData = *reinterpret_cast< TessDataGL* >( userData );
+
+    const VertexGL& vertex = *reinterpret_cast< const VertexGL* >( vtxData );
+    if ( vertex.magic != BLEND_TESS_MAGIC )
+    {
+        ThrowException( "Point returned by GLU Tessellate was probably not one of ours. This indicates we need a new way to store vertex information" );
+    }
+    tessData.vertices.push_back( vertex );
+    if ( tessData.drawCalls.size( ) == 0 )
+    {
+        ThrowException( "\"Vertex\" callback received before \"Begin\"" );
+    }
+    ++( tessData.drawCalls.back( ).vertexCount );
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderTessellatorGL::TessellateCombine( const GLdouble intersection[ 3 ], const GLdouble* [ 4 ], const GLfloat [ 4 ], GLdouble** out, void* userData )
+{
+    ThrowException( "Intersected polygon loops are not yet supported" );
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderTessellatorGL::TessellateEdgeFlag( GLboolean, void* )
+{
+    // Do nothing
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderTessellatorGL::TessellateError( GLenum errorCode, void* )
+{
+    ThrowException( reinterpret_cast< const char* >( gluErrorString( errorCode ) ) );
+}
+
+#endif // ASSIMP_BLEND_WITH_GLU_TESSELLATE
+
+#if ASSIMP_BLEND_WITH_POLY_2_TRI
+
+namespace Assimp
+{
+    template< > const char* LogFunctions< BlenderTessellatorP2T >::Prefix()
+    {
+        static auto prefix = "BLEND_TESS_P2T: ";
+        return prefix;
+    }
+}
+
+using namespace Assimp;
+using namespace Assimp::Blender;
+
+// ------------------------------------------------------------------------------------------------
+BlenderTessellatorP2T::BlenderTessellatorP2T( BlenderBMeshConverter& converter ):
+    converter( &converter )
+{
+}
+
+// ------------------------------------------------------------------------------------------------
+BlenderTessellatorP2T::~BlenderTessellatorP2T( )
+{
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderTessellatorP2T::Tessellate( const MLoop* polyLoop, int vertexCount, const std::vector< MVert >& vertices )
+{
+    AssertVertexCount( vertexCount );
+
+    // NOTE - We have to hope that points in a Blender polygon are roughly on the same plane.
+    //        There may be some triangulation artifacts if they are wildly different.
+
+    std::vector< PointP2T > points;
+    Copy3DVertices( polyLoop, vertexCount, vertices, points );
+
+    PlaneP2T plane = FindLLSQPlane( points );
+
+    aiMatrix4x4 transform = GeneratePointTransformMatrix( plane );
+
+    TransformAndFlattenVectices( transform, points );
+
+    std::vector< p2t::Point* > pointRefs;
+    ReferencePoints( points, pointRefs );
+
+    p2t::CDT cdt( pointRefs );
+
+    cdt.Triangulate( );
+    std::vector< p2t::Triangle* > triangles = cdt.GetTriangles( );
+
+    MakeFacesFromTriangles( triangles );
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderTessellatorP2T::AssertVertexCount( int vertexCount )
+{
+    if ( vertexCount <= 4 )
+    {
+        ThrowException( "Expected more than 4 vertices for tessellation" );
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderTessellatorP2T::Copy3DVertices( const MLoop* polyLoop, int vertexCount, const std::vector< MVert >& vertices, std::vector< PointP2T >& points ) const
+{
+    points.resize( vertexCount );
+    for ( int i = 0; i < vertexCount; ++i )
+    {
+        const MLoop& loop = polyLoop[ i ];
+        const MVert& vert = vertices[ loop.v ];
+
+        PointP2T& point = points[ i ];
+        point.point3D.Set( vert.co[ 0 ], vert.co[ 1 ], vert.co[ 2 ] );
+        point.index = loop.v;
+        point.magic = BLEND_TESS_MAGIC;
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+aiMatrix4x4 BlenderTessellatorP2T::GeneratePointTransformMatrix( const Blender::PlaneP2T& plane ) const
+{
+    aiVector3D sideA( 1.0f, 0.0f, 0.0f );
+    if ( std::fabs( plane.normal * sideA ) > 0.999f )
+    {
+        sideA = aiVector3D( 0.0f, 1.0f, 0.0f );
+    }
+
+    aiVector3D sideB( plane.normal ^ sideA );
+    sideB.Normalize( );
+    sideA = sideB ^ plane.normal;
+
+    aiMatrix4x4 result;
+    result.a1 = sideA.x;
+    result.a2 = sideA.y;
+    result.a3 = sideA.z;
+    result.b1 = sideB.x;
+    result.b2 = sideB.y;
+    result.b3 = sideB.z;
+    result.c1 = plane.normal.x;
+    result.c2 = plane.normal.y;
+    result.c3 = plane.normal.z;
+    result.a4 = plane.centre.x;
+    result.b4 = plane.centre.y;
+    result.c4 = plane.centre.z;
+    result.Inverse( );
+
+    return result;
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderTessellatorP2T::TransformAndFlattenVectices( const aiMatrix4x4& transform, std::vector< Blender::PointP2T >& vertices ) const
+{
+    for ( size_t i = 0; i < vertices.size( ); ++i )
+    {
+        PointP2T& point = vertices[ i ];
+        point.point3D = transform * point.point3D;
+        point.point2D.set( point.point3D.y, point.point3D.z );
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderTessellatorP2T::ReferencePoints( std::vector< Blender::PointP2T >& points, std::vector< p2t::Point* >& pointRefs ) const
+{
+    pointRefs.resize( points.size( ) );
+    for ( size_t i = 0; i < points.size( ); ++i )
+    {
+        pointRefs[ i ] = &points[ i ].point2D;
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+inline PointP2T& BlenderTessellatorP2T::GetActualPointStructure( p2t::Point& point ) const
+{
+    unsigned int pointOffset = offsetof( PointP2T, point2D );
+    PointP2T& pointStruct = *reinterpret_cast< PointP2T* >( reinterpret_cast< char* >( &point ) - pointOffset );
+    if ( pointStruct.magic != static_cast<int>( BLEND_TESS_MAGIC ) )
+    {
+        ThrowException( "Point returned by poly2tri was probably not one of ours. This indicates we need a new way to store vertex information" );
+    }
+    return pointStruct;
+}
+
+// ------------------------------------------------------------------------------------------------
+void BlenderTessellatorP2T::MakeFacesFromTriangles( std::vector< p2t::Triangle* >& triangles ) const
+{
+    for ( size_t i = 0; i < triangles.size( ); ++i )
+    {
+        p2t::Triangle& Triangle = *triangles[ i ];
+
+        PointP2T& pointA = GetActualPointStructure( *Triangle.GetPoint( 0 ) );
+        PointP2T& pointB = GetActualPointStructure( *Triangle.GetPoint( 1 ) );
+        PointP2T& pointC = GetActualPointStructure( *Triangle.GetPoint( 2 ) );
+
+        converter->AddFace( pointA.index, pointB.index, pointC.index );
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+inline float p2tMax( float a, float b )
+{
+    return a > b ? a : b;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Adapted from: http://missingbytes.blogspot.co.uk/2012/06/fitting-plane-to-point-cloud.html
+float BlenderTessellatorP2T::FindLargestMatrixElem( const aiMatrix3x3& mtx ) const
+{
+    float result = 0.0f;
+
+    for ( unsigned int x = 0; x < 3; ++x )
+    {
+        for ( unsigned int y = 0; y < 3; ++y )
+        {
+            result = p2tMax( std::fabs( mtx[ x ][ y ] ), result );
+        }
+    }
+
+    return result;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Apparently Assimp doesn't have matrix scaling
+aiMatrix3x3 BlenderTessellatorP2T::ScaleMatrix( const aiMatrix3x3& mtx, float scale ) const
+{
+    aiMatrix3x3 result;
+
+    for ( unsigned int x = 0; x < 3; ++x )
+    {
+        for ( unsigned int y = 0; y < 3; ++y )
+        {
+            result[ x ][ y ] = mtx[ x ][ y ] * scale;
+        }
+    }
+
+    return result;
+}
+
+
+// ------------------------------------------------------------------------------------------------
+// Adapted from: http://missingbytes.blogspot.co.uk/2012/06/fitting-plane-to-point-cloud.html
+aiVector3D BlenderTessellatorP2T::GetEigenVectorFromLargestEigenValue( const aiMatrix3x3& mtx ) const
+{
+    const float scale = FindLargestMatrixElem( mtx );
+    aiMatrix3x3 mc = ScaleMatrix( mtx, 1.0f / scale );
+    mc = mc * mc * mc;
+
+    aiVector3D v( 1.0f );
+    aiVector3D lastV = v;
+    for ( int i = 0; i < 100; ++i )
+    {
+        v = mc * v;
+        v.Normalize( );
+        if ( ( v - lastV ).SquareLength( ) < 1e-16f )
+        {
+            break;
+        }
+        lastV = v;
+    }
+    return v;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Adapted from: http://missingbytes.blogspot.co.uk/2012/06/fitting-plane-to-point-cloud.html
+PlaneP2T BlenderTessellatorP2T::FindLLSQPlane( const std::vector< PointP2T >& points ) const
+{
+    PlaneP2T result;
+
+    aiVector3D sum( 0.0 );
+    for ( size_t i = 0; i < points.size( ); ++i )
+    {
+        sum += points[ i ].point3D;
+    }
+    result.centre = sum * (ai_real)( 1.0 / points.size( ) );
+
+    ai_real sumXX = 0.0;
+    ai_real sumXY = 0.0;
+    ai_real sumXZ = 0.0;
+    ai_real sumYY = 0.0;
+    ai_real sumYZ = 0.0;
+    ai_real sumZZ = 0.0;
+    for ( size_t i = 0; i < points.size( ); ++i )
+    {
+        aiVector3D offset = points[ i ].point3D - result.centre;
+        sumXX += offset.x * offset.x;
+        sumXY += offset.x * offset.y;
+        sumXZ += offset.x * offset.z;
+        sumYY += offset.y * offset.y;
+        sumYZ += offset.y * offset.z;
+        sumZZ += offset.z * offset.z;
+    }
+
+    aiMatrix3x3 mtx( sumXX, sumXY, sumXZ, sumXY, sumYY, sumYZ, sumXZ, sumYZ, sumZZ );
+
+    const ai_real det = mtx.Determinant( );
+    if ( det == 0.0f )
+    {
+        result.normal = aiVector3D( 0.0f );
+    }
+    else
+    {
+        aiMatrix3x3 invMtx = mtx;
+        invMtx.Inverse( );
+        result.normal = GetEigenVectorFromLargestEigenValue( invMtx );
+    }
+
+    return result;
+}
+
+#endif // ASSIMP_BLEND_WITH_POLY_2_TRI
+
+#endif // ASSIMP_BUILD_NO_BLEND_IMPORTER

+ 210 - 0
Engine/lib/assimp/code/BlenderTessellator.h

@@ -0,0 +1,210 @@
+/*
+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  BlenderTessellator.h
+ *  @brief A simple tessellation wrapper
+ */
+#ifndef INCLUDED_AI_BLEND_TESSELLATOR_H
+#define INCLUDED_AI_BLEND_TESSELLATOR_H
+
+// Use these to toggle between GLU Tessellate or poly2tri
+// Note (acg) keep GLU Tessellate disabled by default - if it is turned on,
+// assimp needs to be linked against GLU, which is currently not yet
+// made configurable in CMake and potentially not wanted by most users
+// as it requires a Gl environment.
+#ifndef ASSIMP_BLEND_WITH_GLU_TESSELLATE
+#   define ASSIMP_BLEND_WITH_GLU_TESSELLATE 0
+#endif
+
+#ifndef ASSIMP_BLEND_WITH_POLY_2_TRI
+#   define ASSIMP_BLEND_WITH_POLY_2_TRI 1
+#endif
+
+#include <assimp/LogAux.h>
+
+#if ASSIMP_BLEND_WITH_GLU_TESSELLATE
+
+#if defined( WIN32 ) || defined( _WIN32 ) || defined( _MSC_VER )
+#include <windows.h>
+#endif
+#include <GL/glu.h>
+
+namespace Assimp
+{
+    class BlenderBMeshConverter;
+
+    // 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;
+    }
+
+    // BlenderScene.h
+    namespace Blender
+    {
+        struct MLoop;
+        struct MVert;
+
+        struct VertexGL
+        {
+            GLdouble X;
+            GLdouble Y;
+            GLdouble Z;
+            int index;
+            int magic;
+
+            VertexGL( GLdouble X, GLdouble Y, GLdouble Z, int index, int magic ): X( X ), Y( Y ), Z( Z ), index( index ), magic( magic ) { }
+        };
+
+        struct DrawCallGL
+        {
+            GLenum drawMode;
+            int baseVertex;
+            int vertexCount;
+
+            DrawCallGL( GLenum drawMode, int baseVertex ): drawMode( drawMode ), baseVertex( baseVertex ), vertexCount( 0 ) { }
+        };
+
+        struct TessDataGL
+        {
+            std::vector< DrawCallGL > drawCalls;
+            std::vector< VertexGL > vertices;
+        };
+    }
+
+    class BlenderTessellatorGL: public LogFunctions< BlenderTessellatorGL >
+    {
+    public:
+        BlenderTessellatorGL( BlenderBMeshConverter& converter );
+        ~BlenderTessellatorGL( );
+
+        void Tessellate( const Blender::MLoop* polyLoop, int vertexCount, const std::vector< Blender::MVert >& vertices );
+
+    private:
+        void AssertVertexCount( int vertexCount );
+        void GenerateLoopVerts( std::vector< Blender::VertexGL >& polyLoopGL, const Blender::MLoop* polyLoop, int vertexCount, const std::vector< Blender::MVert >& vertices );
+        void Tesssellate( std::vector< Blender::VertexGL >& polyLoopGL, Blender::TessDataGL& tessData );
+        void TriangulateDrawCalls( const Blender::TessDataGL& tessData );
+        void MakeFacesFromTris( const Blender::VertexGL* vertices, int vertexCount );
+        void MakeFacesFromTriStrip( const Blender::VertexGL* vertices, int vertexCount );
+        void MakeFacesFromTriFan( const Blender::VertexGL* vertices, int vertexCount );
+
+        static void TessellateBegin( GLenum drawModeGL, void* userData );
+        static void TessellateEnd( void* userData );
+        static void TessellateVertex( const void* vtxData, void* userData );
+        static void TessellateCombine( const GLdouble intersection[ 3 ], const GLdouble* [ 4 ], const GLfloat [ 4 ], GLdouble** out, void* userData );
+        static void TessellateEdgeFlag( GLboolean edgeFlag, void* userData );
+        static void TessellateError( GLenum errorCode, void* userData );
+
+        BlenderBMeshConverter* converter;
+    };
+} // end of namespace Assimp
+
+#endif // ASSIMP_BLEND_WITH_GLU_TESSELLATE
+
+#if ASSIMP_BLEND_WITH_POLY_2_TRI
+
+#include "../contrib/poly2tri/poly2tri/poly2tri.h"
+
+namespace Assimp
+{
+    class BlenderBMeshConverter;
+
+    // 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;
+    }
+
+    // BlenderScene.h
+    namespace Blender
+    {
+        struct MLoop;
+        struct MVert;
+
+        struct PointP2T
+        {
+            aiVector3D point3D;
+            p2t::Point point2D;
+            int magic;
+            int index;
+        };
+
+        struct PlaneP2T
+        {
+            aiVector3D centre;
+            aiVector3D normal;
+        };
+    }
+
+    class BlenderTessellatorP2T: public LogFunctions< BlenderTessellatorP2T >
+    {
+    public:
+        BlenderTessellatorP2T( BlenderBMeshConverter& converter );
+        ~BlenderTessellatorP2T( );
+
+        void Tessellate( const Blender::MLoop* polyLoop, int vertexCount, const std::vector< Blender::MVert >& vertices );
+
+    private:
+        void AssertVertexCount( int vertexCount );
+        void Copy3DVertices( const Blender::MLoop* polyLoop, int vertexCount, const std::vector< Blender::MVert >& vertices, std::vector< Blender::PointP2T >& targetVertices ) const;
+        aiMatrix4x4 GeneratePointTransformMatrix( const Blender::PlaneP2T& plane ) const;
+        void TransformAndFlattenVectices( const aiMatrix4x4& transform, std::vector< Blender::PointP2T >& vertices ) const;
+        void ReferencePoints( std::vector< Blender::PointP2T >& points, std::vector< p2t::Point* >& pointRefs ) const;
+        inline Blender::PointP2T& GetActualPointStructure( p2t::Point& point ) const;
+        void MakeFacesFromTriangles( std::vector< p2t::Triangle* >& triangles ) const;
+
+        // Adapted from: http://missingbytes.blogspot.co.uk/2012/06/fitting-plane-to-point-cloud.html
+        float FindLargestMatrixElem( const aiMatrix3x3& mtx ) const;
+        aiMatrix3x3 ScaleMatrix( const aiMatrix3x3& mtx, float scale ) const;
+        aiVector3D GetEigenVectorFromLargestEigenValue( const aiMatrix3x3& mtx ) const;
+        Blender::PlaneP2T FindLLSQPlane( const std::vector< Blender::PointP2T >& points ) const;
+
+        BlenderBMeshConverter* converter;
+    };
+} // end of namespace Assimp
+
+#endif // ASSIMP_BLEND_WITH_POLY_2_TRI
+
+#endif // INCLUDED_AI_BLEND_TESSELLATOR_H

+ 337 - 0
Engine/lib/assimp/code/BlobIOSystem.h

@@ -0,0 +1,337 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2017, 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 Provides cheat implementations for IOSystem and IOStream to
+ *  redirect exporter output to a blob chain.*/
+
+#ifndef AI_BLOBIOSYSTEM_H_INCLUDED
+#define AI_BLOBIOSYSTEM_H_INCLUDED
+
+#include <assimp/IOStream.hpp>
+#include <assimp/cexport.h>
+#include <assimp/IOSystem.hpp>
+#include <assimp/DefaultLogger.hpp>
+#include <stdint.h>
+#include <set>
+#include <vector>
+
+namespace Assimp    {
+    class BlobIOSystem;
+
+// --------------------------------------------------------------------------------------------
+/** Redirect IOStream to a blob */
+// --------------------------------------------------------------------------------------------
+class BlobIOStream : public IOStream
+{
+public:
+
+    BlobIOStream(BlobIOSystem* creator, const std::string& file, size_t initial = 4096)
+        : buffer()
+        , cur_size()
+        , file_size()
+        , cursor()
+        , initial(initial)
+        , file(file)
+        , creator(creator)
+    {
+    }
+
+
+    virtual ~BlobIOStream();
+
+public:
+
+    // -------------------------------------------------------------------
+    aiExportDataBlob* GetBlob()
+    {
+        aiExportDataBlob* blob = new aiExportDataBlob();
+        blob->size = file_size;
+        blob->data = buffer;
+
+        buffer = NULL;
+
+        return blob;
+    }
+
+
+public:
+
+
+    // -------------------------------------------------------------------
+    virtual size_t Read( void *,
+        size_t,
+        size_t )
+    {
+        return 0;
+    }
+
+    // -------------------------------------------------------------------
+    virtual size_t Write(const void* pvBuffer,
+        size_t pSize,
+        size_t pCount)
+    {
+        pSize *= pCount;
+        if (cursor + pSize > cur_size) {
+            Grow(cursor + pSize);
+        }
+
+        memcpy(buffer+cursor, pvBuffer, pSize);
+        cursor += pSize;
+
+        file_size = std::max(file_size,cursor);
+        return pCount;
+    }
+
+    // -------------------------------------------------------------------
+    virtual aiReturn Seek(size_t pOffset,
+        aiOrigin pOrigin)
+    {
+        switch(pOrigin)
+        {
+        case aiOrigin_CUR:
+            cursor += pOffset;
+            break;
+
+        case aiOrigin_END:
+            cursor = file_size - pOffset;
+            break;
+
+        case aiOrigin_SET:
+            cursor = pOffset;
+            break;
+
+        default:
+            return AI_FAILURE;
+        }
+
+        if (cursor > file_size) {
+            Grow(cursor);
+        }
+
+        file_size = std::max(cursor,file_size);
+        return AI_SUCCESS;
+    }
+
+    // -------------------------------------------------------------------
+    virtual size_t Tell() const
+    {
+        return cursor;
+    }
+
+    // -------------------------------------------------------------------
+    virtual size_t FileSize() const
+    {
+        return file_size;
+    }
+
+    // -------------------------------------------------------------------
+    virtual void Flush()
+    {
+        // ignore
+    }
+
+
+
+private:
+
+    // -------------------------------------------------------------------
+    void Grow(size_t need = 0)
+    {
+        // 1.5 and phi are very heap-friendly growth factors (the first
+        // allows for frequent re-use of heap blocks, the second
+        // forms a fibonacci sequence with similar characteristics -
+        // since this heavily depends on the heap implementation
+        // and other factors as well, i'll just go with 1.5 since
+        // it is quicker to compute).
+        size_t new_size = std::max(initial, std::max( need, cur_size+(cur_size>>1) ));
+
+        const uint8_t* const old = buffer;
+        buffer = new uint8_t[new_size];
+
+        if (old) {
+            memcpy(buffer,old,cur_size);
+            delete[] old;
+        }
+
+        cur_size = new_size;
+    }
+
+private:
+
+    uint8_t* buffer;
+    size_t cur_size,file_size, cursor, initial;
+
+    const std::string file;
+    BlobIOSystem* const creator;
+};
+
+
+#define AI_BLOBIO_MAGIC "$blobfile"
+
+// --------------------------------------------------------------------------------------------
+/** Redirect IOSystem to a blob */
+// --------------------------------------------------------------------------------------------
+class BlobIOSystem : public IOSystem
+{
+
+    friend class BlobIOStream;
+    typedef std::pair<std::string, aiExportDataBlob*> BlobEntry;
+
+public:
+
+    BlobIOSystem()
+    {
+    }
+
+    virtual ~BlobIOSystem()
+    {
+        for(BlobEntry& blobby : blobs) {
+            delete blobby.second;
+        }
+    }
+
+public:
+
+    // -------------------------------------------------------------------
+    const char* GetMagicFileName() const
+    {
+        return AI_BLOBIO_MAGIC;
+    }
+
+
+    // -------------------------------------------------------------------
+    aiExportDataBlob* GetBlobChain()
+    {
+        // one must be the master
+        aiExportDataBlob* master = NULL, *cur;
+        for(const BlobEntry& blobby : blobs) {
+            if (blobby.first == AI_BLOBIO_MAGIC) {
+                master = blobby.second;
+                break;
+            }
+        }
+        if (!master) {
+            DefaultLogger::get()->error("BlobIOSystem: no data written or master file was not closed properly.");
+            return NULL;
+        }
+
+        master->name.Set("");
+
+        cur = master;
+        for(const BlobEntry& blobby : blobs) {
+            if (blobby.second == master) {
+                continue;
+            }
+
+            cur->next = blobby.second;
+            cur = cur->next;
+
+            // extract the file extension from the file written
+            const std::string::size_type s = blobby.first.find_first_of('.');
+            cur->name.Set(s == std::string::npos ? blobby.first : blobby.first.substr(s+1));
+        }
+
+        // give up blob ownership
+        blobs.clear();
+        return master;
+    }
+
+public:
+
+    // -------------------------------------------------------------------
+    virtual bool Exists( const char* pFile) const {
+        return created.find(std::string(pFile)) != created.end();
+    }
+
+
+    // -------------------------------------------------------------------
+    virtual char getOsSeparator() const {
+        return '/';
+    }
+
+
+    // -------------------------------------------------------------------
+    virtual IOStream* Open(const char* pFile,
+        const char* pMode)
+    {
+        if (pMode[0] != 'w') {
+            return NULL;
+        }
+
+        created.insert(std::string(pFile));
+        return new BlobIOStream(this,std::string(pFile));
+    }
+
+    // -------------------------------------------------------------------
+    virtual void Close( IOStream* pFile)
+    {
+        delete pFile;
+    }
+
+private:
+
+    // -------------------------------------------------------------------
+    void OnDestruct(const std::string& filename, BlobIOStream* child)
+    {
+        // we don't know in which the files are closed, so we
+        // can't reliably say that the first must be the master
+        // file ...
+        blobs.push_back( BlobEntry(filename,child->GetBlob()) );
+    }
+
+private:
+    std::set<std::string> created;
+    std::vector< BlobEntry > blobs;
+};
+
+
+// --------------------------------------------------------------------------------------------
+BlobIOStream :: ~BlobIOStream()
+{
+    creator->OnDestruct(file,this);
+    delete[] buffer;
+}
+
+
+} // end Assimp
+
+#endif

+ 287 - 0
Engine/lib/assimp/code/ByteSwapper.h

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

+ 650 - 0
Engine/lib/assimp/code/C4DImporter.cpp

@@ -0,0 +1,650 @@
+/*
+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  C4DImporter.cpp
+ *  @brief Implementation of the Cinema4D importer class.
+ */
+#ifndef ASSIMP_BUILD_NO_C4D_IMPORTER
+
+// no #ifdefing here, Cinema4D support is carried out in a branch of assimp
+// where it is turned on in the CMake settings.
+
+#ifndef _MSC_VER
+#   error C4D support is currently MSVC only
+#endif
+
+#include "C4DImporter.h"
+#include <assimp/TinyFormatter.h>
+#include <memory>
+#include <assimp/IOSystem.hpp>
+#include <assimp/scene.h>
+#include <assimp/ai_assert.h>
+
+#if defined(_M_X64) || defined(__amd64__)
+#   define __C4D_64BIT
+#endif
+
+#define __PC
+#include "c4d_file.h"
+#include "default_alien_overloads.h"
+
+using namespace melange;
+
+// overload this function and fill in your own unique data
+void GetWriterInfo(int &id, String &appname)
+{
+    id = 2424226;
+    appname = "Open Asset Import Library";
+}
+
+using namespace Assimp;
+using namespace Assimp::Formatter;
+
+namespace Assimp {
+    template<> const std::string LogFunctions<C4DImporter>::log_prefix = "C4D: ";
+}
+
+static const aiImporterDesc desc = {
+    "Cinema4D Importer",
+    "",
+    "",
+    "",
+    aiImporterFlags_SupportBinaryFlavour,
+    0,
+    0,
+    0,
+    0,
+    "c4d"
+};
+
+
+// ------------------------------------------------------------------------------------------------
+C4DImporter::C4DImporter()
+{}
+
+// ------------------------------------------------------------------------------------------------
+C4DImporter::~C4DImporter()
+{}
+
+// ------------------------------------------------------------------------------------------------
+bool C4DImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
+{
+    const std::string& extension = GetExtension(pFile);
+    if (extension == "c4d") {
+        return true;
+    }
+
+    else if ((!extension.length() || checkSig) && pIOHandler)   {
+        // TODO
+    }
+    return false;
+}
+
+// ------------------------------------------------------------------------------------------------
+const aiImporterDesc* C4DImporter::GetInfo () const
+{
+    return &desc;
+}
+
+// ------------------------------------------------------------------------------------------------
+void C4DImporter::SetupProperties(const Importer* /*pImp*/)
+{
+    // nothing to be done for the moment
+}
+
+
+// ------------------------------------------------------------------------------------------------
+// Imports the given file into the given scene structure.
+void C4DImporter::InternReadFile( const std::string& pFile,
+    aiScene* pScene, IOSystem* pIOHandler)
+{
+    std::unique_ptr<IOStream> file( pIOHandler->Open( pFile));
+
+    if( file.get() == NULL) {
+        ThrowException("failed to open file " + pFile);
+    }
+
+    const size_t file_size = file->FileSize();
+
+    std::vector<uint8_t> mBuffer(file_size);
+    file->Read(&mBuffer[0], 1, file_size);
+
+    Filename f;
+    f.SetMemoryReadMode(&mBuffer[0], file_size);
+
+    // open document first
+    BaseDocument* doc = LoadDocument(f, SCENEFILTER_OBJECTS | SCENEFILTER_MATERIALS);
+    if(doc == NULL) {
+        ThrowException("failed to read document " + pFile);
+    }
+
+    pScene->mRootNode = new aiNode("<C4DRoot>");
+
+    // first convert all materials
+    ReadMaterials(doc->GetFirstMaterial());
+
+    // process C4D scenegraph recursively
+    try {
+        RecurseHierarchy(doc->GetFirstObject(), pScene->mRootNode);
+    }
+    catch(...) {
+        for(aiMesh* mesh : meshes) {
+            delete mesh;
+        }
+        BaseDocument::Free(doc);
+        throw;
+    }
+    BaseDocument::Free(doc);
+
+    // copy meshes over
+    pScene->mNumMeshes = static_cast<unsigned int>(meshes.size());
+    pScene->mMeshes = new aiMesh*[pScene->mNumMeshes]();
+    std::copy(meshes.begin(), meshes.end(), pScene->mMeshes);
+
+    // copy materials over, adding a default material if necessary
+    unsigned int mat_count = static_cast<unsigned int>(materials.size());
+    for(aiMesh* mesh : meshes) {
+        ai_assert(mesh->mMaterialIndex <= mat_count);
+        if(mesh->mMaterialIndex >= mat_count) {
+            ++mat_count;
+
+            std::unique_ptr<aiMaterial> def_material(new aiMaterial());
+            const aiString name(AI_DEFAULT_MATERIAL_NAME);
+            def_material->AddProperty(&name, AI_MATKEY_NAME);
+
+            materials.push_back(def_material.release());
+            break;
+        }
+    }
+
+    pScene->mNumMaterials = static_cast<unsigned int>(materials.size());
+    pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials]();
+    std::copy(materials.begin(), materials.end(), pScene->mMaterials);
+}
+
+
+// ------------------------------------------------------------------------------------------------
+bool C4DImporter::ReadShader(aiMaterial* out, melange::BaseShader* shader)
+{
+    // based on Melange sample code (C4DImportExport.cpp)
+    while(shader) {
+        if(shader->GetType() == Xlayer) {
+            BaseContainer* container = shader->GetDataInstance();
+            GeData blend = container->GetData(SLA_LAYER_BLEND);
+            iBlendDataType* blend_list = reinterpret_cast<iBlendDataType*>(blend.GetCustomDataType(CUSTOMDATA_BLEND_LIST));
+            if (!blend_list)
+            {
+                LogWarn("ignoring XLayer shader: no blend list given");
+                continue;
+            }
+
+            LayerShaderLayer *lsl = dynamic_cast<LayerShaderLayer*>(blend_list->m_BlendLayers.GetObject(0));
+
+            // Ignore the actual layer blending - models for real-time rendering should not
+            // use them in a non-trivial way. Just try to find textures that we can apply
+            // to the model.
+            while (lsl)
+            {
+                if (lsl->GetType() == TypeFolder)
+                {
+                    BlendFolder* const folder = dynamic_cast<BlendFolder*>(lsl);
+                    LayerShaderLayer *subLsl = dynamic_cast<LayerShaderLayer*>(folder->m_Children.GetObject(0));
+
+                    while (subLsl)
+                    {
+                        if (subLsl->GetType() == TypeShader) {
+                            BlendShader* const shader = dynamic_cast<BlendShader*>(subLsl);
+                            if(ReadShader(out, static_cast<BaseShader*>(shader->m_pLink->GetLink()))) {
+                                return true;
+                            }
+                        }
+
+                        subLsl = subLsl->GetNext();
+                    }
+                }
+                else if (lsl->GetType() == TypeShader) {
+                    BlendShader* const shader = dynamic_cast<BlendShader*>(lsl);
+                    if(ReadShader(out, static_cast<BaseShader*>(shader->m_pLink->GetLink()))) {
+                        return true;
+                    }
+                }
+
+                lsl = lsl->GetNext();
+            }
+        }
+        else if ( shader->GetType() == Xbitmap )
+        {
+            aiString path;
+            shader->GetFileName().GetString().GetCString(path.data, MAXLEN-1);
+            path.length = ::strlen(path.data);
+            out->AddProperty(&path, AI_MATKEY_TEXTURE_DIFFUSE(0));
+            return true;
+        }
+        else {
+            LogWarn("ignoring shader type: " + std::string(GetObjectTypeName(shader->GetType())));
+        }
+        shader = shader->GetNext();
+    }
+    return false;
+}
+
+
+// ------------------------------------------------------------------------------------------------
+void C4DImporter::ReadMaterials(melange::BaseMaterial* mat)
+{
+    // based on Melange sample code
+    while (mat)
+    {
+        const String& name = mat->GetName();
+        if (mat->GetType() == Mmaterial)
+        {
+            aiMaterial* out = new aiMaterial();
+            material_mapping[mat] = static_cast<unsigned int>(materials.size());
+            materials.push_back(out);
+
+            aiString ai_name;
+            name.GetCString(ai_name.data, MAXLEN-1);
+            ai_name.length = ::strlen(ai_name.data);
+            out->AddProperty(&ai_name, AI_MATKEY_NAME);
+
+            Material& m = dynamic_cast<Material&>(*mat);
+
+            if (m.GetChannelState(CHANNEL_COLOR))
+            {
+                GeData data;
+                mat->GetParameter(MATERIAL_COLOR_COLOR, data);
+                Vector color = data.GetVector();
+                mat->GetParameter(MATERIAL_COLOR_BRIGHTNESS, data);
+                const Float brightness = data.GetFloat();
+
+                color *= brightness;
+
+                aiVector3D v;
+                v.x = color.x;
+                v.y = color.y;
+                v.z = color.z;
+                out->AddProperty(&v, 1, AI_MATKEY_COLOR_DIFFUSE);
+            }
+
+            BaseShader* const shader = m.GetShader(MATERIAL_COLOR_SHADER);
+            if(shader) {
+                ReadShader(out, shader);
+            }
+        }
+        else
+        {
+            LogWarn("ignoring plugin material: " + std::string(GetObjectTypeName(mat->GetType())));
+        }
+        mat = mat->GetNext();
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void C4DImporter::RecurseHierarchy(BaseObject* object, aiNode* parent)
+{
+    ai_assert(parent != NULL);
+    std::vector<aiNode*> nodes;
+
+    // based on Melange sample code
+    while (object)
+    {
+        const String& name = object->GetName();
+        const LONG type = object->GetType();
+        const Matrix& ml = object->GetMl();
+
+        aiString string;
+        name.GetCString(string.data, MAXLEN-1);
+        string.length = ::strlen(string.data);
+        aiNode* const nd = new aiNode();
+
+        nd->mParent = parent;
+        nd->mName = string;
+
+        nd->mTransformation.a1 = ml.v1.x;
+        nd->mTransformation.b1 = ml.v1.y;
+        nd->mTransformation.c1 = ml.v1.z;
+
+        nd->mTransformation.a2 = ml.v2.x;
+        nd->mTransformation.b2 = ml.v2.y;
+        nd->mTransformation.c2 = ml.v2.z;
+
+        nd->mTransformation.a3 = ml.v3.x;
+        nd->mTransformation.b3 = ml.v3.y;
+        nd->mTransformation.c3 = ml.v3.z;
+
+        nd->mTransformation.a4 = ml.off.x;
+        nd->mTransformation.b4 = ml.off.y;
+        nd->mTransformation.c4 = ml.off.z;
+
+        nodes.push_back(nd);
+
+        GeData data;
+        if (type == Ocamera)
+        {
+            object->GetParameter(CAMERAOBJECT_FOV, data);
+            // TODO: read camera
+        }
+        else if (type == Olight)
+        {
+            // TODO: read light
+        }
+        else if (type == Opolygon)
+        {
+            aiMesh* const mesh = ReadMesh(object);
+            if(mesh != NULL) {
+                nd->mNumMeshes = 1;
+                nd->mMeshes = new unsigned int[1];
+                nd->mMeshes[0] = static_cast<unsigned int>(meshes.size());
+                meshes.push_back(mesh);
+            }
+        }
+        else {
+            LogWarn("ignoring object: " + std::string(GetObjectTypeName(type)));
+        }
+
+        RecurseHierarchy(object->GetDown(), nd);
+        object = object->GetNext();
+    }
+
+    // copy nodes over to parent
+    parent->mNumChildren = static_cast<unsigned int>(nodes.size());
+    parent->mChildren = new aiNode*[parent->mNumChildren]();
+    std::copy(nodes.begin(), nodes.end(), parent->mChildren);
+}
+
+
+// ------------------------------------------------------------------------------------------------
+aiMesh* C4DImporter::ReadMesh(BaseObject* object)
+{
+    ai_assert(object != NULL && object->GetType() == Opolygon);
+
+    // based on Melange sample code
+    PolygonObject* const polyObject = dynamic_cast<PolygonObject*>(object);
+    ai_assert(polyObject != NULL);
+
+    const LONG pointCount = polyObject->GetPointCount();
+    const LONG polyCount = polyObject->GetPolygonCount();
+    if(!polyObject || !pointCount) {
+        LogWarn("ignoring mesh with zero vertices or faces");
+        return NULL;
+    }
+
+    const Vector* points = polyObject->GetPointR();
+    ai_assert(points != NULL);
+
+    const CPolygon* polys = polyObject->GetPolygonR();
+    ai_assert(polys != NULL);
+
+    std::unique_ptr<aiMesh> mesh(new aiMesh());
+    mesh->mNumFaces = static_cast<unsigned int>(polyCount);
+    aiFace* face = mesh->mFaces = new aiFace[mesh->mNumFaces]();
+
+    mesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
+    mesh->mMaterialIndex = 0;
+
+    unsigned int vcount = 0;
+
+    // first count vertices
+    for (LONG i = 0; i < polyCount; i++)
+    {
+        vcount += 3;
+
+        // TODO: do we also need to handle lines or points with similar checks?
+        if (polys[i].c != polys[i].d)
+        {
+            mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON;
+            ++vcount;
+        }
+    }
+
+    ai_assert(vcount > 0);
+
+    mesh->mNumVertices = vcount;
+    aiVector3D* verts = mesh->mVertices = new aiVector3D[mesh->mNumVertices];
+    aiVector3D* normals, *uvs, *tangents, *bitangents;
+    unsigned int n = 0;
+
+    // check if there are normals, tangents or UVW coordinates
+    BaseTag* tag = object->GetTag(Tnormal);
+    NormalTag* normals_src = NULL;
+    if(tag) {
+        normals_src = dynamic_cast<NormalTag*>(tag);
+        normals = mesh->mNormals = new aiVector3D[mesh->mNumVertices]();
+    }
+
+    tag = object->GetTag(Ttangent);
+    TangentTag* tangents_src = NULL;
+    if(tag) {
+        tangents_src = dynamic_cast<TangentTag*>(tag);
+        tangents = mesh->mTangents = new aiVector3D[mesh->mNumVertices]();
+        bitangents = mesh->mBitangents = new aiVector3D[mesh->mNumVertices]();
+    }
+
+    tag = object->GetTag(Tuvw);
+    UVWTag* uvs_src = NULL;
+    if(tag) {
+        uvs_src = dynamic_cast<UVWTag*>(tag);
+        uvs = mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices]();
+    }
+
+    // copy vertices and extra channels over and populate faces
+    for (LONG i = 0; i < polyCount; ++i, ++face)
+    {
+        ai_assert(polys[i].a < pointCount && polys[i].a >= 0);
+        const Vector& pointA = points[polys[i].a];
+        verts->x = pointA.x;
+        verts->y = pointA.y;
+        verts->z = pointA.z;
+        ++verts;
+
+        ai_assert(polys[i].b < pointCount && polys[i].b >= 0);
+        const Vector& pointB = points[polys[i].b];
+        verts->x = pointB.x;
+        verts->y = pointB.y;
+        verts->z = pointB.z;
+        ++verts;
+
+        ai_assert(polys[i].c < pointCount && polys[i].c >= 0);
+        const Vector& pointC = points[polys[i].c];
+        verts->x = pointC.x;
+        verts->y = pointC.y;
+        verts->z = pointC.z;
+        ++verts;
+
+        // TODO: do we also need to handle lines or points with similar checks?
+        if (polys[i].c != polys[i].d)
+        {
+            ai_assert(polys[i].d < pointCount && polys[i].d >= 0);
+
+            face->mNumIndices = 4;
+            mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON;
+            const Vector& pointD = points[polys[i].d];
+            verts->x = pointD.x;
+            verts->y = pointD.y;
+            verts->z = pointD.z;
+            ++verts;
+        }
+        else {
+            face->mNumIndices = 3;
+        }
+        face->mIndices = new unsigned int[face->mNumIndices];
+        for(unsigned int j = 0; j < face->mNumIndices; ++j) {
+            face->mIndices[j] = n++;
+        }
+
+        // copy normals
+        if (normals_src) {
+            if(i >= normals_src->GetDataCount()) {
+                LogError("unexpected number of normals, ignoring");
+            }
+            else {
+                ConstNormalHandle normal_handle = normals_src->GetDataAddressR();
+                NormalStruct nor;
+                NormalTag::Get(normal_handle, i, nor);
+                normals->x = nor.a.x;
+                normals->y = nor.a.y;
+                normals->z = nor.a.z;
+                ++normals;
+
+                normals->x = nor.b.x;
+                normals->y = nor.b.y;
+                normals->z = nor.b.z;
+                ++normals;
+
+                normals->x = nor.c.x;
+                normals->y = nor.c.y;
+                normals->z = nor.c.z;
+                ++normals;
+
+                if(face->mNumIndices == 4) {
+                    normals->x = nor.d.x;
+                    normals->y = nor.d.y;
+                    normals->z = nor.d.z;
+                    ++normals;
+                }
+            }
+        }
+
+        // copy tangents and bitangents
+        if (tangents_src) {
+
+            for(unsigned int k = 0; k < face->mNumIndices; ++k) {
+                LONG l;
+                switch(k) {
+                case 0:
+                    l = polys[i].a;
+                    break;
+                case 1:
+                    l = polys[i].b;
+                    break;
+                case 2:
+                    l = polys[i].c;
+                    break;
+                case 3:
+                    l = polys[i].d;
+                    break;
+                default:
+                    ai_assert(false);
+                }
+                if(l >= tangents_src->GetDataCount()) {
+                    LogError("unexpected number of tangents, ignoring");
+                    break;
+                }
+
+                Tangent tan = tangents_src->GetDataR()[l];
+                tangents->x = tan.vl.x;
+                tangents->y = tan.vl.y;
+                tangents->z = tan.vl.z;
+                ++tangents;
+
+                bitangents->x = tan.vr.x;
+                bitangents->y = tan.vr.y;
+                bitangents->z = tan.vr.z;
+                ++bitangents;
+            }
+        }
+
+        // copy UVs
+        if (uvs_src) {
+            if(i >= uvs_src->GetDataCount()) {
+                LogError("unexpected number of UV coordinates, ignoring");
+            }
+            else {
+                UVWStruct uvw;
+                uvs_src->Get(uvs_src->GetDataAddressR(),i,uvw);
+
+                uvs->x = uvw.a.x;
+                uvs->y = 1.0f-uvw.a.y;
+                uvs->z = uvw.a.z;
+                ++uvs;
+
+                uvs->x = uvw.b.x;
+                uvs->y = 1.0f-uvw.b.y;
+                uvs->z = uvw.b.z;
+                ++uvs;
+
+                uvs->x = uvw.c.x;
+                uvs->y = 1.0f-uvw.c.y;
+                uvs->z = uvw.c.z;
+                ++uvs;
+
+                if(face->mNumIndices == 4) {
+                    uvs->x = uvw.d.x;
+                    uvs->y = 1.0f-uvw.d.y;
+                    uvs->z = uvw.d.z;
+                    ++uvs;
+                }
+            }
+        }
+    }
+
+    mesh->mMaterialIndex = ResolveMaterial(polyObject);
+    return mesh.release();
+}
+
+
+// ------------------------------------------------------------------------------------------------
+unsigned int C4DImporter::ResolveMaterial(PolygonObject* obj)
+{
+    ai_assert(obj != NULL);
+
+    const unsigned int mat_count = static_cast<unsigned int>(materials.size());
+
+    BaseTag* tag = obj->GetTag(Ttexture);
+    if(tag == NULL) {
+        return mat_count;
+    }
+
+    TextureTag& ttag = dynamic_cast<TextureTag&>(*tag);
+
+    BaseMaterial* const mat = ttag.GetMaterial();
+    ai_assert(mat != NULL);
+
+    const MaterialMap::const_iterator it = material_mapping.find(mat);
+    if(it == material_mapping.end()) {
+        return mat_count;
+    }
+
+    ai_assert((*it).second < mat_count);
+    return (*it).second;
+}
+
+#endif // ASSIMP_BUILD_NO_C4D_IMPORTER
+

+ 123 - 0
Engine/lib/assimp/code/C4DImporter.h

@@ -0,0 +1,123 @@
+/*
+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  C4DImporter.h
+ *  @brief Declaration of the Cinema4D (*.c4d) importer class.
+ */
+#ifndef INCLUDED_AI_CINEMA_4D_LOADER_H
+#define INCLUDED_AI_CINEMA_4D_LOADER_H
+
+#include <assimp/BaseImporter.h>
+#include <assimp/LogAux.h>
+
+#include <map>
+struct aiNode;
+struct aiMesh;
+struct aiMaterial;
+
+struct aiImporterDesc;
+
+namespace melange {
+    class BaseObject; // c4d_file.h
+    class PolygonObject;
+    class BaseMaterial;
+    class BaseShader;
+}
+
+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;
+    }
+
+// -------------------------------------------------------------------------------------------
+/** Importer class to load Cinema4D files using the Melange library to be obtained from
+ *  www.plugincafe.com
+ *
+ *  Note that Melange is not free software. */
+// -------------------------------------------------------------------------------------------
+class C4DImporter : public BaseImporter, public LogFunctions<C4DImporter>
+{
+public:
+
+    C4DImporter();
+    ~C4DImporter();
+
+
+public:
+
+    // --------------------
+    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:
+
+    void ReadMaterials(melange::BaseMaterial* mat);
+    void RecurseHierarchy(melange::BaseObject* object, aiNode* parent);
+    aiMesh* ReadMesh(melange::BaseObject* object);
+    unsigned int ResolveMaterial(melange::PolygonObject* obj);
+
+    bool ReadShader(aiMaterial* out, melange::BaseShader* shader);
+
+    std::vector<aiMesh*> meshes;
+    std::vector<aiMaterial*> materials;
+
+    typedef std::map<melange::BaseMaterial*, unsigned int> MaterialMap;
+    MaterialMap material_mapping;
+
+}; // !class C4DImporter
+
+} // end of namespace Assimp
+#endif // INCLUDED_AI_CINEMA_4D_LOADER_H
+

+ 136 - 0
Engine/lib/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
Engine/lib/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
+

+ 1067 - 0
Engine/lib/assimp/code/CMakeLists.txt

@@ -0,0 +1,1067 @@
+# 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.
+#
+#----------------------------------------------------------------------
+
+# Listing and grouping of all the source files.
+# 1) Set the file lists for each component
+# 2) Create a Source Group for each component, for IDE project orginization
+# 3) Add libassimp using the file lists (eliminates duplication of file names between
+#    source groups and library command)
+#
+cmake_minimum_required( VERSION 2.6 )
+SET( HEADER_PATH ../include/assimp )
+
+if(NOT ANDROID AND ASSIMP_ANDROID_JNIIOSYSTEM)
+    message(WARNING "Requesting Android JNI I/O-System in non-Android toolchain. Resetting ASSIMP_ANDROID_JNIIOSYSTEM to OFF.")
+    set(ASSIMP_ANDROID_JNIIOSYSTEM OFF)
+endif(NOT ANDROID AND ASSIMP_ANDROID_JNIIOSYSTEM)
+
+SET( COMPILER_HEADERS
+  ${HEADER_PATH}/Compiler/pushpack1.h
+  ${HEADER_PATH}/Compiler/poppack1.h
+  ${HEADER_PATH}/Compiler/pstdint.h
+)
+SOURCE_GROUP( Compiler FILES ${COMPILER_HEADERS})
+
+SET( PUBLIC_HEADERS
+  ${HEADER_PATH}/anim.h
+  ${HEADER_PATH}/ai_assert.h
+  ${HEADER_PATH}/camera.h
+  ${HEADER_PATH}/color4.h
+  ${HEADER_PATH}/color4.inl
+  ${CMAKE_CURRENT_BINARY_DIR}/../include/assimp/config.h
+  ${HEADER_PATH}/defs.h
+  ${HEADER_PATH}/Defines.h
+  ${HEADER_PATH}/cfileio.h
+  ${HEADER_PATH}/light.h
+  ${HEADER_PATH}/material.h
+  ${HEADER_PATH}/material.inl
+  ${HEADER_PATH}/matrix3x3.h
+  ${HEADER_PATH}/matrix3x3.inl
+  ${HEADER_PATH}/matrix4x4.h
+  ${HEADER_PATH}/matrix4x4.inl
+  ${HEADER_PATH}/mesh.h
+  ${HEADER_PATH}/pbrmaterial.h
+  ${HEADER_PATH}/postprocess.h
+  ${HEADER_PATH}/quaternion.h
+  ${HEADER_PATH}/quaternion.inl
+  ${HEADER_PATH}/scene.h
+  ${HEADER_PATH}/metadata.h
+  ${HEADER_PATH}/texture.h
+  ${HEADER_PATH}/types.h
+  ${HEADER_PATH}/vector2.h
+  ${HEADER_PATH}/vector2.inl
+  ${HEADER_PATH}/vector3.h
+  ${HEADER_PATH}/vector3.inl
+  ${HEADER_PATH}/version.h
+  ${HEADER_PATH}/cimport.h
+  ${HEADER_PATH}/importerdesc.h
+  ${HEADER_PATH}/Importer.hpp
+  ${HEADER_PATH}/DefaultLogger.hpp
+  ${HEADER_PATH}/ProgressHandler.hpp
+  ${HEADER_PATH}/IOStream.hpp
+  ${HEADER_PATH}/IOSystem.hpp
+  ${HEADER_PATH}/Logger.hpp
+  ${HEADER_PATH}/LogStream.hpp
+  ${HEADER_PATH}/NullLogger.hpp
+  ${HEADER_PATH}/cexport.h
+  ${HEADER_PATH}/Exporter.hpp
+  ${HEADER_PATH}/DefaultIOStream.h
+  ${HEADER_PATH}/DefaultIOSystem.h
+  ${HEADER_PATH}/SceneCombiner.h
+  ${HEADER_PATH}/fast_atof.h
+  ${HEADER_PATH}/qnan.h
+  ${HEADER_PATH}/BaseImporter.h
+  ${HEADER_PATH}/Hash.h
+  ${HEADER_PATH}/MemoryIOWrapper.h
+  ${HEADER_PATH}/ParsingUtils.h
+  ${HEADER_PATH}/StreamReader.h
+  ${HEADER_PATH}/StreamWriter.h
+  ${HEADER_PATH}/StringComparison.h
+  ${HEADER_PATH}/StringUtils.h
+  ${HEADER_PATH}/SGSpatialSort.h
+  ${HEADER_PATH}/GenericProperty.h
+  ${HEADER_PATH}/SpatialSort.h
+  ${HEADER_PATH}/SkeletonMeshBuilder.h
+  ${HEADER_PATH}/SmoothingGroups.h
+  ${HEADER_PATH}/SmoothingGroups.inl
+  ${HEADER_PATH}/StandardShapes.h
+  ${HEADER_PATH}/RemoveComments.h
+  ${HEADER_PATH}/Subdivision.h
+  ${HEADER_PATH}/Vertex.h
+  ${HEADER_PATH}/LineSplitter.h
+  ${HEADER_PATH}/TinyFormatter.h
+  ${HEADER_PATH}/Profiler.h
+  ${HEADER_PATH}/LogAux.h
+  ${HEADER_PATH}/Bitmap.h
+  ${HEADER_PATH}/XMLTools.h
+  ${HEADER_PATH}/IOStreamBuffer.h
+  ${HEADER_PATH}/CreateAnimMesh.h
+  ${HEADER_PATH}/irrXMLWrapper.h
+  ${HEADER_PATH}/BlobIOSystem.h
+  ${HEADER_PATH}/MathFunctions.h
+  ${HEADER_PATH}/Macros.h
+  ${HEADER_PATH}/Exceptional.h
+  ${HEADER_PATH}/ByteSwapper.h
+)
+
+SET( Core_SRCS
+  Assimp.cpp
+)
+
+SET( Logging_SRCS
+  ${HEADER_PATH}/DefaultLogger.hpp
+  ${HEADER_PATH}/LogStream.hpp
+  ${HEADER_PATH}/Logger.hpp
+  ${HEADER_PATH}/NullLogger.hpp
+  Win32DebugLogStream.h
+  DefaultLogger.cpp
+  FileLogStream.h
+  StdOStreamLogStream.h
+)
+SOURCE_GROUP(Logging FILES ${Logging_SRCS})
+
+SET( Common_SRCS
+  BaseImporter.cpp
+  BaseProcess.cpp
+  BaseProcess.h
+  Importer.h
+  ScenePrivate.h
+  PostStepRegistry.cpp
+  ImporterRegistry.cpp
+  DefaultProgressHandler.h
+  DefaultIOStream.cpp
+  DefaultIOSystem.cpp
+  CInterfaceIOWrapper.cpp
+  CInterfaceIOWrapper.h
+  Importer.cpp
+  IFF.h
+  SGSpatialSort.cpp
+  VertexTriangleAdjacency.cpp
+  VertexTriangleAdjacency.h
+  SpatialSort.cpp
+  SceneCombiner.cpp
+  ScenePreprocessor.cpp
+  ScenePreprocessor.h
+  SkeletonMeshBuilder.cpp
+  SplitByBoneCountProcess.cpp
+  SplitByBoneCountProcess.h
+  StandardShapes.cpp
+  TargetAnimation.cpp
+  TargetAnimation.h
+  RemoveComments.cpp
+  Subdivision.cpp
+  scene.cpp
+  Bitmap.cpp
+  Version.cpp
+  CreateAnimMesh.cpp
+  simd.h
+  simd.cpp
+)
+SOURCE_GROUP(Common FILES ${Common_SRCS})
+
+SET( STEPParser_SRCS
+  Importer/STEPParser/STEPFileReader.h
+  Importer/STEPParser/STEPFileReader.cpp
+  Importer/STEPParser/STEPFileEncoding.cpp
+  Importer/STEPParser/STEPFileEncoding.h
+)
+SOURCE_GROUP(STEPParser FILES ${STEPParser_SRCS})
+
+IF ( ASSIMP_BUILD_NONFREE_C4D_IMPORTER )
+  SET( C4D_SRCS
+    C4DImporter.cpp
+    C4DImporter.h
+  )
+  SOURCE_GROUP( C4D FILES ${C4D_SRCS})
+ENDIF ( ASSIMP_BUILD_NONFREE_C4D_IMPORTER )
+
+# if this variable is set to TRUE, the user can manually disable importers by setting
+# ASSIMP_BUILD_XXX_IMPORTER to FALSE for each importer
+# if this variable is set to FALSE, the user can manually enable importers by setting
+# ASSIMP_BUILD_XXX_IMPORTER to TRUE for each importer
+OPTION(ASSIMP_BUILD_ALL_IMPORTERS_BY_DEFAULT "default value of all ASSIMP_BUILD_XXX_IMPORTER value" TRUE)
+
+# macro to add the CMake Option ADD_ASSIMP_IMPORTER_<name> which enables compile of loader
+# this way selective loaders can be compiled (reduces filesize + compile time)
+MACRO(ADD_ASSIMP_IMPORTER name)
+  IF (ASSIMP_BUILD_ALL_IMPORTERS_BY_DEFAULT)
+    set(ASSIMP_IMPORTER_ENABLED TRUE)
+    IF (DEFINED ASSIMP_BUILD_${name}_IMPORTER AND NOT ASSIMP_BUILD_${name}_IMPORTER)
+      set(ASSIMP_IMPORTER_ENABLED FALSE)
+    ENDIF ()
+  ELSE ()
+    set(ASSIMP_IMPORTER_ENABLED ${ASSIMP_BUILD_${name}_IMPORTER})
+  ENDIF ()
+  IF (ASSIMP_IMPORTER_ENABLED)
+    LIST(APPEND ASSIMP_LOADER_SRCS ${ARGN})
+    SET(ASSIMP_IMPORTERS_ENABLED "${ASSIMP_IMPORTERS_ENABLED} ${name}")
+    SET(${name}_SRCS ${ARGN})
+    SOURCE_GROUP(${name} FILES ${ARGN})
+  ELSE()
+    SET(${name}_SRC "")
+    SET(ASSIMP_IMPORTERS_DISABLED "${ASSIMP_IMPORTERS_DISABLED} ${name}")
+    add_definitions(-DASSIMP_BUILD_NO_${name}_IMPORTER)
+    add_definitions(-DASSIMP_BUILD_NO_${name}_EXPORTER)
+  ENDIF()
+ENDMACRO()
+
+SET(ASSIMP_LOADER_SRCS "")
+SET(ASSIMP_IMPORTERS_ENABLED "") # list of enabled importers
+SET(ASSIMP_IMPORTERS_DISABLED "") # disabled list (used to print)
+
+ADD_ASSIMP_IMPORTER( AMF
+  AMFImporter.hpp
+  AMFImporter_Macro.hpp
+  AMFImporter_Node.hpp
+  AMFImporter.cpp
+  AMFImporter_Geometry.cpp
+  AMFImporter_Material.cpp
+  AMFImporter_Postprocess.cpp
+)
+
+ADD_ASSIMP_IMPORTER( 3DS
+  3DSConverter.cpp
+  3DSHelper.h
+  3DSLoader.cpp
+  3DSLoader.h
+  3DSExporter.h
+  3DSExporter.cpp
+)
+
+ADD_ASSIMP_IMPORTER( AC
+  ACLoader.cpp
+  ACLoader.h
+)
+
+ADD_ASSIMP_IMPORTER( ASE
+  ASELoader.cpp
+  ASELoader.h
+  ASEParser.cpp
+  ASEParser.h
+)
+
+ADD_ASSIMP_IMPORTER( ASSBIN
+  AssbinExporter.h
+  AssbinExporter.cpp
+  AssbinLoader.h
+  AssbinLoader.cpp
+)
+
+ADD_ASSIMP_IMPORTER( ASSXML
+  AssxmlExporter.h
+  AssxmlExporter.cpp
+)
+
+ADD_ASSIMP_IMPORTER( B3D
+  B3DImporter.cpp
+  B3DImporter.h
+)
+
+ADD_ASSIMP_IMPORTER( BVH
+  BVHLoader.cpp
+  BVHLoader.h
+)
+
+ADD_ASSIMP_IMPORTER( COLLADA
+  ColladaHelper.h
+  ColladaLoader.cpp
+  ColladaLoader.h
+  ColladaParser.cpp
+  ColladaParser.h
+  ColladaExporter.h
+  ColladaExporter.cpp
+)
+
+ADD_ASSIMP_IMPORTER( DXF
+  DXFLoader.cpp
+  DXFLoader.h
+  DXFHelper.h
+)
+
+ADD_ASSIMP_IMPORTER( CSM
+  CSMLoader.cpp
+  CSMLoader.h
+)
+
+ADD_ASSIMP_IMPORTER( HMP
+  HMPFileData.h
+  HMPLoader.cpp
+  HMPLoader.h
+  HalfLifeFileData.h
+)
+
+ADD_ASSIMP_IMPORTER( IRRMESH
+  IRRMeshLoader.cpp
+  IRRMeshLoader.h
+  IRRShared.cpp
+  IRRShared.h
+)
+
+ADD_ASSIMP_IMPORTER( IRR
+  IRRLoader.cpp
+  IRRLoader.h
+  IRRShared.cpp
+  IRRShared.h
+)
+
+ADD_ASSIMP_IMPORTER( LWO
+  LWOAnimation.cpp
+  LWOAnimation.h
+  LWOBLoader.cpp
+  LWOFileData.h
+  LWOLoader.cpp
+  LWOLoader.h
+  LWOMaterial.cpp
+)
+
+ADD_ASSIMP_IMPORTER( LWS
+  LWSLoader.cpp
+  LWSLoader.h
+)
+
+ADD_ASSIMP_IMPORTER( MD2
+  MD2FileData.h
+  MD2Loader.cpp
+  MD2Loader.h
+  MD2NormalTable.h
+)
+
+ADD_ASSIMP_IMPORTER( MD3
+  MD3FileData.h
+  MD3Loader.cpp
+  MD3Loader.h
+)
+
+ADD_ASSIMP_IMPORTER( MD5
+  MD5Loader.cpp
+  MD5Loader.h
+  MD5Parser.cpp
+  MD5Parser.h
+)
+
+ADD_ASSIMP_IMPORTER( MDC
+  MDCFileData.h
+  MDCLoader.cpp
+  MDCLoader.h
+  MDCNormalTable.h
+)
+
+ADD_ASSIMP_IMPORTER( MDL
+  MDLDefaultColorMap.h
+  MDLFileData.h
+  MDLLoader.cpp
+  MDLLoader.h
+  MDLMaterialLoader.cpp
+)
+
+SET( MaterialSystem_SRCS
+  MaterialSystem.cpp
+  MaterialSystem.h
+)
+SOURCE_GROUP( MaterialSystem FILES ${MaterialSystem_SRCS})
+
+ADD_ASSIMP_IMPORTER( NFF
+  NFFLoader.cpp
+  NFFLoader.h
+)
+
+ADD_ASSIMP_IMPORTER( NDO
+  NDOLoader.cpp
+  NDOLoader.h
+)
+
+ADD_ASSIMP_IMPORTER( OFF
+  OFFLoader.cpp
+  OFFLoader.h
+)
+
+ADD_ASSIMP_IMPORTER( OBJ
+  ObjFileData.h
+  ObjFileImporter.cpp
+  ObjFileImporter.h
+  ObjFileMtlImporter.cpp
+  ObjFileMtlImporter.h
+  ObjFileParser.cpp
+  ObjFileParser.h
+  ObjTools.h
+  ObjExporter.h
+  ObjExporter.cpp
+)
+
+ADD_ASSIMP_IMPORTER( OGRE
+  OgreImporter.h
+  OgreStructs.h
+  OgreParsingUtils.h
+  OgreBinarySerializer.h
+  OgreXmlSerializer.h
+  OgreImporter.cpp
+  OgreStructs.cpp
+  OgreBinarySerializer.cpp
+  OgreXmlSerializer.cpp
+  OgreMaterial.cpp
+)
+
+ADD_ASSIMP_IMPORTER( OPENGEX
+  OpenGEXExporter.cpp
+  OpenGEXExporter.h
+  OpenGEXImporter.cpp
+  OpenGEXImporter.h
+  OpenGEXStructs.h
+)
+
+ADD_ASSIMP_IMPORTER( PLY
+  PlyLoader.cpp
+  PlyLoader.h
+  PlyParser.cpp
+  PlyParser.h
+  PlyExporter.cpp
+  PlyExporter.h
+)
+
+ADD_ASSIMP_IMPORTER( MS3D
+  MS3DLoader.cpp
+  MS3DLoader.h
+)
+
+ADD_ASSIMP_IMPORTER( COB
+  COBLoader.cpp
+  COBLoader.h
+  COBScene.h
+)
+
+ADD_ASSIMP_IMPORTER( BLEND
+  BlenderLoader.cpp
+  BlenderLoader.h
+  BlenderDNA.cpp
+  BlenderDNA.h
+  BlenderDNA.inl
+  BlenderScene.cpp
+  BlenderScene.h
+  BlenderSceneGen.h
+  BlenderIntermediate.h
+  BlenderModifier.h
+  BlenderModifier.cpp
+  BlenderBMesh.h
+  BlenderBMesh.cpp
+  BlenderTessellator.h
+  BlenderTessellator.cpp
+  BlenderCustomData.h
+  BlenderCustomData.cpp
+)
+
+ADD_ASSIMP_IMPORTER( IFC
+  Importer/IFC/IFCLoader.cpp
+  Importer/IFC/IFCLoader.h
+  Importer/IFC/IFCReaderGen1_2x3.cpp
+  Importer/IFC/IFCReaderGen2_2x3.cpp
+  Importer/IFC/IFCReaderGen_2x3.h
+  Importer/IFC/IFCUtil.h
+  Importer/IFC/IFCUtil.cpp
+  Importer/IFC/IFCGeometry.cpp
+  Importer/IFC/IFCMaterial.cpp
+  Importer/IFC/IFCProfile.cpp
+  Importer/IFC/IFCCurve.cpp
+  Importer/IFC/IFCBoolean.cpp
+  Importer/IFC/IFCOpenings.cpp
+)
+
+if (ASSIMP_BUILD_IFC_IMPORTER)
+  if (MSVC)
+    set_source_files_properties(Importer/IFC/IFCReaderGen1_2x3.cpp Importer/IFC/IFCReaderGen2_2x3.cpp PROPERTIES COMPILE_FLAGS "/bigobj")
+  elseif(CMAKE_COMPILER_IS_MINGW)
+    set_source_files_properties(Importer/IFC/IFCReaderGen1_2x3.cpp Importer/IFC/IFCReaderGen2_2x3.cpp PROPERTIES COMPILE_FLAGS "-O2 -Wa,-mbig-obj")
+  endif()
+endif (ASSIMP_BUILD_IFC_IMPORTER)
+
+ADD_ASSIMP_IMPORTER( XGL
+  XGLLoader.cpp
+  XGLLoader.h
+)
+
+ADD_ASSIMP_IMPORTER( FBX
+  FBXImporter.cpp
+  FBXCompileConfig.h
+  FBXImporter.h
+  FBXParser.cpp
+  FBXParser.h
+  FBXTokenizer.cpp
+  FBXTokenizer.h
+  FBXImportSettings.h
+  FBXConverter.h
+  FBXConverter.cpp
+  FBXUtil.h
+  FBXUtil.cpp
+  FBXDocument.h
+  FBXDocument.cpp
+  FBXProperties.h
+  FBXProperties.cpp
+  FBXMeshGeometry.h
+  FBXMeshGeometry.cpp
+  FBXMaterial.cpp
+  FBXModel.cpp
+  FBXAnimation.cpp
+  FBXNodeAttribute.cpp
+  FBXDeformer.cpp
+  FBXBinaryTokenizer.cpp
+  FBXDocumentUtil.cpp
+  FBXExporter.h
+  FBXExporter.cpp
+  FBXExportNode.h
+  FBXExportNode.cpp
+  FBXExportProperty.h
+  FBXExportProperty.cpp
+  FBXCommon.h
+)
+
+SET( PostProcessing_SRCS
+  CalcTangentsProcess.cpp
+  CalcTangentsProcess.h
+  ComputeUVMappingProcess.cpp
+  ComputeUVMappingProcess.h
+  ConvertToLHProcess.cpp
+  ConvertToLHProcess.h
+  EmbedTexturesProcess.cpp
+  EmbedTexturesProcess.h
+  FindDegenerates.cpp
+  FindDegenerates.h
+  FindInstancesProcess.cpp
+  FindInstancesProcess.h
+  FindInvalidDataProcess.cpp
+  FindInvalidDataProcess.h
+  FixNormalsStep.cpp
+  FixNormalsStep.h
+  DropFaceNormalsProcess.cpp
+  DropFaceNormalsProcess.h
+  GenFaceNormalsProcess.cpp
+  GenFaceNormalsProcess.h
+  GenVertexNormalsProcess.cpp
+  GenVertexNormalsProcess.h
+  PretransformVertices.cpp
+  PretransformVertices.h
+  ImproveCacheLocality.cpp
+  ImproveCacheLocality.h
+  JoinVerticesProcess.cpp
+  JoinVerticesProcess.h
+  LimitBoneWeightsProcess.cpp
+  LimitBoneWeightsProcess.h
+  RemoveRedundantMaterials.cpp
+  RemoveRedundantMaterials.h
+  RemoveVCProcess.cpp
+  RemoveVCProcess.h
+  SortByPTypeProcess.cpp
+  SortByPTypeProcess.h
+  SplitLargeMeshes.cpp
+  SplitLargeMeshes.h
+  TextureTransform.cpp
+  TextureTransform.h
+  TriangulateProcess.cpp
+  TriangulateProcess.h
+  ValidateDataStructure.cpp
+  ValidateDataStructure.h
+  OptimizeGraph.cpp
+  OptimizeGraph.h
+  OptimizeMeshes.cpp
+  OptimizeMeshes.h
+  DeboneProcess.cpp
+  DeboneProcess.h
+  ProcessHelper.h
+  ProcessHelper.cpp
+  PolyTools.h
+  MakeVerboseFormat.cpp
+  MakeVerboseFormat.h
+  ScaleProcess.cpp
+  ScaleProcess.h
+)
+SOURCE_GROUP( PostProcessing FILES ${PostProcessing_SRCS})
+
+SET( IrrXML_SRCS ${HEADER_PATH}/irrXMLWrapper.h )
+SOURCE_GROUP( IrrXML FILES ${IrrXML_SRCS})
+
+ADD_ASSIMP_IMPORTER( Q3D
+  Q3DLoader.cpp
+  Q3DLoader.h
+)
+
+ADD_ASSIMP_IMPORTER( Q3BSP
+  Q3BSPFileData.h
+  Q3BSPFileParser.h
+  Q3BSPFileParser.cpp
+  Q3BSPFileImporter.h
+  Q3BSPFileImporter.cpp
+  Q3BSPZipArchive.h
+  Q3BSPZipArchive.cpp
+)
+
+ADD_ASSIMP_IMPORTER( RAW
+  RawLoader.cpp
+  RawLoader.h
+)
+
+ADD_ASSIMP_IMPORTER( SIB
+  SIBImporter.cpp
+  SIBImporter.h
+)
+
+ADD_ASSIMP_IMPORTER( SMD
+  SMDLoader.cpp
+  SMDLoader.h
+)
+
+ADD_ASSIMP_IMPORTER( STL
+  STLLoader.cpp
+  STLLoader.h
+  STLExporter.h
+  STLExporter.cpp
+)
+
+ADD_ASSIMP_IMPORTER( TERRAGEN
+  TerragenLoader.cpp
+  TerragenLoader.h
+)
+
+ADD_ASSIMP_IMPORTER( 3D
+  UnrealLoader.cpp
+  UnrealLoader.h
+)
+
+ADD_ASSIMP_IMPORTER( X
+  XFileHelper.h
+  XFileImporter.cpp
+  XFileImporter.h
+  XFileParser.cpp
+  XFileParser.h
+  XFileExporter.h
+  XFileExporter.cpp
+)
+
+ADD_ASSIMP_IMPORTER( X3D
+  X3DExporter.cpp
+  X3DExporter.hpp
+  X3DImporter.cpp
+  X3DImporter.hpp
+  X3DImporter_Geometry2D.cpp
+  X3DImporter_Geometry3D.cpp
+  X3DImporter_Group.cpp
+  X3DImporter_Light.cpp
+  X3DImporter_Macro.hpp
+  X3DImporter_Metadata.cpp
+  X3DImporter_Networking.cpp
+  X3DImporter_Node.hpp
+  X3DImporter_Postprocess.cpp
+  X3DImporter_Rendering.cpp
+  X3DImporter_Shape.cpp
+  X3DImporter_Texturing.cpp
+  FIReader.hpp
+  FIReader.cpp
+  X3DVocabulary.cpp
+)
+
+ADD_ASSIMP_IMPORTER( GLTF
+  glTFAsset.h
+  glTFAsset.inl
+  glTFAssetWriter.h
+  glTFAssetWriter.inl
+  glTFImporter.cpp
+  glTFImporter.h
+  glTFExporter.h
+  glTFExporter.cpp
+  glTF2Asset.h
+  glTF2Asset.inl
+  glTF2AssetWriter.h
+  glTF2AssetWriter.inl
+  glTF2Importer.cpp
+  glTF2Importer.h
+  glTF2Exporter.h
+  glTF2Exporter.cpp
+)
+
+ADD_ASSIMP_IMPORTER( 3MF
+    D3MFImporter.h
+    D3MFImporter.cpp
+    D3MFExporter.h
+    D3MFExporter.cpp
+    D3MFOpcPackage.h
+    D3MFOpcPackage.cpp
+    3MFXmlTags.h
+)
+
+ADD_ASSIMP_IMPORTER( MMD
+  MMDCpp14.h
+  MMDImporter.cpp
+  MMDImporter.h
+  MMDPmdParser.h
+  MMDPmxParser.h
+  MMDPmxParser.cpp
+  MMDVmdParser.h
+)
+
+ADD_ASSIMP_IMPORTER( STEP
+    STEPFile.h
+    Importer/StepFile/StepFileImporter.h
+    Importer/StepFile/StepFileImporter.cpp
+    Importer/StepFile/StepFileGen1.cpp
+    Importer/StepFile/StepFileGen2.cpp
+    Importer/StepFile/StepFileGen3.cpp
+    Importer/StepFile/StepReaderGen.h
+    StepExporter.h
+    StepExporter.cpp
+)
+
+SET( Exporter_SRCS
+  Exporter.cpp
+  AssimpCExport.cpp
+  ${HEADER_PATH}/BlobIOSystem.h
+)
+SOURCE_GROUP( Exporter FILES ${Exporter_SRCS})
+
+SET( Extra_SRCS
+  MD4FileData.h
+)
+SOURCE_GROUP( Extra FILES ${Extra_SRCS})
+
+
+SET( Clipper_SRCS
+  ../contrib/clipper/clipper.hpp
+  ../contrib/clipper/clipper.cpp
+)
+SOURCE_GROUP( Clipper FILES ${Clipper_SRCS})
+
+SET( Poly2Tri_SRCS
+  ../contrib/poly2tri/poly2tri/common/shapes.cc
+  ../contrib/poly2tri/poly2tri/common/shapes.h
+  ../contrib/poly2tri/poly2tri/common/utils.h
+  ../contrib/poly2tri/poly2tri/sweep/advancing_front.h
+  ../contrib/poly2tri/poly2tri/sweep/advancing_front.cc
+  ../contrib/poly2tri/poly2tri/sweep/cdt.cc
+  ../contrib/poly2tri/poly2tri/sweep/cdt.h
+  ../contrib/poly2tri/poly2tri/sweep/sweep.cc
+  ../contrib/poly2tri/poly2tri/sweep/sweep.h
+  ../contrib/poly2tri/poly2tri/sweep/sweep_context.cc
+  ../contrib/poly2tri/poly2tri/sweep/sweep_context.h
+)
+SOURCE_GROUP( Poly2Tri FILES ${Poly2Tri_SRCS})
+
+SET( unzip_SRCS
+  ../contrib/unzip/crypt.h
+  ../contrib/unzip/ioapi.c
+  ../contrib/unzip/ioapi.h
+  ../contrib/unzip/unzip.c
+  ../contrib/unzip/unzip.h
+)
+SOURCE_GROUP( unzip FILES ${unzip_SRCS})
+
+SET( ziplib_SRCS
+  ../contrib/zip/src/miniz.h
+  ../contrib/zip/src/zip.c
+  ../contrib/zip/src/zip.h
+)
+
+SOURCE_GROUP( ziplib FILES ${ziplib_SRCS} )
+
+SET ( openddl_parser_SRCS
+  ../contrib/openddlparser/code/OpenDDLParser.cpp
+  ../contrib/openddlparser/code/DDLNode.cpp
+  ../contrib/openddlparser/code/OpenDDLCommon.cpp
+  ../contrib/openddlparser/code/OpenDDLExport.cpp
+  ../contrib/openddlparser/code/Value.cpp
+  ../contrib/openddlparser/code/OpenDDLStream.cpp
+  ../contrib/openddlparser/include/openddlparser/OpenDDLParser.h
+  ../contrib/openddlparser/include/openddlparser/OpenDDLParserUtils.h
+  ../contrib/openddlparser/include/openddlparser/OpenDDLCommon.h
+  ../contrib/openddlparser/include/openddlparser/OpenDDLExport.h
+  ../contrib/openddlparser/include/openddlparser/OpenDDLStream.h
+  ../contrib/openddlparser/include/openddlparser/DDLNode.h
+  ../contrib/openddlparser/include/openddlparser/Value.h
+)
+SOURCE_GROUP( openddl_parser FILES ${openddl_parser_SRCS})
+
+SET ( open3dgc_SRCS
+  ../contrib/Open3DGC/o3dgcAdjacencyInfo.h
+  ../contrib/Open3DGC/o3dgcArithmeticCodec.cpp
+  ../contrib/Open3DGC/o3dgcArithmeticCodec.h
+  ../contrib/Open3DGC/o3dgcBinaryStream.h
+  ../contrib/Open3DGC/o3dgcCommon.h
+  ../contrib/Open3DGC/o3dgcDVEncodeParams.h
+  ../contrib/Open3DGC/o3dgcDynamicVectorDecoder.cpp
+  ../contrib/Open3DGC/o3dgcDynamicVectorDecoder.h
+  ../contrib/Open3DGC/o3dgcDynamicVectorEncoder.cpp
+  ../contrib/Open3DGC/o3dgcDynamicVectorEncoder.h
+  ../contrib/Open3DGC/o3dgcDynamicVector.h
+  ../contrib/Open3DGC/o3dgcFIFO.h
+  ../contrib/Open3DGC/o3dgcIndexedFaceSet.h
+  ../contrib/Open3DGC/o3dgcIndexedFaceSet.inl
+  ../contrib/Open3DGC/o3dgcSC3DMCDecoder.h
+  ../contrib/Open3DGC/o3dgcSC3DMCDecoder.inl
+  ../contrib/Open3DGC/o3dgcSC3DMCEncodeParams.h
+  ../contrib/Open3DGC/o3dgcSC3DMCEncoder.h
+  ../contrib/Open3DGC/o3dgcSC3DMCEncoder.inl
+  ../contrib/Open3DGC/o3dgcTimer.h
+  ../contrib/Open3DGC/o3dgcTools.cpp
+  ../contrib/Open3DGC/o3dgcTriangleFans.cpp
+  ../contrib/Open3DGC/o3dgcTriangleFans.h
+  ../contrib/Open3DGC/o3dgcTriangleListDecoder.h
+  ../contrib/Open3DGC/o3dgcTriangleListDecoder.inl
+  ../contrib/Open3DGC/o3dgcTriangleListEncoder.h
+  ../contrib/Open3DGC/o3dgcTriangleListEncoder.inl
+  ../contrib/Open3DGC/o3dgcVector.h
+  ../contrib/Open3DGC/o3dgcVector.inl
+)
+SOURCE_GROUP( open3dgc FILES ${open3dgc_SRCS})
+
+# Check dependencies for glTF importer with Open3DGC-compression.
+# RT-extensions is used in "contrib/Open3DGC/o3dgcTimer.h" for collecting statistics. Pointed file
+# has implementation for different platforms: WIN32, __MACH__ and other ("else" block).
+FIND_PACKAGE(RT QUIET)
+IF (RT_FOUND OR MSVC)
+  SET( ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC 1 )
+  ADD_DEFINITIONS( -DASSIMP_IMPORTER_GLTF_USE_OPEN3DGC=1 )
+ELSE ()
+  SET (open3dgc_SRCS "")
+  MESSAGE (INFO " RT-extension not found. glTF import/export will be built without Open3DGC-compression.")
+  #!TODO: off course is better to remove statistics timers from o3dgc codec. Or propose to choose what to use.
+ENDIF ()
+
+INCLUDE_DIRECTORIES( "../contrib/rapidjson/include" )
+INCLUDE_DIRECTORIES( "../contrib" )
+
+# VC2010 fixes
+if(MSVC10)
+  option( VC10_STDINT_FIX "Fix for VC10 Compiler regarding pstdint.h redefinition errors" OFF )
+  if( VC10_STDINT_FIX )
+    ADD_DEFINITIONS( -D_STDINT )
+  endif( VC10_STDINT_FIX )
+endif(MSVC10)
+
+ADD_DEFINITIONS( -DASSIMP_BUILD_DLL_EXPORT )
+
+if ( MSVC )
+  ADD_DEFINITIONS( -D_SCL_SECURE_NO_WARNINGS )
+  ADD_DEFINITIONS( -D_CRT_SECURE_NO_WARNINGS )
+endif ( MSVC )
+
+if (UNZIP_FOUND)
+  SET (unzip_compile_SRCS "")
+else (UNZIP_FOUND)
+  SET (unzip_compile_SRCS ${unzip_SRCS})
+  INCLUDE_DIRECTORIES( "../contrib/unzip/" )
+endif (UNZIP_FOUND)
+
+MESSAGE(STATUS "Enabled formats:${ASSIMP_IMPORTERS_ENABLED}")
+MESSAGE(STATUS "Disabled formats:${ASSIMP_IMPORTERS_DISABLED}")
+
+SET( assimp_src
+  # Assimp Files
+  ${Core_SRCS}
+  ${Common_SRCS}
+  ${Logging_SRCS}
+  ${Exporter_SRCS}
+  ${PostProcessing_SRCS}
+  ${MaterialSystem_SRCS}
+  ${STEPParser_SRCS}
+#  ${Step_SRCS} check if we need a different approach
+
+  # Model Support
+  ${ASSIMP_LOADER_SRCS}
+
+  # Third-party libraries
+  ${IrrXML_SRCS}
+  ${unzip_compile_SRCS}
+  ${Poly2Tri_SRCS}
+  ${Clipper_SRCS}
+  ${openddl_parser_SRCS}
+  ${open3dgc_SRCS}
+  ${ziplib_SRCS}
+  # Necessary to show the headers in the project when using the VC++ generator:
+
+  ${PUBLIC_HEADERS}
+  ${COMPILER_HEADERS}
+
+)
+ADD_DEFINITIONS( -DOPENDDLPARSER_BUILD )
+
+INCLUDE_DIRECTORIES(
+    ${IRRXML_INCLUDE_DIR}
+    ../contrib/openddlparser/include
+)
+
+IF (ASSIMP_BUILD_NONFREE_C4D_IMPORTER)
+  SET( assimp_src ${assimp_src} ${C4D_SRCS})
+  INCLUDE_DIRECTORIES(${C4D_INCLUDES})
+ENDIF (ASSIMP_BUILD_NONFREE_C4D_IMPORTER)
+
+ADD_LIBRARY( assimp ${assimp_src} )
+ADD_LIBRARY(assimp::assimp ALIAS assimp)
+
+TARGET_INCLUDE_DIRECTORIES ( assimp PUBLIC
+  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../include>
+  $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/../include>
+  $<INSTALL_INTERFACE:${CMAKE_INSTALL_PREFIX}/include>
+)
+
+TARGET_LINK_LIBRARIES(assimp ${ZLIB_LIBRARIES} ${OPENDDL_PARSER_LIBRARIES} ${IRRXML_LIBRARY} )
+
+if(ASSIMP_ANDROID_JNIIOSYSTEM)
+  set(ASSIMP_ANDROID_JNIIOSYSTEM_PATH port/AndroidJNI)
+  add_subdirectory(../${ASSIMP_ANDROID_JNIIOSYSTEM_PATH}/ ../${ASSIMP_ANDROID_JNIIOSYSTEM_PATH}/)
+  target_link_libraries(assimp android_jniiosystem)
+endif(ASSIMP_ANDROID_JNIIOSYSTEM)
+
+IF (ASSIMP_BUILD_NONFREE_C4D_IMPORTER)
+  TARGET_LINK_LIBRARIES(assimp optimized ${C4D_RELEASE_LIBRARIES})
+  TARGET_LINK_LIBRARIES(assimp debug ${C4D_DEBUG_LIBRARIES})
+  TARGET_LINK_LIBRARIES(assimp ${C4D_EXTRA_LIBRARIES})
+ENDIF (ASSIMP_BUILD_NONFREE_C4D_IMPORTER)
+
+if( MSVC )
+  # in order to prevent DLL hell, each of the DLLs have to be suffixed with the major version and msvc prefix
+  if( MSVC70 OR MSVC71 )
+    set(MSVC_PREFIX "vc70")
+  elseif( MSVC80 )
+    set(MSVC_PREFIX "vc80")
+  elseif( MSVC90 )
+    set(MSVC_PREFIX "vc90")
+  elseif( MSVC10 )
+    set(MSVC_PREFIX "vc100")
+  elseif( MSVC11 )
+    set(MSVC_PREFIX "vc110")
+  elseif( MSVC12 )
+    set(MSVC_PREFIX "vc120")
+  elseif( MSVC14 )
+    set(MSVC_PREFIX "vc140")
+  else()
+    set(MSVC_PREFIX "vc150")
+  endif()
+  set(LIBRARY_SUFFIX "${ASSIMP_LIBRARY_SUFFIX}-${MSVC_PREFIX}-mt" CACHE STRING "the suffix for the assimp windows library")
+endif()
+
+if (${CMAKE_SYSTEM_NAME} MATCHES "WindowsStore")
+    set(WindowsStore TRUE)
+endif()
+SET_TARGET_PROPERTIES( assimp PROPERTIES
+  VERSION ${ASSIMP_VERSION}
+  SOVERSION ${ASSIMP_SOVERSION} # use full version
+  OUTPUT_NAME assimp${LIBRARY_SUFFIX}
+)
+
+if (APPLE)
+  SET_TARGET_PROPERTIES( assimp PROPERTIES
+    INSTALL_NAME_DIR "${CMAKE_INSTALL_PREFIX}/${ASSIMP_LIB_INSTALL_DIR}"
+  )
+
+  if (BUILD_FRAMEWORK)
+    SET_TARGET_PROPERTIES( assimp PROPERTIES
+      FRAMEWORK TRUE
+      FRAMEWORK_VERSION C
+      MACOSX_FRAMEWORK_IDENTIFIER net.sf.assimp
+      PUBLIC_HEADER "${PUBLIC_HEADERS}"
+    )
+
+    # PUBLIC_HEADER option does not support directory structure creation
+    # add ./Compiler/*.h to assimp.framework via copy command
+    ADD_CUSTOM_COMMAND(TARGET assimp POST_BUILD
+      COMMAND "${CMAKE_COMMAND}" -E copy_directory
+         "../${HEADER_PATH}/Compiler"
+         assimp.framework/Headers/Compiler
+      COMMENT "Copying public ./Compiler/ header files to framework bundle's Headers/Compiler/")
+  ENDIF(BUILD_FRAMEWORK)
+ENDIF(APPLE)
+
+# Build against external unzip, or add ../contrib/unzip so
+# assimp can #include "unzip.h"
+if (UNZIP_FOUND)
+  INCLUDE_DIRECTORIES(${UNZIP_INCLUDE_DIRS})
+  TARGET_LINK_LIBRARIES(assimp ${UNZIP_LIBRARIES})
+else (UNZIP_FOUND)
+  INCLUDE_DIRECTORIES("../")
+endif (UNZIP_FOUND)
+
+# Add RT-extension library for glTF importer with Open3DGC-compression.
+IF (RT_FOUND AND ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC)
+  TARGET_LINK_LIBRARIES(assimp ${RT_LIBRARY})
+ENDIF (RT_FOUND AND ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC)
+
+INSTALL( TARGETS assimp
+  LIBRARY DESTINATION ${ASSIMP_LIB_INSTALL_DIR}
+  ARCHIVE DESTINATION ${ASSIMP_LIB_INSTALL_DIR}
+  RUNTIME DESTINATION ${ASSIMP_BIN_INSTALL_DIR}
+  FRAMEWORK DESTINATION ${ASSIMP_LIB_INSTALL_DIR}
+  COMPONENT ${LIBASSIMP_COMPONENT})
+INSTALL( FILES ${PUBLIC_HEADERS} DESTINATION ${ASSIMP_INCLUDE_INSTALL_DIR}/assimp COMPONENT assimp-dev)
+INSTALL( FILES ${COMPILER_HEADERS} DESTINATION ${ASSIMP_INCLUDE_INSTALL_DIR}/assimp/Compiler COMPONENT assimp-dev)
+
+if (ASSIMP_ANDROID_JNIIOSYSTEM)
+  INSTALL(FILES ${HEADER_PATH}/${ASSIMP_ANDROID_JNIIOSYSTEM_PATH}/AndroidJNIIOSystem.h
+    DESTINATION ${ASSIMP_INCLUDE_INSTALL_DIR}
+    COMPONENT assimp-dev)
+ENDIF(ASSIMP_ANDROID_JNIIOSYSTEM)
+
+if(MSVC AND ASSIMP_INSTALL_PDB)
+  IF(CMAKE_GENERATOR MATCHES "^Visual Studio")
+    install(FILES ${Assimp_BINARY_DIR}/code/Debug/assimp${LIBRARY_SUFFIX}${CMAKE_DEBUG_POSTFIX}.pdb
+      DESTINATION ${ASSIMP_LIB_INSTALL_DIR}
+      CONFIGURATIONS Debug
+    )
+    install(FILES ${Assimp_BINARY_DIR}/code/RelWithDebInfo/assimp${LIBRARY_SUFFIX}.pdb
+      DESTINATION ${ASSIMP_LIB_INSTALL_DIR}
+      CONFIGURATIONS RelWithDebInfo
+    )
+  ELSE()
+    install(FILES ${Assimp_BINARY_DIR}/code/assimp${LIBRARY_SUFFIX}${CMAKE_DEBUG_POSTFIX}.pdb
+      DESTINATION ${ASSIMP_LIB_INSTALL_DIR}
+      CONFIGURATIONS Debug
+    )
+    install(FILES ${Assimp_BINARY_DIR}/code/assimp${LIBRARY_SUFFIX}.pdb
+      DESTINATION ${ASSIMP_LIB_INSTALL_DIR}
+      CONFIGURATIONS RelWithDebInfo
+    )
+  ENDIF()
+ENDIF ()
+
+if (ASSIMP_COVERALLS)
+    include(Coveralls)
+
+    set(COVERAGE_SRCS ${assimp_src} ${TEST_SRCS} )
+
+    # Create the coveralls target.
+    coveralls_setup(
+        "${COVERAGE_SRCS}" # The source files.
+        ON                 # If we should upload.
+        "${PROJECT_SOURCE_DIR}/cmake-modules/") # (Optional) Alternate project cmake module path.
+ENDIF()

+ 1248 - 0
Engine/lib/assimp/code/COBLoader.cpp

@@ -0,0 +1,1248 @@
+/*
+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  COBLoader.cpp
+ *  @brief Implementation of the TrueSpace COB/SCN importer class.
+ */
+
+
+#ifndef ASSIMP_BUILD_NO_COB_IMPORTER
+#include "COBLoader.h"
+#include "COBScene.h"
+#include "ConvertToLHProcess.h"
+#include <assimp/StreamReader.h>
+#include <assimp/ParsingUtils.h>
+#include <assimp/fast_atof.h>
+#include <assimp/LineSplitter.h>
+#include <assimp/TinyFormatter.h>
+#include <memory>
+#include <assimp/IOSystem.hpp>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/scene.h>
+#include <assimp/importerdesc.h>
+
+using namespace Assimp;
+using namespace Assimp::COB;
+using namespace Assimp::Formatter;
+
+
+static const float units[] = {
+    1000.f,
+    100.f,
+    1.f,
+    0.001f,
+    1.f/0.0254f,
+    1.f/0.3048f,
+    1.f/0.9144f,
+    1.f/1609.344f
+};
+
+static const aiImporterDesc desc = {
+    "TrueSpace Object Importer",
+    "",
+    "",
+    "little-endian files only",
+    aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour,
+    0,
+    0,
+    0,
+    0,
+    "cob scn"
+};
+
+
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by Importer
+COBImporter::COBImporter()
+{}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+COBImporter::~COBImporter()
+{}
+
+// ------------------------------------------------------------------------------------------------
+// Returns whether the class can handle the format of the given file.
+bool COBImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
+{
+    const std::string& extension = GetExtension(pFile);
+    if (extension == "cob" || extension == "scn" || extension == "COB" || extension == "SCN") {
+        return true;
+    }
+
+    else if ((!extension.length() || checkSig) && pIOHandler)   {
+        const char* tokens[] = {"Caligary"};
+        return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1);
+    }
+    return false;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Loader meta information
+const aiImporterDesc* COBImporter::GetInfo () const
+{
+    return &desc;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Setup configuration properties for the loader
+void COBImporter::SetupProperties(const Importer* /*pImp*/)
+{
+    // nothing to be done for the moment
+}
+
+// ------------------------------------------------------------------------------------------------
+/*static*/ AI_WONT_RETURN void COBImporter::ThrowException(const std::string& msg)
+{
+    throw DeadlyImportError("COB: "+msg);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Imports the given file into the given scene structure.
+void COBImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) {
+    COB::Scene scene;
+    std::unique_ptr<StreamReaderLE> stream(new StreamReaderLE( pIOHandler->Open(pFile,"rb")) );
+
+    // check header
+    char head[32];
+    stream->CopyAndAdvance(head,32);
+    if (strncmp(head,"Caligari ",9)) {
+        ThrowException("Could not found magic id: `Caligari`");
+    }
+
+    ASSIMP_LOG_INFO_F("File format tag: ",std::string(head+9,6));
+    if (head[16]!='L') {
+        ThrowException("File is big-endian, which is not supported");
+    }
+
+    // load data into intermediate structures
+    if (head[15]=='A') {
+        ReadAsciiFile(scene, stream.get());
+    }
+    else {
+        ReadBinaryFile(scene, stream.get());
+    }
+    if(scene.nodes.empty()) {
+        ThrowException("No nodes loaded");
+    }
+
+    // sort faces by material indices
+    for(std::shared_ptr< Node >& n : scene.nodes) {
+        if (n->type == Node::TYPE_MESH) {
+            Mesh& mesh = (Mesh&)(*n.get());
+            for(Face& f : mesh.faces) {
+                mesh.temp_map[f.material].push_back(&f);
+            }
+        }
+    }
+
+    // count meshes
+    for(std::shared_ptr< Node >& n : scene.nodes) {
+        if (n->type == Node::TYPE_MESH) {
+            Mesh& mesh = (Mesh&)(*n.get());
+            if (mesh.vertex_positions.size() && mesh.texture_coords.size()) {
+                pScene->mNumMeshes += static_cast<unsigned int>(mesh.temp_map.size());
+            }
+        }
+    }
+    pScene->mMeshes = new aiMesh*[pScene->mNumMeshes]();
+    pScene->mMaterials = new aiMaterial*[pScene->mNumMeshes]();
+    pScene->mNumMeshes = 0;
+
+    // count lights and cameras
+    for(std::shared_ptr< Node >& n : scene.nodes) {
+        if (n->type == Node::TYPE_LIGHT) {
+            ++pScene->mNumLights;
+        }
+        else if (n->type == Node::TYPE_CAMERA) {
+            ++pScene->mNumCameras;
+        }
+    }
+
+    if (pScene->mNumLights) {
+        pScene->mLights  = new aiLight*[pScene->mNumLights]();
+    }
+    if (pScene->mNumCameras) {
+        pScene->mCameras = new aiCamera*[pScene->mNumCameras]();
+    }
+    pScene->mNumLights = pScene->mNumCameras = 0;
+
+    // resolve parents by their IDs and build the output graph
+    std::unique_ptr<Node> root(new Group());
+    for(size_t n = 0; n < scene.nodes.size(); ++n) {
+        const Node& nn = *scene.nodes[n].get();
+        if(nn.parent_id==0) {
+            root->temp_children.push_back(&nn);
+        }
+
+        for(size_t m = n; m < scene.nodes.size(); ++m) {
+            const Node& mm = *scene.nodes[m].get();
+            if (mm.parent_id == nn.id) {
+                nn.temp_children.push_back(&mm);
+            }
+        }
+    }
+
+    pScene->mRootNode = BuildNodes(*root.get(),scene,pScene);
+	//flip normals after import
+    FlipWindingOrderProcess flip;
+    flip.Execute( pScene );
+}
+
+// ------------------------------------------------------------------------------------------------
+void ConvertTexture(std::shared_ptr< Texture > tex, aiMaterial* out, aiTextureType type)
+{
+    const aiString path( tex->path );
+    out->AddProperty(&path,AI_MATKEY_TEXTURE(type,0));
+    out->AddProperty(&tex->transform,1,AI_MATKEY_UVTRANSFORM(type,0));
+}
+
+// ------------------------------------------------------------------------------------------------
+aiNode* COBImporter::BuildNodes(const Node& root,const Scene& scin,aiScene* fill)
+{
+    aiNode* nd = new aiNode();
+    nd->mName.Set(root.name);
+    nd->mTransformation = root.transform;
+
+    // Note to everybody believing Voodoo is appropriate here:
+    // I know polymorphism, run as fast as you can ;-)
+    if (Node::TYPE_MESH == root.type) {
+        const Mesh& ndmesh = (const Mesh&)(root);
+        if (ndmesh.vertex_positions.size() && ndmesh.texture_coords.size()) {
+
+            typedef std::pair<unsigned int,Mesh::FaceRefList> Entry;
+            for(const Entry& reflist : ndmesh.temp_map) {
+                {   // create mesh
+                    size_t n = 0;
+                    for(Face* f : reflist.second) {
+                        n += f->indices.size();
+                    }
+                    if (!n) {
+                        continue;
+                    }
+                    aiMesh* outmesh = fill->mMeshes[fill->mNumMeshes++] = new aiMesh();
+                    ++nd->mNumMeshes;
+
+                    outmesh->mVertices = new aiVector3D[n];
+                    outmesh->mTextureCoords[0] = new aiVector3D[n];
+
+                    outmesh->mFaces = new aiFace[reflist.second.size()]();
+                    for(Face* f : reflist.second) {
+                        if (f->indices.empty()) {
+                            continue;
+                        }
+
+                        aiFace& fout = outmesh->mFaces[outmesh->mNumFaces++];
+                        fout.mIndices = new unsigned int[f->indices.size()];
+
+                        for(VertexIndex& v : f->indices) {
+                            if (v.pos_idx >= ndmesh.vertex_positions.size()) {
+                                ThrowException("Position index out of range");
+                            }
+                            if (v.uv_idx >= ndmesh.texture_coords.size()) {
+                                ThrowException("UV index out of range");
+                            }
+                            outmesh->mVertices[outmesh->mNumVertices] = ndmesh.vertex_positions[ v.pos_idx ];
+                            outmesh->mTextureCoords[0][outmesh->mNumVertices] = aiVector3D(
+                                ndmesh.texture_coords[ v.uv_idx ].x,
+                                ndmesh.texture_coords[ v.uv_idx ].y,
+                                0.f
+                            );
+
+                            fout.mIndices[fout.mNumIndices++] = outmesh->mNumVertices++;
+                        }
+                    }
+                    outmesh->mMaterialIndex = fill->mNumMaterials;
+                }{  // create material
+                    const Material* min = NULL;
+                    for(const Material& m : scin.materials) {
+                        if (m.parent_id == ndmesh.id && m.matnum == reflist.first) {
+                            min = &m;
+                            break;
+                        }
+                    }
+                    std::unique_ptr<const Material> defmat;
+                    if(!min) {
+                        ASSIMP_LOG_DEBUG(format()<<"Could not resolve material index "
+                            <<reflist.first<<" - creating default material for this slot");
+
+                        defmat.reset(min=new Material());
+                    }
+
+                    aiMaterial* mat = new aiMaterial();
+                    fill->mMaterials[fill->mNumMaterials++] = mat;
+
+                    const aiString s(format("#mat_")<<fill->mNumMeshes<<"_"<<min->matnum);
+                    mat->AddProperty(&s,AI_MATKEY_NAME);
+
+                    if(int tmp = ndmesh.draw_flags & Mesh::WIRED ? 1 : 0) {
+                        mat->AddProperty(&tmp,1,AI_MATKEY_ENABLE_WIREFRAME);
+                    }
+
+                    {   int shader;
+                        switch(min->shader)
+                        {
+                        case Material::FLAT:
+                            shader = aiShadingMode_Gouraud;
+                            break;
+
+                        case Material::PHONG:
+                            shader = aiShadingMode_Phong;
+                            break;
+
+                        case Material::METAL:
+                            shader = aiShadingMode_CookTorrance;
+                            break;
+
+                        default:
+                            ai_assert(false); // shouldn't be here
+                        }
+                        mat->AddProperty(&shader,1,AI_MATKEY_SHADING_MODEL);
+                        if(shader != aiShadingMode_Gouraud) {
+                            mat->AddProperty(&min->exp,1,AI_MATKEY_SHININESS);
+                        }
+                    }
+
+                    mat->AddProperty(&min->ior,1,AI_MATKEY_REFRACTI);
+                    mat->AddProperty(&min->rgb,1,AI_MATKEY_COLOR_DIFFUSE);
+
+                    aiColor3D c = aiColor3D(min->rgb)*min->ks;
+                    mat->AddProperty(&c,1,AI_MATKEY_COLOR_SPECULAR);
+
+                    c = aiColor3D(min->rgb)*min->ka;
+                    mat->AddProperty(&c,1,AI_MATKEY_COLOR_AMBIENT);
+
+                    // convert textures if some exist.
+                    if(min->tex_color) {
+                        ConvertTexture(min->tex_color,mat,aiTextureType_DIFFUSE);
+                    }
+                    if(min->tex_env) {
+                        ConvertTexture(min->tex_env  ,mat,aiTextureType_UNKNOWN);
+                    }
+                    if(min->tex_bump) {
+                        ConvertTexture(min->tex_bump ,mat,aiTextureType_HEIGHT);
+                    }
+                }
+            }
+        }
+    }
+    else if (Node::TYPE_LIGHT == root.type) {
+        const Light& ndlight = (const Light&)(root);
+        aiLight* outlight = fill->mLights[fill->mNumLights++] = new aiLight();
+
+        outlight->mName.Set(ndlight.name);
+        outlight->mColorDiffuse = outlight->mColorAmbient = outlight->mColorSpecular = ndlight.color;
+
+        outlight->mAngleOuterCone = AI_DEG_TO_RAD(ndlight.angle);
+        outlight->mAngleInnerCone = AI_DEG_TO_RAD(ndlight.inner_angle);
+
+        // XXX
+        outlight->mType = ndlight.ltype==Light::SPOT ? aiLightSource_SPOT : aiLightSource_DIRECTIONAL;
+    }
+    else if (Node::TYPE_CAMERA == root.type) {
+        const Camera& ndcam = (const Camera&)(root);
+        aiCamera* outcam = fill->mCameras[fill->mNumCameras++] = new aiCamera();
+
+        outcam->mName.Set(ndcam.name);
+    }
+
+    // add meshes
+    if (nd->mNumMeshes) { // mMeshes must be NULL if count is 0
+        nd->mMeshes = new unsigned int[nd->mNumMeshes];
+        for(unsigned int i = 0; i < nd->mNumMeshes;++i) {
+            nd->mMeshes[i] = fill->mNumMeshes-i-1;
+        }
+    }
+
+    // add children recursively
+    nd->mChildren = new aiNode*[root.temp_children.size()]();
+    for(const Node* n : root.temp_children) {
+        (nd->mChildren[nd->mNumChildren++] = BuildNodes(*n,scin,fill))->mParent = nd;
+    }
+
+    return nd;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Read an ASCII file into the given scene data structure
+void COBImporter::ReadAsciiFile(Scene& out, StreamReaderLE* stream)
+{
+    ChunkInfo ci;
+    for(LineSplitter splitter(*stream);splitter;++splitter) {
+
+        // add all chunks to be recognized here. /else ../ omitted intentionally.
+        if (splitter.match_start("PolH ")) {
+            ReadChunkInfo_Ascii(ci,splitter);
+            ReadPolH_Ascii(out,splitter,ci);
+        }
+        if (splitter.match_start("BitM ")) {
+            ReadChunkInfo_Ascii(ci,splitter);
+            ReadBitM_Ascii(out,splitter,ci);
+        }
+        if (splitter.match_start("Mat1 ")) {
+            ReadChunkInfo_Ascii(ci,splitter);
+            ReadMat1_Ascii(out,splitter,ci);
+        }
+        if (splitter.match_start("Grou ")) {
+            ReadChunkInfo_Ascii(ci,splitter);
+            ReadGrou_Ascii(out,splitter,ci);
+        }
+        if (splitter.match_start("Lght ")) {
+            ReadChunkInfo_Ascii(ci,splitter);
+            ReadLght_Ascii(out,splitter,ci);
+        }
+        if (splitter.match_start("Came ")) {
+            ReadChunkInfo_Ascii(ci,splitter);
+            ReadCame_Ascii(out,splitter,ci);
+        }
+        if (splitter.match_start("Bone ")) {
+            ReadChunkInfo_Ascii(ci,splitter);
+            ReadBone_Ascii(out,splitter,ci);
+        }
+        if (splitter.match_start("Chan ")) {
+            ReadChunkInfo_Ascii(ci,splitter);
+            ReadChan_Ascii(out,splitter,ci);
+        }
+        if (splitter.match_start("Unit ")) {
+            ReadChunkInfo_Ascii(ci,splitter);
+            ReadUnit_Ascii(out,splitter,ci);
+        }
+        if (splitter.match_start("END ")) {
+            // we don't need this, but I guess there is a reason this
+            // chunk has been implemented into COB for.
+            return;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void COBImporter::ReadChunkInfo_Ascii(ChunkInfo& out, const LineSplitter& splitter)
+{
+    const char* all_tokens[8];
+    splitter.get_tokens(all_tokens);
+
+    out.version = (all_tokens[1][1]-'0')*100+(all_tokens[1][3]-'0')*10+(all_tokens[1][4]-'0');
+    out.id  = strtoul10(all_tokens[3]);
+    out.parent_id = strtoul10(all_tokens[5]);
+    out.size = strtol10(all_tokens[7]);
+}
+
+// ------------------------------------------------------------------------------------------------
+void COBImporter::UnsupportedChunk_Ascii(LineSplitter& splitter, const ChunkInfo& nfo, const char* name)
+{
+    const std::string error = format("Encountered unsupported chunk: ") <<  name <<
+        " [version: "<<nfo.version<<", size: "<<nfo.size<<"]";
+
+    // we can recover if the chunk size was specified.
+    if(nfo.size != static_cast<unsigned int>(-1)) {
+        ASSIMP_LOG_ERROR(error);
+
+        // (HACK) - our current position in the stream is the beginning of the
+        // head line of the next chunk. That's fine, but the caller is going
+        // to call ++ on `splitter`, which we need to swallow to avoid
+        // missing the next line.
+        splitter.get_stream().IncPtr(nfo.size);
+        splitter.swallow_next_increment();
+    }
+    else ThrowException(error);
+}
+
+// ------------------------------------------------------------------------------------------------
+void COBImporter::ReadBasicNodeInfo_Ascii(Node& msh, LineSplitter& splitter, const ChunkInfo& /*nfo*/)
+{
+    for(;splitter;++splitter) {
+        if (splitter.match_start("Name")) {
+            msh.name = std::string(splitter[1]);
+
+            // make nice names by merging the dupe count
+            std::replace(msh.name.begin(),msh.name.end(),
+                ',','_');
+        }
+        else if (splitter.match_start("Transform")) {
+            for(unsigned int y = 0; y < 4 && ++splitter; ++y) {
+                const char* s = splitter->c_str();
+                for(unsigned int x = 0; x < 4; ++x) {
+                    SkipSpaces(&s);
+                    msh.transform[y][x] = fast_atof(&s);
+                }
+            }
+            // we need the transform chunk, so we won't return until we have it.
+            return;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+template <typename T>
+void COBImporter::ReadFloat3Tuple_Ascii(T& fill, const char** in)
+{
+    const char* rgb = *in;
+    for(unsigned int i = 0; i < 3; ++i) {
+        SkipSpaces(&rgb);
+        if (*rgb == ',')++rgb;
+        SkipSpaces(&rgb);
+
+        fill[i] = fast_atof(&rgb);
+    }
+    *in = rgb;
+}
+
+// ------------------------------------------------------------------------------------------------
+void COBImporter::ReadMat1_Ascii(Scene& out, LineSplitter& splitter, const ChunkInfo& nfo)
+{
+    if(nfo.version > 8) {
+        return UnsupportedChunk_Ascii(splitter,nfo,"Mat1");
+    }
+
+    ++splitter;
+    if (!splitter.match_start("mat# ")) {
+        ASSIMP_LOG_WARN_F( "Expected `mat#` line in `Mat1` chunk ", nfo.id );
+        return;
+    }
+
+    out.materials.push_back(Material());
+    Material& mat = out.materials.back();
+    mat = nfo;
+
+    mat.matnum = strtoul10(splitter[1]);
+    ++splitter;
+
+    if (!splitter.match_start("shader: ")) {
+        ASSIMP_LOG_WARN_F( "Expected `mat#` line in `Mat1` chunk ", nfo.id);
+        return;
+    }
+    std::string shader = std::string(splitter[1]);
+    shader = shader.substr(0,shader.find_first_of(" \t"));
+
+    if (shader == "metal") {
+        mat.shader = Material::METAL;
+    }
+    else if (shader == "phong") {
+        mat.shader = Material::PHONG;
+    }
+    else if (shader != "flat") {
+        ASSIMP_LOG_WARN_F( "Unknown value for `shader` in `Mat1` chunk ", nfo.id );
+    }
+
+    ++splitter;
+    if (!splitter.match_start("rgb ")) {
+        ASSIMP_LOG_WARN_F( "Expected `rgb` line in `Mat1` chunk ", nfo.id);
+    }
+
+    const char* rgb = splitter[1];
+    ReadFloat3Tuple_Ascii(mat.rgb,&rgb);
+
+    ++splitter;
+    if (!splitter.match_start("alpha ")) {
+        ASSIMP_LOG_WARN_F( "Expected `alpha` line in `Mat1` chunk ", nfo.id);
+    }
+
+    const char* tokens[10];
+    splitter.get_tokens(tokens);
+
+    mat.alpha   = fast_atof( tokens[1] );
+    mat.ka      = fast_atof( tokens[3] );
+    mat.ks      = fast_atof( tokens[5] );
+    mat.exp     = fast_atof( tokens[7] );
+    mat.ior     = fast_atof( tokens[9] );
+}
+
+// ------------------------------------------------------------------------------------------------
+void COBImporter::ReadUnit_Ascii(Scene& out, LineSplitter& splitter, const ChunkInfo& nfo)
+{
+    if(nfo.version > 1) {
+        return UnsupportedChunk_Ascii(splitter,nfo,"Unit");
+    }
+    ++splitter;
+    if (!splitter.match_start("Units ")) {
+        ASSIMP_LOG_WARN_F( "Expected `Units` line in `Unit` chunk ", nfo.id);
+        return;
+    }
+
+    // parent chunks preceede their childs, so we should have the
+    // corresponding chunk already.
+    for(std::shared_ptr< Node >& nd : out.nodes) {
+        if (nd->id == nfo.parent_id) {
+            const unsigned int t=strtoul10(splitter[1]);
+
+            nd->unit_scale = t>=sizeof(units)/sizeof(units[0])?(
+                ASSIMP_LOG_WARN_F(t, " is not a valid value for `Units` attribute in `Unit chunk` ", nfo.id)
+                ,1.f):units[t];
+            return;
+        }
+    }
+    ASSIMP_LOG_WARN_F( "`Unit` chunk ", nfo.id, " is a child of ", nfo.parent_id, " which does not exist");
+}
+
+// ------------------------------------------------------------------------------------------------
+void COBImporter::ReadChan_Ascii(Scene& /*out*/, LineSplitter& splitter, const ChunkInfo& nfo)
+{
+    if(nfo.version > 8) {
+        return UnsupportedChunk_Ascii(splitter,nfo,"Chan");
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void COBImporter::ReadLght_Ascii(Scene& out, LineSplitter& splitter, const ChunkInfo& nfo)
+{
+    if(nfo.version > 8) {
+        return UnsupportedChunk_Ascii(splitter,nfo,"Lght");
+    }
+
+    out.nodes.push_back(std::shared_ptr<Light>(new Light()));
+    Light& msh = (Light&)(*out.nodes.back().get());
+    msh = nfo;
+
+    ReadBasicNodeInfo_Ascii(msh,++splitter,nfo);
+
+    if (splitter.match_start("Infinite ")) {
+        msh.ltype = Light::INFINITE;
+    }
+    else if (splitter.match_start("Local ")) {
+        msh.ltype = Light::LOCAL;
+    }
+    else if (splitter.match_start("Spot ")) {
+        msh.ltype = Light::SPOT;
+    }
+    else {
+        ASSIMP_LOG_WARN_F( "Unknown kind of light source in `Lght` chunk ", nfo.id, " : ", *splitter );
+        msh.ltype = Light::SPOT;
+    }
+
+    ++splitter;
+    if (!splitter.match_start("color ")) {
+        ASSIMP_LOG_WARN_F( "Expected `color` line in `Lght` chunk ", nfo.id );
+    }
+
+    const char* rgb = splitter[1];
+    ReadFloat3Tuple_Ascii(msh.color ,&rgb);
+
+    SkipSpaces(&rgb);
+    if (strncmp(rgb,"cone angle",10)) {
+        ASSIMP_LOG_WARN_F( "Expected `cone angle` entity in `color` line in `Lght` chunk ", nfo.id );
+    }
+    SkipSpaces(rgb+10,&rgb);
+    msh.angle = fast_atof(&rgb);
+
+    SkipSpaces(&rgb);
+    if (strncmp(rgb,"inner angle",11)) {
+        ASSIMP_LOG_WARN_F( "Expected `inner angle` entity in `color` line in `Lght` chunk ", nfo.id);
+    }
+    SkipSpaces(rgb+11,&rgb);
+    msh.inner_angle = fast_atof(&rgb);
+
+    // skip the rest for we can't handle this kind of physically-based lighting information.
+}
+
+// ------------------------------------------------------------------------------------------------
+void COBImporter::ReadCame_Ascii(Scene& out, LineSplitter& splitter, const ChunkInfo& nfo)
+{
+    if(nfo.version > 2) {
+        return UnsupportedChunk_Ascii(splitter,nfo,"Came");
+    }
+
+    out.nodes.push_back(std::shared_ptr<Camera>(new Camera()));
+    Camera& msh = (Camera&)(*out.nodes.back().get());
+    msh = nfo;
+
+    ReadBasicNodeInfo_Ascii(msh,++splitter,nfo);
+
+    // skip the next line, we don't know this differenciation between a
+    // standard camera and a panoramic camera.
+    ++splitter;
+}
+
+// ------------------------------------------------------------------------------------------------
+void COBImporter::ReadBone_Ascii(Scene& out, LineSplitter& splitter, const ChunkInfo& nfo)
+{
+    if(nfo.version > 5) {
+        return UnsupportedChunk_Ascii(splitter,nfo,"Bone");
+    }
+
+    out.nodes.push_back(std::shared_ptr<Bone>(new Bone()));
+    Bone& msh = (Bone&)(*out.nodes.back().get());
+    msh = nfo;
+
+    ReadBasicNodeInfo_Ascii(msh,++splitter,nfo);
+
+    // TODO
+}
+
+// ------------------------------------------------------------------------------------------------
+void COBImporter::ReadGrou_Ascii(Scene& out, LineSplitter& splitter, const ChunkInfo& nfo)
+{
+    if(nfo.version > 1) {
+        return UnsupportedChunk_Ascii(splitter,nfo,"Grou");
+    }
+
+    out.nodes.push_back(std::shared_ptr<Group>(new Group()));
+    Group& msh = (Group&)(*out.nodes.back().get());
+    msh = nfo;
+
+    ReadBasicNodeInfo_Ascii(msh,++splitter,nfo);
+}
+
+// ------------------------------------------------------------------------------------------------
+void COBImporter::ReadPolH_Ascii(Scene& out, LineSplitter& splitter, const ChunkInfo& nfo)
+{
+    if(nfo.version > 8) {
+        return UnsupportedChunk_Ascii(splitter,nfo,"PolH");
+    }
+
+    out.nodes.push_back(std::shared_ptr<Mesh>(new Mesh()));
+    Mesh& msh = (Mesh&)(*out.nodes.back().get());
+    msh = nfo;
+
+    ReadBasicNodeInfo_Ascii(msh,++splitter,nfo);
+
+    // the chunk has a fixed order of components, but some are not interesting of us so
+    // we're just looking for keywords in arbitrary order. The end of the chunk is
+    // either the last `Face` or the `DrawFlags` attribute, depending on the format ver.
+    for(;splitter;++splitter) {
+        if (splitter.match_start("World Vertices")) {
+            const unsigned int cnt = strtoul10(splitter[2]);
+            msh.vertex_positions.resize(cnt);
+
+            for(unsigned int cur = 0;cur < cnt && ++splitter;++cur) {
+                const char* s = splitter->c_str();
+
+                aiVector3D& v = msh.vertex_positions[cur];
+
+                SkipSpaces(&s);
+                v.x = fast_atof(&s);
+                SkipSpaces(&s);
+                v.y = fast_atof(&s);
+                SkipSpaces(&s);
+                v.z = fast_atof(&s);
+            }
+        }
+        else if (splitter.match_start("Texture Vertices")) {
+            const unsigned int cnt = strtoul10(splitter[2]);
+            msh.texture_coords.resize(cnt);
+
+            for(unsigned int cur = 0;cur < cnt && ++splitter;++cur) {
+                const char* s = splitter->c_str();
+
+                aiVector2D& v = msh.texture_coords[cur];
+
+                SkipSpaces(&s);
+                v.x = fast_atof(&s);
+                SkipSpaces(&s);
+                v.y = fast_atof(&s);
+            }
+        }
+        else if (splitter.match_start("Faces")) {
+            const unsigned int cnt = strtoul10(splitter[1]);
+            msh.faces.reserve(cnt);
+
+            for(unsigned int cur = 0; cur < cnt && ++splitter ;++cur) {
+                if (splitter.match_start("Hole")) {
+                    ASSIMP_LOG_WARN( "Skipping unsupported `Hole` line" );
+                    continue;
+                }
+
+                if (!splitter.match_start("Face")) {
+                    ThrowException("Expected Face line");
+                }
+
+                msh.faces.push_back(Face());
+                Face& face = msh.faces.back();
+
+                face.indices.resize(strtoul10(splitter[2]));
+                face.flags = strtoul10(splitter[4]);
+                face.material = strtoul10(splitter[6]);
+
+                const char* s = (++splitter)->c_str();
+                for(size_t i = 0; i < face.indices.size(); ++i) {
+                    if(!SkipSpaces(&s)) {
+                        ThrowException("Expected EOL token in Face entry");
+                    }
+                    if ('<' != *s++) {
+                        ThrowException("Expected < token in Face entry");
+                    }
+                    face.indices[i].pos_idx = strtoul10(s,&s);
+                    if (',' != *s++) {
+                        ThrowException("Expected , token in Face entry");
+                    }
+                    face.indices[i].uv_idx = strtoul10(s,&s);
+                    if ('>' != *s++) {
+                        ThrowException("Expected < token in Face entry");
+                    }
+                }
+            }
+            if (nfo.version <= 4) {
+                break;
+            }
+        }
+        else if (splitter.match_start("DrawFlags")) {
+            msh.draw_flags = strtoul10(splitter[1]);
+            break;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void COBImporter::ReadBitM_Ascii(Scene& /*out*/, LineSplitter& splitter, const ChunkInfo& nfo)
+{
+    if(nfo.version > 1) {
+        return UnsupportedChunk_Ascii(splitter,nfo,"BitM");
+    }
+/*
+    "\nThumbNailHdrSize %ld"
+    "\nThumbHeader: %02hx 02hx %02hx "
+    "\nColorBufSize %ld"
+    "\nColorBufZipSize %ld"
+    "\nZippedThumbnail: %02hx 02hx %02hx "
+*/
+
+    const unsigned int head = strtoul10((++splitter)[1]);
+    if (head != sizeof(Bitmap::BitmapHeader)) {
+        ASSIMP_LOG_WARN("Unexpected ThumbNailHdrSize, skipping this chunk");
+        return;
+    }
+
+    /*union {
+        Bitmap::BitmapHeader data;
+        char opaq[sizeof Bitmap::BitmapHeader()];
+    };*/
+//  ReadHexOctets(opaq,head,(++splitter)[1]);
+}
+
+// ------------------------------------------------------------------------------------------------
+void COBImporter::ReadString_Binary(std::string& out, StreamReaderLE& reader)
+{
+    out.resize( reader.GetI2());
+    for(char& c : out) {
+        c = reader.GetI1();
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void COBImporter::ReadBasicNodeInfo_Binary(Node& msh, StreamReaderLE& reader, const ChunkInfo& /*nfo*/)
+{
+    const unsigned int dupes = reader.GetI2();
+    ReadString_Binary(msh.name,reader);
+
+    msh.name = format(msh.name)<<'_'<<dupes;
+
+    // skip local axes for the moment
+    reader.IncPtr(48);
+
+    msh.transform = aiMatrix4x4();
+    for(unsigned int y = 0; y < 3; ++y) {
+        for(unsigned int x =0; x < 4; ++x) {
+            msh.transform[y][x] = reader.GetF4();
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void COBImporter::UnsupportedChunk_Binary( StreamReaderLE& reader, const ChunkInfo& nfo, const char* name)
+{
+    const std::string error = format("Encountered unsupported chunk: ") <<  name <<
+        " [version: "<<nfo.version<<", size: "<<nfo.size<<"]";
+
+    // we can recover if the chunk size was specified.
+    if(nfo.size != static_cast<unsigned int>(-1)) {
+        ASSIMP_LOG_ERROR(error);
+        reader.IncPtr(nfo.size);
+    }
+    else ThrowException(error);
+}
+
+// ------------------------------------------------------------------------------------------------
+// tiny utility guard to aid me at staying within chunk boundaries.
+class chunk_guard {
+public:
+    chunk_guard(const COB::ChunkInfo& nfo, StreamReaderLE& reader)
+    : nfo(nfo)
+    , reader(reader)
+    , cur(reader.GetCurrentPos()) {
+    }
+
+    ~chunk_guard() {
+        // don't do anything if the size is not given
+        if(nfo.size != static_cast<unsigned int>(-1)) {
+            try {
+                reader.IncPtr( static_cast< int >( nfo.size ) - reader.GetCurrentPos() + cur );
+            } catch ( DeadlyImportError e ) {
+                // out of limit so correct the value
+                reader.IncPtr( reader.GetReadLimit() );
+            }
+        }
+    }
+
+private:
+
+    const COB::ChunkInfo& nfo;
+    StreamReaderLE& reader;
+    long cur;
+};
+
+// ------------------------------------------------------------------------------------------------
+void COBImporter::ReadBinaryFile(Scene& out, StreamReaderLE* reader)
+{
+    while(1) {
+        std::string type;
+         type += reader -> GetI1()
+        ,type += reader -> GetI1()
+        ,type += reader -> GetI1()
+        ,type += reader -> GetI1()
+        ;
+
+        ChunkInfo nfo;
+        nfo.version  = reader -> GetI2()*10;
+        nfo.version += reader -> GetI2();
+
+        nfo.id = reader->GetI4();
+        nfo.parent_id = reader->GetI4();
+        nfo.size = reader->GetI4();
+
+        if (type == "PolH") {
+            ReadPolH_Binary(out,*reader,nfo);
+        }
+        else if (type == "BitM") {
+            ReadBitM_Binary(out,*reader,nfo);
+        }
+        else if (type == "Grou") {
+            ReadGrou_Binary(out,*reader,nfo);
+        }
+        else if (type == "Lght") {
+            ReadLght_Binary(out,*reader,nfo);
+        }
+        else if (type == "Came") {
+            ReadCame_Binary(out,*reader,nfo);
+        }
+        else if (type == "Mat1") {
+            ReadMat1_Binary(out,*reader,nfo);
+        }
+    /*  else if (type == "Bone") {
+            ReadBone_Binary(out,*reader,nfo);
+        }
+        else if (type == "Chan") {
+            ReadChan_Binary(out,*reader,nfo);
+        }*/
+        else if (type == "Unit") {
+            ReadUnit_Binary(out,*reader,nfo);
+        }
+        else if (type == "OLay") {
+            // ignore layer index silently.
+            if(nfo.size != static_cast<unsigned int>(-1) ) {
+                reader->IncPtr(nfo.size);
+            }
+            else return UnsupportedChunk_Binary(*reader,nfo,type.c_str());
+        }
+        else if (type == "END ") {
+            return;
+        }
+        else UnsupportedChunk_Binary(*reader,nfo,type.c_str());
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void COBImporter::ReadPolH_Binary(COB::Scene& out, StreamReaderLE& reader, const ChunkInfo& nfo)
+{
+    if(nfo.version > 8) {
+        return UnsupportedChunk_Binary(reader,nfo,"PolH");
+    }
+    const chunk_guard cn(nfo,reader);
+
+    out.nodes.push_back(std::shared_ptr<Mesh>(new Mesh()));
+    Mesh& msh = (Mesh&)(*out.nodes.back().get());
+    msh = nfo;
+
+    ReadBasicNodeInfo_Binary(msh,reader,nfo);
+
+    msh.vertex_positions.resize(reader.GetI4());
+    for(aiVector3D& v : msh.vertex_positions) {
+        v.x = reader.GetF4();
+        v.y = reader.GetF4();
+        v.z = reader.GetF4();
+    }
+
+    msh.texture_coords.resize(reader.GetI4());
+    for(aiVector2D& v : msh.texture_coords) {
+        v.x = reader.GetF4();
+        v.y = reader.GetF4();
+    }
+
+    const size_t numf = reader.GetI4();
+    msh.faces.reserve(numf);
+    for(size_t i = 0; i < numf; ++i) {
+        // XXX backface culling flag is 0x10 in flags
+
+        // hole?
+        bool hole;
+        if ((hole = (reader.GetI1() & 0x08) != 0)) {
+            // XXX Basically this should just work fine - then triangulator
+            // should output properly triangulated data even for polygons
+            // with holes. Test data specific to COB is needed to confirm it.
+            if (msh.faces.empty()) {
+                ThrowException(format("A hole is the first entity in the `PolH` chunk with id ") << nfo.id);
+            }
+        }
+        else msh.faces.push_back(Face());
+        Face& f = msh.faces.back();
+
+        const size_t num = reader.GetI2();
+        f.indices.reserve(f.indices.size() + num);
+
+        if(!hole) {
+            f.material = reader.GetI2();
+            f.flags = 0;
+        }
+
+        for(size_t x = 0; x < num; ++x) {
+            f.indices.push_back(VertexIndex());
+
+            VertexIndex& v = f.indices.back();
+            v.pos_idx = reader.GetI4();
+            v.uv_idx = reader.GetI4();
+        }
+
+        if(hole) {
+            std::reverse(f.indices.rbegin(),f.indices.rbegin()+num);
+        }
+    }
+    if (nfo.version>4) {
+        msh.draw_flags = reader.GetI4();
+    }
+    nfo.version>5 && nfo.version<8 ? reader.GetI4() : 0;
+}
+
+// ------------------------------------------------------------------------------------------------
+void COBImporter::ReadBitM_Binary(COB::Scene& /*out*/, StreamReaderLE& reader, const ChunkInfo& nfo)
+{
+    if(nfo.version > 1) {
+        return UnsupportedChunk_Binary(reader,nfo,"BitM");
+    }
+
+    const chunk_guard cn(nfo,reader);
+
+    const uint32_t len = reader.GetI4();
+    reader.IncPtr(len);
+
+    reader.GetI4();
+    reader.IncPtr(reader.GetI4());
+}
+
+// ------------------------------------------------------------------------------------------------
+void COBImporter::ReadMat1_Binary(COB::Scene& out, StreamReaderLE& reader, const ChunkInfo& nfo)
+{
+    if(nfo.version > 8) {
+        return UnsupportedChunk_Binary(reader,nfo,"Mat1");
+    }
+
+    const chunk_guard cn(nfo,reader);
+
+    out.materials.push_back(Material());
+    Material& mat = out.materials.back();
+    mat = nfo;
+
+    mat.matnum = reader.GetI2();
+    switch(reader.GetI1()) {
+        case 'f':
+            mat.type = Material::FLAT;
+            break;
+        case 'p':
+            mat.type = Material::PHONG;
+            break;
+        case 'm':
+            mat.type = Material::METAL;
+            break;
+        default:
+            ASSIMP_LOG_ERROR_F( "Unrecognized shader type in `Mat1` chunk with id ", nfo.id );
+            mat.type = Material::FLAT;
+    }
+
+    switch(reader.GetI1()) {
+        case 'f':
+            mat.autofacet = Material::FACETED;
+            break;
+        case 'a':
+            mat.autofacet = Material::AUTOFACETED;
+            break;
+        case 's':
+            mat.autofacet = Material::SMOOTH;
+            break;
+        default:
+            ASSIMP_LOG_ERROR_F( "Unrecognized faceting mode in `Mat1` chunk with id ", nfo.id );
+            mat.autofacet = Material::FACETED;
+    }
+    mat.autofacet_angle = static_cast<float>(reader.GetI1());
+
+    mat.rgb.r = reader.GetF4();
+    mat.rgb.g = reader.GetF4();
+    mat.rgb.b = reader.GetF4();
+
+    mat.alpha = reader.GetF4();
+    mat.ka    = reader.GetF4();
+    mat.ks    = reader.GetF4();
+    mat.exp   = reader.GetF4();
+    mat.ior   = reader.GetF4();
+
+    char id[2];
+    id[0] = reader.GetI1(),id[1] = reader.GetI1();
+
+    if (id[0] == 'e' && id[1] == ':') {
+        mat.tex_env.reset(new Texture());
+
+        reader.GetI1();
+        ReadString_Binary(mat.tex_env->path,reader);
+
+        // advance to next texture-id
+        id[0] = reader.GetI1(),id[1] = reader.GetI1();
+    }
+
+    if (id[0] == 't' && id[1] == ':') {
+        mat.tex_color.reset(new Texture());
+
+        reader.GetI1();
+        ReadString_Binary(mat.tex_color->path,reader);
+
+        mat.tex_color->transform.mTranslation.x = reader.GetF4();
+        mat.tex_color->transform.mTranslation.y = reader.GetF4();
+
+        mat.tex_color->transform.mScaling.x = reader.GetF4();
+        mat.tex_color->transform.mScaling.y = reader.GetF4();
+
+        // advance to next texture-id
+        id[0] = reader.GetI1(),id[1] = reader.GetI1();
+    }
+
+    if (id[0] == 'b' && id[1] == ':') {
+        mat.tex_bump.reset(new Texture());
+
+        reader.GetI1();
+        ReadString_Binary(mat.tex_bump->path,reader);
+
+        mat.tex_bump->transform.mTranslation.x = reader.GetF4();
+        mat.tex_bump->transform.mTranslation.y = reader.GetF4();
+
+        mat.tex_bump->transform.mScaling.x = reader.GetF4();
+        mat.tex_bump->transform.mScaling.y = reader.GetF4();
+
+        // skip amplitude for I don't know its purpose.
+        reader.GetF4();
+    }
+    reader.IncPtr(-2);
+}
+
+// ------------------------------------------------------------------------------------------------
+void COBImporter::ReadCame_Binary(COB::Scene& out, StreamReaderLE& reader, const ChunkInfo& nfo)
+{
+    if(nfo.version > 2) {
+        return UnsupportedChunk_Binary(reader,nfo,"Came");
+    }
+
+    const chunk_guard cn(nfo,reader);
+
+    out.nodes.push_back(std::shared_ptr<Camera>(new Camera()));
+    Camera& msh = (Camera&)(*out.nodes.back().get());
+    msh = nfo;
+
+    ReadBasicNodeInfo_Binary(msh,reader,nfo);
+
+    // the rest is not interesting for us, so we skip over it.
+    if(nfo.version > 1) {
+        if (reader.GetI2()==512) {
+            reader.IncPtr(42);
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void COBImporter::ReadLght_Binary(COB::Scene& out, StreamReaderLE& reader, const ChunkInfo& nfo)
+{
+    if(nfo.version > 2) {
+        return UnsupportedChunk_Binary(reader,nfo,"Lght");
+    }
+
+    const chunk_guard cn(nfo,reader);
+
+    out.nodes.push_back(std::shared_ptr<Light>(new Light()));
+    Light& msh = (Light&)(*out.nodes.back().get());
+    msh = nfo;
+
+    ReadBasicNodeInfo_Binary(msh,reader,nfo);
+}
+
+// ------------------------------------------------------------------------------------------------
+void COBImporter::ReadGrou_Binary(COB::Scene& out, StreamReaderLE& reader, const ChunkInfo& nfo)
+{
+    if(nfo.version > 2) {
+        return UnsupportedChunk_Binary(reader,nfo,"Grou");
+    }
+
+    const chunk_guard cn(nfo,reader);
+
+    out.nodes.push_back(std::shared_ptr<Group>(new Group()));
+    Group& msh = (Group&)(*out.nodes.back().get());
+    msh = nfo;
+
+    ReadBasicNodeInfo_Binary(msh,reader,nfo);
+}
+
+// ------------------------------------------------------------------------------------------------
+void COBImporter::ReadUnit_Binary(COB::Scene& out, StreamReaderLE& reader, const ChunkInfo& nfo)
+{
+     if(nfo.version > 1) {
+        return UnsupportedChunk_Binary(reader,nfo,"Unit");
+    }
+
+     const chunk_guard cn(nfo,reader);
+
+    // parent chunks preceede their childs, so we should have the
+    // corresponding chunk already.
+    for(std::shared_ptr< Node >& nd : out.nodes) {
+        if (nd->id == nfo.parent_id) {
+            const unsigned int t=reader.GetI2();
+            nd->unit_scale = t>=sizeof(units)/sizeof(units[0])?(
+                ASSIMP_LOG_WARN_F(t," is not a valid value for `Units` attribute in `Unit chunk` ", nfo.id)
+                ,1.f):units[t];
+
+            return;
+        }
+    }
+    ASSIMP_LOG_WARN_F( "`Unit` chunk ", nfo.id, " is a child of ", nfo.parent_id, " which does not exist");
+}
+
+#endif // ASSIMP_BUILD_NO_COB_IMPORTER

+ 156 - 0
Engine/lib/assimp/code/COBLoader.h

@@ -0,0 +1,156 @@
+/*
+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  COBLoader.h
+ *  @brief Declaration of the TrueSpace (*.cob,*.scn) importer class.
+ */
+#ifndef INCLUDED_AI_COB_LOADER_H
+#define INCLUDED_AI_COB_LOADER_H
+
+#include <assimp/BaseImporter.h>
+#include <assimp/StreamReader.h>
+
+struct aiNode;
+
+namespace Assimp    {
+    class LineSplitter;
+
+    // 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;
+    }
+
+    // COBScene.h
+    namespace COB {
+        struct ChunkInfo;
+        struct Node;
+        struct Scene;
+    }
+
+// -------------------------------------------------------------------------------------------
+/** Importer class to load TrueSpace files (cob,scn) up to v6.
+ *
+ *  Currently relatively limited, loads only ASCII files and needs more test coverage. */
+// -------------------------------------------------------------------------------------------
+class COBImporter : public BaseImporter
+{
+public:
+    COBImporter();
+    ~COBImporter();
+    
+    // --------------------
+    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:
+
+    // -------------------------------------------------------------------
+    /** Prepend 'COB: ' and throw msg.*/
+    AI_WONT_RETURN static void ThrowException(const std::string& msg) AI_WONT_RETURN_SUFFIX;
+
+    // -------------------------------------------------------------------
+    /** @brief Read from an ascii scene/object file
+     *  @param out Receives output data.
+     *  @param stream Stream to read from. */
+    void ReadAsciiFile(COB::Scene& out, StreamReaderLE* stream);
+
+    // -------------------------------------------------------------------
+    /** @brief Read from a binary scene/object file
+     *  @param out Receives output data.
+     *  @param stream Stream to read from.  */
+    void ReadBinaryFile(COB::Scene& out, StreamReaderLE* stream);
+
+    // Conversion to Assimp output format
+
+    aiNode* BuildNodes(const COB::Node& root,const COB::Scene& scin,aiScene* fill);
+
+private:
+    // ASCII file support
+
+    void UnsupportedChunk_Ascii(LineSplitter& splitter, const COB::ChunkInfo& nfo, const char* name);
+    void ReadChunkInfo_Ascii(COB::ChunkInfo& out, const LineSplitter& splitter);
+    void ReadBasicNodeInfo_Ascii(COB::Node& msh, LineSplitter& splitter, const COB::ChunkInfo& nfo);
+    template <typename T> void ReadFloat3Tuple_Ascii(T& fill, const char** in);
+
+    void ReadPolH_Ascii(COB::Scene& out, LineSplitter& splitter, const COB::ChunkInfo& nfo);
+    void ReadBitM_Ascii(COB::Scene& out, LineSplitter& splitter, const COB::ChunkInfo& nfo);
+    void ReadMat1_Ascii(COB::Scene& out, LineSplitter& splitter, const COB::ChunkInfo& nfo);
+    void ReadGrou_Ascii(COB::Scene& out, LineSplitter& splitter, const COB::ChunkInfo& nfo);
+    void ReadBone_Ascii(COB::Scene& out, LineSplitter& splitter, const COB::ChunkInfo& nfo);
+    void ReadCame_Ascii(COB::Scene& out, LineSplitter& splitter, const COB::ChunkInfo& nfo);
+    void ReadLght_Ascii(COB::Scene& out, LineSplitter& splitter, const COB::ChunkInfo& nfo);
+    void ReadUnit_Ascii(COB::Scene& out, LineSplitter& splitter, const COB::ChunkInfo& nfo);
+    void ReadChan_Ascii(COB::Scene& out, LineSplitter& splitter, const COB::ChunkInfo& nfo);
+
+
+    // Binary file support
+
+    void UnsupportedChunk_Binary(StreamReaderLE& reader, const COB::ChunkInfo& nfo, const char* name);
+    void ReadString_Binary(std::string& out, StreamReaderLE& reader);
+    void ReadBasicNodeInfo_Binary(COB::Node& msh, StreamReaderLE& reader, const COB::ChunkInfo& nfo);
+
+    void ReadPolH_Binary(COB::Scene& out, StreamReaderLE& reader, const COB::ChunkInfo& nfo);
+    void ReadBitM_Binary(COB::Scene& out, StreamReaderLE& reader, const COB::ChunkInfo& nfo);
+    void ReadMat1_Binary(COB::Scene& out, StreamReaderLE& reader, const COB::ChunkInfo& nfo);
+    void ReadCame_Binary(COB::Scene& out, StreamReaderLE& reader, const COB::ChunkInfo& nfo);
+    void ReadLght_Binary(COB::Scene& out, StreamReaderLE& reader, const COB::ChunkInfo& nfo);
+    void ReadGrou_Binary(COB::Scene& out, StreamReaderLE& reader, const COB::ChunkInfo& nfo);
+    void ReadUnit_Binary(COB::Scene& out, StreamReaderLE& reader, const COB::ChunkInfo& nfo);
+
+
+}; // !class COBImporter
+
+} // end of namespace Assimp
+#endif // AI_UNREALIMPORTER_H_INC

+ 277 - 0
Engine/lib/assimp/code/COBScene.h

@@ -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  COBScene.h
+*  @brief Utilities for the COB importer.
+*/
+#ifndef INCLUDED_AI_COB_SCENE_H
+#define INCLUDED_AI_COB_SCENE_H
+
+#include <memory>
+#include <deque>
+#include <map>
+
+#include <assimp/BaseImporter.h>
+#include <assimp/material.h>
+
+namespace Assimp {
+namespace COB {
+
+// ------------------
+/** Represents a single vertex index in a face */
+struct VertexIndex
+{
+    // intentionally uninitialized
+    unsigned int pos_idx,uv_idx;
+};
+
+// ------------------
+/** COB Face data structure */
+struct Face
+{
+    // intentionally uninitialized
+    unsigned int material, flags;
+    std::vector<VertexIndex> indices;
+};
+
+// ------------------
+/** COB chunk header information */
+struct ChunkInfo
+{
+    enum {NO_SIZE=UINT_MAX};
+
+    ChunkInfo ()
+        :   id        (0)
+        ,   parent_id (0)
+        ,   version   (0)
+        ,   size      (NO_SIZE)
+    {}
+
+    // Id of this chunk, unique within file
+    unsigned int id;
+
+    // and the corresponding parent
+    unsigned int parent_id;
+
+    // version. v1.23 becomes 123
+    unsigned int version;
+
+    // chunk size in bytes, only relevant for binary files
+    // NO_SIZE is also valid.
+    unsigned int size;
+};
+
+// ------------------
+/** A node in the scenegraph */
+struct Node : public ChunkInfo
+{
+    enum Type {
+        TYPE_MESH,TYPE_GROUP,TYPE_LIGHT,TYPE_CAMERA,TYPE_BONE
+    };
+
+    virtual ~Node() {}
+    Node(Type type) : type(type), unit_scale(1.f){}
+
+    Type type;
+
+    // used during resolving
+    typedef std::deque<const Node*> ChildList;
+    mutable ChildList temp_children;
+
+    // unique name
+    std::string name;
+
+    // local mesh transformation
+    aiMatrix4x4 transform;
+
+    // scaling for this node to get to the metric system
+    float unit_scale;
+};
+
+// ------------------
+/** COB Mesh data structure */
+struct Mesh : public Node
+{
+    using ChunkInfo::operator=;
+    enum DrawFlags {
+        SOLID = 0x1,
+        TRANS = 0x2,
+        WIRED = 0x4,
+        BBOX  = 0x8,
+        HIDE  = 0x10
+    };
+
+    Mesh()
+        : Node(TYPE_MESH)
+        , draw_flags(SOLID)
+    {}
+
+    // vertex elements
+    std::vector<aiVector2D> texture_coords;
+    std::vector<aiVector3D> vertex_positions;
+
+    // face data
+    std::vector<Face> faces;
+
+    // misc. drawing flags
+    unsigned int draw_flags;
+
+    // used during resolving
+    typedef std::deque<Face*> FaceRefList;
+    typedef std::map< unsigned int,FaceRefList > TempMap;
+    TempMap temp_map;
+};
+
+// ------------------
+/** COB Group data structure */
+struct Group : public Node
+{
+    using ChunkInfo::operator=;
+    Group() : Node(TYPE_GROUP) {}
+};
+
+// ------------------
+/** COB Bone data structure */
+struct Bone : public Node
+{
+    using ChunkInfo::operator=;
+    Bone() : Node(TYPE_BONE) {}
+};
+
+// ------------------
+/** COB Light data structure */
+struct Light : public Node
+{
+    enum LightType {
+        SPOT,LOCAL,INFINITE
+    };
+
+    using ChunkInfo::operator=;
+    Light() : Node(TYPE_LIGHT),angle(),inner_angle(),ltype(SPOT) {}
+
+    aiColor3D color;
+    float angle,inner_angle;
+
+    LightType ltype;
+};
+
+// ------------------
+/** COB Camera data structure */
+struct Camera : public Node
+{
+    using ChunkInfo::operator=;
+    Camera() : Node(TYPE_CAMERA) {}
+};
+
+// ------------------
+/** COB Texture data structure */
+struct Texture
+{
+    std::string path;
+    aiUVTransform transform;
+};
+
+// ------------------
+/** COB Material data structure */
+struct Material : ChunkInfo
+{
+    using ChunkInfo::operator=;
+    enum Shader {
+        FLAT,PHONG,METAL
+    };
+
+    enum AutoFacet {
+        FACETED,AUTOFACETED,SMOOTH
+    };
+
+    Material() : alpha(),exp(),ior(),ka(),ks(1.f),
+        matnum(UINT_MAX),
+        shader(FLAT),autofacet(FACETED),
+        autofacet_angle()
+    {}
+
+    std::string type;
+
+    aiColor3D rgb;
+    float alpha, exp, ior,ka,ks;
+
+    unsigned int matnum;
+    Shader shader;
+
+    AutoFacet autofacet;
+    float autofacet_angle;
+
+    std::shared_ptr<Texture> tex_env,tex_bump,tex_color;
+};
+
+// ------------------
+/** Embedded bitmap, for instance for the thumbnail image */
+struct Bitmap : ChunkInfo
+{
+    Bitmap() : orig_size() {}
+    struct BitmapHeader
+    {
+    };
+
+    BitmapHeader head;
+    size_t orig_size;
+    std::vector<char> buff_zipped;
+};
+
+typedef std::deque< std::shared_ptr<Node> > NodeList;
+typedef std::vector< Material > MaterialList;
+
+// ------------------
+/** Represents a master COB scene, even if we loaded just a single COB file */
+struct Scene
+{
+    NodeList nodes;
+    MaterialList materials;
+
+    // becomes *0 later
+    Bitmap thumbnail;
+};
+
+    } // end COB
+} // end Assimp
+
+#endif

+ 308 - 0
Engine/lib/assimp/code/CSMLoader.cpp

@@ -0,0 +1,308 @@
+/*
+---------------------------------------------------------------------------
+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  CSMLoader.cpp
+ *  Implementation of the CSM importer class.
+ */
+
+
+
+#ifndef ASSIMP_BUILD_NO_CSM_IMPORTER
+
+#include "CSMLoader.h"
+#include <assimp/SkeletonMeshBuilder.h>
+#include <assimp/ParsingUtils.h>
+#include <assimp/fast_atof.h>
+#include <assimp/Importer.hpp>
+#include <memory>
+#include <assimp/IOSystem.hpp>
+#include <assimp/anim.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/scene.h>
+#include <assimp/importerdesc.h>
+
+using namespace Assimp;
+
+static const aiImporterDesc desc = {
+    "CharacterStudio Motion Importer (MoCap)",
+    "",
+    "",
+    "",
+    aiImporterFlags_SupportTextFlavour,
+    0,
+    0,
+    0,
+    0,
+    "csm"
+};
+
+
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by Importer
+CSMImporter::CSMImporter()
+: noSkeletonMesh()
+{}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+CSMImporter::~CSMImporter()
+{}
+
+// ------------------------------------------------------------------------------------------------
+// Returns whether the class can handle the format of the given file.
+bool CSMImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
+{
+    // check file extension
+    const std::string extension = GetExtension(pFile);
+
+    if( extension == "csm")
+        return true;
+
+    if ((checkSig || !extension.length()) && pIOHandler) {
+        const char* tokens[] = {"$Filename"};
+        return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1);
+    }
+    return false;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Build a string of all file extensions supported
+const aiImporterDesc* CSMImporter::GetInfo () const
+{
+    return &desc;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Setup configuration properties for the loader
+void CSMImporter::SetupProperties(const Importer* pImp)
+{
+    noSkeletonMesh = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_NO_SKELETON_MESHES,0) != 0;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Imports the given file into the given scene structure.
+void CSMImporter::InternReadFile( const std::string& pFile,
+    aiScene* pScene, IOSystem* pIOHandler)
+{
+    std::unique_ptr<IOStream> file( pIOHandler->Open( pFile, "rb"));
+
+    // Check whether we can read from the file
+    if( file.get() == NULL) {
+        throw DeadlyImportError( "Failed to open CSM file " + pFile + ".");
+    }
+
+    // allocate storage and copy the contents of the file to a memory buffer
+    std::vector<char> mBuffer2;
+    TextFileToBuffer(file.get(),mBuffer2);
+    const char* buffer = &mBuffer2[0];
+
+    std::unique_ptr<aiAnimation> anim(new aiAnimation());
+    int first = 0, last = 0x00ffffff;
+
+    // now process the file and look out for '$' sections
+    while (1)   {
+        SkipSpaces(&buffer);
+        if ('\0' == *buffer)
+            break;
+
+        if ('$'  == *buffer)    {
+            ++buffer;
+            if (TokenMatchI(buffer,"firstframe",10))    {
+                SkipSpaces(&buffer);
+                first = strtol10(buffer,&buffer);
+            }
+            else if (TokenMatchI(buffer,"lastframe",9))     {
+                SkipSpaces(&buffer);
+                last = strtol10(buffer,&buffer);
+            }
+            else if (TokenMatchI(buffer,"rate",4))  {
+                SkipSpaces(&buffer);
+                float d;
+                buffer = fast_atoreal_move<float>(buffer,d);
+                anim->mTicksPerSecond = d;
+            }
+            else if (TokenMatchI(buffer,"order",5)) {
+                std::vector< aiNodeAnim* > anims_temp;
+                anims_temp.reserve(30);
+                while (1)   {
+                    SkipSpaces(&buffer);
+                    if (IsLineEnd(*buffer) && SkipSpacesAndLineEnd(&buffer) && *buffer == '$')
+                        break; // next section
+
+                    // Construct a new node animation channel and setup its name
+                    anims_temp.push_back(new aiNodeAnim());
+                    aiNodeAnim* nda = anims_temp.back();
+
+                    char* ot = nda->mNodeName.data;
+                    while (!IsSpaceOrNewLine(*buffer))
+                        *ot++ = *buffer++;
+
+                    *ot = '\0';
+                    nda->mNodeName.length = (size_t)(ot-nda->mNodeName.data);
+                }
+
+                anim->mNumChannels = static_cast<unsigned int>(anims_temp.size());
+                if (!anim->mNumChannels)
+                    throw DeadlyImportError("CSM: Empty $order section");
+
+                // copy over to the output animation
+                anim->mChannels = new aiNodeAnim*[anim->mNumChannels];
+                ::memcpy(anim->mChannels,&anims_temp[0],sizeof(aiNodeAnim*)*anim->mNumChannels);
+            }
+            else if (TokenMatchI(buffer,"points",6))    {
+                if (!anim->mNumChannels)
+                    throw DeadlyImportError("CSM: \'$order\' section is required to appear prior to \'$points\'");
+
+                // If we know how many frames we'll read, we can preallocate some storage
+                unsigned int alloc = 100;
+                if (last != 0x00ffffff)
+                {
+                    alloc = last-first;
+                    alloc += alloc>>2u; // + 25%
+                    for (unsigned int i = 0; i < anim->mNumChannels;++i)
+                        anim->mChannels[i]->mPositionKeys = new aiVectorKey[alloc];
+                }
+
+                unsigned int filled = 0;
+
+                // Now read all point data.
+                while (1)   {
+                    SkipSpaces(&buffer);
+                    if (IsLineEnd(*buffer) && (!SkipSpacesAndLineEnd(&buffer) || *buffer == '$'))   {
+                        break; // next section
+                    }
+
+                    // read frame
+                    const int frame = ::strtoul10(buffer,&buffer);
+                    last  = std::max(frame,last);
+                    first = std::min(frame,last);
+                    for (unsigned int i = 0; i < anim->mNumChannels;++i)    {
+
+                        aiNodeAnim* s = anim->mChannels[i];
+                        if (s->mNumPositionKeys == alloc)   { /* need to reallocate? */
+
+                            aiVectorKey* old = s->mPositionKeys;
+                            s->mPositionKeys = new aiVectorKey[s->mNumPositionKeys = alloc*2];
+                            ::memcpy(s->mPositionKeys,old,sizeof(aiVectorKey)*alloc);
+                            delete[] old;
+                        }
+
+                        // read x,y,z
+                        if(!SkipSpacesAndLineEnd(&buffer))
+                            throw DeadlyImportError("CSM: Unexpected EOF occurred reading sample x coord");
+
+                        if (TokenMatchI(buffer, "DROPOUT", 7))  {
+                            // seems this is invalid marker data; at least the doc says it's possible
+                            ASSIMP_LOG_WARN("CSM: Encountered invalid marker data (DROPOUT)");
+                        }
+                        else    {
+                            aiVectorKey* sub = s->mPositionKeys + s->mNumPositionKeys;
+                            sub->mTime = (double)frame;
+                            buffer = fast_atoreal_move<float>(buffer, (float&)sub->mValue.x);
+
+                            if(!SkipSpacesAndLineEnd(&buffer))
+                                throw DeadlyImportError("CSM: Unexpected EOF occurred reading sample y coord");
+                            buffer = fast_atoreal_move<float>(buffer, (float&)sub->mValue.y);
+
+                            if(!SkipSpacesAndLineEnd(&buffer))
+                                throw DeadlyImportError("CSM: Unexpected EOF occurred reading sample z coord");
+                            buffer = fast_atoreal_move<float>(buffer, (float&)sub->mValue.z);
+
+                            ++s->mNumPositionKeys;
+                        }
+                    }
+
+                    // update allocation granularity
+                    if (filled == alloc)
+                        alloc *= 2;
+
+                    ++filled;
+                }
+                // all channels must be complete in order to continue safely.
+                for (unsigned int i = 0; i < anim->mNumChannels;++i)    {
+
+                    if (!anim->mChannels[i]->mNumPositionKeys)
+                        throw DeadlyImportError("CSM: Invalid marker track");
+                }
+            }
+        }
+        else    {
+            // advance to the next line
+            SkipLine(&buffer);
+        }
+    }
+
+    // Setup a proper animation duration
+    anim->mDuration = last - std::min( first, 0 );
+
+    // build a dummy root node with the tiny markers as children
+    pScene->mRootNode = new aiNode();
+    pScene->mRootNode->mName.Set("$CSM_DummyRoot");
+
+    pScene->mRootNode->mNumChildren = anim->mNumChannels;
+    pScene->mRootNode->mChildren = new aiNode* [anim->mNumChannels];
+
+    for (unsigned int i = 0; i < anim->mNumChannels;++i)    {
+        aiNodeAnim* na = anim->mChannels[i];
+
+        aiNode* nd  = pScene->mRootNode->mChildren[i] = new aiNode();
+        nd->mName   = anim->mChannels[i]->mNodeName;
+        nd->mParent = pScene->mRootNode;
+
+        aiMatrix4x4::Translation(na->mPositionKeys[0].mValue, nd->mTransformation);
+    }
+
+    // Store the one and only animation in the scene
+    pScene->mAnimations    = new aiAnimation*[pScene->mNumAnimations=1];
+    anim->mName.Set("$CSM_MasterAnim");
+    pScene->mAnimations[0] = anim.release();
+
+    // mark the scene as incomplete and run SkeletonMeshBuilder on it
+    pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
+
+    if (!noSkeletonMesh) {
+        SkeletonMeshBuilder maker(pScene,pScene->mRootNode,true);
+    }
+}
+
+#endif // !! ASSIMP_BUILD_NO_CSM_IMPORTER

+ 93 - 0
Engine/lib/assimp/code/CSMLoader.h

@@ -0,0 +1,93 @@
+/*
+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 CSMLoader.h
+ *  Declaration of the CharacterStudio Motion importer class.
+ */
+#ifndef INCLUDED_AI_CSM_LOADER_H
+#define INCLUDED_AI_CSM_LOADER_H
+
+#include <assimp/BaseImporter.h>
+
+namespace Assimp    {
+
+// ---------------------------------------------------------------------------
+/** Importer class to load MOCAPs in CharacterStudio Motion format.
+ *
+ *  A very rudimentary loader for the moment. No support for the hierarchy,
+ *  every marker is returned as child of root.
+ *
+ *  Link to file format specification:
+ *  <max_8_dvd>\samples\Motion\Docs\CSM.rtf
+*/
+class CSMImporter : public BaseImporter
+{
+public:
+    CSMImporter();
+    ~CSMImporter();
+
+
+public:
+    // -------------------------------------------------------------------
+    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:
+
+    bool noSkeletonMesh;
+
+}; // end of class CSMImporter
+} // end of namespace Assimp
+#endif // AI_AC3DIMPORTER_H_INC
+

+ 319 - 0
Engine/lib/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
Engine/lib/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

+ 1640 - 0
Engine/lib/assimp/code/ColladaExporter.cpp

@@ -0,0 +1,1640 @@
+/*
+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_COLLADA_EXPORTER
+
+#include "ColladaExporter.h"
+#include <assimp/Bitmap.h>
+#include <assimp/fast_atof.h>
+#include <assimp/SceneCombiner.h>
+#include <assimp/StringUtils.h>
+#include <assimp/XMLTools.h>
+#include <assimp/DefaultIOSystem.h>
+#include <assimp/IOSystem.hpp>
+#include <assimp/Exporter.hpp>
+#include <assimp/scene.h>
+
+#include <assimp/Exceptional.h>
+
+#include <memory>
+#include <ctime>
+#include <set>
+#include <vector>
+#include <iostream>
+
+using namespace Assimp;
+
+namespace Assimp
+{
+
+// ------------------------------------------------------------------------------------------------
+// Worker function for exporting a scene to Collada. Prototyped and registered in Exporter.cpp
+void ExportSceneCollada(const char* pFile, IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/)
+{
+    std::string path = DefaultIOSystem::absolutePath(std::string(pFile));
+    std::string file = DefaultIOSystem::completeBaseName(std::string(pFile));
+
+    // invoke the exporter
+    ColladaExporter iDoTheExportThing( pScene, pIOSystem, path, file);
+    
+    if (iDoTheExportThing.mOutput.fail()) {
+        throw DeadlyExportError("output data creation failed. Most likely the file became too large: " + std::string(pFile));
+    }
+
+    // we're still here - export successfully completed. Write result to the given IOSYstem
+    std::unique_ptr<IOStream> outfile (pIOSystem->Open(pFile,"wt"));
+    if(outfile == NULL) {
+        throw DeadlyExportError("could not open output .dae file: " + std::string(pFile));
+    }
+
+    // XXX maybe use a small wrapper around IOStream that behaves like std::stringstream in order to avoid the extra copy.
+    outfile->Write( iDoTheExportThing.mOutput.str().c_str(), static_cast<size_t>(iDoTheExportThing.mOutput.tellp()),1);
+}
+
+} // end of namespace Assimp
+
+
+
+// ------------------------------------------------------------------------------------------------
+// Constructor for a specific scene to export
+ColladaExporter::ColladaExporter( const aiScene* pScene, IOSystem* pIOSystem, const std::string& path, const std::string& file) : mIOSystem(pIOSystem), mPath(path), mFile(file)
+{
+    // make sure that all formatting happens using the standard, C locale and not the user's current locale
+    mOutput.imbue( std::locale("C") );
+    mOutput.precision(16);
+
+    mScene = pScene;
+    mSceneOwned = false;
+
+    // set up strings
+    endstr = "\n";
+
+    // start writing the file
+    WriteFile();
+}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor
+ColladaExporter::~ColladaExporter()
+{
+    if(mSceneOwned) {
+        delete mScene;
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Starts writing the contents
+void ColladaExporter::WriteFile()
+{
+    // write the DTD
+    mOutput << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>" << endstr;
+    // COLLADA element start
+    mOutput << "<COLLADA xmlns=\"http://www.collada.org/2005/11/COLLADASchema\" version=\"1.4.1\">" << endstr;
+    PushTag();
+
+    WriteTextures();
+    WriteHeader();
+
+    WriteCamerasLibrary();
+    WriteLightsLibrary();
+    WriteMaterials();
+    WriteGeometryLibrary();
+    WriteControllerLibrary();
+
+    WriteSceneLibrary();
+	
+	// customized, Writes the animation library
+	WriteAnimationsLibrary();
+
+    // useless Collada fu at the end, just in case we haven't had enough indirections, yet.
+    mOutput << startstr << "<scene>" << endstr;
+    PushTag();
+    mOutput << startstr << "<instance_visual_scene url=\"#" + XMLEscape(mScene->mRootNode->mName.C_Str()) + "\" />" << endstr;
+    PopTag();
+    mOutput << startstr << "</scene>" << endstr;
+    PopTag();
+    mOutput << "</COLLADA>" << endstr;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Writes the asset header
+void ColladaExporter::WriteHeader()
+{
+    static const ai_real epsilon = ai_real( 0.00001 );
+    static const aiQuaternion x_rot(aiMatrix3x3(
+        0, -1,  0,
+        1,  0,  0,
+        0,  0,  1));
+    static const aiQuaternion y_rot(aiMatrix3x3(
+        1,  0,  0,
+        0,  1,  0,
+        0,  0,  1));
+    static const aiQuaternion z_rot(aiMatrix3x3(
+        1,  0,  0,
+        0,  0,  1,
+        0, -1,  0));
+
+    static const unsigned int date_nb_chars = 20;
+    char date_str[date_nb_chars];
+    std::time_t date = std::time(NULL);
+    std::strftime(date_str, date_nb_chars, "%Y-%m-%dT%H:%M:%S", std::localtime(&date));
+
+    aiVector3D scaling;
+    aiQuaternion rotation;
+    aiVector3D position;
+    mScene->mRootNode->mTransformation.Decompose(scaling, rotation, position);
+    rotation.Normalize();
+
+    bool add_root_node = false;
+
+    ai_real scale = 1.0;
+    if(std::abs(scaling.x - scaling.y) <= epsilon && std::abs(scaling.x - scaling.z) <= epsilon && std::abs(scaling.y - scaling.z) <= epsilon) {
+        scale = (ai_real) ((((double) scaling.x) + ((double) scaling.y) + ((double) scaling.z)) / 3.0);
+    } else {
+        add_root_node = true;
+    }
+
+    std::string up_axis = "Y_UP";
+    if(rotation.Equal(x_rot, epsilon)) {
+        up_axis = "X_UP";
+    } else if(rotation.Equal(y_rot, epsilon)) {
+        up_axis = "Y_UP";
+    } else if(rotation.Equal(z_rot, epsilon)) {
+        up_axis = "Z_UP";
+    } else {
+        add_root_node = true;
+    }
+
+    if(! position.Equal(aiVector3D(0, 0, 0))) {
+        add_root_node = true;
+    }
+
+    if(mScene->mRootNode->mNumChildren == 0) {
+        add_root_node = true;
+    }
+
+    if(add_root_node) {
+        aiScene* scene;
+        SceneCombiner::CopyScene(&scene, mScene);
+
+        aiNode* root = new aiNode("Scene");
+
+        root->mNumChildren = 1;
+        root->mChildren = new aiNode*[root->mNumChildren];
+
+        root->mChildren[0] = scene->mRootNode;
+        scene->mRootNode->mParent = root;
+        scene->mRootNode = root;
+
+        mScene = scene;
+        mSceneOwned = true;
+
+        up_axis = "Y_UP";
+        scale = 1.0;
+    }
+
+    mOutput << startstr << "<asset>" << endstr;
+    PushTag();
+    mOutput << startstr << "<contributor>" << endstr;
+    PushTag();
+
+    aiMetadata* meta = mScene->mRootNode->mMetaData;
+    aiString value;
+    if (!meta || !meta->Get("Author", value))
+        mOutput << startstr << "<author>" << "Assimp" << "</author>" << endstr;
+    else
+        mOutput << startstr << "<author>" << XMLEscape(value.C_Str()) << "</author>" << endstr;
+
+    if (!meta || !meta->Get("AuthoringTool", value))
+        mOutput << startstr << "<authoring_tool>" << "Assimp Exporter" << "</authoring_tool>" << endstr;
+    else
+        mOutput << startstr << "<authoring_tool>" << XMLEscape(value.C_Str()) << "</authoring_tool>" << endstr;
+
+    //mOutput << startstr << "<author>" << mScene->author.C_Str() << "</author>" << endstr;
+    //mOutput << startstr << "<authoring_tool>" << mScene->authoringTool.C_Str() << "</authoring_tool>" << endstr;
+
+    PopTag();
+    mOutput << startstr << "</contributor>" << endstr;
+    mOutput << startstr << "<created>" << date_str << "</created>" << endstr;
+    mOutput << startstr << "<modified>" << date_str << "</modified>" << endstr;
+    mOutput << startstr << "<unit name=\"meter\" meter=\"" << scale << "\" />" << endstr;
+    mOutput << startstr << "<up_axis>" << up_axis << "</up_axis>" << endstr;
+    PopTag();
+    mOutput << startstr << "</asset>" << endstr;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Write the embedded textures
+void ColladaExporter::WriteTextures() {
+    static const unsigned int buffer_size = 1024;
+    char str[buffer_size];
+
+    if(mScene->HasTextures()) {
+        for(unsigned int i = 0; i < mScene->mNumTextures; i++) {
+            // It would be great to be able to create a directory in portable standard C++, but it's not the case,
+            // so we just write the textures in the current directory.
+
+            aiTexture* texture = mScene->mTextures[i];
+
+            ASSIMP_itoa10(str, buffer_size, i + 1);
+
+            std::string name = mFile + "_texture_" + (i < 1000 ? "0" : "") + (i < 100 ? "0" : "") + (i < 10 ? "0" : "") + str + "." + ((const char*) texture->achFormatHint);
+
+            std::unique_ptr<IOStream> outfile(mIOSystem->Open(mPath + name, "wb"));
+            if(outfile == NULL) {
+                throw DeadlyExportError("could not open output texture file: " + mPath + name);
+            }
+
+            if(texture->mHeight == 0) {
+                outfile->Write((void*) texture->pcData, texture->mWidth, 1);
+            } else {
+                Bitmap::Save(texture, outfile.get());
+            }
+
+            outfile->Flush();
+
+            textures.insert(std::make_pair(i, name));
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Write the embedded textures
+void ColladaExporter::WriteCamerasLibrary() {
+    if(mScene->HasCameras()) {
+
+        mOutput << startstr << "<library_cameras>" << endstr;
+        PushTag();
+
+        for( size_t a = 0; a < mScene->mNumCameras; ++a)
+            WriteCamera( a);
+
+        PopTag();
+        mOutput << startstr << "</library_cameras>" << endstr;
+
+    }
+}
+
+void ColladaExporter::WriteCamera(size_t pIndex){
+
+    const aiCamera *cam = mScene->mCameras[pIndex];
+    const std::string idstrEscaped = XMLEscape(cam->mName.C_Str());
+
+    mOutput << startstr << "<camera id=\"" << idstrEscaped << "-camera\" name=\"" << idstrEscaped << "_name\" >" << endstr;
+    PushTag();
+    mOutput << startstr << "<optics>" << endstr;
+    PushTag();
+    mOutput << startstr << "<technique_common>" << endstr;
+    PushTag();
+    //assimp doesn't support the import of orthographic cameras! se we write
+    //always perspective
+    mOutput << startstr << "<perspective>" << endstr;
+    PushTag();
+    mOutput << startstr << "<xfov sid=\"xfov\">"<<
+                                AI_RAD_TO_DEG(cam->mHorizontalFOV)
+                        <<"</xfov>" << endstr;
+    mOutput << startstr << "<aspect_ratio>"
+                        <<      cam->mAspect
+                        << "</aspect_ratio>" << endstr;
+    mOutput << startstr << "<znear sid=\"znear\">"
+                        <<      cam->mClipPlaneNear
+                        <<  "</znear>" << endstr;
+    mOutput << startstr << "<zfar sid=\"zfar\">"
+                        <<      cam->mClipPlaneFar
+                        << "</zfar>" << endstr;
+    PopTag();
+    mOutput << startstr << "</perspective>" << endstr;
+    PopTag();
+    mOutput << startstr << "</technique_common>" << endstr;
+    PopTag();
+    mOutput << startstr << "</optics>" << endstr;
+    PopTag();
+    mOutput << startstr << "</camera>" << endstr;
+
+}
+
+
+// ------------------------------------------------------------------------------------------------
+// Write the embedded textures
+void ColladaExporter::WriteLightsLibrary() {
+    if(mScene->HasLights()) {
+
+        mOutput << startstr << "<library_lights>" << endstr;
+        PushTag();
+
+        for( size_t a = 0; a < mScene->mNumLights; ++a)
+            WriteLight( a);
+
+        PopTag();
+        mOutput << startstr << "</library_lights>" << endstr;
+
+    }
+}
+
+void ColladaExporter::WriteLight(size_t pIndex){
+
+    const aiLight *light = mScene->mLights[pIndex];
+    const std::string idstrEscaped = XMLEscape(light->mName.C_Str());
+
+    mOutput << startstr << "<light id=\"" << idstrEscaped << "-light\" name=\""
+            << idstrEscaped << "_name\" >" << endstr;
+    PushTag();
+    mOutput << startstr << "<technique_common>" << endstr;
+    PushTag();
+    switch(light->mType){
+        case aiLightSource_AMBIENT:
+            WriteAmbienttLight(light);
+            break;
+        case aiLightSource_DIRECTIONAL:
+            WriteDirectionalLight(light);
+            break;
+        case aiLightSource_POINT:
+            WritePointLight(light);
+            break;
+        case aiLightSource_SPOT:
+            WriteSpotLight(light);
+            break;
+        case aiLightSource_AREA:
+        case aiLightSource_UNDEFINED:
+        case _aiLightSource_Force32Bit:
+            break;
+    }
+    PopTag();
+    mOutput << startstr << "</technique_common>" << endstr;
+
+    PopTag();
+    mOutput << startstr << "</light>" << endstr;
+
+}
+
+void ColladaExporter::WritePointLight(const aiLight *const light){
+    const aiColor3D &color=  light->mColorDiffuse;
+    mOutput << startstr << "<point>" << endstr;
+    PushTag();
+    mOutput << startstr << "<color sid=\"color\">"
+                            << color.r<<" "<<color.g<<" "<<color.b
+                        <<"</color>" << endstr;
+    mOutput << startstr << "<constant_attenuation>"
+                            << light->mAttenuationConstant
+                        <<"</constant_attenuation>" << endstr;
+    mOutput << startstr << "<linear_attenuation>"
+                            << light->mAttenuationLinear
+                        <<"</linear_attenuation>" << endstr;
+    mOutput << startstr << "<quadratic_attenuation>"
+                            << light->mAttenuationQuadratic
+                        <<"</quadratic_attenuation>" << endstr;
+
+    PopTag();
+    mOutput << startstr << "</point>" << endstr;
+
+}
+void ColladaExporter::WriteDirectionalLight(const aiLight *const light){
+    const aiColor3D &color=  light->mColorDiffuse;
+    mOutput << startstr << "<directional>" << endstr;
+    PushTag();
+    mOutput << startstr << "<color sid=\"color\">"
+                            << color.r<<" "<<color.g<<" "<<color.b
+                        <<"</color>" << endstr;
+
+    PopTag();
+    mOutput << startstr << "</directional>" << endstr;
+
+}
+void ColladaExporter::WriteSpotLight(const aiLight *const light){
+
+    const aiColor3D &color=  light->mColorDiffuse;
+    mOutput << startstr << "<spot>" << endstr;
+    PushTag();
+    mOutput << startstr << "<color sid=\"color\">"
+                            << color.r<<" "<<color.g<<" "<<color.b
+                        <<"</color>" << endstr;
+    mOutput << startstr << "<constant_attenuation>"
+                                << light->mAttenuationConstant
+                            <<"</constant_attenuation>" << endstr;
+    mOutput << startstr << "<linear_attenuation>"
+                            << light->mAttenuationLinear
+                        <<"</linear_attenuation>" << endstr;
+    mOutput << startstr << "<quadratic_attenuation>"
+                            << light->mAttenuationQuadratic
+                        <<"</quadratic_attenuation>" << endstr;
+    /*
+    out->mAngleOuterCone = AI_DEG_TO_RAD (std::acos(std::pow(0.1f,1.f/srcLight->mFalloffExponent))+
+                            srcLight->mFalloffAngle);
+    */
+
+    const ai_real fallOffAngle = AI_RAD_TO_DEG(light->mAngleInnerCone);
+    mOutput << startstr <<"<falloff_angle sid=\"fall_off_angle\">"
+                                << fallOffAngle
+                        <<"</falloff_angle>" << endstr;
+    double temp = light->mAngleOuterCone-light->mAngleInnerCone;
+
+    temp = std::cos(temp);
+    temp = std::log(temp)/std::log(0.1);
+    temp = 1/temp;
+    mOutput << startstr << "<falloff_exponent sid=\"fall_off_exponent\">"
+                            << temp
+                        <<"</falloff_exponent>" << endstr;
+
+
+    PopTag();
+    mOutput << startstr << "</spot>" << endstr;
+
+}
+
+void ColladaExporter::WriteAmbienttLight(const aiLight *const light){
+
+    const aiColor3D &color=  light->mColorAmbient;
+    mOutput << startstr << "<ambient>" << endstr;
+    PushTag();
+    mOutput << startstr << "<color sid=\"color\">"
+                            << color.r<<" "<<color.g<<" "<<color.b
+                        <<"</color>" << endstr;
+
+    PopTag();
+    mOutput << startstr << "</ambient>" << endstr;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a single surface entry from the given material keys
+void ColladaExporter::ReadMaterialSurface( Surface& poSurface, const aiMaterial* pSrcMat, aiTextureType pTexture, const char* pKey, size_t pType, size_t pIndex)
+{
+  if( pSrcMat->GetTextureCount( pTexture) > 0 )
+  {
+    aiString texfile;
+    unsigned int uvChannel = 0;
+    pSrcMat->GetTexture( pTexture, 0, &texfile, NULL, &uvChannel);
+
+    std::string index_str(texfile.C_Str());
+
+    if(index_str.size() != 0 && index_str[0] == '*')
+    {
+        unsigned int index;
+
+        index_str = index_str.substr(1, std::string::npos);
+
+        try {
+            index = (unsigned int) strtoul10_64(index_str.c_str());
+        } catch(std::exception& error) {
+            throw DeadlyExportError(error.what());
+        }
+
+        std::map<unsigned int, std::string>::const_iterator name = textures.find(index);
+
+        if(name != textures.end()) {
+            poSurface.texture = name->second;
+        } else {
+            throw DeadlyExportError("could not find embedded texture at index " + index_str);
+        }
+    } else
+    {
+        poSurface.texture = texfile.C_Str();
+    }
+
+    poSurface.channel = uvChannel;
+    poSurface.exist = true;
+  } else
+  {
+    if( pKey )
+      poSurface.exist = pSrcMat->Get( pKey, static_cast<unsigned int>(pType), static_cast<unsigned int>(pIndex), poSurface.color) == aiReturn_SUCCESS;
+  }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reimplementation of isalnum(,C locale), because AppVeyor does not see standard version.
+static bool isalnum_C(char c)
+{
+  return ( nullptr != strchr("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",c) );
+}
+
+// ------------------------------------------------------------------------------------------------
+// Writes an image entry for the given surface
+void ColladaExporter::WriteImageEntry( const Surface& pSurface, const std::string& pNameAdd)
+{
+  if( !pSurface.texture.empty() )
+  {
+    mOutput << startstr << "<image id=\"" << XMLEscape(pNameAdd) << "\">" << endstr;
+    PushTag();
+    mOutput << startstr << "<init_from>";
+
+    // URL encode image file name first, then XML encode on top
+    std::stringstream imageUrlEncoded;
+    for( std::string::const_iterator it = pSurface.texture.begin(); it != pSurface.texture.end(); ++it )
+    {
+      if( isalnum_C( (unsigned char) *it) || *it == ':' || *it == '_' || *it == '-' || *it == '.' || *it == '/' || *it == '\\' )
+        imageUrlEncoded << *it;
+      else
+        imageUrlEncoded << '%' << std::hex << size_t( (unsigned char) *it) << std::dec;
+    }
+    mOutput << XMLEscape(imageUrlEncoded.str());
+    mOutput << "</init_from>" << endstr;
+    PopTag();
+    mOutput << startstr << "</image>" << endstr;
+  }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Writes a color-or-texture entry into an effect definition
+void ColladaExporter::WriteTextureColorEntry( const Surface& pSurface, const std::string& pTypeName, const std::string& pImageName)
+{
+  if(pSurface.exist) {
+    mOutput << startstr << "<" << pTypeName << ">" << endstr;
+    PushTag();
+    if( pSurface.texture.empty() )
+    {
+      mOutput << startstr << "<color sid=\"" << pTypeName << "\">" << pSurface.color.r << "   " << pSurface.color.g << "   " << pSurface.color.b << "   " << pSurface.color.a << "</color>" << endstr;
+    }
+    else
+    {
+      mOutput << startstr << "<texture texture=\"" << XMLEscape(pImageName) << "\" texcoord=\"CHANNEL" << pSurface.channel << "\" />" << endstr;
+    }
+    PopTag();
+    mOutput << startstr << "</" << pTypeName << ">" << endstr;
+  }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Writes the two parameters necessary for referencing a texture in an effect entry
+void ColladaExporter::WriteTextureParamEntry( const Surface& pSurface, const std::string& pTypeName, const std::string& pMatName)
+{
+  // if surface is a texture, write out the sampler and the surface parameters necessary to reference the texture
+  if( !pSurface.texture.empty() )
+  {
+    mOutput << startstr << "<newparam sid=\"" << XMLEscape(pMatName) << "-" << pTypeName << "-surface\">" << endstr;
+    PushTag();
+    mOutput << startstr << "<surface type=\"2D\">" << endstr;
+    PushTag();
+    mOutput << startstr << "<init_from>" << XMLEscape(pMatName) << "-" << pTypeName << "-image</init_from>" << endstr;
+    PopTag();
+    mOutput << startstr << "</surface>" << endstr;
+    PopTag();
+    mOutput << startstr << "</newparam>" << endstr;
+
+    mOutput << startstr << "<newparam sid=\"" << XMLEscape(pMatName) << "-" << pTypeName << "-sampler\">" << endstr;
+    PushTag();
+    mOutput << startstr << "<sampler2D>" << endstr;
+    PushTag();
+    mOutput << startstr << "<source>" << XMLEscape(pMatName) << "-" << pTypeName << "-surface</source>" << endstr;
+    PopTag();
+    mOutput << startstr << "</sampler2D>" << endstr;
+    PopTag();
+    mOutput << startstr << "</newparam>" << endstr;
+  }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Writes a scalar property
+void ColladaExporter::WriteFloatEntry( const Property& pProperty, const std::string& pTypeName)
+{
+    if(pProperty.exist) {
+        mOutput << startstr << "<" << pTypeName << ">" << endstr;
+        PushTag();
+        mOutput << startstr << "<float sid=\"" << pTypeName << "\">" << pProperty.value << "</float>" << endstr;
+        PopTag();
+        mOutput << startstr << "</" << pTypeName << ">" << endstr;
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Writes the material setup
+void ColladaExporter::WriteMaterials()
+{
+  materials.resize( mScene->mNumMaterials);
+
+  /// collect all materials from the scene
+  size_t numTextures = 0;
+  for( size_t a = 0; a < mScene->mNumMaterials; ++a )
+  {
+    const aiMaterial* mat = mScene->mMaterials[a];
+
+    aiString name;
+    if( mat->Get( AI_MATKEY_NAME, name) != aiReturn_SUCCESS ) {
+      name = "mat";
+      materials[a].name = std::string( "m") + to_string(a) + name.C_Str();
+    } else {
+      // try to use the material's name if no other material has already taken it, else append #
+      std::string testName = name.C_Str();
+      size_t materialCountWithThisName = 0;
+      for( size_t i = 0; i < a; i ++ ) {
+        if( materials[i].name == testName ) {
+          materialCountWithThisName ++;
+        }
+      }
+      if( materialCountWithThisName == 0 ) {
+        materials[a].name = name.C_Str();
+      } else {
+        materials[a].name = std::string(name.C_Str()) + to_string(materialCountWithThisName);
+      }
+    }
+    for( std::string::iterator it = materials[a].name.begin(); it != materials[a].name.end(); ++it ) {
+      if( !isalnum_C( *it ) ) {
+        *it = '_';
+      }
+    }
+
+    aiShadingMode shading = aiShadingMode_Flat;
+    materials[a].shading_model = "phong";
+    if(mat->Get( AI_MATKEY_SHADING_MODEL, shading) == aiReturn_SUCCESS) {
+        if(shading == aiShadingMode_Phong) {
+            materials[a].shading_model = "phong";
+        } else if(shading == aiShadingMode_Blinn) {
+            materials[a].shading_model = "blinn";
+        } else if(shading == aiShadingMode_NoShading) {
+            materials[a].shading_model = "constant";
+        } else if(shading == aiShadingMode_Gouraud) {
+            materials[a].shading_model = "lambert";
+        }
+    }
+
+    ReadMaterialSurface( materials[a].ambient, mat, aiTextureType_AMBIENT, AI_MATKEY_COLOR_AMBIENT);
+    if( !materials[a].ambient.texture.empty() ) numTextures++;
+    ReadMaterialSurface( materials[a].diffuse, mat, aiTextureType_DIFFUSE, AI_MATKEY_COLOR_DIFFUSE);
+    if( !materials[a].diffuse.texture.empty() ) numTextures++;
+    ReadMaterialSurface( materials[a].specular, mat, aiTextureType_SPECULAR, AI_MATKEY_COLOR_SPECULAR);
+    if( !materials[a].specular.texture.empty() ) numTextures++;
+    ReadMaterialSurface( materials[a].emissive, mat, aiTextureType_EMISSIVE, AI_MATKEY_COLOR_EMISSIVE);
+    if( !materials[a].emissive.texture.empty() ) numTextures++;
+    ReadMaterialSurface( materials[a].reflective, mat, aiTextureType_REFLECTION, AI_MATKEY_COLOR_REFLECTIVE);
+    if( !materials[a].reflective.texture.empty() ) numTextures++;
+    ReadMaterialSurface( materials[a].transparent, mat, aiTextureType_OPACITY, AI_MATKEY_COLOR_TRANSPARENT);
+    if( !materials[a].transparent.texture.empty() ) numTextures++;
+    ReadMaterialSurface( materials[a].normal, mat, aiTextureType_NORMALS, NULL, 0, 0);
+    if( !materials[a].normal.texture.empty() ) numTextures++;
+
+    materials[a].shininess.exist = mat->Get( AI_MATKEY_SHININESS, materials[a].shininess.value) == aiReturn_SUCCESS;
+    materials[a].transparency.exist = mat->Get( AI_MATKEY_OPACITY, materials[a].transparency.value) == aiReturn_SUCCESS;
+    materials[a].index_refraction.exist = mat->Get( AI_MATKEY_REFRACTI, materials[a].index_refraction.value) == aiReturn_SUCCESS;
+  }
+
+  // output textures if present
+  if( numTextures > 0 )
+  {
+    mOutput << startstr << "<library_images>" << endstr;
+    PushTag();
+    for( std::vector<Material>::const_iterator it = materials.begin(); it != materials.end(); ++it )
+    {
+      const Material& mat = *it;
+      WriteImageEntry( mat.ambient, mat.name + "-ambient-image");
+      WriteImageEntry( mat.diffuse, mat.name + "-diffuse-image");
+      WriteImageEntry( mat.specular, mat.name + "-specular-image");
+      WriteImageEntry( mat.emissive, mat.name + "-emission-image");
+      WriteImageEntry( mat.reflective, mat.name + "-reflective-image");
+      WriteImageEntry( mat.transparent, mat.name + "-transparent-image");
+      WriteImageEntry( mat.normal, mat.name + "-normal-image");
+    }
+    PopTag();
+    mOutput << startstr << "</library_images>" << endstr;
+  }
+
+  // output effects - those are the actual carriers of information
+  if( !materials.empty() )
+  {
+    mOutput << startstr << "<library_effects>" << endstr;
+    PushTag();
+    for( std::vector<Material>::const_iterator it = materials.begin(); it != materials.end(); ++it )
+    {
+      const Material& mat = *it;
+      // this is so ridiculous it must be right
+      mOutput << startstr << "<effect id=\"" << XMLEscape(mat.name) << "-fx\" name=\"" << XMLEscape(mat.name) << "\">" << endstr;
+      PushTag();
+      mOutput << startstr << "<profile_COMMON>" << endstr;
+      PushTag();
+
+      // write sampler- and surface params for the texture entries
+      WriteTextureParamEntry( mat.emissive, "emission", mat.name);
+      WriteTextureParamEntry( mat.ambient, "ambient", mat.name);
+      WriteTextureParamEntry( mat.diffuse, "diffuse", mat.name);
+      WriteTextureParamEntry( mat.specular, "specular", mat.name);
+      WriteTextureParamEntry( mat.reflective, "reflective", mat.name);
+      WriteTextureParamEntry( mat.transparent, "transparent", mat.name);
+      WriteTextureParamEntry( mat.normal, "normal", mat.name);
+
+      mOutput << startstr << "<technique sid=\"standard\">" << endstr;
+      PushTag();
+      mOutput << startstr << "<" << mat.shading_model << ">" << endstr;
+      PushTag();
+
+      WriteTextureColorEntry( mat.emissive, "emission", mat.name + "-emission-sampler");
+      WriteTextureColorEntry( mat.ambient, "ambient", mat.name + "-ambient-sampler");
+      WriteTextureColorEntry( mat.diffuse, "diffuse", mat.name + "-diffuse-sampler");
+      WriteTextureColorEntry( mat.specular, "specular", mat.name + "-specular-sampler");
+      WriteFloatEntry(mat.shininess, "shininess");
+      WriteTextureColorEntry( mat.reflective, "reflective", mat.name + "-reflective-sampler");
+      WriteTextureColorEntry( mat.transparent, "transparent", mat.name + "-transparent-sampler");
+      WriteFloatEntry(mat.transparency, "transparency");
+      WriteFloatEntry(mat.index_refraction, "index_of_refraction");
+
+      if(! mat.normal.texture.empty()) {
+        WriteTextureColorEntry( mat.normal, "bump", mat.name + "-normal-sampler");
+      }
+
+      PopTag();
+      mOutput << startstr << "</" << mat.shading_model << ">" << endstr;
+      PopTag();
+      mOutput << startstr << "</technique>" << endstr;
+      PopTag();
+      mOutput << startstr << "</profile_COMMON>" << endstr;
+      PopTag();
+      mOutput << startstr << "</effect>" << endstr;
+    }
+    PopTag();
+    mOutput << startstr << "</library_effects>" << endstr;
+
+    // write materials - they're just effect references
+    mOutput << startstr << "<library_materials>" << endstr;
+    PushTag();
+    for( std::vector<Material>::const_iterator it = materials.begin(); it != materials.end(); ++it )
+    {
+      const Material& mat = *it;
+      mOutput << startstr << "<material id=\"" << XMLEscape(mat.name) << "\" name=\"" << mat.name << "\">" << endstr;
+      PushTag();
+      mOutput << startstr << "<instance_effect url=\"#" << XMLEscape(mat.name) << "-fx\"/>" << endstr;
+      PopTag();
+      mOutput << startstr << "</material>" << endstr;
+    }
+    PopTag();
+    mOutput << startstr << "</library_materials>" << endstr;
+  }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Writes the controller library
+void ColladaExporter::WriteControllerLibrary()
+{
+    mOutput << startstr << "<library_controllers>" << endstr;
+    PushTag();
+    
+    for( size_t a = 0; a < mScene->mNumMeshes; ++a)
+        WriteController( a);
+
+    PopTag();
+    mOutput << startstr << "</library_controllers>" << endstr;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Writes a skin controller of the given mesh
+void ColladaExporter::WriteController( size_t pIndex)
+{
+    const aiMesh* mesh = mScene->mMeshes[pIndex];
+    const std::string idstr = GetMeshId( pIndex);
+    const std::string idstrEscaped = XMLEscape(idstr);
+
+    if ( mesh->mNumFaces == 0 || mesh->mNumVertices == 0 )
+        return;
+
+    if ( mesh->mNumBones == 0 )
+        return;
+
+    mOutput << startstr << "<controller id=\"" << idstrEscaped << "-skin\" ";
+    mOutput << "name=\"skinCluster" << pIndex << "\">"<< endstr;
+    PushTag();
+
+    mOutput << startstr << "<skin source=\"#" << idstrEscaped << "\">" << endstr;
+    PushTag();
+
+    // bind pose matrix
+    mOutput << startstr << "<bind_shape_matrix>" << endstr;
+    PushTag();
+
+    // I think it is identity in general cases.
+    aiMatrix4x4 mat;
+    mOutput << startstr << mat.a1 << " " << mat.a2 << " " << mat.a3 << " " << mat.a4 << endstr;
+    mOutput << startstr << mat.b1 << " " << mat.b2 << " " << mat.b3 << " " << mat.b4 << endstr;
+    mOutput << startstr << mat.c1 << " " << mat.c2 << " " << mat.c3 << " " << mat.c4 << endstr;
+    mOutput << startstr << mat.d1 << " " << mat.d2 << " " << mat.d3 << " " << mat.d4 << endstr;
+
+    PopTag();
+    mOutput << startstr << "</bind_shape_matrix>" << endstr;
+
+    mOutput << startstr << "<source id=\"" << idstrEscaped << "-skin-joints\" name=\"" << idstrEscaped << "-skin-joints\">" << endstr;
+    PushTag();
+
+    mOutput << startstr << "<Name_array id=\"" << idstrEscaped << "-skin-joints-array\" count=\"" << mesh->mNumBones << "\">";
+
+    for( size_t i = 0; i < mesh->mNumBones; ++i )
+        mOutput << XMLEscape(mesh->mBones[i]->mName.C_Str()) << " ";
+
+    mOutput << "</Name_array>" << endstr;
+
+    mOutput << startstr << "<technique_common>" << endstr;
+    PushTag();
+    
+    mOutput << startstr << "<accessor source=\"#" << idstrEscaped << "-skin-joints-array\" count=\"" << mesh->mNumBones << "\" stride=\"" << 1 << "\">" << endstr;
+    PushTag();
+
+    mOutput << startstr << "<param name=\"JOINT\" type=\"Name\"></param>" << endstr;
+
+    PopTag();
+    mOutput << startstr << "</accessor>" << endstr;
+
+    PopTag();
+    mOutput << startstr << "</technique_common>" << endstr;
+
+    PopTag();
+    mOutput << startstr << "</source>" << endstr;
+
+    std::vector<ai_real> bind_poses;
+    bind_poses.reserve(mesh->mNumBones * 16);
+    for(unsigned int i = 0; i < mesh->mNumBones; ++i)
+        for( unsigned int j = 0; j < 4; ++j)
+            bind_poses.insert(bind_poses.end(), mesh->mBones[i]->mOffsetMatrix[j], mesh->mBones[i]->mOffsetMatrix[j] + 4);
+
+    WriteFloatArray( idstr + "-skin-bind_poses", FloatType_Mat4x4, (const ai_real*) bind_poses.data(), bind_poses.size() / 16);
+
+    bind_poses.clear();
+    
+    std::vector<ai_real> skin_weights;
+    skin_weights.reserve(mesh->mNumVertices * mesh->mNumBones);
+    for( size_t i = 0; i < mesh->mNumBones; ++i)
+        for( size_t j = 0; j < mesh->mBones[i]->mNumWeights; ++j)
+            skin_weights.push_back(mesh->mBones[i]->mWeights[j].mWeight);
+
+    WriteFloatArray( idstr + "-skin-weights", FloatType_Weight, (const ai_real*) skin_weights.data(), skin_weights.size());
+
+    skin_weights.clear();
+
+    mOutput << startstr << "<joints>" << endstr;
+    PushTag();
+
+    mOutput << startstr << "<input semantic=\"JOINT\" source=\"#" << idstrEscaped << "-skin-joints\"></input>" << endstr;
+    mOutput << startstr << "<input semantic=\"INV_BIND_MATRIX\" source=\"#" << idstrEscaped << "-skin-bind_poses\"></input>" << endstr;
+
+    PopTag();
+    mOutput << startstr << "</joints>" << endstr;
+
+    mOutput << startstr << "<vertex_weights count=\"" << mesh->mNumVertices << "\">" << endstr;
+    PushTag();
+
+    mOutput << startstr << "<input semantic=\"JOINT\" source=\"#" << idstrEscaped << "-skin-joints\" offset=\"0\"></input>" << endstr;
+    mOutput << startstr << "<input semantic=\"WEIGHT\" source=\"#" << idstrEscaped << "-skin-weights\" offset=\"1\"></input>" << endstr;
+
+    mOutput << startstr << "<vcount>";
+
+    std::vector<ai_uint> num_influences(mesh->mNumVertices, (ai_uint)0);
+    for( size_t i = 0; i < mesh->mNumBones; ++i)
+        for( size_t j = 0; j < mesh->mBones[i]->mNumWeights; ++j)
+            ++num_influences[mesh->mBones[i]->mWeights[j].mVertexId];
+
+    for( size_t i = 0; i < mesh->mNumVertices; ++i)
+        mOutput << num_influences[i] << " ";
+
+    mOutput << "</vcount>" << endstr;
+
+    mOutput << startstr << "<v>";
+
+    ai_uint joint_weight_indices_length = 0;
+    std::vector<ai_uint> accum_influences;
+    accum_influences.reserve(num_influences.size());
+    for( size_t i = 0; i < num_influences.size(); ++i)
+    {
+        accum_influences.push_back(joint_weight_indices_length);
+        joint_weight_indices_length += num_influences[i];
+    }
+
+    ai_uint weight_index = 0;
+    std::vector<ai_int> joint_weight_indices(2 * joint_weight_indices_length, (ai_int)-1);
+    for( unsigned int i = 0; i < mesh->mNumBones; ++i)
+        for( unsigned j = 0; j < mesh->mBones[i]->mNumWeights; ++j)
+        {
+            unsigned int vId = mesh->mBones[i]->mWeights[j].mVertexId;
+            for( ai_uint k = 0; k < num_influences[vId]; ++k)
+            {
+                if (joint_weight_indices[2 * (accum_influences[vId] + k)] == -1)
+                {
+                    joint_weight_indices[2 * (accum_influences[vId] + k)] = i;
+                    joint_weight_indices[2 * (accum_influences[vId] + k) + 1] = weight_index;
+                    break;
+                }
+            }
+            ++weight_index;
+        }
+
+    for( size_t i = 0; i < joint_weight_indices.size(); ++i)
+        mOutput << joint_weight_indices[i] << " ";
+
+    num_influences.clear();
+    accum_influences.clear();
+    joint_weight_indices.clear();
+
+    mOutput << "</v>" << endstr;
+
+    PopTag();
+    mOutput << startstr << "</vertex_weights>" << endstr;
+
+    PopTag();
+    mOutput << startstr << "</skin>" << endstr;
+    
+    PopTag();
+    mOutput << startstr << "</controller>" << endstr;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Writes the geometry library
+void ColladaExporter::WriteGeometryLibrary()
+{
+    mOutput << startstr << "<library_geometries>" << endstr;
+    PushTag();
+
+    for( size_t a = 0; a < mScene->mNumMeshes; ++a)
+        WriteGeometry( a);
+
+    PopTag();
+    mOutput << startstr << "</library_geometries>" << endstr;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Writes the given mesh
+void ColladaExporter::WriteGeometry( size_t pIndex)
+{
+    const aiMesh* mesh = mScene->mMeshes[pIndex];
+    const std::string idstr = GetMeshId( pIndex);
+    const std::string idstrEscaped = XMLEscape(idstr);
+
+    if ( mesh->mNumFaces == 0 || mesh->mNumVertices == 0 )
+        return;
+
+    // opening tag
+    mOutput << startstr << "<geometry id=\"" << idstrEscaped << "\" name=\"" << idstrEscaped << "_name\" >" << endstr;
+    PushTag();
+
+    mOutput << startstr << "<mesh>" << endstr;
+    PushTag();
+
+    // Positions
+    WriteFloatArray( idstr + "-positions", FloatType_Vector, (ai_real*) mesh->mVertices, mesh->mNumVertices);
+    // Normals, if any
+    if( mesh->HasNormals() )
+        WriteFloatArray( idstr + "-normals", FloatType_Vector, (ai_real*) mesh->mNormals, mesh->mNumVertices);
+
+    // texture coords
+    for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a)
+    {
+        if( mesh->HasTextureCoords(static_cast<unsigned int>(a)) )
+        {
+            WriteFloatArray( idstr + "-tex" + to_string(a), mesh->mNumUVComponents[a] == 3 ? FloatType_TexCoord3 : FloatType_TexCoord2,
+                (ai_real*) mesh->mTextureCoords[a], mesh->mNumVertices);
+        }
+    }
+
+    // vertex colors
+    for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a)
+    {
+        if( mesh->HasVertexColors(static_cast<unsigned int>(a)) )
+            WriteFloatArray( idstr + "-color" + to_string(a), FloatType_Color, (ai_real*) mesh->mColors[a], mesh->mNumVertices);
+    }
+
+    // assemble vertex structure
+    // Only write input for POSITION since we will write other as shared inputs in polygon definition
+    mOutput << startstr << "<vertices id=\"" << idstrEscaped << "-vertices" << "\">" << endstr;
+    PushTag();
+    mOutput << startstr << "<input semantic=\"POSITION\" source=\"#" << idstrEscaped << "-positions\" />" << endstr;
+    PopTag();
+    mOutput << startstr << "</vertices>" << endstr;
+
+    // count the number of lines, triangles and polygon meshes
+    int countLines = 0;
+    int countPoly = 0;
+    for( size_t a = 0; a < mesh->mNumFaces; ++a )
+    {
+        if (mesh->mFaces[a].mNumIndices == 2) countLines++;
+        else if (mesh->mFaces[a].mNumIndices >= 3) countPoly++;
+    }
+
+    // lines
+    if (countLines)
+    {
+        mOutput << startstr << "<lines count=\"" << countLines << "\" material=\"defaultMaterial\">" << endstr;
+        PushTag();
+        mOutput << startstr << "<input offset=\"0\" semantic=\"VERTEX\" source=\"#" << idstrEscaped << "-vertices\" />" << endstr;
+        if( mesh->HasNormals() )
+            mOutput << startstr << "<input semantic=\"NORMAL\" source=\"#" << idstrEscaped << "-normals\" />" << endstr;
+        for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a )
+        {
+            if( mesh->HasTextureCoords(static_cast<unsigned int>(a)) )
+                mOutput << startstr << "<input semantic=\"TEXCOORD\" source=\"#" << idstrEscaped << "-tex" << a << "\" " << "set=\"" << a << "\""  << " />" << endstr;
+        }
+        for( size_t a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a )
+        {
+            if( mesh->HasVertexColors(static_cast<unsigned int>(a) ) )
+                mOutput << startstr << "<input semantic=\"COLOR\" source=\"#" << idstrEscaped << "-color" << a << "\" " << "set=\"" << a << "\""  << " />" << endstr;
+        }
+
+        mOutput << startstr << "<p>";
+        for( size_t a = 0; a < mesh->mNumFaces; ++a )
+        {
+            const aiFace& face = mesh->mFaces[a];
+            if (face.mNumIndices != 2) continue;
+            for( size_t b = 0; b < face.mNumIndices; ++b )
+                mOutput << face.mIndices[b] << " ";
+        }
+        mOutput << "</p>" << endstr;
+        PopTag();
+        mOutput << startstr << "</lines>" << endstr;
+    }
+
+    // triangle - don't use it, because compatibility problems
+
+    // polygons
+    if (countPoly)
+    {
+        mOutput << startstr << "<polylist count=\"" << countPoly << "\" material=\"defaultMaterial\">" << endstr;
+        PushTag();
+        mOutput << startstr << "<input offset=\"0\" semantic=\"VERTEX\" source=\"#" << idstrEscaped << "-vertices\" />" << endstr;
+        if( mesh->HasNormals() )
+            mOutput << startstr << "<input offset=\"0\" semantic=\"NORMAL\" source=\"#" << idstrEscaped << "-normals\" />" << endstr;
+        for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a )
+        {
+            if( mesh->HasTextureCoords(static_cast<unsigned int>(a)) )
+                mOutput << startstr << "<input offset=\"0\" semantic=\"TEXCOORD\" source=\"#" << idstrEscaped << "-tex" << a << "\" " << "set=\"" << a << "\""  << " />" << endstr;
+        }
+        for( size_t a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a )
+        {
+            if( mesh->HasVertexColors(static_cast<unsigned int>(a) ) )
+                mOutput << startstr << "<input offset=\"0\" semantic=\"COLOR\" source=\"#" << idstrEscaped << "-color" << a << "\" " << "set=\"" << a << "\""  << " />" << endstr;
+        }
+
+        mOutput << startstr << "<vcount>";
+        for( size_t a = 0; a < mesh->mNumFaces; ++a )
+        {
+            if (mesh->mFaces[a].mNumIndices < 3) continue;
+            mOutput << mesh->mFaces[a].mNumIndices << " ";
+        }
+        mOutput << "</vcount>" << endstr;
+
+        mOutput << startstr << "<p>";
+        for( size_t a = 0; a < mesh->mNumFaces; ++a )
+        {
+            const aiFace& face = mesh->mFaces[a];
+            if (face.mNumIndices < 3) continue;
+            for( size_t b = 0; b < face.mNumIndices; ++b )
+                mOutput << face.mIndices[b] << " ";
+        }
+        mOutput << "</p>" << endstr;
+        PopTag();
+        mOutput << startstr << "</polylist>" << endstr;
+    }
+
+    // closing tags
+    PopTag();
+    mOutput << startstr << "</mesh>" << endstr;
+    PopTag();
+    mOutput << startstr << "</geometry>" << endstr;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Writes a float array of the given type
+void ColladaExporter::WriteFloatArray( const std::string& pIdString, FloatDataType pType, const ai_real* pData, size_t pElementCount)
+{
+    size_t floatsPerElement = 0;
+    switch( pType )
+    {
+        case FloatType_Vector: floatsPerElement = 3; break;
+        case FloatType_TexCoord2: floatsPerElement = 2; break;
+        case FloatType_TexCoord3: floatsPerElement = 3; break;
+        case FloatType_Color: floatsPerElement = 3; break;
+        case FloatType_Mat4x4: floatsPerElement = 16; break;
+        case FloatType_Weight: floatsPerElement = 1; break;
+		case FloatType_Time: floatsPerElement = 1; break;
+        default:
+            return;
+    }
+
+    std::string arrayId = pIdString + "-array";
+
+    mOutput << startstr << "<source id=\"" << XMLEscape(pIdString) << "\" name=\"" << XMLEscape(pIdString) << "\">" << endstr;
+    PushTag();
+
+    // source array
+    mOutput << startstr << "<float_array id=\"" << XMLEscape(arrayId) << "\" count=\"" << pElementCount * floatsPerElement << "\"> ";
+    PushTag();
+
+    if( pType == FloatType_TexCoord2 )
+    {
+        for( size_t a = 0; a < pElementCount; ++a )
+        {
+            mOutput << pData[a*3+0] << " ";
+            mOutput << pData[a*3+1] << " ";
+        }
+    }
+    else if( pType == FloatType_Color )
+    {
+        for( size_t a = 0; a < pElementCount; ++a )
+        {
+            mOutput << pData[a*4+0] << " ";
+            mOutput << pData[a*4+1] << " ";
+            mOutput << pData[a*4+2] << " ";
+        }
+    }
+    else
+    {
+        for( size_t a = 0; a < pElementCount * floatsPerElement; ++a )
+            mOutput << pData[a] << " ";
+    }
+    mOutput << "</float_array>" << endstr;
+    PopTag();
+
+    // the usual Collada fun. Let's bloat it even more!
+    mOutput << startstr << "<technique_common>" << endstr;
+    PushTag();
+    mOutput << startstr << "<accessor count=\"" << pElementCount << "\" offset=\"0\" source=\"#" << arrayId << "\" stride=\"" << floatsPerElement << "\">" << endstr;
+    PushTag();
+
+    switch( pType )
+    {
+        case FloatType_Vector:
+            mOutput << startstr << "<param name=\"X\" type=\"float\" />" << endstr;
+            mOutput << startstr << "<param name=\"Y\" type=\"float\" />" << endstr;
+            mOutput << startstr << "<param name=\"Z\" type=\"float\" />" << endstr;
+            break;
+
+        case FloatType_TexCoord2:
+            mOutput << startstr << "<param name=\"S\" type=\"float\" />" << endstr;
+            mOutput << startstr << "<param name=\"T\" type=\"float\" />" << endstr;
+            break;
+
+        case FloatType_TexCoord3:
+            mOutput << startstr << "<param name=\"S\" type=\"float\" />" << endstr;
+            mOutput << startstr << "<param name=\"T\" type=\"float\" />" << endstr;
+            mOutput << startstr << "<param name=\"P\" type=\"float\" />" << endstr;
+            break;
+
+        case FloatType_Color:
+            mOutput << startstr << "<param name=\"R\" type=\"float\" />" << endstr;
+            mOutput << startstr << "<param name=\"G\" type=\"float\" />" << endstr;
+            mOutput << startstr << "<param name=\"B\" type=\"float\" />" << endstr;
+            break;
+
+        case FloatType_Mat4x4:
+            mOutput << startstr << "<param name=\"TRANSFORM\" type=\"float4x4\" />" << endstr;
+            break;
+
+        case FloatType_Weight:
+            mOutput << startstr << "<param name=\"WEIGHT\" type=\"float\" />" << endstr;
+            break;
+
+		// customized, add animation related
+		case FloatType_Time:
+			mOutput << startstr << "<param name=\"TIME\" type=\"float\" />" << endstr;
+			break;
+
+	}
+
+    PopTag();
+    mOutput << startstr << "</accessor>" << endstr;
+    PopTag();
+    mOutput << startstr << "</technique_common>" << endstr;
+    PopTag();
+    mOutput << startstr << "</source>" << endstr;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Writes the scene library
+void ColladaExporter::WriteSceneLibrary()
+{
+    const std::string scene_name_escaped = XMLEscape(mScene->mRootNode->mName.C_Str());
+
+    mOutput << startstr << "<library_visual_scenes>" << endstr;
+    PushTag();
+    mOutput << startstr << "<visual_scene id=\"" + scene_name_escaped + "\" name=\"" + scene_name_escaped + "\">" << endstr;
+    PushTag();
+
+    // start recursive write at the root node
+    for( size_t a = 0; a < mScene->mRootNode->mNumChildren; ++a )
+        WriteNode( mScene, mScene->mRootNode->mChildren[a]);
+
+    PopTag();
+    mOutput << startstr << "</visual_scene>" << endstr;
+    PopTag();
+    mOutput << startstr << "</library_visual_scenes>" << endstr;
+}
+// ------------------------------------------------------------------------------------------------
+void ColladaExporter::WriteAnimationLibrary(size_t pIndex)
+{
+	const aiAnimation * anim = mScene->mAnimations[pIndex];
+	
+	if ( anim->mNumChannels == 0 && anim->mNumMeshChannels == 0 && anim->mNumMorphMeshChannels ==0 )
+		return;
+	
+	const std::string animation_name_escaped = XMLEscape( anim->mName.C_Str() );
+	std::string idstr = anim->mName.C_Str();
+	std::string ending = std::string( "AnimId" ) + to_string(pIndex);
+	if (idstr.length() >= ending.length()) {
+		if (0 != idstr.compare (idstr.length() - ending.length(), ending.length(), ending)) {
+			idstr = idstr + ending;
+		}
+	} else {
+		idstr = idstr + ending;
+	}
+
+	const std::string idstrEscaped = XMLEscape(idstr);
+	
+	mOutput << startstr << "<animation id=\"" + idstrEscaped + "\" name=\"" + animation_name_escaped + "\">" << endstr;
+	PushTag();
+
+    std::string node_idstr;
+	for (size_t a = 0; a < anim->mNumChannels; ++a) {
+		const aiNodeAnim * nodeAnim = anim->mChannels[a];
+		
+		// sanity check
+		if ( nodeAnim->mNumPositionKeys != nodeAnim->mNumScalingKeys ||  nodeAnim->mNumPositionKeys != nodeAnim->mNumRotationKeys ) continue;
+		
+		{
+            node_idstr.clear();
+            node_idstr += nodeAnim->mNodeName.data;
+            node_idstr += std::string( "_matrix-input" );
+
+			std::vector<ai_real> frames;
+			for( size_t i = 0; i < nodeAnim->mNumPositionKeys; ++i) {
+				frames.push_back(static_cast<ai_real>(nodeAnim->mPositionKeys[i].mTime));
+			}
+			
+			WriteFloatArray( node_idstr , FloatType_Time, (const ai_real*) frames.data(), frames.size());
+			frames.clear();
+		}
+		
+		{
+            node_idstr.clear();
+
+            node_idstr += nodeAnim->mNodeName.data;
+            node_idstr += std::string("_matrix-output");
+			
+			std::vector<ai_real> keyframes;
+			keyframes.reserve(nodeAnim->mNumPositionKeys * 16);
+			for( size_t i = 0; i < nodeAnim->mNumPositionKeys; ++i) {
+				aiVector3D Scaling = nodeAnim->mScalingKeys[i].mValue;
+				aiMatrix4x4 ScalingM;  // identity
+				ScalingM[0][0] = Scaling.x; ScalingM[1][1] = Scaling.y; ScalingM[2][2] = Scaling.z;
+				
+				aiQuaternion RotationQ = nodeAnim->mRotationKeys[i].mValue;
+				aiMatrix4x4 s = aiMatrix4x4( RotationQ.GetMatrix() );
+				aiMatrix4x4 RotationM(s.a1, s.a2, s.a3, 0, s.b1, s.b2, s.b3, 0, s.c1, s.c2, s.c3, 0, 0, 0, 0, 1);
+				
+				aiVector3D Translation = nodeAnim->mPositionKeys[i].mValue;
+				aiMatrix4x4 TranslationM;	// identity
+				TranslationM[0][3] = Translation.x; TranslationM[1][3] = Translation.y; TranslationM[2][3] = Translation.z;
+				
+				// Combine the above transformations
+				aiMatrix4x4 mat = TranslationM * RotationM * ScalingM;
+				
+				for( unsigned int j = 0; j < 4; ++j) {
+					keyframes.insert(keyframes.end(), mat[j], mat[j] + 4);
+                }
+			}
+			
+			WriteFloatArray( node_idstr, FloatType_Mat4x4, (const ai_real*) keyframes.data(), keyframes.size() / 16);
+		}
+		
+		{
+			std::vector<std::string> names;
+			for ( size_t i = 0; i < nodeAnim->mNumPositionKeys; ++i) {
+				if ( nodeAnim->mPreState == aiAnimBehaviour_DEFAULT
+					|| nodeAnim->mPreState == aiAnimBehaviour_LINEAR
+					|| nodeAnim->mPreState == aiAnimBehaviour_REPEAT
+					) {
+					names.push_back( "LINEAR" );
+				} else if (nodeAnim->mPostState == aiAnimBehaviour_CONSTANT) {
+					names.push_back( "STEP" );
+				}
+			}
+			
+			const std::string node_idstr = nodeAnim->mNodeName.data + std::string("_matrix-interpolation");
+			std::string arrayId = node_idstr + "-array";
+			
+			mOutput << startstr << "<source id=\"" << XMLEscape(node_idstr) << "\">" << endstr;
+			PushTag();
+			
+			// source array
+			mOutput << startstr << "<Name_array id=\"" << XMLEscape(arrayId) << "\" count=\"" << names.size() << "\"> ";
+			for( size_t a = 0; a < names.size(); ++a ) {
+				mOutput << names[a] << " ";
+            }
+			mOutput << "</Name_array>" << endstr;
+			
+			mOutput << startstr << "<technique_common>" << endstr;
+			PushTag();
+
+			mOutput << startstr << "<accessor source=\"#" << XMLEscape(arrayId) << "\" count=\"" << names.size() << "\" stride=\"" << 1 << "\">" << endstr;
+			PushTag();
+			
+			mOutput << startstr << "<param name=\"INTERPOLATION\" type=\"name\"></param>" << endstr;
+			
+			PopTag();
+			mOutput << startstr << "</accessor>" << endstr;
+			
+			PopTag();
+			mOutput << startstr << "</technique_common>" << endstr;
+
+			PopTag();
+			mOutput << startstr << "</source>" << endstr;
+		}
+	}
+	
+	for (size_t a = 0; a < anim->mNumChannels; ++a) {
+		const aiNodeAnim * nodeAnim = anim->mChannels[a];
+		
+		{
+		// samplers
+			const std::string node_idstr = nodeAnim->mNodeName.data + std::string("_matrix-sampler");
+			mOutput << startstr << "<sampler id=\"" << XMLEscape(node_idstr) << "\">" << endstr;
+			PushTag();
+			
+			mOutput << startstr << "<input semantic=\"INPUT\" source=\"#" << XMLEscape( nodeAnim->mNodeName.data + std::string("_matrix-input") ) << "\"/>" << endstr;
+			mOutput << startstr << "<input semantic=\"OUTPUT\" source=\"#" << XMLEscape( nodeAnim->mNodeName.data + std::string("_matrix-output") ) << "\"/>" << endstr;
+			mOutput << startstr << "<input semantic=\"INTERPOLATION\" source=\"#" << XMLEscape( nodeAnim->mNodeName.data + std::string("_matrix-interpolation") ) << "\"/>" << endstr;
+			
+			PopTag();
+			mOutput << startstr << "</sampler>" << endstr;
+		}
+	}
+	
+	for (size_t a = 0; a < anim->mNumChannels; ++a) {
+		const aiNodeAnim * nodeAnim = anim->mChannels[a];
+		
+		{
+		// channels
+			mOutput << startstr << "<channel source=\"#" << XMLEscape( nodeAnim->mNodeName.data + std::string("_matrix-sampler") ) << "\" target=\"" << XMLEscape(nodeAnim->mNodeName.data) << "/matrix\"/>" << endstr;
+		}
+	}
+	
+	PopTag();
+	mOutput << startstr << "</animation>" << endstr;
+	
+}
+// ------------------------------------------------------------------------------------------------
+void ColladaExporter::WriteAnimationsLibrary()
+{
+	const std::string scene_name_escaped = XMLEscape(mScene->mRootNode->mName.C_Str());
+	
+	if ( mScene->mNumAnimations > 0 ) {
+		mOutput << startstr << "<library_animations>" << endstr;
+		PushTag();
+		
+		// start recursive write at the root node
+		for( size_t a = 0; a < mScene->mNumAnimations; ++a)
+			WriteAnimationLibrary( a );
+
+		PopTag();
+		mOutput << startstr << "</library_animations>" << endstr;
+	}
+}
+// ------------------------------------------------------------------------------------------------
+// Helper to find a bone by name in the scene
+aiBone* findBone( const aiScene* scene, const char * name) {
+    for (size_t m=0; m<scene->mNumMeshes; m++) {
+        aiMesh * mesh = scene->mMeshes[m];
+        for (size_t b=0; b<mesh->mNumBones; b++) {
+            aiBone * bone = mesh->mBones[b];
+            if (0 == strcmp(name, bone->mName.C_Str())) {
+                return bone;
+            }
+        }
+    }
+    return NULL;
+}
+
+// ------------------------------------------------------------------------------------------------
+const aiNode * findBoneNode( const aiNode* aNode, const aiBone* bone)
+{
+	if ( aNode && bone && aNode->mName == bone->mName ) {
+		return aNode;
+	}
+	
+	if ( aNode && bone ) {
+		for (unsigned int i=0; i < aNode->mNumChildren; ++i) {
+			aiNode * aChild = aNode->mChildren[i];
+			const aiNode * foundFromChild = 0;
+			if ( aChild ) {
+				foundFromChild = findBoneNode( aChild, bone );
+				if ( foundFromChild ) return foundFromChild;
+			}
+		}
+	}
+	
+	return NULL;
+}
+
+const aiNode * findSkeletonRootNode( const aiScene* scene, const aiMesh * mesh)
+{
+	std::set<const aiNode*> topParentBoneNodes;
+	if ( mesh && mesh->mNumBones > 0 ) {
+		for (unsigned int i=0; i < mesh->mNumBones; ++i) {
+			aiBone * bone = mesh->mBones[i];
+
+			const aiNode * node = findBoneNode( scene->mRootNode, bone);
+			if ( node ) {
+				while ( node->mParent && findBone(scene, node->mParent->mName.C_Str() ) != 0 ) {
+					node = node->mParent;
+				}
+				topParentBoneNodes.insert( node );
+			}
+		}
+	}
+	
+	if ( !topParentBoneNodes.empty() ) {
+		const aiNode * parentBoneNode = *topParentBoneNodes.begin();
+		if ( topParentBoneNodes.size() == 1 ) {
+			return parentBoneNode;
+		} else {
+			for (auto it : topParentBoneNodes) {
+				if ( it->mParent ) return it->mParent;
+			}
+			return parentBoneNode;
+		}
+	}
+	
+	return NULL;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Recursively writes the given node
+void ColladaExporter::WriteNode( const aiScene* pScene, aiNode* pNode)
+{
+    // the node must have a name
+    if (pNode->mName.length == 0)
+    {
+        std::stringstream ss;
+        ss << "Node_" << pNode;
+        pNode->mName.Set(ss.str());
+    }
+
+    // If the node is associated with a bone, it is a joint node (JOINT)
+    // otherwise it is a normal node (NODE)
+    const char * node_type;
+    bool is_joint, is_skeleton_root = false;
+    if (nullptr == findBone(pScene, pNode->mName.C_Str())) {
+        node_type = "NODE";
+        is_joint = false;
+    } else {
+        node_type = "JOINT";
+        is_joint = true;
+        if (!pNode->mParent || nullptr == findBone(pScene, pNode->mParent->mName.C_Str())) {
+            is_skeleton_root = true;
+        }
+    }
+
+    const std::string node_name_escaped = XMLEscape(pNode->mName.data);
+	mOutput << startstr << "<node ";
+	if(is_skeleton_root) {
+		mOutput << "id=\"" << node_name_escaped << "\" " << (is_joint ? "sid=\"" + node_name_escaped +"\"" : "") ; // For now, only support one skeleton in a scene.
+		mFoundSkeletonRootNodeID = node_name_escaped;
+	} else {
+		mOutput << "id=\"" << node_name_escaped << "\" " << (is_joint ? "sid=\"" + node_name_escaped +"\"": "") ;
+	}
+	
+    mOutput << " name=\"" << node_name_escaped
+            << "\" type=\"" << node_type
+            << "\">" << endstr;
+    PushTag();
+
+    // write transformation - we can directly put the matrix there
+    // TODO: (thom) decompose into scale - rot - quad to allow addressing it by animations afterwards
+    aiMatrix4x4 mat = pNode->mTransformation;
+
+    // If this node is a Camera node, the camera coordinate system needs to be multiplied in.
+    // When importing from Collada, the mLookAt is set to 0, 0, -1, and the node transform is unchanged.
+    // When importing from a different format, mLookAt is set to 0, 0, 1. Therefore, the local camera
+    // coordinate system must be changed to matche the Collada specification.
+    for (size_t i = 0; i<mScene->mNumCameras; i++){
+        if (mScene->mCameras[i]->mName == pNode->mName){
+            aiMatrix4x4 sourceView;
+            mScene->mCameras[i]->GetCameraMatrix(sourceView);
+
+            aiMatrix4x4 colladaView;
+            colladaView.a1 = colladaView.c3 = -1; // move into -z space.
+            mat *= (sourceView * colladaView);
+            break;
+        }
+    }
+	
+	// customized, sid should be 'matrix' to match with loader code.
+    //mOutput << startstr << "<matrix sid=\"transform\">";
+	mOutput << startstr << "<matrix sid=\"matrix\">";
+	
+    mOutput << mat.a1 << " " << mat.a2 << " " << mat.a3 << " " << mat.a4 << " ";
+    mOutput << mat.b1 << " " << mat.b2 << " " << mat.b3 << " " << mat.b4 << " ";
+    mOutput << mat.c1 << " " << mat.c2 << " " << mat.c3 << " " << mat.c4 << " ";
+    mOutput << mat.d1 << " " << mat.d2 << " " << mat.d3 << " " << mat.d4;
+    mOutput << "</matrix>" << endstr;
+
+    if(pNode->mNumMeshes==0){
+        //check if it is a camera node
+        for(size_t i=0; i<mScene->mNumCameras; i++){
+            if(mScene->mCameras[i]->mName == pNode->mName){
+                mOutput << startstr <<"<instance_camera url=\"#" << node_name_escaped << "-camera\"/>" << endstr;
+                break;
+            }
+        }
+        //check if it is a light node
+        for(size_t i=0; i<mScene->mNumLights; i++){
+            if(mScene->mLights[i]->mName == pNode->mName){
+                mOutput << startstr <<"<instance_light url=\"#" << node_name_escaped << "-light\"/>" << endstr;
+                break;
+            }
+        }
+
+    }else
+    // instance every geometry
+    for( size_t a = 0; a < pNode->mNumMeshes; ++a )
+    {
+        const aiMesh* mesh = mScene->mMeshes[pNode->mMeshes[a]];
+        // do not instantiate mesh if empty. I wonder how this could happen
+        if( mesh->mNumFaces == 0 || mesh->mNumVertices == 0 )
+            continue;
+
+        if( mesh->mNumBones == 0 )
+        {
+            mOutput << startstr << "<instance_geometry url=\"#" << XMLEscape(GetMeshId( pNode->mMeshes[a])) << "\">" << endstr;
+            PushTag();
+        }
+        else
+        {
+            mOutput << startstr
+                    << "<instance_controller url=\"#" << XMLEscape(GetMeshId( pNode->mMeshes[a])) << "-skin\">"
+                    << endstr;
+            PushTag();
+
+			// note! this mFoundSkeletonRootNodeID some how affects animation, it makes the mesh attaches to armature skeleton root node.
+			// use the first bone to find skeleton root
+			const aiNode * skeletonRootBoneNode = findSkeletonRootNode( pScene, mesh );
+			if ( skeletonRootBoneNode ) {
+				mFoundSkeletonRootNodeID = XMLEscape( skeletonRootBoneNode->mName.C_Str() );
+			}
+            mOutput << startstr << "<skeleton>#" << mFoundSkeletonRootNodeID << "</skeleton>" << endstr;
+        }
+        mOutput << startstr << "<bind_material>" << endstr;
+        PushTag();
+        mOutput << startstr << "<technique_common>" << endstr;
+        PushTag();
+        mOutput << startstr << "<instance_material symbol=\"defaultMaterial\" target=\"#" << XMLEscape(materials[mesh->mMaterialIndex].name) << "\">" << endstr;
+        PushTag();
+        for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a )
+        {
+            if( mesh->HasTextureCoords( static_cast<unsigned int>(a) ) )
+                // semantic       as in <texture texcoord=...>
+                // input_semantic as in <input semantic=...>
+                // input_set      as in <input set=...>
+                mOutput << startstr << "<bind_vertex_input semantic=\"CHANNEL" << a << "\" input_semantic=\"TEXCOORD\" input_set=\"" << a << "\"/>" << endstr;
+        }
+        PopTag();
+        mOutput << startstr << "</instance_material>" << endstr;
+        PopTag();
+        mOutput << startstr << "</technique_common>" << endstr;
+        PopTag();
+        mOutput << startstr << "</bind_material>" << endstr;
+        
+        PopTag();
+        if( mesh->mNumBones == 0)
+            mOutput << startstr << "</instance_geometry>" << endstr;
+        else
+            mOutput << startstr << "</instance_controller>" << endstr;
+    }
+
+    // recurse into subnodes
+    for( size_t a = 0; a < pNode->mNumChildren; ++a )
+        WriteNode( pScene, pNode->mChildren[a]);
+
+    PopTag();
+    mOutput << startstr << "</node>" << endstr;
+}
+
+#endif
+#endif

+ 223 - 0
Engine/lib/assimp/code/ColladaExporter.h

@@ -0,0 +1,223 @@
+/*
+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 ColladaExporter.h
+ * Declares the exporter class to write a scene to a Collada file
+ */
+#ifndef AI_COLLADAEXPORTER_H_INC
+#define AI_COLLADAEXPORTER_H_INC
+
+#include <assimp/ai_assert.h>
+#include <assimp/material.h>
+#include <assimp/mesh.h>
+#include <assimp/light.h>
+#include <assimp/Exporter.hpp>
+#include <sstream>
+#include <vector>
+#include <map>
+
+#include <assimp/StringUtils.h>
+
+struct aiScene;
+struct aiNode;
+
+namespace Assimp
+{
+
+/// Helper class to export a given scene to a Collada file. Just for my personal
+/// comfort when implementing it.
+class ColladaExporter
+{
+public:
+    /// Constructor for a specific scene to export
+    ColladaExporter( const aiScene* pScene, IOSystem* pIOSystem, const std::string& path, const std::string& file);
+
+    /// Destructor
+    virtual ~ColladaExporter();
+
+protected:
+    /// Starts writing the contents
+    void WriteFile();
+
+    /// Writes the asset header
+    void WriteHeader();
+
+    /// Writes the embedded textures
+    void WriteTextures();
+
+    /// Writes the material setup
+    void WriteMaterials();
+
+    /// Writes the cameras library
+    void WriteCamerasLibrary();
+
+    // Write a camera entry
+    void WriteCamera(size_t pIndex);
+
+    /// Writes the cameras library
+    void WriteLightsLibrary();
+
+    // Write a camera entry
+    void WriteLight(size_t pIndex);
+    void WritePointLight(const aiLight *const light);
+    void WriteDirectionalLight(const aiLight *const light);
+    void WriteSpotLight(const aiLight *const light);
+    void WriteAmbienttLight(const aiLight *const light);
+
+    /// Writes the controller library
+    void WriteControllerLibrary();
+
+    /// Writes a skin controller of the given mesh
+    void WriteController( size_t pIndex);
+
+    /// Writes the geometry library
+    void WriteGeometryLibrary();
+
+    /// Writes the given mesh
+    void WriteGeometry( size_t pIndex);
+
+    //enum FloatDataType { FloatType_Vector, FloatType_TexCoord2, FloatType_TexCoord3, FloatType_Color, FloatType_Mat4x4, FloatType_Weight };
+    // customized to add animation related type
+	enum FloatDataType { FloatType_Vector, FloatType_TexCoord2, FloatType_TexCoord3, FloatType_Color, FloatType_Mat4x4, FloatType_Weight, FloatType_Time };
+
+    /// Writes a float array of the given type
+    void WriteFloatArray( const std::string& pIdString, FloatDataType pType, const ai_real* pData, size_t pElementCount);
+
+    /// Writes the scene library
+    void WriteSceneLibrary();
+
+	// customized, Writes the animation library
+	void WriteAnimationsLibrary();
+	void WriteAnimationLibrary( size_t pIndex);
+	std::string mFoundSkeletonRootNodeID = "skeleton_root";	 	// will be replaced by found node id in the WriteNode call.
+	
+    /// Recursively writes the given node
+    void WriteNode( const aiScene* scene, aiNode* pNode);
+
+    /// Enters a new xml element, which increases the indentation
+    void PushTag() { startstr.append( "  "); }
+    /// Leaves an element, decreasing the indentation
+    void PopTag() { 
+        ai_assert( startstr.length() > 1); 
+        startstr.erase( startstr.length() - 2); 
+    }
+
+    /// Creates a mesh ID for the given mesh
+    std::string GetMeshId( size_t pIndex) const {
+        return std::string( "meshId" ) + to_string(pIndex);
+    }
+
+public:
+    /// Stringstream to write all output into
+    std::stringstream mOutput;
+
+protected:
+    /// The IOSystem for output
+    IOSystem* mIOSystem;
+
+    /// Path of the directory where the scene will be exported
+    const std::string mPath;
+
+    /// Name of the file (without extension) where the scene will be exported
+    const std::string mFile;
+
+    /// The scene to be written
+    const aiScene* mScene;
+    bool mSceneOwned;
+
+    /// current line start string, contains the current indentation for simple stream insertion
+    std::string startstr;
+    /// current line end string for simple stream insertion
+    std::string endstr;
+
+  // pair of color and texture - texture precedences color
+  struct Surface
+  {
+    bool exist;
+    aiColor4D color;
+    std::string texture;
+    size_t channel;
+    Surface() { exist = false; channel = 0; }
+  };
+
+  struct Property
+  {
+    bool exist;
+     ai_real value;
+     Property()
+         : exist(false)
+         , value(0.0)
+     {}
+  };
+
+  // summarize a material in an convenient way.
+  struct Material
+  {
+    std::string name;
+    std::string shading_model;
+    Surface ambient, diffuse, specular, emissive, reflective, transparent, normal;
+    Property shininess, transparency, index_refraction;
+
+    Material() {}
+  };
+
+  std::vector<Material> materials;
+
+  std::map<unsigned int, std::string> textures;
+
+protected:
+  /// Dammit C++ - y u no compile two-pass? No I have to add all methods below the struct definitions
+  /// Reads a single surface entry from the given material keys
+  void ReadMaterialSurface( Surface& poSurface, const aiMaterial* pSrcMat, aiTextureType pTexture, const char* pKey, size_t pType, size_t pIndex);
+  /// Writes an image entry for the given surface
+  void WriteImageEntry( const Surface& pSurface, const std::string& pNameAdd);
+  /// Writes the two parameters necessary for referencing a texture in an effect entry
+  void WriteTextureParamEntry( const Surface& pSurface, const std::string& pTypeName, const std::string& pMatName);
+  /// Writes a color-or-texture entry into an effect definition
+  void WriteTextureColorEntry( const Surface& pSurface, const std::string& pTypeName, const std::string& pImageName);
+  /// Writes a scalar property
+  void WriteFloatEntry( const Property& pProperty, const std::string& pTypeName);
+};
+
+}
+
+#endif // !! AI_COLLADAEXPORTER_H_INC

+ 705 - 0
Engine/lib/assimp/code/ColladaHelper.h

@@ -0,0 +1,705 @@
+/** Helper structures for the Collada loader */
+
+/*
+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 AI_COLLADAHELPER_H_INC
+#define AI_COLLADAHELPER_H_INC
+
+#include <map>
+#include <vector>
+#include <stdint.h>
+#include <assimp/light.h>
+#include <assimp/mesh.h>
+#include <assimp/material.h>
+
+struct aiMaterial;
+
+namespace Assimp    {
+namespace Collada       {
+
+/** Collada file versions which evolved during the years ... */
+enum FormatVersion
+{
+    FV_1_5_n,
+    FV_1_4_n,
+    FV_1_3_n
+};
+
+
+/** Transformation types that can be applied to a node */
+enum TransformType
+{
+    TF_LOOKAT,
+    TF_ROTATE,
+    TF_TRANSLATE,
+    TF_SCALE,
+    TF_SKEW,
+    TF_MATRIX
+};
+
+/** Different types of input data to a vertex or face */
+enum InputType
+{
+    IT_Invalid,
+    IT_Vertex,  // special type for per-index data referring to the <vertices> element carrying the per-vertex data.
+    IT_Position,
+    IT_Normal,
+    IT_Texcoord,
+    IT_Color,
+    IT_Tangent,
+    IT_Bitangent
+};
+
+/** Supported controller types */
+enum ControllerType
+{
+    Skin,
+    Morph
+};
+
+/** Supported morph methods */
+enum MorphMethod
+{
+    Normalized,
+    Relative
+};
+
+
+/** Contains all data for one of the different transformation types */
+struct Transform
+{
+    std::string mID;  ///< SID of the transform step, by which anim channels address their target node
+    TransformType mType;
+    ai_real f[16]; ///< Interpretation of data depends on the type of the transformation
+};
+
+/** A collada camera. */
+struct Camera
+{
+    Camera()
+        :   mOrtho  (false)
+        ,   mHorFov (10e10f)
+        ,   mVerFov (10e10f)
+        ,   mAspect (10e10f)
+        ,   mZNear  (0.1f)
+        ,   mZFar   (1000.f)
+    {}
+
+    // Name of camera
+    std::string mName;
+
+    // True if it is an orthografic camera
+    bool mOrtho;
+
+    //! Horizontal field of view in degrees
+    ai_real mHorFov;
+
+    //! Vertical field of view in degrees
+    ai_real mVerFov;
+
+    //! Screen aspect
+    ai_real mAspect;
+
+    //! Near& far z
+    ai_real mZNear, mZFar;
+};
+
+#define ASSIMP_COLLADA_LIGHT_ANGLE_NOT_SET 1e9f
+
+/** A collada light source. */
+struct Light
+{
+    Light()
+        :   mType            (aiLightSource_UNDEFINED)
+        ,   mAttConstant     (1.f)
+        ,   mAttLinear       (0.f)
+        ,   mAttQuadratic    (0.f)
+        ,   mFalloffAngle    (180.f)
+        ,   mFalloffExponent (0.f)
+        ,   mPenumbraAngle   (ASSIMP_COLLADA_LIGHT_ANGLE_NOT_SET)
+        ,   mOuterAngle      (ASSIMP_COLLADA_LIGHT_ANGLE_NOT_SET)
+        ,   mIntensity       (1.f)
+    {}
+
+    //! Type of the light source aiLightSourceType + ambient
+    unsigned int mType;
+
+    //! Color of the light
+    aiColor3D mColor;
+
+    //! Light attenuation
+    ai_real mAttConstant,mAttLinear,mAttQuadratic;
+
+    //! Spot light falloff
+    ai_real mFalloffAngle;
+    ai_real mFalloffExponent;
+
+    // -----------------------------------------------------
+    // FCOLLADA extension from here
+
+    //! ... related stuff from maja and max extensions
+    ai_real mPenumbraAngle;
+    ai_real mOuterAngle;
+
+    //! Common light intensity
+    ai_real mIntensity;
+};
+
+/** Short vertex index description */
+struct InputSemanticMapEntry
+{
+    InputSemanticMapEntry()
+        :   mSet(0)
+        ,   mType(IT_Invalid)
+    {}
+
+    //! Index of set, optional
+    unsigned int mSet;
+
+    //! Type of referenced vertex input
+    InputType mType;
+};
+
+/** Table to map from effect to vertex input semantics */
+struct SemanticMappingTable
+{
+    //! Name of material
+    std::string mMatName;
+
+    //! List of semantic map commands, grouped by effect semantic name
+    std::map<std::string, InputSemanticMapEntry> mMap;
+
+    //! For std::find
+    bool operator == (const std::string& s) const {
+        return s == mMatName;
+    }
+};
+
+/** A reference to a mesh inside a node, including materials assigned to the various subgroups.
+ * The ID refers to either a mesh or a controller which specifies the mesh
+ */
+struct MeshInstance
+{
+    ///< ID of the mesh or controller to be instanced
+    std::string mMeshOrController;
+
+    ///< Map of materials by the subgroup ID they're applied to
+    std::map<std::string, SemanticMappingTable> mMaterials;
+};
+
+/** A reference to a camera inside a node*/
+struct CameraInstance
+{
+     ///< ID of the camera
+    std::string mCamera;
+};
+
+/** A reference to a light inside a node*/
+struct LightInstance
+{
+     ///< ID of the camera
+    std::string mLight;
+};
+
+/** A reference to a node inside a node*/
+struct NodeInstance
+{
+     ///< ID of the node
+    std::string mNode;
+};
+
+/** A node in a scene hierarchy */
+struct Node
+{
+    std::string mName;
+    std::string mID;
+    std::string mSID;
+    Node* mParent;
+    std::vector<Node*> mChildren;
+
+    /** Operations in order to calculate the resulting transformation to parent. */
+    std::vector<Transform> mTransforms;
+
+    /** Meshes at this node */
+    std::vector<MeshInstance> mMeshes;
+
+    /** Lights at this node */
+    std::vector<LightInstance> mLights;
+
+    /** Cameras at this node */
+    std::vector<CameraInstance> mCameras;
+
+    /** Node instances at this node */
+    std::vector<NodeInstance> mNodeInstances;
+
+    /** Root-nodes: Name of primary camera, if any */
+    std::string mPrimaryCamera;
+
+    //! Constructor. Begin with a zero parent
+    Node()
+    : mParent( nullptr ){
+        // empty
+    }
+
+    //! Destructor: delete all children subsequently
+    ~Node() {
+        for( std::vector<Node*>::iterator it = mChildren.begin(); it != mChildren.end(); ++it)
+            delete *it;
+    }
+};
+
+/** Data source array: either floats or strings */
+struct Data
+{
+    bool mIsStringArray;
+    std::vector<ai_real> mValues;
+    std::vector<std::string> mStrings;
+};
+
+/** Accessor to a data array */
+struct Accessor
+{
+    size_t mCount;   // in number of objects
+    size_t mSize;    // size of an object, in elements (floats or strings, mostly 1)
+    size_t mOffset;  // in number of values
+    size_t mStride;  // Stride in number of values
+    std::vector<std::string> mParams; // names of the data streams in the accessors. Empty string tells to ignore.
+    size_t mSubOffset[4]; // Suboffset inside the object for the common 4 elements. For a vector, that's XYZ, for a color RGBA and so on.
+                          // For example, SubOffset[0] denotes which of the values inside the object is the vector X component.
+    std::string mSource;   // URL of the source array
+    mutable const Data* mData; // Pointer to the source array, if resolved. NULL else
+
+    Accessor()
+    {
+        mCount = 0; mSize = 0; mOffset = 0; mStride = 0; mData = NULL;
+        mSubOffset[0] = mSubOffset[1] = mSubOffset[2] = mSubOffset[3] = 0;
+    }
+};
+
+/** A single face in a mesh */
+struct Face
+{
+    std::vector<size_t> mIndices;
+};
+
+/** An input channel for mesh data, referring to a single accessor */
+struct InputChannel
+{
+    InputType mType;      // Type of the data
+    size_t mIndex;        // Optional index, if multiple sets of the same data type are given
+    size_t mOffset;       // Index offset in the indices array of per-face indices. Don't ask, can't explain that any better.
+    std::string mAccessor; // ID of the accessor where to read the actual values from.
+    mutable const Accessor* mResolved; // Pointer to the accessor, if resolved. NULL else
+
+    InputChannel() { mType = IT_Invalid; mIndex = 0; mOffset = 0; mResolved = NULL; }
+};
+
+/** Subset of a mesh with a certain material */
+struct SubMesh
+{
+    std::string mMaterial; ///< subgroup identifier
+    size_t mNumFaces; ///< number of faces in this submesh
+};
+
+/** Contains data for a single mesh */
+struct Mesh
+{
+    Mesh()
+    {
+        for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS;++i)
+            mNumUVComponents[i] = 2;
+    }
+
+    std::string mName;
+
+    // just to check if there's some sophisticated addressing involved...
+    // which we don't support, and therefore should warn about.
+    std::string mVertexID;
+
+    // Vertex data addressed by vertex indices
+    std::vector<InputChannel> mPerVertexData;
+
+    // actual mesh data, assembled on encounter of a <p> element. Verbose format, not indexed
+    std::vector<aiVector3D> mPositions;
+    std::vector<aiVector3D> mNormals;
+    std::vector<aiVector3D> mTangents;
+    std::vector<aiVector3D> mBitangents;
+    std::vector<aiVector3D> mTexCoords[AI_MAX_NUMBER_OF_TEXTURECOORDS];
+    std::vector<aiColor4D>  mColors[AI_MAX_NUMBER_OF_COLOR_SETS];
+
+    unsigned int mNumUVComponents[AI_MAX_NUMBER_OF_TEXTURECOORDS];
+
+    // Faces. Stored are only the number of vertices for each face.
+    // 1 == point, 2 == line, 3 == triangle, 4+ == poly
+    std::vector<size_t> mFaceSize;
+
+    // Position indices for all faces in the sequence given in mFaceSize -
+    // necessary for bone weight assignment
+    std::vector<size_t> mFacePosIndices;
+
+    // Submeshes in this mesh, each with a given material
+    std::vector<SubMesh> mSubMeshes;
+};
+
+/** Which type of primitives the ReadPrimitives() function is going to read */
+enum PrimitiveType
+{
+    Prim_Invalid,
+    Prim_Lines,
+    Prim_LineStrip,
+    Prim_Triangles,
+    Prim_TriStrips,
+    Prim_TriFans,
+    Prim_Polylist,
+    Prim_Polygon
+};
+
+/** A skeleton controller to deform a mesh with the use of joints */
+struct Controller
+{
+    // controller type
+    ControllerType mType;
+
+    // Morphing method if type is Morph
+    MorphMethod mMethod;
+
+    // the URL of the mesh deformed by the controller.
+    std::string mMeshId;
+
+    // accessor URL of the joint names
+    std::string mJointNameSource;
+
+    ///< The bind shape matrix, as array of floats. I'm not sure what this matrix actually describes, but it can't be ignored in all cases
+    ai_real mBindShapeMatrix[16];
+
+    // accessor URL of the joint inverse bind matrices
+    std::string mJointOffsetMatrixSource;
+
+    // input channel: joint names.
+    InputChannel mWeightInputJoints;
+    // input channel: joint weights
+    InputChannel mWeightInputWeights;
+
+    // Number of weights per vertex.
+    std::vector<size_t> mWeightCounts;
+
+    // JointIndex-WeightIndex pairs for all vertices
+    std::vector< std::pair<size_t, size_t> > mWeights;
+
+    std::string mMorphTarget;
+    std::string mMorphWeight;
+};
+
+/** A collada material. Pretty much the only member is a reference to an effect. */
+struct Material
+{
+    std::string mName;
+    std::string mEffect;
+};
+
+/** Type of the effect param */
+enum ParamType
+{
+    Param_Sampler,
+    Param_Surface
+};
+
+/** A param for an effect. Might be of several types, but they all just refer to each other, so I summarize them */
+struct EffectParam
+{
+    ParamType mType;
+    std::string mReference; // to which other thing the param is referring to.
+};
+
+/** Shading type supported by the standard effect spec of Collada */
+enum ShadeType
+{
+    Shade_Invalid,
+    Shade_Constant,
+    Shade_Lambert,
+    Shade_Phong,
+    Shade_Blinn
+};
+
+/** Represents a texture sampler in collada */
+struct Sampler
+{
+    Sampler()
+        :   mWrapU      (true)
+        ,   mWrapV      (true)
+        ,   mMirrorU    ()
+        ,   mMirrorV    ()
+        ,   mOp         (aiTextureOp_Multiply)
+        ,   mUVId       (UINT_MAX)
+        ,   mWeighting  (1.f)
+        ,   mMixWithPrevious (1.f)
+    {}
+
+    /** Name of image reference
+     */
+    std::string mName;
+
+    /** Wrap U?
+     */
+    bool mWrapU;
+
+    /** Wrap V?
+     */
+    bool mWrapV;
+
+    /** Mirror U?
+     */
+    bool mMirrorU;
+
+    /** Mirror V?
+     */
+    bool mMirrorV;
+
+    /** Blend mode
+     */
+    aiTextureOp mOp;
+
+    /** UV transformation
+     */
+    aiUVTransform mTransform;
+
+    /** Name of source UV channel
+     */
+    std::string mUVChannel;
+
+    /** Resolved UV channel index or UINT_MAX if not known
+     */
+    unsigned int mUVId;
+
+    // OKINO/MAX3D extensions from here
+    // -------------------------------------------------------
+
+    /** Weighting factor
+     */
+    ai_real mWeighting;
+
+    /** Mixing factor from OKINO
+     */
+    ai_real mMixWithPrevious;
+};
+
+/** A collada effect. Can contain about anything according to the Collada spec,
+    but we limit our version to a reasonable subset. */
+struct Effect
+{
+    // Shading mode
+    ShadeType mShadeType;
+
+    // Colors
+    aiColor4D mEmissive, mAmbient, mDiffuse, mSpecular,
+        mTransparent, mReflective;
+
+    // Textures
+    Sampler mTexEmissive, mTexAmbient, mTexDiffuse, mTexSpecular,
+        mTexTransparent, mTexBump, mTexReflective;
+
+    // Scalar factory
+    ai_real mShininess, mRefractIndex, mReflectivity;
+    ai_real mTransparency;
+    bool mHasTransparency;
+    bool mRGBTransparency;
+    bool mInvertTransparency;
+
+    // local params referring to each other by their SID
+    typedef std::map<std::string, Collada::EffectParam> ParamLibrary;
+    ParamLibrary mParams;
+
+    // MAX3D extensions
+    // ---------------------------------------------------------
+    // Double-sided?
+    bool mDoubleSided, mWireframe, mFaceted;
+
+    Effect()
+        : mShadeType    (Shade_Phong)
+        , mEmissive     ( 0, 0, 0, 1)
+        , mAmbient      ( 0.1f, 0.1f, 0.1f, 1)
+        , mDiffuse      ( 0.6f, 0.6f, 0.6f, 1)
+        , mSpecular     ( 0.4f, 0.4f, 0.4f, 1)
+        , mTransparent  ( 0, 0, 0, 1)
+        , mShininess    (10.0f)
+        , mRefractIndex (1.f)
+        , mReflectivity (0.f)
+        , mTransparency (1.f)
+        , mHasTransparency (false)
+        , mRGBTransparency(false)
+        , mInvertTransparency(false)
+        , mDoubleSided  (false)
+        , mWireframe    (false)
+        , mFaceted      (false)
+    {
+    }
+};
+
+/** An image, meaning texture */
+struct Image
+{
+    std::string mFileName;
+
+    /** If image file name is zero, embedded image data
+     */
+    std::vector<uint8_t> mImageData;
+
+    /** If image file name is zero, file format of
+     *  embedded image data.
+     */
+    std::string mEmbeddedFormat;
+
+};
+
+/** An animation channel. */
+struct AnimationChannel
+{
+    /** URL of the data to animate. Could be about anything, but we support only the
+     * "NodeID/TransformID.SubElement" notation
+     */
+    std::string mTarget;
+
+    /** Source URL of the time values. Collada calls them "input". Meh. */
+    std::string mSourceTimes;
+    /** Source URL of the value values. Collada calls them "output". */
+    std::string mSourceValues;
+    /** Source URL of the IN_TANGENT semantic values. */
+    std::string mInTanValues;
+    /** Source URL of the OUT_TANGENT semantic values. */
+    std::string mOutTanValues;
+    /** Source URL of the INTERPOLATION semantic values. */
+    std::string mInterpolationValues;
+};
+
+/** An animation. Container for 0-x animation channels or 0-x animations */
+struct Animation
+{
+    /** Anim name */
+    std::string mName;
+
+    /** the animation channels, if any */
+    std::vector<AnimationChannel> mChannels;
+
+    /** the sub-animations, if any */
+    std::vector<Animation*> mSubAnims;
+
+    /** Destructor */
+    ~Animation()
+    {
+        for( std::vector<Animation*>::iterator it = mSubAnims.begin(); it != mSubAnims.end(); ++it)
+            delete *it;
+    }
+
+	/** Collect all channels in the animation hierarchy into a single channel list. */
+	void CollectChannelsRecursively(std::vector<AnimationChannel> &channels)
+	{
+		channels.insert(channels.end(), mChannels.begin(), mChannels.end());
+
+		for (std::vector<Animation*>::iterator it = mSubAnims.begin(); it != mSubAnims.end(); ++it)
+		{
+			Animation *pAnim = (*it);
+
+			pAnim->CollectChannelsRecursively(channels);
+		}
+	}
+
+	/** Combine all single-channel animations' channel into the same (parent) animation channel list. */
+	void CombineSingleChannelAnimations()
+	{
+		CombineSingleChannelAnimationsRecursively(this);
+	}
+
+	void CombineSingleChannelAnimationsRecursively(Animation *pParent)
+	{
+		for (std::vector<Animation*>::iterator it = pParent->mSubAnims.begin(); it != pParent->mSubAnims.end();)
+		{
+			Animation *anim = *it;
+
+			CombineSingleChannelAnimationsRecursively(anim);
+
+			if (anim->mChannels.size() == 1)
+			{
+				pParent->mChannels.push_back(anim->mChannels[0]);
+
+				it = pParent->mSubAnims.erase(it);
+
+				delete anim;
+			}
+			else
+			{
+				++it;
+			}
+		}
+	}
+};
+
+/** Description of a collada animation channel which has been determined to affect the current node */
+struct ChannelEntry
+{
+    const Collada::AnimationChannel* mChannel; ///> the source channel
+    std::string mTargetId;
+    std::string mTransformId;   // the ID of the transformation step of the node which is influenced
+    size_t mTransformIndex; // Index into the node's transform chain to apply the channel to
+    size_t mSubElement; // starting index inside the transform data
+
+    // resolved data references
+    const Collada::Accessor* mTimeAccessor; ///> Collada accessor to the time values
+    const Collada::Data* mTimeData; ///> Source data array for the time values
+    const Collada::Accessor* mValueAccessor; ///> Collada accessor to the key value values
+    const Collada::Data* mValueData; ///> Source datat array for the key value values
+
+    ChannelEntry()
+      : mChannel()
+      , mTransformIndex()
+      , mSubElement()
+      , mTimeAccessor()
+      , mTimeData()
+      , mValueAccessor()
+      , mValueData()
+   {}
+};
+
+} // end of namespace Collada
+} // end of namespace Assimp
+
+#endif // AI_COLLADAHELPER_H_INC

+ 1954 - 0
Engine/lib/assimp/code/ColladaLoader.cpp

@@ -0,0 +1,1954 @@
+/*
+---------------------------------------------------------------------------
+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 Collada loader */
+
+
+#ifndef ASSIMP_BUILD_NO_COLLADA_IMPORTER
+
+#include "ColladaLoader.h"
+#include "ColladaParser.h"
+
+#include <assimp/anim.h>
+#include <assimp/scene.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/Importer.hpp>
+#include <assimp/importerdesc.h>
+#include <assimp/Defines.h>
+
+#include <assimp/fast_atof.h>
+#include <assimp/ParsingUtils.h>
+#include <assimp/SkeletonMeshBuilder.h>
+#include <assimp/CreateAnimMesh.h>
+
+#include "time.h"
+#include "math.h"
+#include <algorithm>
+#include <numeric>
+
+using namespace Assimp;
+using namespace Assimp::Formatter;
+
+static const aiImporterDesc desc = {
+    "Collada Importer",
+    "",
+    "",
+    "http://collada.org",
+    aiImporterFlags_SupportTextFlavour,
+    1,
+    3,
+    1,
+    5,
+    "dae"
+};
+
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by Importer
+ColladaLoader::ColladaLoader()
+: mFileName()
+, mMeshIndexByID()
+, mMaterialIndexByName()
+, mMeshes()
+, newMats()
+, mCameras()
+, mLights()
+, mTextures()
+, mAnims()
+, noSkeletonMesh( false )
+, ignoreUpDirection(false)
+, useColladaName( false )
+, mNodeNameCounter( 0 ) {
+    // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+ColladaLoader::~ColladaLoader() {
+    // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+// Returns whether the class can handle the format of the given file.
+bool ColladaLoader::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const {
+    // check file extension
+    std::string extension = GetExtension(pFile);
+
+    if (extension == "dae") {
+        return true;
+    }
+
+    // XML - too generic, we need to open the file and search for typical keywords
+    if( extension == "xml" || !extension.length() || checkSig)  {
+        /*  If CanRead() is called in order to check whether we
+         *  support a specific file extension in general pIOHandler
+         *  might be NULL and it's our duty to return true here.
+         */
+        if (!pIOHandler) {
+            return true;
+        }
+        const char* tokens[] = {"<collada"};
+        return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1);
+    }
+
+    return false;
+}
+
+// ------------------------------------------------------------------------------------------------
+void ColladaLoader::SetupProperties(const Importer* pImp)
+{
+    noSkeletonMesh = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_NO_SKELETON_MESHES,0) != 0;
+    ignoreUpDirection = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_COLLADA_IGNORE_UP_DIRECTION,0) != 0;
+    useColladaName = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_COLLADA_USE_COLLADA_NAMES,0) != 0;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Get file extension list
+const aiImporterDesc* ColladaLoader::GetInfo () const
+{
+    return &desc;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Imports the given file into the given scene structure.
+void ColladaLoader::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) {
+    mFileName = pFile;
+
+    // clean all member arrays - just for safety, it should work even if we did not
+    mMeshIndexByID.clear();
+    mMaterialIndexByName.clear();
+    mMeshes.clear();
+    mTargetMeshes.clear();
+    newMats.clear();
+    mLights.clear();
+    mCameras.clear();
+    mTextures.clear();
+    mAnims.clear();
+
+    // parse the input file
+    ColladaParser parser( pIOHandler, pFile);
+
+    if( !parser.mRootNode)
+        throw DeadlyImportError( "Collada: File came out empty. Something is wrong here.");
+
+    // reserve some storage to avoid unnecessary reallocs
+    newMats.reserve(parser.mMaterialLibrary.size()*2);
+    mMeshes.reserve(parser.mMeshLibrary.size()*2);
+
+    mCameras.reserve(parser.mCameraLibrary.size());
+    mLights.reserve(parser.mLightLibrary.size());
+
+    // create the materials first, for the meshes to find
+    BuildMaterials( parser, pScene);
+
+    // build the node hierarchy from it
+    pScene->mRootNode = BuildHierarchy( parser, parser.mRootNode);
+
+    // ... then fill the materials with the now adjusted settings
+    FillMaterials(parser, pScene);
+
+    // Apply unit-size scale calculation
+    pScene->mRootNode->mTransformation *= aiMatrix4x4(parser.mUnitSize, 0,  0,  0,
+                                                        0,  parser.mUnitSize,  0,  0,
+                                                        0,  0,  parser.mUnitSize,  0,
+                                                        0,  0,  0,  1);
+    if( !ignoreUpDirection ) {
+    // Convert to Y_UP, if different orientation
+    if( parser.mUpDirection == ColladaParser::UP_X)
+        pScene->mRootNode->mTransformation *= aiMatrix4x4(
+                0, -1,  0,  0,
+                1,  0,  0,  0,
+                0,  0,  1,  0,
+                0,  0,  0,  1);
+    else if( parser.mUpDirection == ColladaParser::UP_Z)
+        pScene->mRootNode->mTransformation *= aiMatrix4x4(
+                1,  0,  0,  0,
+                0,  0,  1,  0,
+                0, -1,  0,  0,
+                0,  0,  0,  1);
+    }
+
+    // store all meshes
+    StoreSceneMeshes( pScene);
+
+    // store all materials
+    StoreSceneMaterials( pScene);
+
+    // store all lights
+    StoreSceneLights( pScene);
+
+    // store all cameras
+    StoreSceneCameras( pScene);
+
+    // store all animations
+    StoreAnimations( pScene, parser);
+
+
+    // If no meshes have been loaded, it's probably just an animated skeleton.
+    if (!pScene->mNumMeshes) {
+
+        if (!noSkeletonMesh) {
+            SkeletonMeshBuilder hero(pScene);
+        }
+        pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Recursively constructs a scene node for the given parser node and returns it.
+aiNode* ColladaLoader::BuildHierarchy( const ColladaParser& pParser, const Collada::Node* pNode)
+{
+    // create a node for it
+    aiNode* node = new aiNode();
+
+    // find a name for the new node. It's more complicated than you might think
+    node->mName.Set( FindNameForNode( pNode));
+
+    // calculate the transformation matrix for it
+    node->mTransformation = pParser.CalculateResultTransform( pNode->mTransforms);
+
+    // now resolve node instances
+    std::vector<const Collada::Node*> instances;
+    ResolveNodeInstances(pParser,pNode,instances);
+
+    // add children. first the *real* ones
+    node->mNumChildren = static_cast<unsigned int>(pNode->mChildren.size()+instances.size());
+    node->mChildren = new aiNode*[node->mNumChildren];
+
+    for( size_t a = 0; a < pNode->mChildren.size(); a++)
+    {
+        node->mChildren[a] = BuildHierarchy( pParser, pNode->mChildren[a]);
+        node->mChildren[a]->mParent = node;
+    }
+
+    // ... and finally the resolved node instances
+    for( size_t a = 0; a < instances.size(); a++)
+    {
+        node->mChildren[pNode->mChildren.size() + a] = BuildHierarchy( pParser, instances[a]);
+        node->mChildren[pNode->mChildren.size() + a]->mParent = node;
+    }
+
+    // construct meshes
+    BuildMeshesForNode( pParser, pNode, node);
+
+    // construct cameras
+    BuildCamerasForNode(pParser, pNode, node);
+
+    // construct lights
+    BuildLightsForNode(pParser, pNode, node);
+    return node;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Resolve node instances
+void ColladaLoader::ResolveNodeInstances( const ColladaParser& pParser, const Collada::Node* pNode,
+    std::vector<const Collada::Node*>& resolved)
+{
+    // reserve enough storage
+    resolved.reserve(pNode->mNodeInstances.size());
+
+    // ... and iterate through all nodes to be instanced as children of pNode
+    for (const auto &nodeInst: pNode->mNodeInstances)
+    {
+        // find the corresponding node in the library
+        const ColladaParser::NodeLibrary::const_iterator itt = pParser.mNodeLibrary.find(nodeInst.mNode);
+        const Collada::Node* nd = itt == pParser.mNodeLibrary.end() ? NULL : (*itt).second;
+
+        // FIX for http://sourceforge.net/tracker/?func=detail&aid=3054873&group_id=226462&atid=1067632
+        // need to check for both name and ID to catch all. To avoid breaking valid files,
+        // the workaround is only enabled when the first attempt to resolve the node has failed.
+        if (!nd) {
+            nd = FindNode(pParser.mRootNode, nodeInst.mNode);
+        }
+        if (!nd)
+            ASSIMP_LOG_ERROR_F("Collada: Unable to resolve reference to instanced node ", nodeInst.mNode);
+
+        else {
+            //  attach this node to the list of children
+            resolved.push_back(nd);
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Resolve UV channels
+void ColladaLoader::ApplyVertexToEffectSemanticMapping(Collada::Sampler& sampler,
+     const Collada::SemanticMappingTable& table)
+{
+    std::map<std::string, Collada::InputSemanticMapEntry>::const_iterator it = table.mMap.find(sampler.mUVChannel);
+    if (it != table.mMap.end()) {
+        if (it->second.mType != Collada::IT_Texcoord)
+            ASSIMP_LOG_ERROR("Collada: Unexpected effect input mapping");
+
+        sampler.mUVId = it->second.mSet;
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Builds lights for the given node and references them
+void ColladaLoader::BuildLightsForNode( const ColladaParser& pParser, const Collada::Node* pNode, aiNode* pTarget)
+{
+    for( const Collada::LightInstance& lid : pNode->mLights)
+    {
+        // find the referred light
+        ColladaParser::LightLibrary::const_iterator srcLightIt = pParser.mLightLibrary.find( lid.mLight);
+        if( srcLightIt == pParser.mLightLibrary.end())
+        {
+            ASSIMP_LOG_WARN_F("Collada: Unable to find light for ID \"" , lid.mLight , "\". Skipping.");
+            continue;
+        }
+        const Collada::Light* srcLight = &srcLightIt->second;
+
+        // now fill our ai data structure
+        aiLight* out = new aiLight();
+        out->mName = pTarget->mName;
+        out->mType = (aiLightSourceType)srcLight->mType;
+
+        // collada lights point in -Z by default, rest is specified in node transform
+        out->mDirection = aiVector3D(0.f,0.f,-1.f);
+
+        out->mAttenuationConstant = srcLight->mAttConstant;
+        out->mAttenuationLinear = srcLight->mAttLinear;
+        out->mAttenuationQuadratic = srcLight->mAttQuadratic;
+
+        out->mColorDiffuse = out->mColorSpecular = out->mColorAmbient = srcLight->mColor*srcLight->mIntensity;
+        if (out->mType == aiLightSource_AMBIENT) {
+            out->mColorDiffuse = out->mColorSpecular = aiColor3D(0, 0, 0);
+            out->mColorAmbient = srcLight->mColor*srcLight->mIntensity;
+        }
+        else {
+            // collada doesn't differentiate between these color types
+            out->mColorDiffuse = out->mColorSpecular = srcLight->mColor*srcLight->mIntensity;
+            out->mColorAmbient = aiColor3D(0, 0, 0);
+        }
+
+        // convert falloff angle and falloff exponent in our representation, if given
+        if (out->mType == aiLightSource_SPOT) {
+
+            out->mAngleInnerCone = AI_DEG_TO_RAD( srcLight->mFalloffAngle );
+
+            // ... some extension magic.
+            if (srcLight->mOuterAngle >= ASSIMP_COLLADA_LIGHT_ANGLE_NOT_SET*(1-1e-6f))
+            {
+                // ... some deprecation magic.
+                if (srcLight->mPenumbraAngle >= ASSIMP_COLLADA_LIGHT_ANGLE_NOT_SET*(1-1e-6f))
+                {
+                    // Need to rely on falloff_exponent. I don't know how to interpret it, so I need to guess ....
+                    // epsilon chosen to be 0.1
+                    out->mAngleOuterCone = std::acos(std::pow(0.1f,1.f/srcLight->mFalloffExponent))+
+                            out->mAngleInnerCone;
+                }
+                else {
+                    out->mAngleOuterCone = out->mAngleInnerCone + AI_DEG_TO_RAD(  srcLight->mPenumbraAngle );
+                    if (out->mAngleOuterCone < out->mAngleInnerCone)
+                        std::swap(out->mAngleInnerCone,out->mAngleOuterCone);
+                }
+            }
+            else out->mAngleOuterCone = AI_DEG_TO_RAD(  srcLight->mOuterAngle );
+        }
+
+        // add to light list
+        mLights.push_back(out);
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Builds cameras for the given node and references them
+void ColladaLoader::BuildCamerasForNode( const ColladaParser& pParser, const Collada::Node* pNode, aiNode* pTarget)
+{
+    for( const Collada::CameraInstance& cid : pNode->mCameras)
+    {
+        // find the referred light
+        ColladaParser::CameraLibrary::const_iterator srcCameraIt = pParser.mCameraLibrary.find( cid.mCamera);
+        if( srcCameraIt == pParser.mCameraLibrary.end())
+        {
+            ASSIMP_LOG_WARN_F("Collada: Unable to find camera for ID \"" , cid.mCamera , "\". Skipping.");
+            continue;
+        }
+        const Collada::Camera* srcCamera = &srcCameraIt->second;
+
+        // orthographic cameras not yet supported in Assimp
+        if (srcCamera->mOrtho) {
+            ASSIMP_LOG_WARN("Collada: Orthographic cameras are not supported.");
+        }
+
+        // now fill our ai data structure
+        aiCamera* out = new aiCamera();
+        out->mName = pTarget->mName;
+
+        // collada cameras point in -Z by default, rest is specified in node transform
+        out->mLookAt = aiVector3D(0.f,0.f,-1.f);
+
+        // near/far z is already ok
+        out->mClipPlaneFar = srcCamera->mZFar;
+        out->mClipPlaneNear = srcCamera->mZNear;
+
+        // ... but for the rest some values are optional
+        // and we need to compute the others in any combination.
+         if (srcCamera->mAspect != 10e10f)
+            out->mAspect = srcCamera->mAspect;
+
+        if (srcCamera->mHorFov != 10e10f) {
+            out->mHorizontalFOV = srcCamera->mHorFov;
+
+            if (srcCamera->mVerFov != 10e10f && srcCamera->mAspect == 10e10f) {
+                out->mAspect = std::tan(AI_DEG_TO_RAD(srcCamera->mHorFov)) /
+                    std::tan(AI_DEG_TO_RAD(srcCamera->mVerFov));
+            }
+        }
+        else if (srcCamera->mAspect != 10e10f && srcCamera->mVerFov != 10e10f)  {
+            out->mHorizontalFOV = 2.0f * AI_RAD_TO_DEG(std::atan(srcCamera->mAspect *
+                std::tan(AI_DEG_TO_RAD(srcCamera->mVerFov) * 0.5f)));
+        }
+
+        // Collada uses degrees, we use radians
+        out->mHorizontalFOV = AI_DEG_TO_RAD(out->mHorizontalFOV);
+
+        // add to camera list
+        mCameras.push_back(out);
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Builds meshes for the given node and references them
+void ColladaLoader::BuildMeshesForNode( const ColladaParser& pParser, const Collada::Node* pNode, aiNode* pTarget)
+{
+    // accumulated mesh references by this node
+    std::vector<size_t> newMeshRefs;
+    newMeshRefs.reserve(pNode->mMeshes.size());
+
+    // add a mesh for each subgroup in each collada mesh
+    for( const Collada::MeshInstance& mid : pNode->mMeshes)
+    {
+        const Collada::Mesh* srcMesh = NULL;
+        const Collada::Controller* srcController = NULL;
+
+        // find the referred mesh
+        ColladaParser::MeshLibrary::const_iterator srcMeshIt = pParser.mMeshLibrary.find( mid.mMeshOrController);
+        if( srcMeshIt == pParser.mMeshLibrary.end())
+        {
+            // if not found in the mesh-library, it might also be a controller referring to a mesh
+            ColladaParser::ControllerLibrary::const_iterator srcContrIt = pParser.mControllerLibrary.find( mid.mMeshOrController);
+            if( srcContrIt != pParser.mControllerLibrary.end())
+            {
+                srcController = &srcContrIt->second;
+                srcMeshIt = pParser.mMeshLibrary.find( srcController->mMeshId);
+                if( srcMeshIt != pParser.mMeshLibrary.end())
+                    srcMesh = srcMeshIt->second;
+            }
+
+            if( !srcMesh)
+            {
+                ASSIMP_LOG_WARN_F( "Collada: Unable to find geometry for ID \"", mid.mMeshOrController, "\". Skipping." );
+                continue;
+            }
+        } else
+        {
+            // ID found in the mesh library -> direct reference to an unskinned mesh
+            srcMesh = srcMeshIt->second;
+        }
+
+        // build a mesh for each of its subgroups
+        size_t vertexStart = 0, faceStart = 0;
+        for( size_t sm = 0; sm < srcMesh->mSubMeshes.size(); ++sm)
+        {
+            const Collada::SubMesh& submesh = srcMesh->mSubMeshes[sm];
+            if( submesh.mNumFaces == 0)
+                continue;
+
+            // find material assigned to this submesh
+            std::string meshMaterial;
+            std::map<std::string, Collada::SemanticMappingTable >::const_iterator meshMatIt = mid.mMaterials.find( submesh.mMaterial);
+
+            const Collada::SemanticMappingTable* table = NULL;
+            if( meshMatIt != mid.mMaterials.end())
+            {
+                table = &meshMatIt->second;
+                meshMaterial = table->mMatName;
+            }
+            else
+            {
+                ASSIMP_LOG_WARN_F( "Collada: No material specified for subgroup <", submesh.mMaterial, "> in geometry <",
+                    mid.mMeshOrController, ">." );
+                if( !mid.mMaterials.empty() )
+                    meshMaterial = mid.mMaterials.begin()->second.mMatName;
+            }
+
+            // OK ... here the *real* fun starts ... we have the vertex-input-to-effect-semantic-table
+            // given. The only mapping stuff which we do actually support is the UV channel.
+            std::map<std::string, size_t>::const_iterator matIt = mMaterialIndexByName.find( meshMaterial);
+            unsigned int matIdx;
+            if( matIt != mMaterialIndexByName.end())
+                matIdx = static_cast<unsigned int>(matIt->second);
+            else
+                matIdx = 0;
+
+            if (table && !table->mMap.empty() ) {
+                std::pair<Collada::Effect*, aiMaterial*>&  mat = newMats[matIdx];
+
+                // Iterate through all texture channels assigned to the effect and
+                // check whether we have mapping information for it.
+                ApplyVertexToEffectSemanticMapping(mat.first->mTexDiffuse,    *table);
+                ApplyVertexToEffectSemanticMapping(mat.first->mTexAmbient,    *table);
+                ApplyVertexToEffectSemanticMapping(mat.first->mTexSpecular,   *table);
+                ApplyVertexToEffectSemanticMapping(mat.first->mTexEmissive,   *table);
+                ApplyVertexToEffectSemanticMapping(mat.first->mTexTransparent,*table);
+                ApplyVertexToEffectSemanticMapping(mat.first->mTexBump,       *table);
+            }
+
+            // built lookup index of the Mesh-Submesh-Material combination
+            ColladaMeshIndex index( mid.mMeshOrController, sm, meshMaterial);
+
+            // if we already have the mesh at the library, just add its index to the node's array
+            std::map<ColladaMeshIndex, size_t>::const_iterator dstMeshIt = mMeshIndexByID.find( index);
+            if( dstMeshIt != mMeshIndexByID.end())  {
+                newMeshRefs.push_back( dstMeshIt->second);
+            }
+            else
+            {
+                // else we have to add the mesh to the collection and store its newly assigned index at the node
+                aiMesh* dstMesh = CreateMesh( pParser, srcMesh, submesh, srcController, vertexStart, faceStart);
+
+                // store the mesh, and store its new index in the node
+                newMeshRefs.push_back( mMeshes.size());
+                mMeshIndexByID[index] = mMeshes.size();
+                mMeshes.push_back( dstMesh);
+                vertexStart += dstMesh->mNumVertices; faceStart += submesh.mNumFaces;
+
+                // assign the material index
+                dstMesh->mMaterialIndex = matIdx;
+                if(dstMesh->mName.length == 0)
+                {
+                    dstMesh->mName = mid.mMeshOrController;
+                }
+      }
+        }
+    }
+
+    // now place all mesh references we gathered in the target node
+    pTarget->mNumMeshes = static_cast<unsigned int>(newMeshRefs.size());
+    if( newMeshRefs.size())
+    {
+        struct UIntTypeConverter
+        {
+            unsigned int operator()(const size_t& v) const
+            {
+                return static_cast<unsigned int>(v);
+            }
+        };
+
+        pTarget->mMeshes = new unsigned int[pTarget->mNumMeshes];
+        std::transform( newMeshRefs.begin(), newMeshRefs.end(), pTarget->mMeshes, UIntTypeConverter());
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Find mesh from either meshes or morph target meshes
+aiMesh *ColladaLoader::findMesh(std::string meshid)
+{
+    for (unsigned int i = 0; i < mMeshes.size(); i++)
+        if (std::string(mMeshes[i]->mName.data) == meshid)
+            return mMeshes[i];
+
+    for (unsigned int i = 0; i < mTargetMeshes.size(); i++)
+        if (std::string(mTargetMeshes[i]->mName.data) == meshid)
+            return mTargetMeshes[i];
+
+    return NULL;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Creates a mesh for the given ColladaMesh face subset and returns the newly created mesh
+aiMesh* ColladaLoader::CreateMesh( const ColladaParser& pParser, const Collada::Mesh* pSrcMesh, const Collada::SubMesh& pSubMesh,
+    const Collada::Controller* pSrcController, size_t pStartVertex, size_t pStartFace)
+{
+    aiMesh* dstMesh = new aiMesh;
+
+    dstMesh->mName = pSrcMesh->mName;
+
+    // count the vertices addressed by its faces
+    const size_t numVertices = std::accumulate( pSrcMesh->mFaceSize.begin() + pStartFace,
+        pSrcMesh->mFaceSize.begin() + pStartFace + pSubMesh.mNumFaces, size_t(0));
+
+    // copy positions
+    dstMesh->mNumVertices = static_cast<unsigned int>(numVertices);
+    dstMesh->mVertices = new aiVector3D[numVertices];
+    std::copy( pSrcMesh->mPositions.begin() + pStartVertex, pSrcMesh->mPositions.begin() +
+        pStartVertex + numVertices, dstMesh->mVertices);
+
+    // normals, if given. HACK: (thom) Due to the glorious Collada spec we never
+    // know if we have the same number of normals as there are positions. So we
+    // also ignore any vertex attribute if it has a different count
+    if( pSrcMesh->mNormals.size() >= pStartVertex + numVertices)
+    {
+        dstMesh->mNormals = new aiVector3D[numVertices];
+        std::copy( pSrcMesh->mNormals.begin() + pStartVertex, pSrcMesh->mNormals.begin() +
+            pStartVertex + numVertices, dstMesh->mNormals);
+    }
+
+    // tangents, if given.
+    if( pSrcMesh->mTangents.size() >= pStartVertex + numVertices)
+    {
+        dstMesh->mTangents = new aiVector3D[numVertices];
+        std::copy( pSrcMesh->mTangents.begin() + pStartVertex, pSrcMesh->mTangents.begin() +
+            pStartVertex + numVertices, dstMesh->mTangents);
+    }
+
+    // bitangents, if given.
+    if( pSrcMesh->mBitangents.size() >= pStartVertex + numVertices)
+    {
+        dstMesh->mBitangents = new aiVector3D[numVertices];
+        std::copy( pSrcMesh->mBitangents.begin() + pStartVertex, pSrcMesh->mBitangents.begin() +
+            pStartVertex + numVertices, dstMesh->mBitangents);
+    }
+
+    // same for texturecoords, as many as we have
+    // empty slots are not allowed, need to pack and adjust UV indexes accordingly
+    for( size_t a = 0, real = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; a++)
+    {
+        if( pSrcMesh->mTexCoords[a].size() >= pStartVertex + numVertices)
+        {
+            dstMesh->mTextureCoords[real] = new aiVector3D[numVertices];
+            for( size_t b = 0; b < numVertices; ++b)
+                dstMesh->mTextureCoords[real][b] = pSrcMesh->mTexCoords[a][pStartVertex+b];
+
+            dstMesh->mNumUVComponents[real] = pSrcMesh->mNumUVComponents[a];
+            ++real;
+        }
+    }
+
+    // same for vertex colors, as many as we have. again the same packing to avoid empty slots
+    for( size_t a = 0, real = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; a++)
+    {
+        if( pSrcMesh->mColors[a].size() >= pStartVertex + numVertices)
+        {
+            dstMesh->mColors[real] = new aiColor4D[numVertices];
+            std::copy( pSrcMesh->mColors[a].begin() + pStartVertex, pSrcMesh->mColors[a].begin() + pStartVertex + numVertices,dstMesh->mColors[real]);
+            ++real;
+        }
+    }
+
+    // create faces. Due to the fact that each face uses unique vertices, we can simply count up on each vertex
+    size_t vertex = 0;
+    dstMesh->mNumFaces = static_cast<unsigned int>(pSubMesh.mNumFaces);
+    dstMesh->mFaces = new aiFace[dstMesh->mNumFaces];
+    for( size_t a = 0; a < dstMesh->mNumFaces; ++a)
+    {
+        size_t s = pSrcMesh->mFaceSize[ pStartFace + a];
+        aiFace& face = dstMesh->mFaces[a];
+        face.mNumIndices = static_cast<unsigned int>(s);
+        face.mIndices = new unsigned int[s];
+        for( size_t b = 0; b < s; ++b)
+            face.mIndices[b] = static_cast<unsigned int>(vertex++);
+    }
+
+    // create morph target meshes if any
+    std::vector<aiMesh*> targetMeshes;
+    std::vector<float> targetWeights;
+    Collada::MorphMethod method = Collada::Normalized;
+
+    for(std::map<std::string, Collada::Controller>::const_iterator it = pParser.mControllerLibrary.begin();
+        it != pParser.mControllerLibrary.end(); it++)
+    {
+        const Collada::Controller &c = it->second;
+        const Collada::Mesh* baseMesh = pParser.ResolveLibraryReference( pParser.mMeshLibrary, c.mMeshId);
+
+        if (c.mType == Collada::Morph && baseMesh->mName == pSrcMesh->mName)
+        {
+            const Collada::Accessor& targetAccessor = pParser.ResolveLibraryReference( pParser.mAccessorLibrary, c.mMorphTarget);
+            const Collada::Accessor& weightAccessor = pParser.ResolveLibraryReference( pParser.mAccessorLibrary, c.mMorphWeight);
+            const Collada::Data& targetData = pParser.ResolveLibraryReference( pParser.mDataLibrary, targetAccessor.mSource);
+            const Collada::Data& weightData = pParser.ResolveLibraryReference( pParser.mDataLibrary, weightAccessor.mSource);
+
+            // take method
+            method = c.mMethod;
+
+            if (!targetData.mIsStringArray)
+                throw DeadlyImportError( "target data must contain id. ");
+            if (weightData.mIsStringArray)
+                throw DeadlyImportError( "target weight data must not be textual ");
+
+            for (unsigned int i = 0; i < targetData.mStrings.size(); ++i)
+            {
+                const Collada::Mesh* targetMesh = pParser.ResolveLibraryReference(pParser.mMeshLibrary, targetData.mStrings.at(i));
+
+                aiMesh *aimesh = findMesh(targetMesh->mName);
+                if (!aimesh)
+                {
+                    if (targetMesh->mSubMeshes.size() > 1)
+                        throw DeadlyImportError( "Morhing target mesh must be a single");
+                    aimesh = CreateMesh(pParser, targetMesh, targetMesh->mSubMeshes.at(0), NULL, 0, 0);
+                    mTargetMeshes.push_back(aimesh);
+                }
+                targetMeshes.push_back(aimesh);
+            }
+            for (unsigned int i = 0; i < weightData.mValues.size(); ++i)
+                targetWeights.push_back(weightData.mValues.at(i));
+        }
+    }
+    if (targetMeshes.size() > 0 && targetWeights.size() == targetMeshes.size())
+    {
+        std::vector<aiAnimMesh*> animMeshes;
+        for (unsigned int i = 0; i < targetMeshes.size(); i++)
+        {
+            aiMesh* targetMesh = targetMeshes.at(i);
+            aiAnimMesh *animMesh = aiCreateAnimMesh(targetMesh);
+            float weight = targetWeights[i];
+            animMesh->mWeight = weight == 0 ? 1.0f : weight;
+            animMesh->mName = targetMesh->mName;
+            animMeshes.push_back(animMesh);
+        }
+        dstMesh->mMethod = (method == Collada::Relative)
+                                ? aiMorphingMethod_MORPH_RELATIVE
+                                : aiMorphingMethod_MORPH_NORMALIZED;
+        dstMesh->mAnimMeshes = new aiAnimMesh*[animMeshes.size()];
+        dstMesh->mNumAnimMeshes = static_cast<unsigned int>(animMeshes.size());
+        for (unsigned int i = 0; i < animMeshes.size(); i++)
+            dstMesh->mAnimMeshes[i] = animMeshes.at(i);
+    }
+
+    // create bones if given
+    if( pSrcController && pSrcController->mType == Collada::Skin)
+    {
+        // resolve references - joint names
+        const Collada::Accessor& jointNamesAcc = pParser.ResolveLibraryReference( pParser.mAccessorLibrary, pSrcController->mJointNameSource);
+        const Collada::Data& jointNames = pParser.ResolveLibraryReference( pParser.mDataLibrary, jointNamesAcc.mSource);
+        // joint offset matrices
+        const Collada::Accessor& jointMatrixAcc = pParser.ResolveLibraryReference( pParser.mAccessorLibrary, pSrcController->mJointOffsetMatrixSource);
+        const Collada::Data& jointMatrices = pParser.ResolveLibraryReference( pParser.mDataLibrary, jointMatrixAcc.mSource);
+        // joint vertex_weight name list - should refer to the same list as the joint names above. If not, report and reconsider
+        const Collada::Accessor& weightNamesAcc = pParser.ResolveLibraryReference( pParser.mAccessorLibrary, pSrcController->mWeightInputJoints.mAccessor);
+        if( &weightNamesAcc != &jointNamesAcc)
+            throw DeadlyImportError( "Temporary implementational laziness. If you read this, please report to the author.");
+        // vertex weights
+        const Collada::Accessor& weightsAcc = pParser.ResolveLibraryReference( pParser.mAccessorLibrary, pSrcController->mWeightInputWeights.mAccessor);
+        const Collada::Data& weights = pParser.ResolveLibraryReference( pParser.mDataLibrary, weightsAcc.mSource);
+
+        if( !jointNames.mIsStringArray || jointMatrices.mIsStringArray || weights.mIsStringArray)
+            throw DeadlyImportError( "Data type mismatch while resolving mesh joints");
+        // sanity check: we rely on the vertex weights always coming as pairs of BoneIndex-WeightIndex
+        if( pSrcController->mWeightInputJoints.mOffset != 0 || pSrcController->mWeightInputWeights.mOffset != 1)
+            throw DeadlyImportError( "Unsupported vertex_weight addressing scheme. ");
+
+        // create containers to collect the weights for each bone
+        size_t numBones = jointNames.mStrings.size();
+        std::vector<std::vector<aiVertexWeight> > dstBones( numBones);
+
+        // build a temporary array of pointers to the start of each vertex's weights
+        typedef std::vector< std::pair<size_t, size_t> > IndexPairVector;
+        std::vector<IndexPairVector::const_iterator> weightStartPerVertex;
+        weightStartPerVertex.resize(pSrcController->mWeightCounts.size(),pSrcController->mWeights.end());
+
+        IndexPairVector::const_iterator pit = pSrcController->mWeights.begin();
+        for( size_t a = 0; a < pSrcController->mWeightCounts.size(); ++a)
+        {
+            weightStartPerVertex[a] = pit;
+            pit += pSrcController->mWeightCounts[a];
+        }
+
+        // now for each vertex put the corresponding vertex weights into each bone's weight collection
+        for( size_t a = pStartVertex; a < pStartVertex + numVertices; ++a)
+        {
+            // which position index was responsible for this vertex? that's also the index by which
+            // the controller assigns the vertex weights
+            size_t orgIndex = pSrcMesh->mFacePosIndices[a];
+            // find the vertex weights for this vertex
+            IndexPairVector::const_iterator iit = weightStartPerVertex[orgIndex];
+            size_t pairCount = pSrcController->mWeightCounts[orgIndex];
+
+            for( size_t b = 0; b < pairCount; ++b, ++iit)
+            {
+                size_t jointIndex = iit->first;
+                size_t vertexIndex = iit->second;
+
+                ai_real weight = ReadFloat( weightsAcc, weights, vertexIndex, 0);
+
+                // one day I gonna kill that XSI Collada exporter
+                if( weight > 0.0f)
+                {
+                    aiVertexWeight w;
+                    w.mVertexId = static_cast<unsigned int>(a - pStartVertex);
+                    w.mWeight = weight;
+                    dstBones[jointIndex].push_back( w);
+                }
+            }
+        }
+
+        // count the number of bones which influence vertices of the current submesh
+        size_t numRemainingBones = 0;
+        for( std::vector<std::vector<aiVertexWeight> >::const_iterator it = dstBones.begin(); it != dstBones.end(); ++it)
+            if( it->size() > 0)
+                numRemainingBones++;
+
+        // create bone array and copy bone weights one by one
+        dstMesh->mNumBones = static_cast<unsigned int>(numRemainingBones);
+        dstMesh->mBones = new aiBone*[numRemainingBones];
+        size_t boneCount = 0;
+        for( size_t a = 0; a < numBones; ++a)
+        {
+            // omit bones without weights
+            if( dstBones[a].size() == 0)
+                continue;
+
+            // create bone with its weights
+            aiBone* bone = new aiBone;
+            bone->mName = ReadString( jointNamesAcc, jointNames, a);
+            bone->mOffsetMatrix.a1 = ReadFloat( jointMatrixAcc, jointMatrices, a, 0);
+            bone->mOffsetMatrix.a2 = ReadFloat( jointMatrixAcc, jointMatrices, a, 1);
+            bone->mOffsetMatrix.a3 = ReadFloat( jointMatrixAcc, jointMatrices, a, 2);
+            bone->mOffsetMatrix.a4 = ReadFloat( jointMatrixAcc, jointMatrices, a, 3);
+            bone->mOffsetMatrix.b1 = ReadFloat( jointMatrixAcc, jointMatrices, a, 4);
+            bone->mOffsetMatrix.b2 = ReadFloat( jointMatrixAcc, jointMatrices, a, 5);
+            bone->mOffsetMatrix.b3 = ReadFloat( jointMatrixAcc, jointMatrices, a, 6);
+            bone->mOffsetMatrix.b4 = ReadFloat( jointMatrixAcc, jointMatrices, a, 7);
+            bone->mOffsetMatrix.c1 = ReadFloat( jointMatrixAcc, jointMatrices, a, 8);
+            bone->mOffsetMatrix.c2 = ReadFloat( jointMatrixAcc, jointMatrices, a, 9);
+            bone->mOffsetMatrix.c3 = ReadFloat( jointMatrixAcc, jointMatrices, a, 10);
+            bone->mOffsetMatrix.c4 = ReadFloat( jointMatrixAcc, jointMatrices, a, 11);
+            bone->mNumWeights = static_cast<unsigned int>(dstBones[a].size());
+            bone->mWeights = new aiVertexWeight[bone->mNumWeights];
+            std::copy( dstBones[a].begin(), dstBones[a].end(), bone->mWeights);
+
+            // apply bind shape matrix to offset matrix
+            aiMatrix4x4 bindShapeMatrix;
+            bindShapeMatrix.a1 = pSrcController->mBindShapeMatrix[0];
+            bindShapeMatrix.a2 = pSrcController->mBindShapeMatrix[1];
+            bindShapeMatrix.a3 = pSrcController->mBindShapeMatrix[2];
+            bindShapeMatrix.a4 = pSrcController->mBindShapeMatrix[3];
+            bindShapeMatrix.b1 = pSrcController->mBindShapeMatrix[4];
+            bindShapeMatrix.b2 = pSrcController->mBindShapeMatrix[5];
+            bindShapeMatrix.b3 = pSrcController->mBindShapeMatrix[6];
+            bindShapeMatrix.b4 = pSrcController->mBindShapeMatrix[7];
+            bindShapeMatrix.c1 = pSrcController->mBindShapeMatrix[8];
+            bindShapeMatrix.c2 = pSrcController->mBindShapeMatrix[9];
+            bindShapeMatrix.c3 = pSrcController->mBindShapeMatrix[10];
+            bindShapeMatrix.c4 = pSrcController->mBindShapeMatrix[11];
+            bindShapeMatrix.d1 = pSrcController->mBindShapeMatrix[12];
+            bindShapeMatrix.d2 = pSrcController->mBindShapeMatrix[13];
+            bindShapeMatrix.d3 = pSrcController->mBindShapeMatrix[14];
+            bindShapeMatrix.d4 = pSrcController->mBindShapeMatrix[15];
+            bone->mOffsetMatrix *= bindShapeMatrix;
+
+            // HACK: (thom) Some exporters address the bone nodes by SID, others address them by ID or even name.
+            // Therefore I added a little name replacement here: I search for the bone's node by either name, ID or SID,
+            // and replace the bone's name by the node's name so that the user can use the standard
+            // find-by-name method to associate nodes with bones.
+            const Collada::Node* bnode = FindNode( pParser.mRootNode, bone->mName.data);
+            if( !bnode)
+                bnode = FindNodeBySID( pParser.mRootNode, bone->mName.data);
+
+            // assign the name that we would have assigned for the source node
+            if( bnode)
+                bone->mName.Set( FindNameForNode( bnode));
+            else
+                ASSIMP_LOG_WARN_F( "ColladaLoader::CreateMesh(): could not find corresponding node for joint \"", bone->mName.data, "\"." );
+
+            // and insert bone
+            dstMesh->mBones[boneCount++] = bone;
+        }
+    }
+
+    return dstMesh;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Stores all meshes in the given scene
+void ColladaLoader::StoreSceneMeshes( aiScene* pScene)
+{
+    pScene->mNumMeshes = static_cast<unsigned int>(mMeshes.size());
+    if( mMeshes.size() > 0)
+    {
+        pScene->mMeshes = new aiMesh*[mMeshes.size()];
+        std::copy( mMeshes.begin(), mMeshes.end(), pScene->mMeshes);
+        mMeshes.clear();
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Stores all cameras in the given scene
+void ColladaLoader::StoreSceneCameras( aiScene* pScene)
+{
+    pScene->mNumCameras = static_cast<unsigned int>(mCameras.size());
+    if( mCameras.size() > 0)
+    {
+        pScene->mCameras = new aiCamera*[mCameras.size()];
+        std::copy( mCameras.begin(), mCameras.end(), pScene->mCameras);
+        mCameras.clear();
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Stores all lights in the given scene
+void ColladaLoader::StoreSceneLights( aiScene* pScene)
+{
+    pScene->mNumLights = static_cast<unsigned int>(mLights.size());
+    if( mLights.size() > 0)
+    {
+        pScene->mLights = new aiLight*[mLights.size()];
+        std::copy( mLights.begin(), mLights.end(), pScene->mLights);
+        mLights.clear();
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Stores all textures in the given scene
+void ColladaLoader::StoreSceneTextures( aiScene* pScene)
+{
+    pScene->mNumTextures = static_cast<unsigned int>(mTextures.size());
+    if( mTextures.size() > 0)
+    {
+        pScene->mTextures = new aiTexture*[mTextures.size()];
+        std::copy( mTextures.begin(), mTextures.end(), pScene->mTextures);
+        mTextures.clear();
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Stores all materials in the given scene
+void ColladaLoader::StoreSceneMaterials( aiScene* pScene)
+{
+    pScene->mNumMaterials = static_cast<unsigned int>(newMats.size());
+
+    if (newMats.size() > 0) {
+        pScene->mMaterials = new aiMaterial*[newMats.size()];
+        for (unsigned int i = 0; i < newMats.size();++i)
+            pScene->mMaterials[i] = newMats[i].second;
+
+        newMats.clear();
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Stores all animations
+void ColladaLoader::StoreAnimations( aiScene* pScene, const ColladaParser& pParser)
+{
+    // recursively collect all animations from the collada scene
+    StoreAnimations( pScene, pParser, &pParser.mAnims, "");
+
+    // catch special case: many animations with the same length, each affecting only a single node.
+    // we need to unite all those single-node-anims to a proper combined animation
+    for( size_t a = 0; a < mAnims.size(); ++a)
+    {
+        aiAnimation* templateAnim = mAnims[a];
+        if( templateAnim->mNumChannels == 1)
+        {
+            // search for other single-channel-anims with the same duration
+            std::vector<size_t> collectedAnimIndices;
+            for( size_t b = a+1; b < mAnims.size(); ++b)
+            {
+                aiAnimation* other = mAnims[b];
+                if( other->mNumChannels == 1 && other->mDuration == templateAnim->mDuration && 
+                        other->mTicksPerSecond == templateAnim->mTicksPerSecond )
+                    collectedAnimIndices.push_back( b);
+            }
+
+            // if there are other animations which fit the template anim, combine all channels into a single anim
+            if( !collectedAnimIndices.empty() )
+            {
+                aiAnimation* combinedAnim = new aiAnimation();
+                combinedAnim->mName = aiString( std::string( "combinedAnim_") + char( '0' + a));
+                combinedAnim->mDuration = templateAnim->mDuration;
+                combinedAnim->mTicksPerSecond = templateAnim->mTicksPerSecond;
+                combinedAnim->mNumChannels = static_cast<unsigned int>(collectedAnimIndices.size() + 1);
+                combinedAnim->mChannels = new aiNodeAnim*[combinedAnim->mNumChannels];
+                // add the template anim as first channel by moving its aiNodeAnim to the combined animation
+                combinedAnim->mChannels[0] = templateAnim->mChannels[0];
+                templateAnim->mChannels[0] = NULL;
+                delete templateAnim;
+                // combined animation replaces template animation in the anim array
+                mAnims[a] = combinedAnim;
+
+                // move the memory of all other anims to the combined anim and erase them from the source anims
+                for( size_t b = 0; b < collectedAnimIndices.size(); ++b)
+                {
+                    aiAnimation* srcAnimation = mAnims[collectedAnimIndices[b]];
+                    combinedAnim->mChannels[1 + b] = srcAnimation->mChannels[0];
+                    srcAnimation->mChannels[0] = NULL;
+                    delete srcAnimation;
+                }
+
+                // in a second go, delete all the single-channel-anims that we've stripped from their channels
+                // back to front to preserve indices - you know, removing an element from a vector moves all elements behind the removed one
+                while( !collectedAnimIndices.empty() )
+                {
+                    mAnims.erase( mAnims.begin() + collectedAnimIndices.back());
+                    collectedAnimIndices.pop_back();
+                }
+            }
+        }
+    }
+
+    // now store all anims in the scene
+    if( !mAnims.empty())
+    {
+        pScene->mNumAnimations = static_cast<unsigned int>(mAnims.size());
+        pScene->mAnimations = new aiAnimation*[mAnims.size()];
+        std::copy( mAnims.begin(), mAnims.end(), pScene->mAnimations);
+    }
+
+    mAnims.clear();
+}
+
+// ------------------------------------------------------------------------------------------------
+// Constructs the animations for the given source anim
+void ColladaLoader::StoreAnimations( aiScene* pScene, const ColladaParser& pParser, const Collada::Animation* pSrcAnim, const std::string &pPrefix)
+{
+    std::string animName = pPrefix.empty() ? pSrcAnim->mName : pPrefix + "_" + pSrcAnim->mName;
+
+    // create nested animations, if given
+    for( std::vector<Collada::Animation*>::const_iterator it = pSrcAnim->mSubAnims.begin(); it != pSrcAnim->mSubAnims.end(); ++it)
+        StoreAnimations( pScene, pParser, *it, animName);
+
+    // create animation channels, if any
+    if( !pSrcAnim->mChannels.empty())
+        CreateAnimation( pScene, pParser, pSrcAnim, animName);
+}
+
+struct MorphTimeValues
+{
+    float mTime;
+    struct key
+    {
+        float mWeight;
+        unsigned int mValue;
+    };
+    std::vector<key> mKeys;
+};
+
+void insertMorphTimeValue(std::vector<MorphTimeValues> &values, float time, float weight, unsigned int value)
+{
+    MorphTimeValues::key k;
+    k.mValue = value;
+    k.mWeight = weight;
+    if (values.size() == 0 || time < values[0].mTime)
+    {
+        MorphTimeValues val;
+        val.mTime = time;
+        val.mKeys.push_back(k);
+        values.insert(values.begin(), val);
+        return;
+    }
+    if (time > values.back().mTime)
+    {
+        MorphTimeValues val;
+        val.mTime = time;
+        val.mKeys.push_back(k);
+        values.insert(values.end(), val);
+        return;
+    }
+    for (unsigned int i = 0; i < values.size(); i++)
+    {
+        if (std::abs(time - values[i].mTime) < 1e-6f)
+        {
+            values[i].mKeys.push_back(k);
+            return;
+        } else if (time > values[i].mTime && time < values[i+1].mTime)
+        {
+            MorphTimeValues val;
+            val.mTime = time;
+            val.mKeys.push_back(k);
+            values.insert(values.begin() + i, val);
+            return;
+        }
+    }
+    // should not get here
+}
+
+float getWeightAtKey(const std::vector<MorphTimeValues> &values, int key, unsigned int value)
+{
+    for (unsigned int i = 0; i < values[key].mKeys.size(); i++)
+    {
+        if (values[key].mKeys[i].mValue == value)
+            return values[key].mKeys[i].mWeight;
+    }
+    // no value at key found, try to interpolate if present at other keys. if not, return zero
+    // TODO: interpolation
+    return 0.0f;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Constructs the animation for the given source anim
+void ColladaLoader::CreateAnimation( aiScene* pScene, const ColladaParser& pParser, const Collada::Animation* pSrcAnim, const std::string& pName)
+{
+    // collect a list of animatable nodes
+    std::vector<const aiNode*> nodes;
+    CollectNodes( pScene->mRootNode, nodes);
+
+    std::vector<aiNodeAnim*> anims;
+    std::vector<aiMeshMorphAnim*> morphAnims;
+
+    for( std::vector<const aiNode*>::const_iterator nit = nodes.begin(); nit != nodes.end(); ++nit)
+    {
+        // find all the collada anim channels which refer to the current node
+        std::vector<Collada::ChannelEntry> entries;
+        std::string nodeName = (*nit)->mName.data;
+
+        // find the collada node corresponding to the aiNode
+        const Collada::Node* srcNode = FindNode( pParser.mRootNode, nodeName);
+//      ai_assert( srcNode != NULL);
+        if( !srcNode)
+            continue;
+
+        // now check all channels if they affect the current node
+        std::string targetID, subElement;
+        for( std::vector<Collada::AnimationChannel>::const_iterator cit = pSrcAnim->mChannels.begin();
+            cit != pSrcAnim->mChannels.end(); ++cit)
+        {
+            const Collada::AnimationChannel& srcChannel = *cit;
+            Collada::ChannelEntry entry;
+
+            // we expect the animation target to be of type "nodeName/transformID.subElement". Ignore all others
+            // find the slash that separates the node name - there should be only one
+            std::string::size_type slashPos = srcChannel.mTarget.find( '/');
+            if( slashPos == std::string::npos)
+            {
+                std::string::size_type targetPos = srcChannel.mTarget.find(srcNode->mID);
+                if (targetPos == std::string::npos)
+                    continue;
+
+                // not node transform, but something else. store as unknown animation channel for now
+                entry.mChannel = &(*cit);
+                entry.mTargetId = srcChannel.mTarget.substr(targetPos + pSrcAnim->mName.length(),
+                                        srcChannel.mTarget.length() - targetPos - pSrcAnim->mName.length());
+                if (entry.mTargetId.front() == '-')
+                    entry.mTargetId = entry.mTargetId.substr(1);
+                entries.push_back(entry);
+                continue;
+            }
+            if( srcChannel.mTarget.find( '/', slashPos+1) != std::string::npos)
+                continue;
+
+            targetID.clear();
+            targetID = srcChannel.mTarget.substr( 0, slashPos);
+            if( targetID != srcNode->mID)
+                continue;
+
+            // find the dot that separates the transformID - there should be only one or zero
+            std::string::size_type dotPos = srcChannel.mTarget.find( '.');
+            if( dotPos != std::string::npos)
+            {
+                if( srcChannel.mTarget.find( '.', dotPos+1) != std::string::npos)
+                    continue;
+
+                entry.mTransformId = srcChannel.mTarget.substr( slashPos+1, dotPos - slashPos - 1);
+
+                subElement.clear();
+                subElement = srcChannel.mTarget.substr( dotPos+1);
+                if( subElement == "ANGLE")
+                    entry.mSubElement = 3; // last number in an Axis-Angle-Transform is the angle
+                else if( subElement == "X")
+                    entry.mSubElement = 0;
+                else if( subElement == "Y")
+                    entry.mSubElement = 1;
+                else if( subElement == "Z")
+                    entry.mSubElement = 2;
+                else
+                    ASSIMP_LOG_WARN_F( "Unknown anim subelement <", subElement, ">. Ignoring" );
+            } else {
+                // no subelement following, transformId is remaining string
+                entry.mTransformId = srcChannel.mTarget.substr( slashPos+1);
+            }
+
+            std::string::size_type bracketPos = srcChannel.mTarget.find('(');
+            if (bracketPos != std::string::npos)
+            {
+                entry.mTransformId = srcChannel.mTarget.substr(slashPos + 1, bracketPos - slashPos - 1);
+                subElement.clear();
+                subElement = srcChannel.mTarget.substr(bracketPos);
+
+                if (subElement == "(0)(0)")
+                    entry.mSubElement = 0;
+                else if (subElement == "(1)(0)")
+                    entry.mSubElement = 1;
+                else if (subElement == "(2)(0)")
+                    entry.mSubElement = 2;
+                else if (subElement == "(3)(0)")
+                    entry.mSubElement = 3;
+                else if (subElement == "(0)(1)")
+                    entry.mSubElement = 4;
+                else if (subElement == "(1)(1)")
+                    entry.mSubElement = 5;
+                else if (subElement == "(2)(1)")
+                    entry.mSubElement = 6;
+                else if (subElement == "(3)(1)")
+                    entry.mSubElement = 7;
+                else if (subElement == "(0)(2)")
+                    entry.mSubElement = 8;
+                else if (subElement == "(1)(2)")
+                    entry.mSubElement = 9;
+                else if (subElement == "(2)(2)")
+                    entry.mSubElement = 10;
+                else if (subElement == "(3)(2)")
+                    entry.mSubElement = 11;
+                else if (subElement == "(0)(3)")
+                    entry.mSubElement = 12;
+                else if (subElement == "(1)(3)")
+                    entry.mSubElement = 13;
+                else if (subElement == "(2)(3)")
+                    entry.mSubElement = 14;
+                else if (subElement == "(3)(3)")
+                    entry.mSubElement = 15;
+            }
+
+            // determine which transform step is affected by this channel
+            entry.mTransformIndex = SIZE_MAX;
+            for( size_t a = 0; a < srcNode->mTransforms.size(); ++a)
+                if( srcNode->mTransforms[a].mID == entry.mTransformId)
+                    entry.mTransformIndex = a;
+
+            if( entry.mTransformIndex == SIZE_MAX)
+            {
+                if (entry.mTransformId.find("morph-weights") != std::string::npos)
+                {
+                    entry.mTargetId = entry.mTransformId;
+                    entry.mTransformId = "";
+                } else
+                    continue;
+            }
+
+            entry.mChannel = &(*cit);
+            entries.push_back( entry);
+        }
+
+        // if there's no channel affecting the current node, we skip it
+        if( entries.empty())
+            continue;
+
+        // resolve the data pointers for all anim channels. Find the minimum time while we're at it
+        ai_real startTime = ai_real( 1e20 ), endTime = ai_real( -1e20 );
+        for( std::vector<Collada::ChannelEntry>::iterator it = entries.begin(); it != entries.end(); ++it)
+        {
+            Collada::ChannelEntry& e = *it;
+            e.mTimeAccessor = &pParser.ResolveLibraryReference( pParser.mAccessorLibrary, e.mChannel->mSourceTimes);
+            e.mTimeData = &pParser.ResolveLibraryReference( pParser.mDataLibrary, e.mTimeAccessor->mSource);
+            e.mValueAccessor = &pParser.ResolveLibraryReference( pParser.mAccessorLibrary, e.mChannel->mSourceValues);
+            e.mValueData = &pParser.ResolveLibraryReference( pParser.mDataLibrary, e.mValueAccessor->mSource);
+
+            // time count and value count must match
+            if( e.mTimeAccessor->mCount != e.mValueAccessor->mCount)
+                throw DeadlyImportError( format() << "Time count / value count mismatch in animation channel \"" << e.mChannel->mTarget << "\"." );
+
+      if( e.mTimeAccessor->mCount > 0 )
+      {
+              // find bounding times
+              startTime = std::min( startTime, ReadFloat( *e.mTimeAccessor, *e.mTimeData, 0, 0));
+            endTime = std::max( endTime, ReadFloat( *e.mTimeAccessor, *e.mTimeData, e.mTimeAccessor->mCount-1, 0));
+      }
+        }
+
+    std::vector<aiMatrix4x4> resultTrafos;
+    if( !entries.empty() && entries.front().mTimeAccessor->mCount > 0 )
+    {
+          // create a local transformation chain of the node's transforms
+          std::vector<Collada::Transform> transforms = srcNode->mTransforms;
+
+          // now for every unique point in time, find or interpolate the key values for that time
+          // and apply them to the transform chain. Then the node's present transformation can be calculated.
+          ai_real time = startTime;
+          while( 1)
+          {
+              for( std::vector<Collada::ChannelEntry>::iterator it = entries.begin(); it != entries.end(); ++it)
+              {
+                  Collada::ChannelEntry& e = *it;
+
+                  // find the keyframe behind the current point in time
+                  size_t pos = 0;
+                  ai_real postTime = 0.0;
+                  while( 1)
+                  {
+                      if( pos >= e.mTimeAccessor->mCount)
+                          break;
+                      postTime = ReadFloat( *e.mTimeAccessor, *e.mTimeData, pos, 0);
+                      if( postTime >= time)
+                          break;
+                      ++pos;
+                  }
+
+                  pos = std::min( pos, e.mTimeAccessor->mCount-1);
+
+                  // read values from there
+                  ai_real temp[16];
+                  for( size_t c = 0; c < e.mValueAccessor->mSize; ++c)
+                      temp[c] = ReadFloat( *e.mValueAccessor, *e.mValueData, pos, c);
+
+                  // if not exactly at the key time, interpolate with previous value set
+                  if( postTime > time && pos > 0)
+                  {
+                      ai_real preTime = ReadFloat( *e.mTimeAccessor, *e.mTimeData, pos-1, 0);
+                      ai_real factor = (time - postTime) / (preTime - postTime);
+
+                      for( size_t c = 0; c < e.mValueAccessor->mSize; ++c)
+                      {
+                          ai_real v = ReadFloat( *e.mValueAccessor, *e.mValueData, pos-1, c);
+                          temp[c] += (v - temp[c]) * factor;
+                      }
+                  }
+
+                  // Apply values to current transformation
+                  std::copy( temp, temp + e.mValueAccessor->mSize, transforms[e.mTransformIndex].f + e.mSubElement);
+              }
+
+              // Calculate resulting transformation
+              aiMatrix4x4 mat = pParser.CalculateResultTransform( transforms);
+
+              // out of laziness: we store the time in matrix.d4
+              mat.d4 = time;
+              resultTrafos.push_back( mat);
+
+              // find next point in time to evaluate. That's the closest frame larger than the current in any channel
+              ai_real nextTime = ai_real( 1e20 );
+              for( std::vector<Collada::ChannelEntry>::iterator it = entries.begin(); it != entries.end(); ++it)
+              {
+                  Collada::ChannelEntry& channelElement = *it;
+
+                  // find the next time value larger than the current
+                  size_t pos = 0;
+                  while( pos < channelElement.mTimeAccessor->mCount)
+                  {
+                      const ai_real t = ReadFloat( *channelElement.mTimeAccessor, *channelElement.mTimeData, pos, 0);
+                      if( t > time)
+                      {
+                          nextTime = std::min( nextTime, t);
+                          break;
+                      }
+                      ++pos;
+                  }
+
+				  // https://github.com/assimp/assimp/issues/458
+			  	  // Sub-sample axis-angle channels if the delta between two consecutive
+                  // key-frame angles is >= 180 degrees.
+				  if (transforms[channelElement.mTransformIndex].mType == Collada::TF_ROTATE && channelElement.mSubElement == 3 && pos > 0 && pos < channelElement.mTimeAccessor->mCount) {
+					  const ai_real cur_key_angle = ReadFloat(*channelElement.mValueAccessor, *channelElement.mValueData, pos, 0);
+                      const ai_real last_key_angle = ReadFloat(*channelElement.mValueAccessor, *channelElement.mValueData, pos - 1, 0);
+                      const ai_real cur_key_time = ReadFloat(*channelElement.mTimeAccessor, *channelElement.mTimeData, pos, 0);
+                      const ai_real last_key_time = ReadFloat(*channelElement.mTimeAccessor, *channelElement.mTimeData, pos - 1, 0);
+                      const ai_real last_eval_angle = last_key_angle + (cur_key_angle - last_key_angle) * (time - last_key_time) / (cur_key_time - last_key_time);
+                      const ai_real delta = std::abs(cur_key_angle - last_eval_angle);
+				      if (delta >= 180.0) {
+						const int subSampleCount = static_cast<int>(std::floor(delta / 90.0));
+						if (cur_key_time != time) {
+							const ai_real nextSampleTime = time + (cur_key_time - time) / subSampleCount;
+							nextTime = std::min(nextTime, nextSampleTime);
+						  }
+					  }
+				  }
+              }
+
+              // no more keys on any channel after the current time -> we're done
+              if( nextTime > 1e19)
+                  break;
+
+              // else construct next keyframe at this following time point
+              time = nextTime;
+          }
+    }
+
+        // there should be some keyframes, but we aren't that fixated on valid input data
+//      ai_assert( resultTrafos.size() > 0);
+
+        // build an animation channel for the given node out of these trafo keys
+        if( !resultTrafos.empty() )
+        {
+              aiNodeAnim* dstAnim = new aiNodeAnim;
+              dstAnim->mNodeName = nodeName;
+              dstAnim->mNumPositionKeys = static_cast<unsigned int>(resultTrafos.size());
+              dstAnim->mNumRotationKeys = static_cast<unsigned int>(resultTrafos.size());
+              dstAnim->mNumScalingKeys = static_cast<unsigned int>(resultTrafos.size());
+              dstAnim->mPositionKeys = new aiVectorKey[resultTrafos.size()];
+              dstAnim->mRotationKeys = new aiQuatKey[resultTrafos.size()];
+              dstAnim->mScalingKeys = new aiVectorKey[resultTrafos.size()];
+
+              for( size_t a = 0; a < resultTrafos.size(); ++a)
+              {
+                  aiMatrix4x4 mat = resultTrafos[a];
+                  double time = double( mat.d4); // remember? time is stored in mat.d4
+                    mat.d4 = 1.0f;
+
+                  dstAnim->mPositionKeys[a].mTime = time;
+                  dstAnim->mRotationKeys[a].mTime = time;
+                  dstAnim->mScalingKeys[a].mTime = time;
+                  mat.Decompose( dstAnim->mScalingKeys[a].mValue, dstAnim->mRotationKeys[a].mValue, dstAnim->mPositionKeys[a].mValue);
+              }
+
+              anims.push_back( dstAnim);
+        } else
+        {
+            ASSIMP_LOG_WARN( "Collada loader: found empty animation channel, ignored. Please check your exporter.");
+        }
+
+        if( !entries.empty() && entries.front().mTimeAccessor->mCount > 0 )
+        {
+            std::vector<Collada::ChannelEntry> morphChannels;
+            for( std::vector<Collada::ChannelEntry>::iterator it = entries.begin(); it != entries.end(); ++it)
+            {
+                Collada::ChannelEntry& e = *it;
+
+                // skip non-transform types
+                if (e.mTargetId.empty())
+                    continue;
+
+                if (e.mTargetId.find("morph-weights") != std::string::npos)
+                    morphChannels.push_back(e);
+            }
+            if (morphChannels.size() > 0)
+            {
+                // either 1) morph weight animation count should contain morph target count channels
+                // or     2) one channel with morph target count arrays
+                // assume first
+
+                aiMeshMorphAnim *morphAnim = new aiMeshMorphAnim;
+                morphAnim->mName.Set(nodeName);
+
+                std::vector<MorphTimeValues> morphTimeValues;
+
+                int morphAnimChannelIndex = 0;
+                for( std::vector<Collada::ChannelEntry>::iterator it = morphChannels.begin(); it != morphChannels.end(); ++it)
+                {
+                    Collada::ChannelEntry& e = *it;
+                    std::string::size_type apos = e.mTargetId.find('(');
+                    std::string::size_type bpos = e.mTargetId.find(')');
+                    if (apos == std::string::npos || bpos == std::string::npos)
+                        // unknown way to specify weight -> ignore this animation
+                        continue;
+
+                    // weight target can be in format Weight_M_N, Weight_N, WeightN, or some other way
+                    // we ignore the name and just assume the channels are in the right order
+                    for (unsigned int i = 0; i < e.mTimeData->mValues.size(); i++)
+                        insertMorphTimeValue(morphTimeValues, e.mTimeData->mValues.at(i), e.mValueData->mValues.at(i), morphAnimChannelIndex);
+
+                    ++morphAnimChannelIndex;
+                }
+
+                morphAnim->mNumKeys = static_cast<unsigned int>(morphTimeValues.size());
+                morphAnim->mKeys = new aiMeshMorphKey[morphAnim->mNumKeys];
+                for (unsigned int key = 0; key < morphAnim->mNumKeys; key++)
+                {
+                    morphAnim->mKeys[key].mNumValuesAndWeights = static_cast<unsigned int>(morphChannels.size());
+                    morphAnim->mKeys[key].mValues = new unsigned int [morphChannels.size()];
+                    morphAnim->mKeys[key].mWeights = new double [morphChannels.size()];
+
+                    morphAnim->mKeys[key].mTime = morphTimeValues[key].mTime;
+                    for (unsigned int valueIndex = 0; valueIndex < morphChannels.size(); valueIndex++)
+                    {
+                        morphAnim->mKeys[key].mValues[valueIndex] = valueIndex;
+                        morphAnim->mKeys[key].mWeights[valueIndex] = getWeightAtKey(morphTimeValues, key, valueIndex);
+                    }
+                }
+
+                morphAnims.push_back(morphAnim);
+            }
+        }
+    }
+
+    if( !anims.empty() || !morphAnims.empty())
+    {
+        aiAnimation* anim = new aiAnimation;
+        anim->mName.Set( pName);
+        anim->mNumChannels = static_cast<unsigned int>(anims.size());
+        if (anim->mNumChannels > 0)
+        {
+            anim->mChannels = new aiNodeAnim*[anims.size()];
+            std::copy( anims.begin(), anims.end(), anim->mChannels);
+        }
+        anim->mNumMorphMeshChannels = static_cast<unsigned int>(morphAnims.size());
+        if (anim->mNumMorphMeshChannels > 0)
+        {
+            anim->mMorphMeshChannels = new aiMeshMorphAnim*[anim->mNumMorphMeshChannels];
+            std::copy( morphAnims.begin(), morphAnims.end(), anim->mMorphMeshChannels);
+        }
+        anim->mDuration = 0.0f;
+        for( size_t a = 0; a < anims.size(); ++a)
+        {
+                anim->mDuration = std::max( anim->mDuration, anims[a]->mPositionKeys[anims[a]->mNumPositionKeys-1].mTime);
+                anim->mDuration = std::max( anim->mDuration, anims[a]->mRotationKeys[anims[a]->mNumRotationKeys-1].mTime);
+                anim->mDuration = std::max( anim->mDuration, anims[a]->mScalingKeys[anims[a]->mNumScalingKeys-1].mTime);
+        }
+        for (size_t a = 0; a < morphAnims.size(); ++a)
+        {
+            anim->mDuration = std::max(anim->mDuration, morphAnims[a]->mKeys[morphAnims[a]->mNumKeys-1].mTime);
+        }
+        anim->mTicksPerSecond = 1;
+        mAnims.push_back( anim);
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Add a texture to a material structure
+void ColladaLoader::AddTexture ( aiMaterial& mat, const ColladaParser& pParser,
+    const Collada::Effect& effect,
+    const Collada::Sampler& sampler,
+    aiTextureType type, unsigned int idx)
+{
+    // first of all, basic file name
+    const aiString name = FindFilenameForEffectTexture( pParser, effect, sampler.mName );
+    mat.AddProperty( &name, _AI_MATKEY_TEXTURE_BASE, type, idx );
+
+    // mapping mode
+    int map = aiTextureMapMode_Clamp;
+    if (sampler.mWrapU)
+        map = aiTextureMapMode_Wrap;
+    if (sampler.mWrapU && sampler.mMirrorU)
+        map = aiTextureMapMode_Mirror;
+
+    mat.AddProperty( &map, 1, _AI_MATKEY_MAPPINGMODE_U_BASE, type, idx);
+
+    map = aiTextureMapMode_Clamp;
+    if (sampler.mWrapV)
+        map = aiTextureMapMode_Wrap;
+    if (sampler.mWrapV && sampler.mMirrorV)
+        map = aiTextureMapMode_Mirror;
+
+    mat.AddProperty( &map, 1, _AI_MATKEY_MAPPINGMODE_V_BASE, type, idx);
+
+    // UV transformation
+    mat.AddProperty(&sampler.mTransform, 1,
+        _AI_MATKEY_UVTRANSFORM_BASE, type, idx);
+
+    // Blend mode
+    mat.AddProperty((int*)&sampler.mOp , 1,
+        _AI_MATKEY_TEXBLEND_BASE, type, idx);
+
+    // Blend factor
+    mat.AddProperty((ai_real*)&sampler.mWeighting , 1,
+        _AI_MATKEY_TEXBLEND_BASE, type, idx);
+
+    // UV source index ... if we didn't resolve the mapping, it is actually just
+    // a guess but it works in most cases. We search for the frst occurrence of a
+    // number in the channel name. We assume it is the zero-based index into the
+    // UV channel array of all corresponding meshes. It could also be one-based
+    // for some exporters, but we won't care of it unless someone complains about.
+    if (sampler.mUVId != UINT_MAX)
+        map = sampler.mUVId;
+    else {
+        map = -1;
+        for (std::string::const_iterator it = sampler.mUVChannel.begin();it != sampler.mUVChannel.end(); ++it){
+            if (IsNumeric(*it)) {
+                map = strtoul10(&(*it));
+                break;
+            }
+        }
+        if (-1 == map) {
+            ASSIMP_LOG_WARN("Collada: unable to determine UV channel for texture");
+            map = 0;
+        }
+    }
+    mat.AddProperty(&map,1,_AI_MATKEY_UVWSRC_BASE,type,idx);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Fills materials from the collada material definitions
+void ColladaLoader::FillMaterials( const ColladaParser& pParser, aiScene* /*pScene*/)
+{
+    for (auto &elem : newMats)
+    {
+        aiMaterial&  mat = (aiMaterial&)*elem.second;
+        Collada::Effect& effect = *elem.first;
+
+        // resolve shading mode
+        int shadeMode;
+        if (effect.mFaceted) /* fixme */
+            shadeMode = aiShadingMode_Flat;
+        else {
+            switch( effect.mShadeType)
+            {
+            case Collada::Shade_Constant:
+                shadeMode = aiShadingMode_NoShading;
+                break;
+            case Collada::Shade_Lambert:
+                shadeMode = aiShadingMode_Gouraud;
+                break;
+            case Collada::Shade_Blinn:
+                shadeMode = aiShadingMode_Blinn;
+                break;
+            case Collada::Shade_Phong:
+                shadeMode = aiShadingMode_Phong;
+                break;
+
+            default:
+                ASSIMP_LOG_WARN("Collada: Unrecognized shading mode, using gouraud shading");
+                shadeMode = aiShadingMode_Gouraud;
+                break;
+            }
+        }
+        mat.AddProperty<int>( &shadeMode, 1, AI_MATKEY_SHADING_MODEL);
+
+        // double-sided?
+        shadeMode = effect.mDoubleSided;
+        mat.AddProperty<int>( &shadeMode, 1, AI_MATKEY_TWOSIDED);
+
+        // wireframe?
+        shadeMode = effect.mWireframe;
+        mat.AddProperty<int>( &shadeMode, 1, AI_MATKEY_ENABLE_WIREFRAME);
+
+        // add material colors
+        mat.AddProperty( &effect.mAmbient, 1,AI_MATKEY_COLOR_AMBIENT);
+        mat.AddProperty( &effect.mDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE);
+        mat.AddProperty( &effect.mSpecular, 1,AI_MATKEY_COLOR_SPECULAR);
+        mat.AddProperty( &effect.mEmissive, 1,  AI_MATKEY_COLOR_EMISSIVE);
+        mat.AddProperty( &effect.mReflective, 1, AI_MATKEY_COLOR_REFLECTIVE);
+
+        // scalar properties
+        mat.AddProperty( &effect.mShininess, 1, AI_MATKEY_SHININESS);
+        mat.AddProperty( &effect.mReflectivity, 1, AI_MATKEY_REFLECTIVITY);
+        mat.AddProperty( &effect.mRefractIndex, 1, AI_MATKEY_REFRACTI);
+
+        // transparency, a very hard one. seemingly not all files are following the
+        // specification here (1.0 transparency => completely opaque)...
+        // therefore, we let the opportunity for the user to manually invert
+        // the transparency if necessary and we add preliminary support for RGB_ZERO mode
+        if(effect.mTransparency >= 0.f && effect.mTransparency <= 1.f) {
+            // handle RGB transparency completely, cf Collada specs 1.5.0 pages 249 and 304
+            if(effect.mRGBTransparency) {
+				// use luminance as defined by ISO/CIE color standards (see ITU-R Recommendation BT.709-4)
+                effect.mTransparency *= (
+                    0.212671f * effect.mTransparent.r +
+                    0.715160f * effect.mTransparent.g +
+                    0.072169f * effect.mTransparent.b
+                );
+
+                effect.mTransparent.a = 1.f;
+
+                mat.AddProperty( &effect.mTransparent, 1, AI_MATKEY_COLOR_TRANSPARENT );
+            } else {
+                effect.mTransparency *=  effect.mTransparent.a;
+            }
+
+            if(effect.mInvertTransparency) {
+                effect.mTransparency = 1.f - effect.mTransparency;
+            }
+
+            // Is the material finally transparent ?
+            if (effect.mHasTransparency || effect.mTransparency < 1.f) {
+                mat.AddProperty( &effect.mTransparency, 1, AI_MATKEY_OPACITY );
+            }
+        }
+
+        // add textures, if given
+        if (!effect.mTexAmbient.mName.empty()) {
+            // It is merely a light-map
+            AddTexture(mat, pParser, effect, effect.mTexAmbient, aiTextureType_LIGHTMAP);
+        }
+
+        if( !effect.mTexEmissive.mName.empty())
+            AddTexture( mat, pParser, effect, effect.mTexEmissive, aiTextureType_EMISSIVE);
+
+        if( !effect.mTexSpecular.mName.empty())
+            AddTexture( mat, pParser, effect, effect.mTexSpecular, aiTextureType_SPECULAR);
+
+        if( !effect.mTexDiffuse.mName.empty())
+            AddTexture( mat, pParser, effect, effect.mTexDiffuse, aiTextureType_DIFFUSE);
+
+        if( !effect.mTexBump.mName.empty())
+            AddTexture( mat, pParser, effect, effect.mTexBump, aiTextureType_NORMALS);
+
+        if( !effect.mTexTransparent.mName.empty())
+            AddTexture( mat, pParser, effect, effect.mTexTransparent, aiTextureType_OPACITY);
+
+        if( !effect.mTexReflective.mName.empty())
+            AddTexture( mat, pParser, effect, effect.mTexReflective, aiTextureType_REFLECTION);
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Constructs materials from the collada material definitions
+void ColladaLoader::BuildMaterials( ColladaParser& pParser, aiScene* /*pScene*/)
+{
+    newMats.reserve(pParser.mMaterialLibrary.size());
+
+    for( ColladaParser::MaterialLibrary::const_iterator matIt = pParser.mMaterialLibrary.begin(); 
+        matIt != pParser.mMaterialLibrary.end(); ++matIt) {
+        const Collada::Material& material = matIt->second;
+        // a material is only a reference to an effect
+        ColladaParser::EffectLibrary::iterator effIt = pParser.mEffectLibrary.find( material.mEffect);
+        if( effIt == pParser.mEffectLibrary.end())
+            continue;
+        Collada::Effect& effect = effIt->second;
+
+        // create material
+        aiMaterial* mat = new aiMaterial;
+        aiString name( material.mName.empty() ? matIt->first : material.mName );
+        mat->AddProperty(&name,AI_MATKEY_NAME);
+
+        // store the material
+        mMaterialIndexByName[matIt->first] = newMats.size();
+        newMats.push_back( std::pair<Collada::Effect*, aiMaterial*>( &effect,mat) );
+    }
+    // ScenePreprocessor generates a default material automatically if none is there.
+    // All further code here in this loader works well without a valid material so
+    // we can safely let it to ScenePreprocessor.
+#if 0
+    if( newMats.size() == 0)
+    {
+        aiMaterial* mat = new aiMaterial;
+        aiString name( AI_DEFAULT_MATERIAL_NAME );
+        mat->AddProperty( &name, AI_MATKEY_NAME);
+
+        const int shadeMode = aiShadingMode_Phong;
+        mat->AddProperty<int>( &shadeMode, 1, AI_MATKEY_SHADING_MODEL);
+        aiColor4D colAmbient( 0.2, 0.2, 0.2, 1.0), colDiffuse( 0.8, 0.8, 0.8, 1.0), colSpecular( 0.5, 0.5, 0.5, 0.5);
+        mat->AddProperty( &colAmbient, 1, AI_MATKEY_COLOR_AMBIENT);
+        mat->AddProperty( &colDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE);
+        mat->AddProperty( &colSpecular, 1, AI_MATKEY_COLOR_SPECULAR);
+        const ai_real specExp = 5.0;
+        mat->AddProperty( &specExp, 1, AI_MATKEY_SHININESS);
+    }
+#endif
+}
+
+// ------------------------------------------------------------------------------------------------
+// Resolves the texture name for the given effect texture entry
+aiString ColladaLoader::FindFilenameForEffectTexture( const ColladaParser& pParser,
+    const Collada::Effect& pEffect, const std::string& pName)
+{
+    aiString result;
+
+    // recurse through the param references until we end up at an image
+    std::string name = pName;
+    while( 1)
+    {
+        // the given string is a param entry. Find it
+        Collada::Effect::ParamLibrary::const_iterator it = pEffect.mParams.find( name);
+        // if not found, we're at the end of the recursion. The resulting string should be the image ID
+        if( it == pEffect.mParams.end())
+            break;
+
+        // else recurse on
+        name = it->second.mReference;
+    }
+
+    // find the image referred by this name in the image library of the scene
+    ColladaParser::ImageLibrary::const_iterator imIt = pParser.mImageLibrary.find( name);
+    if( imIt == pParser.mImageLibrary.end())
+    {
+        ASSIMP_LOG_WARN_F("Collada: Unable to resolve effect texture entry \"", pName, "\", ended up at ID \"", name, "\".");
+
+        //set default texture file name
+        result.Set(name + ".jpg");
+        ConvertPath(result);
+        return result;
+    }
+
+    // if this is an embedded texture image setup an aiTexture for it
+    if (imIt->second.mFileName.empty())
+    {
+        if (imIt->second.mImageData.empty())  {
+            throw DeadlyImportError("Collada: Invalid texture, no data or file reference given");
+        }
+
+        aiTexture* tex = new aiTexture();
+
+        // setup format hint
+        if (imIt->second.mEmbeddedFormat.length() > 3) {
+            ASSIMP_LOG_WARN("Collada: texture format hint is too long, truncating to 3 characters");
+        }
+        strncpy(tex->achFormatHint,imIt->second.mEmbeddedFormat.c_str(),3);
+
+        // and copy texture data
+        tex->mHeight = 0;
+        tex->mWidth = static_cast<unsigned int>(imIt->second.mImageData.size());
+        tex->pcData = (aiTexel*)new char[tex->mWidth];
+        memcpy(tex->pcData,&imIt->second.mImageData[0],tex->mWidth);
+
+        // 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
+        // setup texture reference string
+        result.data[0] = '*';
+        result.length = 1 + ASSIMP_itoa10(result.data+1,static_cast<unsigned int>(MAXLEN-1),static_cast<int32_t>(mTextures.size()));
+
+        // and add this texture to the list
+        mTextures.push_back(tex);
+    }
+    else
+    {
+        result.Set( imIt->second.mFileName );
+        ConvertPath(result);
+    }
+    return result;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Convert a path read from a collada file to the usual representation
+void ColladaLoader::ConvertPath (aiString& ss)
+{
+    // TODO: collada spec, p 22. Handle URI correctly.
+    // For the moment we're just stripping the file:// away to make it work.
+    // Windows doesn't seem to be able to find stuff like
+    // 'file://..\LWO\LWO2\MappingModes\earthSpherical.jpg'
+    if (0 == strncmp(ss.data,"file://",7))
+    {
+        ss.length -= 7;
+        memmove(ss.data,ss.data+7,ss.length);
+        ss.data[ss.length] = '\0';
+    }
+
+  // Maxon Cinema Collada Export writes "file:///C:\andsoon" with three slashes...
+  // I need to filter it without destroying linux paths starting with "/somewhere"
+#if defined( _MSC_VER )
+    if( ss.data[0] == '/' && isalpha( (unsigned char) ss.data[1]) && ss.data[2] == ':' ) {
+#else
+    if (ss.data[ 0 ] == '/' && isalpha( ss.data[ 1 ] ) && ss.data[ 2 ] == ':') {
+#endif
+    --ss.length;
+    ::memmove( ss.data, ss.data+1, ss.length);
+    ss.data[ss.length] = 0;
+  }
+
+  // find and convert all %xy special chars
+  char* out = ss.data;
+  for( const char* it = ss.data; it != ss.data + ss.length; /**/ )
+  {
+    if( *it == '%' && (it + 3) < ss.data + ss.length )
+    {
+      // separate the number to avoid dragging in chars from behind into the parsing
+      char mychar[3] = { it[1], it[2], 0 };
+      size_t nbr = strtoul16( mychar);
+      it += 3;
+      *out++ = (char)(nbr & 0xFF);
+    } else
+    {
+      *out++ = *it++;
+    }
+  }
+
+  // adjust length and terminator of the shortened string
+  *out = 0;
+  ss.length = (ptrdiff_t) (out - ss.data);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a float value from an accessor and its data array.
+ai_real ColladaLoader::ReadFloat( const Collada::Accessor& pAccessor, const Collada::Data& pData, size_t pIndex, size_t pOffset) const
+{
+    // FIXME: (thom) Test for data type here in every access? For the moment, I leave this to the caller
+    size_t pos = pAccessor.mStride * pIndex + pAccessor.mOffset + pOffset;
+    ai_assert( pos < pData.mValues.size());
+    return pData.mValues[pos];
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a string value from an accessor and its data array.
+const std::string& ColladaLoader::ReadString( const Collada::Accessor& pAccessor, const Collada::Data& pData, size_t pIndex) const
+{
+    size_t pos = pAccessor.mStride * pIndex + pAccessor.mOffset;
+    ai_assert( pos < pData.mStrings.size());
+    return pData.mStrings[pos];
+}
+
+// ------------------------------------------------------------------------------------------------
+// Collects all nodes into the given array
+void ColladaLoader::CollectNodes( const aiNode* pNode, std::vector<const aiNode*>& poNodes) const
+{
+    poNodes.push_back( pNode);
+    for (size_t a = 0; a < pNode->mNumChildren; ++a) {
+        CollectNodes(pNode->mChildren[a], poNodes);
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Finds a node in the collada scene by the given name
+const Collada::Node* ColladaLoader::FindNode( const Collada::Node* pNode, const std::string& pName) const
+{
+    if( pNode->mName == pName || pNode->mID == pName)
+        return pNode;
+
+    for( size_t a = 0; a < pNode->mChildren.size(); ++a)
+    {
+        const Collada::Node* node = FindNode( pNode->mChildren[a], pName);
+        if( node)
+            return node;
+    }
+
+    return NULL;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Finds a node in the collada scene by the given SID
+const Collada::Node* ColladaLoader::FindNodeBySID( const Collada::Node* pNode, const std::string& pSID) const
+{
+  if( pNode->mSID == pSID)
+    return pNode;
+
+  for( size_t a = 0; a < pNode->mChildren.size(); ++a)
+  {
+    const Collada::Node* node = FindNodeBySID( pNode->mChildren[a], pSID);
+    if( node)
+      return node;
+  }
+
+  return NULL;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Finds a proper unique name for a node derived from the collada-node's properties.
+// The name must be unique for proper node-bone association.
+std::string ColladaLoader::FindNameForNode( const Collada::Node* pNode)
+{
+    // If explicitly requested, just use the collada name.
+    if (useColladaName)
+    {
+        if (!pNode->mName.empty()) {
+            return pNode->mName;
+        } else {
+            return format() << "$ColladaAutoName$_" << mNodeNameCounter++;
+        }
+    }
+    else
+    {
+        // Now setup the name of the assimp node. The collada name might not be
+        // unique, so we use the collada ID.
+        if (!pNode->mID.empty())
+            return pNode->mID;
+        else if (!pNode->mSID.empty())
+            return pNode->mSID;
+        else
+        {
+            // No need to worry. Unnamed nodes are no problem at all, except
+            // if cameras or lights need to be assigned to them.
+            return format() << "$ColladaAutoName$_" << mNodeNameCounter++;
+        }
+    }
+}
+
+#endif // !! ASSIMP_BUILD_NO_DAE_IMPORTER

+ 259 - 0
Engine/lib/assimp/code/ColladaLoader.h

@@ -0,0 +1,259 @@
+/** Defines the collada loader class */
+
+/*
+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 AI_COLLADALOADER_H_INC
+#define AI_COLLADALOADER_H_INC
+
+#include <assimp/BaseImporter.h>
+#include "ColladaParser.h"
+
+struct aiNode;
+struct aiCamera;
+struct aiLight;
+struct aiTexture;
+struct aiAnimation;
+
+namespace Assimp
+{
+
+struct ColladaMeshIndex
+{
+    std::string mMeshID;
+    size_t mSubMesh;
+    std::string mMaterial;
+    ColladaMeshIndex( const std::string& pMeshID, size_t pSubMesh, const std::string& pMaterial)
+        : mMeshID( pMeshID), mSubMesh( pSubMesh), mMaterial( pMaterial)
+    {   }
+
+    bool operator < (const ColladaMeshIndex& p) const
+    {
+        if( mMeshID == p.mMeshID)
+        {
+            if( mSubMesh == p.mSubMesh)
+                return mMaterial < p.mMaterial;
+            else
+                return mSubMesh < p.mSubMesh;
+        } else
+        {
+            return mMeshID < p.mMeshID;
+        }
+    }
+};
+
+/** Loader class to read Collada scenes. Collada is over-engineered to death, with every new iteration bringing
+ * more useless stuff, so I limited the data to what I think is useful for games.
+*/
+class ColladaLoader : public BaseImporter
+{
+public:
+    ColladaLoader();
+    ~ColladaLoader();
+
+
+public:
+    /** Returns whether the class can handle the format of the given file.
+     * See BaseImporter::CanRead() for details. */
+    bool CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const;
+
+protected:
+    /** Return importer meta information.
+     * See #BaseImporter::GetInfo for the details
+     */
+    const aiImporterDesc* GetInfo () const;
+
+    void SetupProperties(const Importer* pImp);
+
+    /** Imports the given file into the given scene structure.
+     * See BaseImporter::InternReadFile() for details
+     */
+    void InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler);
+
+    /** Recursively constructs a scene node for the given parser node and returns it. */
+    aiNode* BuildHierarchy( const ColladaParser& pParser, const Collada::Node* pNode);
+
+    /** Resolve node instances */
+    void ResolveNodeInstances( const ColladaParser& pParser, const Collada::Node* pNode,
+        std::vector<const Collada::Node*>& resolved);
+
+    /** Builds meshes for the given node and references them */
+    void BuildMeshesForNode( const ColladaParser& pParser, const Collada::Node* pNode,
+        aiNode* pTarget);
+		
+    aiMesh *findMesh(std::string meshid);
+
+    /** Creates a mesh for the given ColladaMesh face subset and returns the newly created mesh */
+    aiMesh* CreateMesh( const ColladaParser& pParser, const Collada::Mesh* pSrcMesh, const Collada::SubMesh& pSubMesh,
+        const Collada::Controller* pSrcController, size_t pStartVertex, size_t pStartFace);
+
+    /** Builds cameras for the given node and references them */
+    void BuildCamerasForNode( const ColladaParser& pParser, const Collada::Node* pNode,
+        aiNode* pTarget);
+
+    /** Builds lights for the given node and references them */
+    void BuildLightsForNode( const ColladaParser& pParser, const Collada::Node* pNode,
+        aiNode* pTarget);
+
+    /** Stores all meshes in the given scene */
+    void StoreSceneMeshes( aiScene* pScene);
+
+    /** Stores all materials in the given scene */
+    void StoreSceneMaterials( aiScene* pScene);
+
+    /** Stores all lights in the given scene */
+    void StoreSceneLights( aiScene* pScene);
+
+    /** Stores all cameras in the given scene */
+    void StoreSceneCameras( aiScene* pScene);
+
+    /** Stores all textures in the given scene */
+    void StoreSceneTextures( aiScene* pScene);
+
+    /** Stores all animations
+     * @param pScene target scene to store the anims
+     */
+    void StoreAnimations( aiScene* pScene, const ColladaParser& pParser);
+
+    /** Stores all animations for the given source anim and its nested child animations
+     * @param pScene target scene to store the anims
+     * @param pSrcAnim the source animation to process
+     * @param pPrefix Prefix to the name in case of nested animations
+     */
+    void StoreAnimations( aiScene* pScene, const ColladaParser& pParser, const Collada::Animation* pSrcAnim, const std::string& pPrefix);
+
+    /** Constructs the animation for the given source anim */
+    void CreateAnimation( aiScene* pScene, const ColladaParser& pParser, const Collada::Animation* pSrcAnim, const std::string& pName);
+
+    /** Constructs materials from the collada material definitions */
+    void BuildMaterials( ColladaParser& pParser, aiScene* pScene);
+
+    /** Fill materials from the collada material definitions */
+    void FillMaterials( const ColladaParser& pParser, aiScene* pScene);
+
+    /** Resolve UV channel mappings*/
+    void ApplyVertexToEffectSemanticMapping(Collada::Sampler& sampler,
+        const Collada::SemanticMappingTable& table);
+
+    /** Add a texture and all of its sampling properties to a material*/
+    void AddTexture ( aiMaterial& mat, const ColladaParser& pParser,
+        const Collada::Effect& effect,
+        const Collada::Sampler& sampler,
+        aiTextureType type, unsigned int idx = 0);
+
+    /** Resolves the texture name for the given effect texture entry */
+    aiString FindFilenameForEffectTexture( const ColladaParser& pParser,
+        const Collada::Effect& pEffect, const std::string& pName);
+
+    /** Converts a path read from a collada file to the usual representation */
+    void ConvertPath( aiString& ss);
+
+    /** Reads a float value from an accessor and its data array.
+     * @param pAccessor The accessor to use for reading
+     * @param pData The data array to read from
+     * @param pIndex The index of the element to retrieve
+     * @param pOffset Offset into the element, for multipart elements such as vectors or matrices
+     * @return the specified value
+     */
+    ai_real ReadFloat( const Collada::Accessor& pAccessor, const Collada::Data& pData, size_t pIndex, size_t pOffset) const;
+
+    /** Reads a string value from an accessor and its data array.
+     * @param pAccessor The accessor to use for reading
+     * @param pData The data array to read from
+     * @param pIndex The index of the element to retrieve
+     * @return the specified value
+     */
+    const std::string& ReadString( const Collada::Accessor& pAccessor, const Collada::Data& pData, size_t pIndex) const;
+
+    /** Recursively collects all nodes into the given array */
+    void CollectNodes( const aiNode* pNode, std::vector<const aiNode*>& poNodes) const;
+
+    /** Finds a node in the collada scene by the given name */
+    const Collada::Node* FindNode( const Collada::Node* pNode, const std::string& pName) const;
+    /** Finds a node in the collada scene by the given SID */
+    const Collada::Node* FindNodeBySID( const Collada::Node* pNode, const std::string& pSID) const;
+
+    /** Finds a proper name for a node derived from the collada-node's properties */
+    std::string FindNameForNode( const Collada::Node* pNode);
+
+protected:
+    /** Filename, for a verbose error message */
+    std::string mFileName;
+
+    /** Which mesh-material compound was stored under which mesh ID */
+    std::map<ColladaMeshIndex, size_t> mMeshIndexByID;
+
+    /** Which material was stored under which index in the scene */
+    std::map<std::string, size_t> mMaterialIndexByName;
+
+    /** Accumulated meshes for the target scene */
+    std::vector<aiMesh*> mMeshes;
+	
+    /** Accumulated morph target meshes */
+    std::vector<aiMesh*> mTargetMeshes;
+
+    /** Temporary material list */
+    std::vector<std::pair<Collada::Effect*, aiMaterial*> > newMats;
+
+    /** Temporary camera list */
+    std::vector<aiCamera*> mCameras;
+
+    /** Temporary light list */
+    std::vector<aiLight*> mLights;
+
+    /** Temporary texture list */
+    std::vector<aiTexture*> mTextures;
+
+    /** Accumulated animations for the target scene */
+    std::vector<aiAnimation*> mAnims;
+
+    bool noSkeletonMesh;
+    bool ignoreUpDirection;
+    bool useColladaName;
+
+    /** Used by FindNameForNode() to generate unique node names */
+    unsigned int mNodeNameCounter;
+};
+
+} // end of namespace Assimp
+
+#endif // AI_COLLADALOADER_H_INC

+ 3215 - 0
Engine/lib/assimp/code/ColladaParser.cpp

@@ -0,0 +1,3215 @@
+/*
+---------------------------------------------------------------------------
+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 ColladaParser.cpp
+ *  @brief Implementation of the Collada parser helper
+ */
+
+#ifndef ASSIMP_BUILD_NO_COLLADA_IMPORTER
+
+#include <sstream>
+#include <stdarg.h>
+#include "ColladaParser.h"
+#include <assimp/fast_atof.h>
+#include <assimp/ParsingUtils.h>
+#include <assimp/StringUtils.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/IOSystem.hpp>
+#include <assimp/light.h>
+#include <assimp/TinyFormatter.h>
+
+#include <memory>
+
+using namespace Assimp;
+using namespace Assimp::Collada;
+using namespace Assimp::Formatter;
+
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by Importer
+ColladaParser::ColladaParser( IOSystem* pIOHandler, const std::string& pFile)
+    : mFileName( pFile )
+    , mReader( nullptr )
+    , mDataLibrary()
+    , mAccessorLibrary()
+    , mMeshLibrary()
+    , mNodeLibrary()
+    , mImageLibrary()
+    , mEffectLibrary()
+    , mMaterialLibrary()
+    , mLightLibrary()
+    , mCameraLibrary()
+    , mControllerLibrary()
+    , mRootNode( nullptr )
+    , mAnims()
+    , mUnitSize( 1.0f )
+    , mUpDirection( UP_Y )
+    , mFormat(FV_1_5_n )    // We assume the newest file format by default
+{
+    // validate io-handler instance
+    if (nullptr == pIOHandler ) {
+        throw DeadlyImportError("IOSystem is NULL." );
+    }
+
+    // open the file
+    std::unique_ptr<IOStream> file( pIOHandler->Open(pFile ) );
+    if (file.get() == nullptr) {
+        throw DeadlyImportError( "Failed to open file " + pFile + "." );
+    }
+
+    // generate a XML reader for it
+    std::unique_ptr<CIrrXML_IOStreamReader> mIOWrapper(new CIrrXML_IOStreamReader(file.get()));
+    mReader = irr::io::createIrrXMLReader( mIOWrapper.get());
+    if (!mReader) {
+        ThrowException("Collada: Unable to open file.");
+    }
+
+    // start reading
+    ReadContents();
+}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+ColladaParser::~ColladaParser()
+{
+    delete mReader;
+    for( NodeLibrary::iterator it = mNodeLibrary.begin(); it != mNodeLibrary.end(); ++it)
+        delete it->second;
+    for( MeshLibrary::iterator it = mMeshLibrary.begin(); it != mMeshLibrary.end(); ++it)
+        delete it->second;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Read bool from text contents of current element
+bool ColladaParser::ReadBoolFromTextContent()
+{
+    const char* cur = GetTextContent();
+    return (!ASSIMP_strincmp(cur,"true",4) || '0' != *cur);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Read float from text contents of current element
+ai_real ColladaParser::ReadFloatFromTextContent()
+{
+    const char* cur = GetTextContent();
+    return fast_atof(cur);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the contents of the file
+void ColladaParser::ReadContents()
+{
+    while( mReader->read())
+    {
+        // handle the root element "COLLADA"
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT)
+        {
+            if( IsElement( "COLLADA"))
+            {
+                // check for 'version' attribute
+                const int attrib = TestAttribute("version");
+                if (attrib != -1) {
+                    const char* version = mReader->getAttributeValue(attrib);
+
+                    if (!::strncmp(version,"1.5",3)) {
+                        mFormat =  FV_1_5_n;
+                        ASSIMP_LOG_DEBUG("Collada schema version is 1.5.n");
+                    }
+                    else if (!::strncmp(version,"1.4",3)) {
+                        mFormat =  FV_1_4_n;
+                        ASSIMP_LOG_DEBUG("Collada schema version is 1.4.n");
+                    }
+                    else if (!::strncmp(version,"1.3",3)) {
+                        mFormat =  FV_1_3_n;
+                        ASSIMP_LOG_DEBUG("Collada schema version is 1.3.n");
+                    }
+                }
+
+                ReadStructure();
+            } else
+            {
+                ASSIMP_LOG_DEBUG_F( "Ignoring global element <", mReader->getNodeName(), ">." );
+                SkipElement();
+            }
+        } else
+        {
+            // skip everything else silently
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the structure of the file
+void ColladaParser::ReadStructure()
+{
+    while( mReader->read())
+    {
+        // beginning of elements
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT)
+        {
+            if( IsElement( "asset"))
+                ReadAssetInfo();
+            else if( IsElement( "library_animations"))
+                ReadAnimationLibrary();
+			else if (IsElement("library_animation_clips"))
+				ReadAnimationClipLibrary();
+            else if( IsElement( "library_controllers"))
+                ReadControllerLibrary();
+            else if( IsElement( "library_images"))
+                ReadImageLibrary();
+            else if( IsElement( "library_materials"))
+                ReadMaterialLibrary();
+            else if( IsElement( "library_effects"))
+                ReadEffectLibrary();
+            else if( IsElement( "library_geometries"))
+                ReadGeometryLibrary();
+            else if( IsElement( "library_visual_scenes"))
+                ReadSceneLibrary();
+            else if( IsElement( "library_lights"))
+                ReadLightLibrary();
+            else if( IsElement( "library_cameras"))
+                ReadCameraLibrary();
+            else if( IsElement( "library_nodes"))
+                ReadSceneNode(NULL); /* some hacking to reuse this piece of code */
+            else if( IsElement( "scene"))
+                ReadScene();
+            else
+                SkipElement();
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
+        {
+            break;
+        }
+    }
+
+	PostProcessRootAnimations();
+    PostProcessControllers();
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads asset information such as coordinate system information and legal blah
+void ColladaParser::ReadAssetInfo()
+{
+    if( mReader->isEmptyElement())
+        return;
+
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT)
+        {
+            if( IsElement( "unit"))
+            {
+                // read unit data from the element's attributes
+                const int attrIndex = TestAttribute( "meter");
+                if (attrIndex == -1) {
+                    mUnitSize = 1.f;
+                }
+                else {
+                    mUnitSize = mReader->getAttributeValueAsFloat( attrIndex);
+                }
+
+                // consume the trailing stuff
+                if( !mReader->isEmptyElement())
+                    SkipElement();
+            }
+            else if( IsElement( "up_axis"))
+            {
+                // read content, strip whitespace, compare
+                const char* content = GetTextContent();
+                if( strncmp( content, "X_UP", 4) == 0)
+                    mUpDirection = UP_X;
+                else if( strncmp( content, "Z_UP", 4) == 0)
+                    mUpDirection = UP_Z;
+                else
+                    mUpDirection = UP_Y;
+
+                // check element end
+                TestClosing( "up_axis");
+            } else
+            {
+                SkipElement();
+            }
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
+        {
+            if( strcmp( mReader->getNodeName(), "asset") != 0)
+                ThrowException( "Expected end of <asset> element.");
+
+            break;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the animation clips
+void ColladaParser::ReadAnimationClipLibrary()
+{
+	if (mReader->isEmptyElement())
+		return;
+
+	while (mReader->read())
+	{
+		if (mReader->getNodeType() == irr::io::EXN_ELEMENT)
+		{
+			if (IsElement("animation_clip"))
+			{
+				// optional name given as an attribute
+				std::string animName;
+				int indexName = TestAttribute("name");
+				int indexID = TestAttribute("id");
+				if (indexName >= 0)
+					animName = mReader->getAttributeValue(indexName);
+				else if (indexID >= 0)
+					animName = mReader->getAttributeValue(indexID);
+				else
+					animName = std::string("animation_") + to_string(mAnimationClipLibrary.size());
+
+				std::pair<std::string, std::vector<std::string> > clip;
+
+				clip.first = animName;
+
+				while (mReader->read())
+				{
+					if (mReader->getNodeType() == irr::io::EXN_ELEMENT)
+					{
+						if (IsElement("instance_animation"))
+						{
+							int indexUrl = TestAttribute("url");
+							if (indexUrl >= 0)
+							{
+								const char* url = mReader->getAttributeValue(indexUrl);
+								if (url[0] != '#')
+									ThrowException("Unknown reference format");
+
+								url++;
+
+								clip.second.push_back(url);
+							}
+						}
+						else
+						{
+							// ignore the rest
+							SkipElement();
+						}
+					}
+					else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
+					{
+						if (strcmp(mReader->getNodeName(), "animation_clip") != 0)
+							ThrowException("Expected end of <animation_clip> element.");
+
+						break;
+					}
+				}
+
+				if (clip.second.size() > 0)
+				{
+					mAnimationClipLibrary.push_back(clip);
+				}
+			}
+			else
+			{
+				// ignore the rest
+				SkipElement();
+			}
+		}
+		else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
+		{
+			if (strcmp(mReader->getNodeName(), "library_animation_clips") != 0)
+				ThrowException("Expected end of <library_animation_clips> element.");
+
+			break;
+		}
+	}
+}
+
+void ColladaParser::PostProcessControllers()
+{
+    std::string meshId;
+    for (ControllerLibrary::iterator it = mControllerLibrary.begin(); it != mControllerLibrary.end(); ++it) {
+        meshId = it->second.mMeshId;
+        ControllerLibrary::iterator findItr = mControllerLibrary.find(meshId);
+        while(findItr != mControllerLibrary.end()) {
+            meshId = findItr->second.mMeshId;
+            findItr = mControllerLibrary.find(meshId);
+        }
+    
+        it->second.mMeshId = meshId;
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Re-build animations from animation clip library, if present, otherwise combine single-channel animations
+void ColladaParser::PostProcessRootAnimations()
+{
+	if (mAnimationClipLibrary.size() > 0)
+	{
+		Animation temp;
+
+		for (AnimationClipLibrary::iterator it = mAnimationClipLibrary.begin(); it != mAnimationClipLibrary.end(); ++it)
+		{
+			std::string clipName = it->first;
+
+			Animation *clip = new Animation();
+			clip->mName = clipName;
+
+			temp.mSubAnims.push_back(clip);
+
+			for (std::vector<std::string>::iterator a = it->second.begin(); a != it->second.end(); ++a)
+			{
+				std::string animationID = *a;
+
+				AnimationLibrary::iterator animation = mAnimationLibrary.find(animationID);
+
+				if (animation != mAnimationLibrary.end())
+				{
+					Animation *pSourceAnimation = animation->second;
+
+					pSourceAnimation->CollectChannelsRecursively(clip->mChannels);
+				}
+			}
+		}
+
+		mAnims = temp;
+
+		// Ensure no double deletes.
+		temp.mSubAnims.clear();
+	}
+	else
+	{
+		mAnims.CombineSingleChannelAnimations();
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the animation library
+void ColladaParser::ReadAnimationLibrary()
+{
+    if (mReader->isEmptyElement())
+        return;
+
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT)
+        {
+            if( IsElement( "animation"))
+            {
+                // delegate the reading. Depending on the inner elements it will be a container or a anim channel
+                ReadAnimation( &mAnims);
+            } else
+            {
+                // ignore the rest
+                SkipElement();
+            }
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
+        {
+            if( strcmp( mReader->getNodeName(), "library_animations") != 0)
+                ThrowException( "Expected end of <library_animations> element.");
+
+            break;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads an animation into the given parent structure
+void ColladaParser::ReadAnimation( Collada::Animation* pParent)
+{
+    if( mReader->isEmptyElement())
+        return;
+
+    // an <animation> element may be a container for grouping sub-elements or an animation channel
+    // this is the channel collection by ID, in case it has channels
+    typedef std::map<std::string, AnimationChannel> ChannelMap;
+    ChannelMap channels;
+    // this is the anim container in case we're a container
+    Animation* anim = NULL;
+
+    // optional name given as an attribute
+    std::string animName;
+	std::string animID;
+    int indexName = TestAttribute( "name");
+    int indexID = TestAttribute( "id");
+
+	if (indexID >= 0)
+		animID = mReader->getAttributeValue(indexID);
+
+    if( indexName >= 0)
+        animName = mReader->getAttributeValue( indexName);
+    else if( indexID >= 0)
+        animName = animID;
+    else
+        animName = "animation";
+
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT)
+        {
+            // we have subanimations
+            if( IsElement( "animation"))
+            {
+                // create container from our element
+                if( !anim)
+                {
+                    anim = new Animation;
+                    anim->mName = animName;
+                    pParent->mSubAnims.push_back( anim);
+                }
+
+                // recurse into the subelement
+                ReadAnimation( anim);
+            }
+            else if( IsElement( "source"))
+            {
+                // possible animation data - we'll never know. Better store it
+                ReadSource();
+            }
+            else if( IsElement( "sampler"))
+            {
+                // read the ID to assign the corresponding collada channel afterwards.
+                int indexID = GetAttribute( "id");
+                std::string id = mReader->getAttributeValue( indexID);
+                ChannelMap::iterator newChannel = channels.insert( std::make_pair( id, AnimationChannel())).first;
+
+                // have it read into a channel
+                ReadAnimationSampler( newChannel->second);
+            }
+            else if( IsElement( "channel"))
+            {
+                // the binding element whose whole purpose is to provide the target to animate
+                // Thanks, Collada! A directly posted information would have been too simple, I guess.
+                // Better add another indirection to that! Can't have enough of those.
+                int indexTarget = GetAttribute( "target");
+                int indexSource = GetAttribute( "source");
+                const char* sourceId = mReader->getAttributeValue( indexSource);
+                if( sourceId[0] == '#')
+                    sourceId++;
+                ChannelMap::iterator cit = channels.find( sourceId);
+                if( cit != channels.end())
+                    cit->second.mTarget = mReader->getAttributeValue( indexTarget);
+
+                if( !mReader->isEmptyElement())
+                    SkipElement();
+            }
+            else
+            {
+                // ignore the rest
+                SkipElement();
+            }
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
+        {
+            if( strcmp( mReader->getNodeName(), "animation") != 0)
+                ThrowException( "Expected end of <animation> element.");
+
+            break;
+        }
+    }
+
+    // it turned out to have channels - add them
+    if( !channels.empty())
+    {
+		// FIXME: Is this essentially doing the same as "single-anim-node" codepath in
+		//        ColladaLoader::StoreAnimations? For now, this has been deferred to after
+		//        all animations and all clips have been read. Due to handling of
+		//        <library_animation_clips> this cannot be done here, as the channel owner
+		//        is lost, and some exporters make up animations by referring to multiple
+		//        single-channel animations from an <instance_animation>.
+/*
+        // special filtering for stupid exporters packing each channel into a separate animation
+        if( channels.size() == 1)
+        {
+            pParent->mChannels.push_back( channels.begin()->second);
+        } else
+*/
+        {
+            // else create the animation, if not done yet, and store the channels
+            if( !anim)
+            {
+                anim = new Animation;
+                anim->mName = animName;
+                pParent->mSubAnims.push_back( anim);
+            }
+            for( ChannelMap::const_iterator it = channels.begin(); it != channels.end(); ++it)
+                anim->mChannels.push_back( it->second);
+
+			if (indexID >= 0)
+			{
+				mAnimationLibrary[animID] = anim;
+			}
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads an animation sampler into the given anim channel
+void ColladaParser::ReadAnimationSampler( Collada::AnimationChannel& pChannel)
+{
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT)
+        {
+            if( IsElement( "input"))
+            {
+                int indexSemantic = GetAttribute( "semantic");
+                const char* semantic = mReader->getAttributeValue( indexSemantic);
+                int indexSource = GetAttribute( "source");
+                const char* source = mReader->getAttributeValue( indexSource);
+                if( source[0] != '#')
+                    ThrowException( "Unsupported URL format");
+                source++;
+
+                if( strcmp( semantic, "INPUT") == 0)
+                    pChannel.mSourceTimes = source;
+                else if( strcmp( semantic, "OUTPUT") == 0)
+                    pChannel.mSourceValues = source;
+                else if( strcmp( semantic, "IN_TANGENT") == 0)
+                    pChannel.mInTanValues = source;
+                else if( strcmp( semantic, "OUT_TANGENT") == 0)
+                    pChannel.mOutTanValues = source;
+                else if( strcmp( semantic, "INTERPOLATION") == 0)
+                    pChannel.mInterpolationValues = source;
+
+                if( !mReader->isEmptyElement())
+                    SkipElement();
+            }
+            else
+            {
+                // ignore the rest
+                SkipElement();
+            }
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
+        {
+            if( strcmp( mReader->getNodeName(), "sampler") != 0)
+                ThrowException( "Expected end of <sampler> element.");
+
+            break;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the skeleton controller library
+void ColladaParser::ReadControllerLibrary()
+{
+    if (mReader->isEmptyElement())
+        return;
+
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT)
+        {
+            if( IsElement( "controller"))
+            {
+                // read ID. Ask the spec if it's necessary or optional... you might be surprised.
+                int attrID = GetAttribute( "id");
+                std::string id = mReader->getAttributeValue( attrID);
+
+                // create an entry and store it in the library under its ID
+                mControllerLibrary[id] = Controller();
+
+                // read on from there
+                ReadController( mControllerLibrary[id]);
+            } else
+            {
+                // ignore the rest
+                SkipElement();
+            }
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
+        {
+            if( strcmp( mReader->getNodeName(), "library_controllers") != 0)
+                ThrowException( "Expected end of <library_controllers> element.");
+
+            break;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a controller into the given mesh structure
+void ColladaParser::ReadController( Collada::Controller& pController)
+{
+    // initial values
+    pController.mType = Skin;
+    pController.mMethod = Normalized;
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT)
+        {
+            // two types of controllers: "skin" and "morph". Only the first one is relevant, we skip the other
+            if( IsElement( "morph"))
+            {
+                pController.mType = Morph;
+                int baseIndex = GetAttribute("source");
+                pController.mMeshId = mReader->getAttributeValue(baseIndex) + 1;
+                int methodIndex = GetAttribute("method");
+                if (methodIndex > 0) {
+                    const char *method = mReader->getAttributeValue(methodIndex);
+                    if (strcmp(method, "RELATIVE") == 0)
+                        pController.mMethod = Relative;
+                }
+            }
+            else if( IsElement( "skin"))
+            {
+                // read the mesh it refers to. According to the spec this could also be another
+                // controller, but I refuse to implement every single idea they've come up with
+                int sourceIndex = GetAttribute( "source");
+                pController.mMeshId = mReader->getAttributeValue( sourceIndex) + 1;
+            }
+            else if( IsElement( "bind_shape_matrix"))
+            {
+                // content is 16 floats to define a matrix... it seems to be important for some models
+                const char* content = GetTextContent();
+
+                // read the 16 floats
+                for( unsigned int a = 0; a < 16; a++)
+                {
+                    // read a number
+                    content = fast_atoreal_move<ai_real>( content, pController.mBindShapeMatrix[a]);
+                    // skip whitespace after it
+                    SkipSpacesAndLineEnd( &content);
+                }
+
+                TestClosing( "bind_shape_matrix");
+            }
+            else if( IsElement( "source"))
+            {
+                // data array - we have specialists to handle this
+                ReadSource();
+            }
+            else if( IsElement( "joints"))
+            {
+                ReadControllerJoints( pController);
+            }
+            else if( IsElement( "vertex_weights"))
+            {
+                ReadControllerWeights( pController);
+            }
+            else if ( IsElement( "targets" ))
+            {
+                while (mReader->read()) {
+                    if( mReader->getNodeType() == irr::io::EXN_ELEMENT) {
+                        if ( IsElement( "input")) {
+                            int semanticsIndex = GetAttribute("semantic");
+                            int sourceIndex = GetAttribute("source");
+
+                            const char *semantics = mReader->getAttributeValue(semanticsIndex);
+                            const char *source = mReader->getAttributeValue(sourceIndex);
+                            if (strcmp(semantics, "MORPH_TARGET") == 0) {
+                                pController.mMorphTarget = source + 1;
+                            }
+                            else if (strcmp(semantics, "MORPH_WEIGHT") == 0)
+                            {
+                                pController.mMorphWeight = source + 1;
+                            }
+                        }
+                    } else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) {
+                        if( strcmp( mReader->getNodeName(), "targets") == 0)
+                            break;
+                        else
+                            ThrowException( "Expected end of <targets> element.");
+                    }
+                }
+            }
+            else
+            {
+                // ignore the rest
+                SkipElement();
+            }
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
+        {
+            if( strcmp( mReader->getNodeName(), "controller") == 0)
+                break;
+            else if( strcmp( mReader->getNodeName(), "skin") != 0 && strcmp( mReader->getNodeName(), "morph") != 0)
+                ThrowException( "Expected end of <controller> element.");
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the joint definitions for the given controller
+void ColladaParser::ReadControllerJoints( Collada::Controller& pController)
+{
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT)
+        {
+            // Input channels for joint data. Two possible semantics: "JOINT" and "INV_BIND_MATRIX"
+            if( IsElement( "input"))
+            {
+                int indexSemantic = GetAttribute( "semantic");
+                const char* attrSemantic = mReader->getAttributeValue( indexSemantic);
+                int indexSource = GetAttribute( "source");
+                const char* attrSource = mReader->getAttributeValue( indexSource);
+
+                // local URLS always start with a '#'. We don't support global URLs
+                if( attrSource[0] != '#')
+                    ThrowException( format() << "Unsupported URL format in \"" << attrSource << "\" in source attribute of <joints> data <input> element" );
+                attrSource++;
+
+                // parse source URL to corresponding source
+                if( strcmp( attrSemantic, "JOINT") == 0)
+                    pController.mJointNameSource = attrSource;
+                else if( strcmp( attrSemantic, "INV_BIND_MATRIX") == 0)
+                    pController.mJointOffsetMatrixSource = attrSource;
+                else
+                    ThrowException( format() << "Unknown semantic \"" << attrSemantic << "\" in <joints> data <input> element" );
+
+                // skip inner data, if present
+                if( !mReader->isEmptyElement())
+                    SkipElement();
+            }
+            else
+            {
+                // ignore the rest
+                SkipElement();
+            }
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
+        {
+            if( strcmp( mReader->getNodeName(), "joints") != 0)
+                ThrowException( "Expected end of <joints> element.");
+
+            break;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the joint weights for the given controller
+void ColladaParser::ReadControllerWeights( Collada::Controller& pController)
+{
+    // read vertex count from attributes and resize the array accordingly
+    int indexCount = GetAttribute( "count");
+    size_t vertexCount = (size_t) mReader->getAttributeValueAsInt( indexCount);
+    pController.mWeightCounts.resize( vertexCount);
+
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT)
+        {
+            // Input channels for weight data. Two possible semantics: "JOINT" and "WEIGHT"
+            if( IsElement( "input") && vertexCount > 0 )
+            {
+                InputChannel channel;
+
+                int indexSemantic = GetAttribute( "semantic");
+                const char* attrSemantic = mReader->getAttributeValue( indexSemantic);
+                int indexSource = GetAttribute( "source");
+                const char* attrSource = mReader->getAttributeValue( indexSource);
+                int indexOffset = TestAttribute( "offset");
+                if( indexOffset >= 0)
+                    channel.mOffset = mReader->getAttributeValueAsInt( indexOffset);
+
+                // local URLS always start with a '#'. We don't support global URLs
+                if( attrSource[0] != '#')
+                    ThrowException( format() << "Unsupported URL format in \"" << attrSource << "\" in source attribute of <vertex_weights> data <input> element" );
+                channel.mAccessor = attrSource + 1;
+
+                // parse source URL to corresponding source
+                if( strcmp( attrSemantic, "JOINT") == 0)
+                    pController.mWeightInputJoints = channel;
+                else if( strcmp( attrSemantic, "WEIGHT") == 0)
+                    pController.mWeightInputWeights = channel;
+                else
+                    ThrowException( format() << "Unknown semantic \"" << attrSemantic << "\" in <vertex_weights> data <input> element" );
+
+                // skip inner data, if present
+                if( !mReader->isEmptyElement())
+                    SkipElement();
+            }
+            else if( IsElement( "vcount") && vertexCount > 0 )
+            {
+                // read weight count per vertex
+                const char* text = GetTextContent();
+                size_t numWeights = 0;
+                for( std::vector<size_t>::iterator it = pController.mWeightCounts.begin(); it != pController.mWeightCounts.end(); ++it)
+                {
+                    if( *text == 0)
+                        ThrowException( "Out of data while reading <vcount>");
+
+                    *it = strtoul10( text, &text);
+                    numWeights += *it;
+                    SkipSpacesAndLineEnd( &text);
+                }
+
+                TestClosing( "vcount");
+
+                // reserve weight count
+                pController.mWeights.resize( numWeights);
+            }
+            else if( IsElement( "v") && vertexCount > 0 )
+            {
+                // read JointIndex - WeightIndex pairs
+                const char* text = GetTextContent();
+
+                for( std::vector< std::pair<size_t, size_t> >::iterator it = pController.mWeights.begin(); it != pController.mWeights.end(); ++it)
+                {
+                    if( *text == 0)
+                        ThrowException( "Out of data while reading <vertex_weights>");
+                    it->first = strtoul10( text, &text);
+                    SkipSpacesAndLineEnd( &text);
+                    if( *text == 0)
+                        ThrowException( "Out of data while reading <vertex_weights>");
+                    it->second = strtoul10( text, &text);
+                    SkipSpacesAndLineEnd( &text);
+                }
+
+                TestClosing( "v");
+            }
+            else
+            {
+                // ignore the rest
+                SkipElement();
+            }
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
+        {
+            if( strcmp( mReader->getNodeName(), "vertex_weights") != 0)
+                ThrowException( "Expected end of <vertex_weights> element.");
+
+            break;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the image library contents
+void ColladaParser::ReadImageLibrary()
+{
+    if( mReader->isEmptyElement())
+        return;
+
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT) {
+            if( IsElement( "image"))
+            {
+                // read ID. Another entry which is "optional" by design but obligatory in reality
+                int attrID = GetAttribute( "id");
+                std::string id = mReader->getAttributeValue( attrID);
+
+                // create an entry and store it in the library under its ID
+                mImageLibrary[id] = Image();
+
+                // read on from there
+                ReadImage( mImageLibrary[id]);
+            } else
+            {
+                // ignore the rest
+                SkipElement();
+            }
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) {
+            if( strcmp( mReader->getNodeName(), "library_images") != 0)
+                ThrowException( "Expected end of <library_images> element.");
+
+            break;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads an image entry into the given image
+void ColladaParser::ReadImage( Collada::Image& pImage)
+{
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT){
+            // Need to run different code paths here, depending on the Collada XSD version
+            if (IsElement("image")) {
+                SkipElement();
+            }
+            else if(  IsElement( "init_from"))
+            {
+                if (mFormat == FV_1_4_n)
+                {
+                    // FIX: C4D exporter writes empty <init_from/> tags
+                    if (!mReader->isEmptyElement()) {
+                        // element content is filename - hopefully
+                        const char* sz = TestTextContent();
+                        if (sz)pImage.mFileName = sz;
+                        TestClosing( "init_from");
+                    }
+                    if (!pImage.mFileName.length()) {
+                        pImage.mFileName = "unknown_texture";
+                    }
+                }
+                else if (mFormat == FV_1_5_n)
+                {
+                    // make sure we skip over mip and array initializations, which
+                    // we don't support, but which could confuse the loader if
+                    // they're not skipped.
+                    int attrib = TestAttribute("array_index");
+                    if (attrib != -1 && mReader->getAttributeValueAsInt(attrib) > 0) {
+                        ASSIMP_LOG_WARN("Collada: Ignoring texture array index");
+                        continue;
+                    }
+
+                    attrib = TestAttribute("mip_index");
+                    if (attrib != -1 && mReader->getAttributeValueAsInt(attrib) > 0) {
+                        ASSIMP_LOG_WARN("Collada: Ignoring MIP map layer");
+                        continue;
+                    }
+
+                    // TODO: correctly jump over cube and volume maps?
+                }
+            }
+            else if (mFormat == FV_1_5_n)
+            {
+                if( IsElement( "ref"))
+                {
+                    // element content is filename - hopefully
+                    const char* sz = TestTextContent();
+                    if (sz)pImage.mFileName = sz;
+                    TestClosing( "ref");
+                }
+                else if( IsElement( "hex") && !pImage.mFileName.length())
+                {
+                    // embedded image. get format
+                    const int attrib = TestAttribute("format");
+                    if (-1 == attrib)
+                        ASSIMP_LOG_WARN("Collada: Unknown image file format");
+                    else pImage.mEmbeddedFormat = mReader->getAttributeValue(attrib);
+
+                    const char* data = GetTextContent();
+
+                    // hexadecimal-encoded binary octets. First of all, find the
+                    // required buffer size to reserve enough storage.
+                    const char* cur = data;
+                    while (!IsSpaceOrNewLine(*cur)) cur++;
+
+                    const unsigned int size = (unsigned int)(cur-data) * 2;
+                    pImage.mImageData.resize(size);
+                    for (unsigned int i = 0; i < size;++i)
+                        pImage.mImageData[i] = HexOctetToDecimal(data+(i<<1));
+
+                    TestClosing( "hex");
+                }
+            }
+            else
+            {
+                // ignore the rest
+                SkipElement();
+            }
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) {
+            if( strcmp( mReader->getNodeName(), "image") == 0)
+                break;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the material library
+void ColladaParser::ReadMaterialLibrary()
+{
+    if( mReader->isEmptyElement())
+        return;
+
+    std::map<std::string, int> names;
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT)
+        {
+            if( IsElement( "material"))
+            {
+                // read ID. By now you probably know my opinion about this "specification"
+                int attrID = GetAttribute( "id");
+                std::string id = mReader->getAttributeValue( attrID);
+
+                std::string name;
+                int attrName = TestAttribute("name");
+                if (attrName >= 0)
+                    name = mReader->getAttributeValue( attrName);
+
+                // create an entry and store it in the library under its ID
+                mMaterialLibrary[id] = Material();
+
+                if( !name.empty())
+                {
+                    std::map<std::string, int>::iterator it = names.find( name);
+                    if( it != names.end())
+                    {
+                        std::ostringstream strStream;
+                        strStream << ++it->second;
+                        name.append( " " + strStream.str());
+                    }
+                    else
+                    {
+                        names[name] = 0;
+                    }
+
+                    mMaterialLibrary[id].mName = name;
+                }
+
+                ReadMaterial( mMaterialLibrary[id]);
+            } else
+            {
+                // ignore the rest
+                SkipElement();
+            }
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
+        {
+            if( strcmp( mReader->getNodeName(), "library_materials") != 0)
+                ThrowException( "Expected end of <library_materials> element.");
+
+            break;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the light library
+void ColladaParser::ReadLightLibrary()
+{
+    if( mReader->isEmptyElement())
+        return;
+
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT) {
+            if( IsElement( "light"))
+            {
+                // read ID. By now you probably know my opinion about this "specification"
+                int attrID = GetAttribute( "id");
+                std::string id = mReader->getAttributeValue( attrID);
+
+                // create an entry and store it in the library under its ID
+                ReadLight(mLightLibrary[id] = Light());
+
+            } else
+            {
+                // ignore the rest
+                SkipElement();
+            }
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END)    {
+            if( strcmp( mReader->getNodeName(), "library_lights") != 0)
+                ThrowException( "Expected end of <library_lights> element.");
+
+            break;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the camera library
+void ColladaParser::ReadCameraLibrary()
+{
+    if( mReader->isEmptyElement())
+        return;
+
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT) {
+            if( IsElement( "camera"))
+            {
+                // read ID. By now you probably know my opinion about this "specification"
+                int attrID = GetAttribute( "id");
+                std::string id = mReader->getAttributeValue( attrID);
+
+                // create an entry and store it in the library under its ID
+                Camera& cam = mCameraLibrary[id];
+                attrID = TestAttribute( "name");
+                if (attrID != -1)
+                    cam.mName = mReader->getAttributeValue( attrID);
+
+                ReadCamera(cam);
+
+            } else
+            {
+                // ignore the rest
+                SkipElement();
+            }
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END)    {
+            if( strcmp( mReader->getNodeName(), "library_cameras") != 0)
+                ThrowException( "Expected end of <library_cameras> element.");
+
+            break;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a material entry into the given material
+void ColladaParser::ReadMaterial( Collada::Material& pMaterial)
+{
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT) {
+            if (IsElement("material")) {
+                SkipElement();
+            }
+            else if( IsElement( "instance_effect"))
+            {
+                // referred effect by URL
+                int attrUrl = GetAttribute( "url");
+                const char* url = mReader->getAttributeValue( attrUrl);
+                if( url[0] != '#')
+                    ThrowException( "Unknown reference format");
+
+                pMaterial.mEffect = url+1;
+
+                SkipElement();
+            } else
+            {
+                // ignore the rest
+                SkipElement();
+            }
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) {
+            if( strcmp( mReader->getNodeName(), "material") != 0)
+                ThrowException( "Expected end of <material> element.");
+
+            break;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a light entry into the given light
+void ColladaParser::ReadLight( Collada::Light& pLight)
+{
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT) {
+            if (IsElement("light")) {
+                SkipElement();
+            }
+            else if (IsElement("spot")) {
+                pLight.mType = aiLightSource_SPOT;
+            }
+            else if (IsElement("ambient")) {
+                pLight.mType = aiLightSource_AMBIENT;
+            }
+            else if (IsElement("directional")) {
+                pLight.mType = aiLightSource_DIRECTIONAL;
+            }
+            else if (IsElement("point")) {
+                pLight.mType = aiLightSource_POINT;
+            }
+            else if (IsElement("color")) {
+                // text content contains 3 floats
+                const char* content = GetTextContent();
+
+                content = fast_atoreal_move<ai_real>( content, (ai_real&)pLight.mColor.r);
+                SkipSpacesAndLineEnd( &content);
+
+                content = fast_atoreal_move<ai_real>( content, (ai_real&)pLight.mColor.g);
+                SkipSpacesAndLineEnd( &content);
+
+                content = fast_atoreal_move<ai_real>( content, (ai_real&)pLight.mColor.b);
+                SkipSpacesAndLineEnd( &content);
+
+                TestClosing( "color");
+            }
+            else if (IsElement("constant_attenuation")) {
+                pLight.mAttConstant = ReadFloatFromTextContent();
+                TestClosing("constant_attenuation");
+            }
+            else if (IsElement("linear_attenuation")) {
+                pLight.mAttLinear = ReadFloatFromTextContent();
+                TestClosing("linear_attenuation");
+            }
+            else if (IsElement("quadratic_attenuation")) {
+                pLight.mAttQuadratic = ReadFloatFromTextContent();
+                TestClosing("quadratic_attenuation");
+            }
+            else if (IsElement("falloff_angle")) {
+                pLight.mFalloffAngle = ReadFloatFromTextContent();
+                TestClosing("falloff_angle");
+            }
+            else if (IsElement("falloff_exponent")) {
+                pLight.mFalloffExponent = ReadFloatFromTextContent();
+                TestClosing("falloff_exponent");
+            }
+            // FCOLLADA extensions
+            // -------------------------------------------------------
+            else if (IsElement("outer_cone")) {
+                pLight.mOuterAngle = ReadFloatFromTextContent();
+                TestClosing("outer_cone");
+            }
+            // ... and this one is even deprecated
+            else if (IsElement("penumbra_angle")) {
+                pLight.mPenumbraAngle = ReadFloatFromTextContent();
+                TestClosing("penumbra_angle");
+            }
+            else if (IsElement("intensity")) {
+                pLight.mIntensity = ReadFloatFromTextContent();
+                TestClosing("intensity");
+            }
+            else if (IsElement("falloff")) {
+                pLight.mOuterAngle = ReadFloatFromTextContent();
+                TestClosing("falloff");
+            }
+            else if (IsElement("hotspot_beam")) {
+                pLight.mFalloffAngle = ReadFloatFromTextContent();
+                TestClosing("hotspot_beam");
+            }
+            // OpenCOLLADA extensions
+            // -------------------------------------------------------
+            else if (IsElement("decay_falloff")) {
+                pLight.mOuterAngle = ReadFloatFromTextContent();
+                TestClosing("decay_falloff");
+            }
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) {
+            if( strcmp( mReader->getNodeName(), "light") == 0)
+                break;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a camera entry into the given light
+void ColladaParser::ReadCamera( Collada::Camera& pCamera)
+{
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT) {
+            if (IsElement("camera")) {
+                SkipElement();
+            }
+            else if (IsElement("orthographic")) {
+                pCamera.mOrtho = true;
+            }
+            else if (IsElement("xfov") || IsElement("xmag")) {
+                pCamera.mHorFov = ReadFloatFromTextContent();
+                TestClosing((pCamera.mOrtho ? "xmag" : "xfov"));
+            }
+            else if (IsElement("yfov") || IsElement("ymag")) {
+                pCamera.mVerFov = ReadFloatFromTextContent();
+                TestClosing((pCamera.mOrtho ? "ymag" : "yfov"));
+            }
+            else if (IsElement("aspect_ratio")) {
+                pCamera.mAspect = ReadFloatFromTextContent();
+                TestClosing("aspect_ratio");
+            }
+            else if (IsElement("znear")) {
+                pCamera.mZNear = ReadFloatFromTextContent();
+                TestClosing("znear");
+            }
+            else if (IsElement("zfar")) {
+                pCamera.mZFar = ReadFloatFromTextContent();
+                TestClosing("zfar");
+            }
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) {
+            if( strcmp( mReader->getNodeName(), "camera") == 0)
+                break;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the effect library
+void ColladaParser::ReadEffectLibrary()
+{
+    if (mReader->isEmptyElement()) {
+        return;
+    }
+
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT) {
+            if( IsElement( "effect"))
+            {
+                // read ID. Do I have to repeat my ranting about "optional" attributes?
+                int attrID = GetAttribute( "id");
+                std::string id = mReader->getAttributeValue( attrID);
+
+                // create an entry and store it in the library under its ID
+                mEffectLibrary[id] = Effect();
+                // read on from there
+                ReadEffect( mEffectLibrary[id]);
+            } else
+            {
+                // ignore the rest
+                SkipElement();
+            }
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) {
+            if( strcmp( mReader->getNodeName(), "library_effects") != 0)
+                ThrowException( "Expected end of <library_effects> element.");
+
+            break;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads an effect entry into the given effect
+void ColladaParser::ReadEffect( Collada::Effect& pEffect)
+{
+    // for the moment we don't support any other type of effect.
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT)
+        {
+            if( IsElement( "profile_COMMON"))
+                ReadEffectProfileCommon( pEffect);
+            else
+                SkipElement();
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
+        {
+            if( strcmp( mReader->getNodeName(), "effect") != 0)
+                ThrowException( "Expected end of <effect> element.");
+
+            break;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads an COMMON effect profile
+void ColladaParser::ReadEffectProfileCommon( Collada::Effect& pEffect)
+{
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT)
+        {
+            if( IsElement( "newparam")) {
+                // save ID
+                int attrSID = GetAttribute( "sid");
+                std::string sid = mReader->getAttributeValue( attrSID);
+                pEffect.mParams[sid] = EffectParam();
+                ReadEffectParam( pEffect.mParams[sid]);
+            }
+            else if( IsElement( "technique") || IsElement( "extra"))
+            {
+                // just syntactic sugar
+            }
+
+            else if( mFormat == FV_1_4_n && IsElement( "image"))
+            {
+                // read ID. Another entry which is "optional" by design but obligatory in reality
+                int attrID = GetAttribute( "id");
+                std::string id = mReader->getAttributeValue( attrID);
+
+                // create an entry and store it in the library under its ID
+                mImageLibrary[id] = Image();
+
+                // read on from there
+                ReadImage( mImageLibrary[id]);
+            }
+
+            /* Shading modes */
+            else if( IsElement( "phong"))
+                pEffect.mShadeType = Shade_Phong;
+            else if( IsElement( "constant"))
+                pEffect.mShadeType = Shade_Constant;
+            else if( IsElement( "lambert"))
+                pEffect.mShadeType = Shade_Lambert;
+            else if( IsElement( "blinn"))
+                pEffect.mShadeType = Shade_Blinn;
+
+            /* Color + texture properties */
+            else if( IsElement( "emission"))
+                ReadEffectColor( pEffect.mEmissive, pEffect.mTexEmissive);
+            else if( IsElement( "ambient"))
+                ReadEffectColor( pEffect.mAmbient, pEffect.mTexAmbient);
+            else if( IsElement( "diffuse"))
+                ReadEffectColor( pEffect.mDiffuse, pEffect.mTexDiffuse);
+            else if( IsElement( "specular"))
+                ReadEffectColor( pEffect.mSpecular, pEffect.mTexSpecular);
+            else if( IsElement( "reflective")) {
+                ReadEffectColor( pEffect.mReflective, pEffect.mTexReflective);
+            }
+            else if( IsElement( "transparent")) {
+                pEffect.mHasTransparency = true;
+
+                const char* opaque = mReader->getAttributeValueSafe("opaque");
+
+                if(::strcmp(opaque, "RGB_ZERO") == 0 || ::strcmp(opaque, "RGB_ONE") == 0) {
+                    pEffect.mRGBTransparency = true;
+                }
+
+                // In RGB_ZERO mode, the transparency is interpreted in reverse, go figure...
+				if(::strcmp(opaque, "RGB_ZERO") == 0 || ::strcmp(opaque, "A_ZERO") == 0) {
+					pEffect.mInvertTransparency = true;
+				}
+
+                ReadEffectColor( pEffect.mTransparent,pEffect.mTexTransparent);
+            }
+            else if( IsElement( "shininess"))
+                ReadEffectFloat( pEffect.mShininess);
+            else if( IsElement( "reflectivity"))
+                ReadEffectFloat( pEffect.mReflectivity);
+
+            /* Single scalar properties */
+            else if( IsElement( "transparency"))
+                ReadEffectFloat( pEffect.mTransparency);
+            else if( IsElement( "index_of_refraction"))
+                ReadEffectFloat( pEffect.mRefractIndex);
+
+            // GOOGLEEARTH/OKINO extensions
+            // -------------------------------------------------------
+            else if( IsElement( "double_sided"))
+                pEffect.mDoubleSided = ReadBoolFromTextContent();
+
+            // FCOLLADA extensions
+            // -------------------------------------------------------
+            else if( IsElement( "bump")) {
+                aiColor4D dummy;
+                ReadEffectColor( dummy,pEffect.mTexBump);
+            }
+
+            // MAX3D extensions
+            // -------------------------------------------------------
+            else if( IsElement( "wireframe"))   {
+                pEffect.mWireframe = ReadBoolFromTextContent();
+                TestClosing( "wireframe");
+            }
+            else if( IsElement( "faceted")) {
+                pEffect.mFaceted = ReadBoolFromTextContent();
+                TestClosing( "faceted");
+            }
+            else
+            {
+                // ignore the rest
+                SkipElement();
+            }
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) {
+            if( strcmp( mReader->getNodeName(), "profile_COMMON") == 0)
+            {
+                break;
+            }
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Read texture wrapping + UV transform settings from a profile==Maya chunk
+void ColladaParser::ReadSamplerProperties( Sampler& out )
+{
+    if (mReader->isEmptyElement()) {
+        return;
+    }
+
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT) {
+
+            // MAYA extensions
+            // -------------------------------------------------------
+            if( IsElement( "wrapU"))        {
+                out.mWrapU = ReadBoolFromTextContent();
+                TestClosing( "wrapU");
+            }
+            else if( IsElement( "wrapV"))   {
+                out.mWrapV = ReadBoolFromTextContent();
+                TestClosing( "wrapV");
+            }
+            else if( IsElement( "mirrorU"))     {
+                out.mMirrorU = ReadBoolFromTextContent();
+                TestClosing( "mirrorU");
+            }
+            else if( IsElement( "mirrorV")) {
+                out.mMirrorV = ReadBoolFromTextContent();
+                TestClosing( "mirrorV");
+            }
+            else if( IsElement( "repeatU")) {
+                out.mTransform.mScaling.x = ReadFloatFromTextContent();
+                TestClosing( "repeatU");
+            }
+            else if( IsElement( "repeatV")) {
+                out.mTransform.mScaling.y = ReadFloatFromTextContent();
+                TestClosing( "repeatV");
+            }
+            else if( IsElement( "offsetU")) {
+                out.mTransform.mTranslation.x = ReadFloatFromTextContent();
+                TestClosing( "offsetU");
+            }
+            else if( IsElement( "offsetV")) {
+                out.mTransform.mTranslation.y = ReadFloatFromTextContent();
+                TestClosing( "offsetV");
+            }
+            else if( IsElement( "rotateUV"))    {
+                out.mTransform.mRotation = ReadFloatFromTextContent();
+                TestClosing( "rotateUV");
+            }
+            else if( IsElement( "blend_mode"))  {
+
+                const char* sz = GetTextContent();
+                // http://www.feelingsoftware.com/content/view/55/72/lang,en/
+                // NONE, OVER, IN, OUT, ADD, SUBTRACT, MULTIPLY, DIFFERENCE, LIGHTEN, DARKEN, SATURATE, DESATURATE and ILLUMINATE
+                if (0 == ASSIMP_strincmp(sz,"ADD",3))
+                    out.mOp = aiTextureOp_Add;
+
+                else if (0 == ASSIMP_strincmp(sz,"SUBTRACT",8))
+                    out.mOp = aiTextureOp_Subtract;
+
+                else if (0 == ASSIMP_strincmp(sz,"MULTIPLY",8))
+                    out.mOp = aiTextureOp_Multiply;
+
+                else  {
+                    ASSIMP_LOG_WARN("Collada: Unsupported MAYA texture blend mode");
+                }
+                TestClosing( "blend_mode");
+            }
+            // OKINO extensions
+            // -------------------------------------------------------
+            else if( IsElement( "weighting"))   {
+                out.mWeighting = ReadFloatFromTextContent();
+                TestClosing( "weighting");
+            }
+            else if( IsElement( "mix_with_previous_layer")) {
+                out.mMixWithPrevious = ReadFloatFromTextContent();
+                TestClosing( "mix_with_previous_layer");
+            }
+            // MAX3D extensions
+            // -------------------------------------------------------
+            else if( IsElement( "amount"))  {
+                out.mWeighting = ReadFloatFromTextContent();
+                TestClosing( "amount");
+            }
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) {
+            if( strcmp( mReader->getNodeName(), "technique") == 0)
+                break;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads an effect entry containing a color or a texture defining that color
+void ColladaParser::ReadEffectColor( aiColor4D& pColor, Sampler& pSampler)
+{
+    if (mReader->isEmptyElement())
+        return;
+
+    // Save current element name
+    const std::string curElem = mReader->getNodeName();
+
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT) {
+            if( IsElement( "color"))
+            {
+                // text content contains 4 floats
+                const char* content = GetTextContent();
+
+                content = fast_atoreal_move<ai_real>( content, (ai_real&)pColor.r);
+                SkipSpacesAndLineEnd( &content);
+
+                content = fast_atoreal_move<ai_real>( content, (ai_real&)pColor.g);
+                SkipSpacesAndLineEnd( &content);
+
+                content = fast_atoreal_move<ai_real>( content, (ai_real&)pColor.b);
+                SkipSpacesAndLineEnd( &content);
+
+                content = fast_atoreal_move<ai_real>( content, (ai_real&)pColor.a);
+                SkipSpacesAndLineEnd( &content);
+                TestClosing( "color");
+            }
+            else if( IsElement( "texture"))
+            {
+                // get name of source texture/sampler
+                int attrTex = GetAttribute( "texture");
+                pSampler.mName = mReader->getAttributeValue( attrTex);
+
+                // get name of UV source channel. Specification demands it to be there, but some exporters
+                // don't write it. It will be the default UV channel in case it's missing.
+                attrTex = TestAttribute( "texcoord");
+                if( attrTex >= 0 )
+                    pSampler.mUVChannel = mReader->getAttributeValue( attrTex);
+                //SkipElement();
+
+                // as we've read texture, the color needs to be 1,1,1,1
+                pColor = aiColor4D(1.f, 1.f, 1.f, 1.f);
+            }
+            else if( IsElement( "technique"))
+            {
+                const int _profile = GetAttribute( "profile");
+                const char* profile = mReader->getAttributeValue( _profile );
+
+                // Some extensions are quite useful ... ReadSamplerProperties processes
+                // several extensions in MAYA, OKINO and MAX3D profiles.
+                if (!::strcmp(profile,"MAYA") || !::strcmp(profile,"MAX3D") || !::strcmp(profile,"OKINO"))
+                {
+                    // get more information on this sampler
+                    ReadSamplerProperties(pSampler);
+                }
+                else SkipElement();
+            }
+            else if( !IsElement( "extra"))
+            {
+                // ignore the rest
+                SkipElement();
+            }
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END){
+            if (mReader->getNodeName() == curElem)
+                break;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads an effect entry containing a float
+void ColladaParser::ReadEffectFloat( ai_real& pFloat)
+{
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT){
+            if( IsElement( "float"))
+            {
+                // text content contains a single floats
+                const char* content = GetTextContent();
+                content = fast_atoreal_move<ai_real>( content, pFloat);
+                SkipSpacesAndLineEnd( &content);
+
+                TestClosing( "float");
+            } else
+            {
+                // ignore the rest
+                SkipElement();
+            }
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END){
+            break;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads an effect parameter specification of any kind
+void ColladaParser::ReadEffectParam( Collada::EffectParam& pParam)
+{
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT) {
+            if( IsElement( "surface"))
+            {
+                // image ID given inside <init_from> tags
+                TestOpening( "init_from");
+                const char* content = GetTextContent();
+                pParam.mType = Param_Surface;
+                pParam.mReference = content;
+                TestClosing( "init_from");
+
+                // don't care for remaining stuff
+                SkipElement( "surface");
+            }
+            else if( IsElement( "sampler2D") && (FV_1_4_n == mFormat || FV_1_3_n == mFormat))
+            {
+                // surface ID is given inside <source> tags
+                TestOpening( "source");
+                const char* content = GetTextContent();
+                pParam.mType = Param_Sampler;
+                pParam.mReference = content;
+                TestClosing( "source");
+
+                // don't care for remaining stuff
+                SkipElement( "sampler2D");
+            }
+            else if( IsElement( "sampler2D"))
+            {
+                // surface ID is given inside <instance_image> tags
+                TestOpening( "instance_image");
+                int attrURL = GetAttribute("url");
+                const char* url = mReader->getAttributeValue( attrURL);
+                if( url[0] != '#')
+                    ThrowException( "Unsupported URL format in instance_image");
+                url++;
+                pParam.mType = Param_Sampler;
+                pParam.mReference = url;
+                SkipElement( "sampler2D");
+            } else
+            {
+                // ignore unknown element
+                SkipElement();
+            }
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) {
+            break;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the geometry library contents
+void ColladaParser::ReadGeometryLibrary()
+{
+    if( mReader->isEmptyElement())
+        return;
+
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT)
+        {
+            if( IsElement( "geometry"))
+            {
+                // read ID. Another entry which is "optional" by design but obligatory in reality
+                int indexID = GetAttribute( "id");
+                std::string id = mReader->getAttributeValue( indexID);
+
+                // TODO: (thom) support SIDs
+                // ai_assert( TestAttribute( "sid") == -1);
+
+                // create a mesh and store it in the library under its ID
+                Mesh* mesh = new Mesh;
+                mMeshLibrary[id] = mesh;
+
+                // read the mesh name if it exists
+                const int nameIndex = TestAttribute("name");
+                if(nameIndex != -1)
+                {
+                    mesh->mName = mReader->getAttributeValue(nameIndex);
+                }
+
+                // read on from there
+                ReadGeometry( mesh);
+            } else
+            {
+                // ignore the rest
+                SkipElement();
+            }
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
+        {
+            if( strcmp( mReader->getNodeName(), "library_geometries") != 0)
+                ThrowException( "Expected end of <library_geometries> element.");
+
+            break;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a geometry from the geometry library.
+void ColladaParser::ReadGeometry( Collada::Mesh* pMesh)
+{
+    if( mReader->isEmptyElement())
+        return;
+
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT)
+        {
+            if( IsElement( "mesh"))
+            {
+                // read on from there
+                ReadMesh( pMesh);
+            } else
+            {
+                // ignore the rest
+                SkipElement();
+            }
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
+        {
+            if( strcmp( mReader->getNodeName(), "geometry") != 0)
+                ThrowException( "Expected end of <geometry> element.");
+
+            break;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a mesh from the geometry library
+void ColladaParser::ReadMesh( Mesh* pMesh)
+{
+    if( mReader->isEmptyElement())
+        return;
+
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT)
+        {
+            if( IsElement( "source"))
+            {
+                // we have professionals dealing with this
+                ReadSource();
+            }
+            else if( IsElement( "vertices"))
+            {
+                // read per-vertex mesh data
+                ReadVertexData( pMesh);
+            }
+            else if( IsElement( "triangles") || IsElement( "lines") || IsElement( "linestrips")
+                || IsElement( "polygons") || IsElement( "polylist") || IsElement( "trifans") || IsElement( "tristrips"))
+            {
+                // read per-index mesh data and faces setup
+                ReadIndexData( pMesh);
+            } else
+            {
+                // ignore the restf
+                SkipElement();
+            }
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
+        {
+            if( strcmp( mReader->getNodeName(), "technique_common") == 0)
+            {
+                // end of another meaningless element - read over it
+            }
+            else if( strcmp( mReader->getNodeName(), "mesh") == 0)
+            {
+                // end of <mesh> element - we're done here
+                break;
+            } else
+            {
+                // everything else should be punished
+                ThrowException( "Expected end of <mesh> element.");
+            }
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a source element
+void ColladaParser::ReadSource()
+{
+    int indexID = GetAttribute( "id");
+    std::string sourceID = mReader->getAttributeValue( indexID);
+
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT)
+        {
+            if( IsElement( "float_array") || IsElement( "IDREF_array") || IsElement( "Name_array"))
+            {
+                ReadDataArray();
+            }
+            else if( IsElement( "technique_common"))
+            {
+                // I don't care for your profiles
+            }
+            else if( IsElement( "accessor"))
+            {
+                ReadAccessor( sourceID);
+            } else
+            {
+                // ignore the rest
+                SkipElement();
+            }
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
+        {
+            if( strcmp( mReader->getNodeName(), "source") == 0)
+            {
+                // end of <source> - we're done
+                break;
+            }
+            else if( strcmp( mReader->getNodeName(), "technique_common") == 0)
+            {
+                // end of another meaningless element - read over it
+            } else
+            {
+                // everything else should be punished
+                ThrowException( "Expected end of <source> element.");
+            }
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a data array holding a number of floats, and stores it in the global library
+void ColladaParser::ReadDataArray()
+{
+    std::string elmName = mReader->getNodeName();
+    bool isStringArray = (elmName == "IDREF_array" || elmName == "Name_array");
+  bool isEmptyElement = mReader->isEmptyElement();
+
+    // read attributes
+    int indexID = GetAttribute( "id");
+    std::string id = mReader->getAttributeValue( indexID);
+    int indexCount = GetAttribute( "count");
+    unsigned int count = (unsigned int) mReader->getAttributeValueAsInt( indexCount);
+    const char* content = TestTextContent();
+
+  // read values and store inside an array in the data library
+  mDataLibrary[id] = Data();
+  Data& data = mDataLibrary[id];
+  data.mIsStringArray = isStringArray;
+
+  // some exporters write empty data arrays, but we need to conserve them anyways because others might reference them
+  if (content)
+  {
+        if( isStringArray)
+        {
+            data.mStrings.reserve( count);
+            std::string s;
+
+            for( unsigned int a = 0; a < count; a++)
+            {
+                if( *content == 0)
+                    ThrowException( "Expected more values while reading IDREF_array contents.");
+
+                s.clear();
+                while( !IsSpaceOrNewLine( *content))
+                    s += *content++;
+                data.mStrings.push_back( s);
+
+                SkipSpacesAndLineEnd( &content);
+            }
+        } else
+        {
+            data.mValues.reserve( count);
+
+            for( unsigned int a = 0; a < count; a++)
+            {
+                if( *content == 0)
+                    ThrowException( "Expected more values while reading float_array contents.");
+
+                ai_real value;
+                // read a number
+                content = fast_atoreal_move<ai_real>( content, value);
+                data.mValues.push_back( value);
+                // skip whitespace after it
+                SkipSpacesAndLineEnd( &content);
+            }
+        }
+    }
+
+  // test for closing tag
+  if( !isEmptyElement )
+    TestClosing( elmName.c_str());
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads an accessor and stores it in the global library
+void ColladaParser::ReadAccessor( const std::string& pID)
+{
+    // read accessor attributes
+    int attrSource = GetAttribute( "source");
+    const char* source = mReader->getAttributeValue( attrSource);
+    if( source[0] != '#')
+        ThrowException( format() << "Unknown reference format in url \"" << source << "\" in source attribute of <accessor> element." );
+    int attrCount = GetAttribute( "count");
+    unsigned int count = (unsigned int) mReader->getAttributeValueAsInt( attrCount);
+    int attrOffset = TestAttribute( "offset");
+    unsigned int offset = 0;
+    if( attrOffset > -1)
+        offset = (unsigned int) mReader->getAttributeValueAsInt( attrOffset);
+    int attrStride = TestAttribute( "stride");
+    unsigned int stride = 1;
+    if( attrStride > -1)
+        stride = (unsigned int) mReader->getAttributeValueAsInt( attrStride);
+
+    // store in the library under the given ID
+    mAccessorLibrary[pID] = Accessor();
+    Accessor& acc = mAccessorLibrary[pID];
+    acc.mCount = count;
+    acc.mOffset = offset;
+    acc.mStride = stride;
+    acc.mSource = source+1; // ignore the leading '#'
+    acc.mSize = 0; // gets incremented with every param
+
+    // and read the components
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT)
+        {
+            if( IsElement( "param"))
+            {
+                // read data param
+                int attrName = TestAttribute( "name");
+                std::string name;
+                if( attrName > -1)
+                {
+                    name = mReader->getAttributeValue( attrName);
+
+                    // analyse for common type components and store it's sub-offset in the corresponding field
+
+                    /* Cartesian coordinates */
+                    if( name == "X") acc.mSubOffset[0] = acc.mParams.size();
+                    else if( name == "Y") acc.mSubOffset[1] = acc.mParams.size();
+                    else if( name == "Z") acc.mSubOffset[2] = acc.mParams.size();
+
+                    /* RGBA colors */
+                    else if( name == "R") acc.mSubOffset[0] = acc.mParams.size();
+                    else if( name == "G") acc.mSubOffset[1] = acc.mParams.size();
+                    else if( name == "B") acc.mSubOffset[2] = acc.mParams.size();
+                    else if( name == "A") acc.mSubOffset[3] = acc.mParams.size();
+
+                    /* UVWQ (STPQ) texture coordinates */
+                    else if( name == "S") acc.mSubOffset[0] = acc.mParams.size();
+                    else if( name == "T") acc.mSubOffset[1] = acc.mParams.size();
+                    else if( name == "P") acc.mSubOffset[2] = acc.mParams.size();
+                //  else if( name == "Q") acc.mSubOffset[3] = acc.mParams.size();
+                    /* 4D uv coordinates are not supported in Assimp */
+
+                    /* Generic extra data, interpreted as UV data, too*/
+                    else if( name == "U") acc.mSubOffset[0] = acc.mParams.size();
+                    else if( name == "V") acc.mSubOffset[1] = acc.mParams.size();
+                    //else
+                    //  DefaultLogger::get()->warn( format() << "Unknown accessor parameter \"" << name << "\". Ignoring data channel." );
+                }
+
+                // read data type
+                int attrType = TestAttribute( "type");
+                if( attrType > -1)
+                {
+                    // for the moment we only distinguish between a 4x4 matrix and anything else.
+                    // TODO: (thom) I don't have a spec here at work. Check if there are other multi-value types
+                    // which should be tested for here.
+                    std::string type = mReader->getAttributeValue( attrType);
+                    if( type == "float4x4")
+                        acc.mSize += 16;
+                    else
+                        acc.mSize += 1;
+                }
+
+                acc.mParams.push_back( name);
+
+                // skip remaining stuff of this element, if any
+                SkipElement();
+            } else
+            {
+                ThrowException( format() << "Unexpected sub element <" << mReader->getNodeName() << "> in tag <accessor>" );
+            }
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
+        {
+            if( strcmp( mReader->getNodeName(), "accessor") != 0)
+                ThrowException( "Expected end of <accessor> element.");
+            break;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads input declarations of per-vertex mesh data into the given mesh
+void ColladaParser::ReadVertexData( Mesh* pMesh)
+{
+    // extract the ID of the <vertices> element. Not that we care, but to catch strange referencing schemes we should warn about
+    int attrID= GetAttribute( "id");
+    pMesh->mVertexID = mReader->getAttributeValue( attrID);
+
+    // a number of <input> elements
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT)
+        {
+            if( IsElement( "input"))
+            {
+                ReadInputChannel( pMesh->mPerVertexData);
+            } else
+            {
+                ThrowException( format() << "Unexpected sub element <" << mReader->getNodeName() << "> in tag <vertices>" );
+            }
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
+        {
+            if( strcmp( mReader->getNodeName(), "vertices") != 0)
+                ThrowException( "Expected end of <vertices> element.");
+
+            break;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads input declarations of per-index mesh data into the given mesh
+void ColladaParser::ReadIndexData( Mesh* pMesh)
+{
+    std::vector<size_t> vcount;
+    std::vector<InputChannel> perIndexData;
+
+    // read primitive count from the attribute
+    int attrCount = GetAttribute( "count");
+    size_t numPrimitives = (size_t) mReader->getAttributeValueAsInt( attrCount);
+    // some mesh types (e.g. tristrips) don't specify primitive count upfront,
+    // so we need to sum up the actual number of primitives while we read the <p>-tags
+    size_t actualPrimitives = 0;
+
+    // material subgroup
+    int attrMaterial = TestAttribute( "material");
+    SubMesh subgroup;
+    if( attrMaterial > -1)
+        subgroup.mMaterial = mReader->getAttributeValue( attrMaterial);
+
+    // distinguish between polys and triangles
+    std::string elementName = mReader->getNodeName();
+    PrimitiveType primType = Prim_Invalid;
+    if( IsElement( "lines"))
+        primType = Prim_Lines;
+    else if( IsElement( "linestrips"))
+        primType = Prim_LineStrip;
+    else if( IsElement( "polygons"))
+        primType = Prim_Polygon;
+    else if( IsElement( "polylist"))
+        primType = Prim_Polylist;
+    else if( IsElement( "triangles"))
+        primType = Prim_Triangles;
+    else if( IsElement( "trifans"))
+        primType = Prim_TriFans;
+    else if( IsElement( "tristrips"))
+        primType = Prim_TriStrips;
+
+    ai_assert( primType != Prim_Invalid);
+
+    // also a number of <input> elements, but in addition a <p> primitive collection and probably index counts for all primitives
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT)
+        {
+            if( IsElement( "input"))
+            {
+                ReadInputChannel( perIndexData);
+            }
+            else if( IsElement( "vcount"))
+            {
+                if( !mReader->isEmptyElement())
+                {
+                    if (numPrimitives)  // It is possible to define a mesh without any primitives
+                    {
+                        // case <polylist> - specifies the number of indices for each polygon
+                        const char* content = GetTextContent();
+                        vcount.reserve( numPrimitives);
+                        for( unsigned int a = 0; a < numPrimitives; a++)
+                        {
+                            if( *content == 0)
+                                ThrowException( "Expected more values while reading <vcount> contents.");
+                            // read a number
+                            vcount.push_back( (size_t) strtoul10( content, &content));
+                            // skip whitespace after it
+                            SkipSpacesAndLineEnd( &content);
+                        }
+                    }
+
+                    TestClosing( "vcount");
+                }
+            }
+            else if( IsElement( "p"))
+            {
+                if( !mReader->isEmptyElement())
+                {
+                    // now here the actual fun starts - these are the indices to construct the mesh data from
+                    actualPrimitives += ReadPrimitives(pMesh, perIndexData, numPrimitives, vcount, primType);
+                }
+            }
+            else if (IsElement("extra"))
+            {
+                SkipElement("extra");
+            } else if ( IsElement("ph")) {                
+                SkipElement("ph");
+            } else {
+                ThrowException( format() << "Unexpected sub element <" << mReader->getNodeName() << "> in tag <" << elementName << ">" );
+            }
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
+        {
+            if( mReader->getNodeName() != elementName)
+                ThrowException( format() << "Expected end of <" << elementName << "> element." );
+
+            break;
+        }
+    }
+
+#ifdef ASSIMP_BUILD_DEBUG
+	if (primType != Prim_TriFans && primType != Prim_TriStrips && primType != Prim_LineStrip &&
+        primType != Prim_Lines) { // this is ONLY to workaround a bug in SketchUp 15.3.331 where it writes the wrong 'count' when it writes out the 'lines'.
+        ai_assert(actualPrimitives == numPrimitives);
+    }
+#endif
+
+    // only when we're done reading all <p> tags (and thus know the final vertex count) can we commit the submesh
+    subgroup.mNumFaces = actualPrimitives;
+    pMesh->mSubMeshes.push_back(subgroup);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a single input channel element and stores it in the given array, if valid
+void ColladaParser::ReadInputChannel( std::vector<InputChannel>& poChannels)
+{
+    InputChannel channel;
+
+    // read semantic
+    int attrSemantic = GetAttribute( "semantic");
+    std::string semantic = mReader->getAttributeValue( attrSemantic);
+    channel.mType = GetTypeForSemantic( semantic);
+
+    // read source
+    int attrSource = GetAttribute( "source");
+    const char* source = mReader->getAttributeValue( attrSource);
+    if( source[0] != '#')
+        ThrowException( format() << "Unknown reference format in url \"" << source << "\" in source attribute of <input> element." );
+    channel.mAccessor = source+1; // skipping the leading #, hopefully the remaining text is the accessor ID only
+
+    // read index offset, if per-index <input>
+    int attrOffset = TestAttribute( "offset");
+    if( attrOffset > -1)
+        channel.mOffset = mReader->getAttributeValueAsInt( attrOffset);
+
+    // read set if texture coordinates
+    if(channel.mType == IT_Texcoord || channel.mType == IT_Color){
+        int attrSet = TestAttribute("set");
+        if(attrSet > -1){
+            attrSet = mReader->getAttributeValueAsInt( attrSet);
+            if(attrSet < 0)
+                ThrowException( format() << "Invalid index \"" << (attrSet) << "\" in set attribute of <input> element" );
+
+            channel.mIndex = attrSet;
+        }
+    }
+
+    // store, if valid type
+    if( channel.mType != IT_Invalid)
+        poChannels.push_back( channel);
+
+    // skip remaining stuff of this element, if any
+    SkipElement();
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a <p> primitive index list and assembles the mesh data into the given mesh
+size_t ColladaParser::ReadPrimitives( Mesh* pMesh, std::vector<InputChannel>& pPerIndexChannels,
+    size_t pNumPrimitives, const std::vector<size_t>& pVCount, PrimitiveType pPrimType)
+{
+    // determine number of indices coming per vertex
+    // find the offset index for all per-vertex channels
+    size_t numOffsets = 1;
+    size_t perVertexOffset = SIZE_MAX; // invalid value
+    for( const InputChannel& channel : pPerIndexChannels)
+    {
+        numOffsets = std::max( numOffsets, channel.mOffset+1);
+        if( channel.mType == IT_Vertex)
+            perVertexOffset = channel.mOffset;
+    }
+
+    // determine the expected number of indices
+    size_t expectedPointCount = 0;
+    switch( pPrimType)
+    {
+        case Prim_Polylist:
+        {
+            for( size_t i : pVCount)
+                expectedPointCount += i;
+            break;
+        }
+        case Prim_Lines:
+            expectedPointCount = 2 * pNumPrimitives;
+            break;
+        case Prim_Triangles:
+            expectedPointCount = 3 * pNumPrimitives;
+            break;
+        default:
+            // other primitive types don't state the index count upfront... we need to guess
+            break;
+    }
+
+    // and read all indices into a temporary array
+    std::vector<size_t> indices;
+    if( expectedPointCount > 0)
+        indices.reserve( expectedPointCount * numOffsets);
+
+    if (pNumPrimitives > 0) // It is possible to not contain any indices
+    {
+        const char* content = GetTextContent();
+        while( *content != 0)
+        {
+            // read a value.
+            // Hack: (thom) Some exporters put negative indices sometimes. We just try to carry on anyways.
+            int value = std::max( 0, strtol10( content, &content));
+            indices.push_back( size_t( value));
+            // skip whitespace after it
+            SkipSpacesAndLineEnd( &content);
+        }
+    }
+
+	// complain if the index count doesn't fit
+    if( expectedPointCount > 0 && indices.size() != expectedPointCount * numOffsets) {
+        if (pPrimType == Prim_Lines) {
+            // HACK: We just fix this number since SketchUp 15.3.331 writes the wrong 'count' for 'lines'
+            ReportWarning( "Expected different index count in <p> element, %zu instead of %zu.", indices.size(), expectedPointCount * numOffsets);
+            pNumPrimitives = (indices.size() / numOffsets) / 2;
+        } else
+            ThrowException( "Expected different index count in <p> element.");
+
+    } else if( expectedPointCount == 0 && (indices.size() % numOffsets) != 0)
+		ThrowException( "Expected different index count in <p> element.");
+
+	// find the data for all sources
+    for( std::vector<InputChannel>::iterator it = pMesh->mPerVertexData.begin(); it != pMesh->mPerVertexData.end(); ++it)
+    {
+        InputChannel& input = *it;
+        if( input.mResolved)
+            continue;
+
+        // find accessor
+        input.mResolved = &ResolveLibraryReference( mAccessorLibrary, input.mAccessor);
+        // resolve accessor's data pointer as well, if necessary
+        const Accessor* acc = input.mResolved;
+        if( !acc->mData)
+            acc->mData = &ResolveLibraryReference( mDataLibrary, acc->mSource);
+    }
+    // and the same for the per-index channels
+    for( std::vector<InputChannel>::iterator it = pPerIndexChannels.begin(); it != pPerIndexChannels.end(); ++it)
+    {
+        InputChannel& input = *it;
+        if( input.mResolved)
+            continue;
+
+        // ignore vertex pointer, it doesn't refer to an accessor
+        if( input.mType == IT_Vertex)
+        {
+            // warn if the vertex channel does not refer to the <vertices> element in the same mesh
+            if( input.mAccessor != pMesh->mVertexID)
+                ThrowException( "Unsupported vertex referencing scheme.");
+            continue;
+        }
+
+        // find accessor
+        input.mResolved = &ResolveLibraryReference( mAccessorLibrary, input.mAccessor);
+        // resolve accessor's data pointer as well, if necessary
+        const Accessor* acc = input.mResolved;
+        if( !acc->mData)
+            acc->mData = &ResolveLibraryReference( mDataLibrary, acc->mSource);
+    }
+
+    // For continued primitives, the given count does not come all in one <p>, but only one primitive per <p>
+    size_t numPrimitives = pNumPrimitives;
+    if( pPrimType == Prim_TriFans || pPrimType == Prim_Polygon)
+        numPrimitives = 1;
+    // For continued primitives, the given count is actually the number of <p>'s inside the parent tag
+    if ( pPrimType == Prim_TriStrips){
+        size_t numberOfVertices = indices.size() / numOffsets;
+        numPrimitives = numberOfVertices - 2;
+    }
+    if (pPrimType == Prim_LineStrip) {
+        size_t numberOfVertices = indices.size() / numOffsets;
+        numPrimitives = numberOfVertices - 1;
+    }
+
+    pMesh->mFaceSize.reserve( numPrimitives);
+    pMesh->mFacePosIndices.reserve( indices.size() / numOffsets);
+
+    size_t polylistStartVertex = 0;
+    for (size_t currentPrimitive = 0; currentPrimitive < numPrimitives; currentPrimitive++)
+    {
+        // determine number of points for this primitive
+        size_t numPoints = 0;
+        switch( pPrimType)
+        {
+            case Prim_Lines:
+                numPoints = 2;
+                for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++)
+                    CopyVertex(currentVertex, numOffsets, numPoints, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
+                break;
+            case Prim_LineStrip:
+                numPoints = 2;
+                for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++)
+                    CopyVertex(currentVertex, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
+                break;
+            case Prim_Triangles:
+                numPoints = 3;
+                for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++)
+                    CopyVertex(currentVertex, numOffsets, numPoints, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
+                break;
+            case Prim_TriStrips:
+                numPoints = 3;
+                ReadPrimTriStrips(numOffsets, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
+                break;
+            case Prim_Polylist:
+                numPoints = pVCount[currentPrimitive];
+                for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++)
+                    CopyVertex(polylistStartVertex + currentVertex, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, 0, indices);
+                polylistStartVertex += numPoints;
+                break;
+            case Prim_TriFans:
+            case Prim_Polygon:
+                numPoints = indices.size() / numOffsets;
+                for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++)
+                    CopyVertex(currentVertex, numOffsets, numPoints, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
+                break;
+            default:
+                // LineStrip is not supported due to expected index unmangling
+                ThrowException( "Unsupported primitive type.");
+                break;
+        }
+
+        // store the face size to later reconstruct the face from
+        pMesh->mFaceSize.push_back( numPoints);
+    }
+
+    // if I ever get my hands on that guy who invented this steaming pile of indirection...
+    TestClosing( "p");
+    return numPrimitives;
+}
+
+///@note This function willn't work correctly if both PerIndex and PerVertex channels have same channels.
+///For example if TEXCOORD present in both <vertices> and <polylist> tags this function will create wrong uv coordinates.
+///It's not clear from COLLADA documentation is this allowed or not. For now only exporter fixed to avoid such behavior
+void ColladaParser::CopyVertex(size_t currentVertex, size_t numOffsets, size_t numPoints, size_t perVertexOffset, Mesh* pMesh, std::vector<InputChannel>& pPerIndexChannels, size_t currentPrimitive, const std::vector<size_t>& indices){
+    // calculate the base offset of the vertex whose attributes we ant to copy
+    size_t baseOffset = currentPrimitive * numOffsets * numPoints + currentVertex * numOffsets;
+
+    // don't overrun the boundaries of the index list
+    ai_assert((baseOffset + numOffsets - 1) < indices.size());
+
+    // extract per-vertex channels using the global per-vertex offset
+    for (std::vector<InputChannel>::iterator it = pMesh->mPerVertexData.begin(); it != pMesh->mPerVertexData.end(); ++it)
+        ExtractDataObjectFromChannel(*it, indices[baseOffset + perVertexOffset], pMesh);
+    // and extract per-index channels using there specified offset
+    for (std::vector<InputChannel>::iterator it = pPerIndexChannels.begin(); it != pPerIndexChannels.end(); ++it)
+        ExtractDataObjectFromChannel(*it, indices[baseOffset + it->mOffset], pMesh);
+
+    // store the vertex-data index for later assignment of bone vertex weights
+    pMesh->mFacePosIndices.push_back(indices[baseOffset + perVertexOffset]);
+}
+
+void ColladaParser::ReadPrimTriStrips(size_t numOffsets, size_t perVertexOffset, Mesh* pMesh, std::vector<InputChannel>& pPerIndexChannels, size_t currentPrimitive, const std::vector<size_t>& indices){
+    if (currentPrimitive % 2 != 0){
+        //odd tristrip triangles need their indices mangled, to preserve winding direction
+        CopyVertex(1, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
+        CopyVertex(0, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
+        CopyVertex(2, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
+    }
+    else {//for non tristrips or even tristrip triangles
+        CopyVertex(0, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
+        CopyVertex(1, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
+        CopyVertex(2, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Extracts a single object from an input channel and stores it in the appropriate mesh data array
+void ColladaParser::ExtractDataObjectFromChannel( const InputChannel& pInput, size_t pLocalIndex, Mesh* pMesh)
+{
+    // ignore vertex referrer - we handle them that separate
+    if( pInput.mType == IT_Vertex)
+        return;
+
+    const Accessor& acc = *pInput.mResolved;
+    if( pLocalIndex >= acc.mCount)
+        ThrowException( format() << "Invalid data index (" << pLocalIndex << "/" << acc.mCount << ") in primitive specification" );
+
+    // get a pointer to the start of the data object referred to by the accessor and the local index
+    const ai_real* dataObject = &(acc.mData->mValues[0]) + acc.mOffset + pLocalIndex* acc.mStride;
+
+    // assemble according to the accessors component sub-offset list. We don't care, yet,
+    // what kind of object exactly we're extracting here
+    ai_real obj[4];
+    for( size_t c = 0; c < 4; ++c)
+        obj[c] = dataObject[acc.mSubOffset[c]];
+
+    // now we reinterpret it according to the type we're reading here
+    switch( pInput.mType)
+    {
+        case IT_Position: // ignore all position streams except 0 - there can be only one position
+            if( pInput.mIndex == 0)
+                pMesh->mPositions.push_back( aiVector3D( obj[0], obj[1], obj[2]));
+            else
+                ASSIMP_LOG_ERROR("Collada: just one vertex position stream supported");
+            break;
+        case IT_Normal:
+            // pad to current vertex count if necessary
+            if( pMesh->mNormals.size() < pMesh->mPositions.size()-1)
+                pMesh->mNormals.insert( pMesh->mNormals.end(), pMesh->mPositions.size() - pMesh->mNormals.size() - 1, aiVector3D( 0, 1, 0));
+
+            // ignore all normal streams except 0 - there can be only one normal
+            if( pInput.mIndex == 0)
+                pMesh->mNormals.push_back( aiVector3D( obj[0], obj[1], obj[2]));
+            else
+                ASSIMP_LOG_ERROR("Collada: just one vertex normal stream supported");
+            break;
+        case IT_Tangent:
+            // pad to current vertex count if necessary
+            if( pMesh->mTangents.size() < pMesh->mPositions.size()-1)
+                pMesh->mTangents.insert( pMesh->mTangents.end(), pMesh->mPositions.size() - pMesh->mTangents.size() - 1, aiVector3D( 1, 0, 0));
+
+            // ignore all tangent streams except 0 - there can be only one tangent
+            if( pInput.mIndex == 0)
+                pMesh->mTangents.push_back( aiVector3D( obj[0], obj[1], obj[2]));
+            else
+                ASSIMP_LOG_ERROR("Collada: just one vertex tangent stream supported");
+            break;
+        case IT_Bitangent:
+            // pad to current vertex count if necessary
+            if( pMesh->mBitangents.size() < pMesh->mPositions.size()-1)
+                pMesh->mBitangents.insert( pMesh->mBitangents.end(), pMesh->mPositions.size() - pMesh->mBitangents.size() - 1, aiVector3D( 0, 0, 1));
+
+            // ignore all bitangent streams except 0 - there can be only one bitangent
+            if( pInput.mIndex == 0)
+                pMesh->mBitangents.push_back( aiVector3D( obj[0], obj[1], obj[2]));
+            else
+                ASSIMP_LOG_ERROR("Collada: just one vertex bitangent stream supported");
+            break;
+        case IT_Texcoord:
+            // up to 4 texture coord sets are fine, ignore the others
+            if( pInput.mIndex < AI_MAX_NUMBER_OF_TEXTURECOORDS)
+            {
+                // pad to current vertex count if necessary
+                if( pMesh->mTexCoords[pInput.mIndex].size() < pMesh->mPositions.size()-1)
+                    pMesh->mTexCoords[pInput.mIndex].insert( pMesh->mTexCoords[pInput.mIndex].end(),
+                        pMesh->mPositions.size() - pMesh->mTexCoords[pInput.mIndex].size() - 1, aiVector3D( 0, 0, 0));
+
+                pMesh->mTexCoords[pInput.mIndex].push_back( aiVector3D( obj[0], obj[1], obj[2]));
+                if (0 != acc.mSubOffset[2] || 0 != acc.mSubOffset[3]) /* hack ... consider cleaner solution */
+                    pMesh->mNumUVComponents[pInput.mIndex]=3;
+            }   else
+            {
+                ASSIMP_LOG_ERROR("Collada: too many texture coordinate sets. Skipping.");
+            }
+            break;
+        case IT_Color:
+            // up to 4 color sets are fine, ignore the others
+            if( pInput.mIndex < AI_MAX_NUMBER_OF_COLOR_SETS)
+            {
+                // pad to current vertex count if necessary
+                if( pMesh->mColors[pInput.mIndex].size() < pMesh->mPositions.size()-1)
+                    pMesh->mColors[pInput.mIndex].insert( pMesh->mColors[pInput.mIndex].end(),
+                        pMesh->mPositions.size() - pMesh->mColors[pInput.mIndex].size() - 1, aiColor4D( 0, 0, 0, 1));
+
+                aiColor4D result(0, 0, 0, 1);
+                for (size_t i = 0; i < pInput.mResolved->mSize; ++i)
+                {
+                    result[static_cast<unsigned int>(i)] = obj[pInput.mResolved->mSubOffset[i]];
+                }
+                pMesh->mColors[pInput.mIndex].push_back(result);
+            } else
+            {
+                ASSIMP_LOG_ERROR("Collada: too many vertex color sets. Skipping.");
+            }
+
+            break;
+        default:
+            // IT_Invalid and IT_Vertex
+            ai_assert(false && "shouldn't ever get here");
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the library of node hierarchies and scene parts
+void ColladaParser::ReadSceneLibrary()
+{
+    if( mReader->isEmptyElement())
+        return;
+
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT)
+        {
+            // a visual scene - generate root node under its ID and let ReadNode() do the recursive work
+            if( IsElement( "visual_scene"))
+            {
+                // read ID. Is optional according to the spec, but how on earth should a scene_instance refer to it then?
+                int indexID = GetAttribute( "id");
+                const char* attrID = mReader->getAttributeValue( indexID);
+
+                // read name if given.
+                int indexName = TestAttribute( "name");
+                const char* attrName = "unnamed";
+                if( indexName > -1)
+                    attrName = mReader->getAttributeValue( indexName);
+
+                // create a node and store it in the library under its ID
+                Node* node = new Node;
+                node->mID = attrID;
+                node->mName = attrName;
+                mNodeLibrary[node->mID] = node;
+
+                ReadSceneNode( node);
+            } else
+            {
+                // ignore the rest
+                SkipElement();
+            }
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
+        {
+            if( strcmp( mReader->getNodeName(), "library_visual_scenes") == 0)
+                //ThrowException( "Expected end of \"library_visual_scenes\" element.");
+
+            break;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a scene node's contents including children and stores it in the given node
+void ColladaParser::ReadSceneNode( Node* pNode)
+{
+    // quit immediately on <bla/> elements
+    if( mReader->isEmptyElement())
+        return;
+
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT)
+        {
+            if( IsElement( "node"))
+            {
+                Node* child = new Node;
+                int attrID = TestAttribute( "id");
+                if( attrID > -1)
+                    child->mID = mReader->getAttributeValue( attrID);
+                int attrSID = TestAttribute( "sid");
+                if( attrSID > -1)
+                    child->mSID = mReader->getAttributeValue( attrSID);
+
+                int attrName = TestAttribute( "name");
+                if( attrName > -1)
+                    child->mName = mReader->getAttributeValue( attrName);
+
+                // TODO: (thom) support SIDs
+                // ai_assert( TestAttribute( "sid") == -1);
+
+                if (pNode)
+                {
+                    pNode->mChildren.push_back( child);
+                    child->mParent = pNode;
+                }
+                else
+                {
+                    // no parent node given, probably called from <library_nodes> element.
+                    // create new node in node library
+                    mNodeLibrary[child->mID] = child;
+                }
+
+                // read on recursively from there
+                ReadSceneNode( child);
+                continue;
+            }
+            // For any further stuff we need a valid node to work on
+            else if (!pNode)
+                continue;
+
+            if( IsElement( "lookat"))
+                ReadNodeTransformation( pNode, TF_LOOKAT);
+            else if( IsElement( "matrix"))
+                ReadNodeTransformation( pNode, TF_MATRIX);
+            else if( IsElement( "rotate"))
+                ReadNodeTransformation( pNode, TF_ROTATE);
+            else if( IsElement( "scale"))
+                ReadNodeTransformation( pNode, TF_SCALE);
+            else if( IsElement( "skew"))
+                ReadNodeTransformation( pNode, TF_SKEW);
+            else if( IsElement( "translate"))
+                ReadNodeTransformation( pNode, TF_TRANSLATE);
+            else if( IsElement( "render") && pNode->mParent == NULL && 0 == pNode->mPrimaryCamera.length())
+            {
+                // ... scene evaluation or, in other words, postprocessing pipeline,
+                // or, again in other words, a turing-complete description how to
+                // render a Collada scene. The only thing that is interesting for
+                // us is the primary camera.
+                int attrId = TestAttribute("camera_node");
+                if (-1 != attrId)
+                {
+                    const char* s = mReader->getAttributeValue(attrId);
+                    if (s[0] != '#')
+                        ASSIMP_LOG_ERROR("Collada: Unresolved reference format of camera");
+                    else
+                        pNode->mPrimaryCamera = s+1;
+                }
+            }
+            else if( IsElement( "instance_node"))
+            {
+                // find the node in the library
+                int attrID = TestAttribute( "url");
+                if( attrID != -1)
+                {
+                    const char* s = mReader->getAttributeValue(attrID);
+                    if (s[0] != '#')
+                        ASSIMP_LOG_ERROR("Collada: Unresolved reference format of node");
+                    else
+                    {
+                        pNode->mNodeInstances.push_back(NodeInstance());
+                        pNode->mNodeInstances.back().mNode = s+1;
+                    }
+                }
+            }
+            else if( IsElement( "instance_geometry") || IsElement( "instance_controller"))
+            {
+                // Reference to a mesh or controller, with possible material associations
+                ReadNodeGeometry( pNode);
+            }
+            else if( IsElement( "instance_light"))
+            {
+                // Reference to a light, name given in 'url' attribute
+                int attrID = TestAttribute("url");
+                if (-1 == attrID)
+                    ASSIMP_LOG_WARN("Collada: Expected url attribute in <instance_light> element");
+                else
+                {
+                    const char* url = mReader->getAttributeValue( attrID);
+                    if( url[0] != '#')
+                        ThrowException( "Unknown reference format in <instance_light> element");
+
+                    pNode->mLights.push_back(LightInstance());
+                    pNode->mLights.back().mLight = url+1;
+                }
+            }
+            else if( IsElement( "instance_camera"))
+            {
+                // Reference to a camera, name given in 'url' attribute
+                int attrID = TestAttribute("url");
+                if (-1 == attrID)
+                    ASSIMP_LOG_WARN("Collada: Expected url attribute in <instance_camera> element");
+                else
+                {
+                    const char* url = mReader->getAttributeValue( attrID);
+                    if( url[0] != '#')
+                        ThrowException( "Unknown reference format in <instance_camera> element");
+
+                    pNode->mCameras.push_back(CameraInstance());
+                    pNode->mCameras.back().mCamera = url+1;
+                }
+            }
+            else
+            {
+                // skip everything else for the moment
+                SkipElement();
+            }
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) {
+            break;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a node transformation entry of the given type and adds it to the given node's transformation list.
+void ColladaParser::ReadNodeTransformation( Node* pNode, TransformType pType)
+{
+    if( mReader->isEmptyElement())
+        return;
+
+    std::string tagName = mReader->getNodeName();
+
+    Transform tf;
+    tf.mType = pType;
+
+    // read SID
+    int indexSID = TestAttribute( "sid");
+    if( indexSID >= 0)
+        tf.mID = mReader->getAttributeValue( indexSID);
+
+    // how many parameters to read per transformation type
+    static const unsigned int sNumParameters[] = { 9, 4, 3, 3, 7, 16 };
+    const char* content = GetTextContent();
+
+    // read as many parameters and store in the transformation
+    for( unsigned int a = 0; a < sNumParameters[pType]; a++)
+    {
+        // read a number
+        content = fast_atoreal_move<ai_real>( content, tf.f[a]);
+        // skip whitespace after it
+        SkipSpacesAndLineEnd( &content);
+    }
+
+    // place the transformation at the queue of the node
+    pNode->mTransforms.push_back( tf);
+
+    // and consume the closing tag
+    TestClosing( tagName.c_str());
+}
+
+// ------------------------------------------------------------------------------------------------
+// Processes bind_vertex_input and bind elements
+void ColladaParser::ReadMaterialVertexInputBinding( Collada::SemanticMappingTable& tbl)
+{
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT) {
+            if( IsElement( "bind_vertex_input"))
+            {
+                Collada::InputSemanticMapEntry vn;
+
+                // effect semantic
+                int n = GetAttribute("semantic");
+                std::string s = mReader->getAttributeValue(n);
+
+                // input semantic
+                n = GetAttribute("input_semantic");
+                vn.mType = GetTypeForSemantic( mReader->getAttributeValue(n) );
+
+                // index of input set
+                n = TestAttribute("input_set");
+                if (-1 != n)
+                    vn.mSet = mReader->getAttributeValueAsInt(n);
+
+                tbl.mMap[s] = vn;
+            }
+            else if( IsElement( "bind")) {
+                ASSIMP_LOG_WARN("Collada: Found unsupported <bind> element");
+            }
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END)    {
+            if( strcmp( mReader->getNodeName(), "instance_material") == 0)
+                break;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a mesh reference in a node and adds it to the node's mesh list
+void ColladaParser::ReadNodeGeometry( Node* pNode)
+{
+    // referred mesh is given as an attribute of the <instance_geometry> element
+    int attrUrl = GetAttribute( "url");
+    const char* url = mReader->getAttributeValue( attrUrl);
+    if( url[0] != '#')
+        ThrowException( "Unknown reference format");
+
+    Collada::MeshInstance instance;
+    instance.mMeshOrController = url+1; // skipping the leading #
+
+    if( !mReader->isEmptyElement())
+    {
+        // read material associations. Ignore additional elements in between
+        while( mReader->read())
+        {
+            if( mReader->getNodeType() == irr::io::EXN_ELEMENT)
+            {
+                if( IsElement( "instance_material"))
+                {
+                    // read ID of the geometry subgroup and the target material
+                    int attrGroup = GetAttribute( "symbol");
+                    std::string group = mReader->getAttributeValue( attrGroup);
+                    int attrMaterial = GetAttribute( "target");
+                    const char* urlMat = mReader->getAttributeValue( attrMaterial);
+                    Collada::SemanticMappingTable s;
+                    if( urlMat[0] == '#')
+                        urlMat++;
+
+                    s.mMatName = urlMat;
+
+                    // resolve further material details + THIS UGLY AND NASTY semantic mapping stuff
+                    if( !mReader->isEmptyElement())
+                        ReadMaterialVertexInputBinding(s);
+
+                    // store the association
+                    instance.mMaterials[group] = s;
+                }
+            }
+            else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
+            {
+                if( strcmp( mReader->getNodeName(), "instance_geometry") == 0
+                    || strcmp( mReader->getNodeName(), "instance_controller") == 0)
+                    break;
+            }
+        }
+    }
+
+    // store it
+    pNode->mMeshes.push_back( instance);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the collada scene
+void ColladaParser::ReadScene()
+{
+    if( mReader->isEmptyElement())
+        return;
+
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT) {
+            if( IsElement( "instance_visual_scene"))
+            {
+                // should be the first and only occurrence
+                if( mRootNode)
+                    ThrowException( "Invalid scene containing multiple root nodes in <instance_visual_scene> element");
+
+                // read the url of the scene to instance. Should be of format "#some_name"
+                int urlIndex = GetAttribute( "url");
+                const char* url = mReader->getAttributeValue( urlIndex);
+                if( url[0] != '#')
+                    ThrowException( "Unknown reference format in <instance_visual_scene> element");
+
+                // find the referred scene, skip the leading #
+                NodeLibrary::const_iterator sit = mNodeLibrary.find( url+1);
+                if( sit == mNodeLibrary.end())
+                    ThrowException( "Unable to resolve visual_scene reference \"" + std::string(url) + "\" in <instance_visual_scene> element.");
+                mRootNode = sit->second;
+            } else  {
+                SkipElement();
+            }
+        }
+        else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END){
+            break;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Aborts the file reading with an exception
+AI_WONT_RETURN void ColladaParser::ThrowException( const std::string& pError) const
+{
+    throw DeadlyImportError( format() << "Collada: " << mFileName << " - " << pError );
+}
+void ColladaParser::ReportWarning(const char* msg,...)
+{
+    ai_assert(NULL != msg);
+
+    va_list args;
+    va_start(args,msg);
+
+    char szBuffer[3000];
+    const int iLen = vsprintf(szBuffer,msg,args);
+    ai_assert(iLen > 0);
+
+    va_end(args);
+    ASSIMP_LOG_WARN_F("Validation warning: ", std::string(szBuffer,iLen));
+}
+
+// ------------------------------------------------------------------------------------------------
+// Skips all data until the end node of the current element
+void ColladaParser::SkipElement()
+{
+    // nothing to skip if it's an <element />
+    if( mReader->isEmptyElement())
+        return;
+
+    // reroute
+    SkipElement( mReader->getNodeName());
+}
+
+// ------------------------------------------------------------------------------------------------
+// Skips all data until the end node of the given element
+void ColladaParser::SkipElement( const char* pElement)
+{
+    // copy the current node's name because it'a pointer to the reader's internal buffer,
+    // which is going to change with the upcoming parsing
+    std::string element = pElement;
+    while( mReader->read())
+    {
+        if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
+            if( mReader->getNodeName() == element)
+                break;
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Tests for an opening element of the given name, throws an exception if not found
+void ColladaParser::TestOpening( const char* pName)
+{
+    // read element start
+    if( !mReader->read())
+        ThrowException( format() << "Unexpected end of file while beginning of <" << pName << "> element." );
+    // whitespace in front is ok, just read again if found
+    if( mReader->getNodeType() == irr::io::EXN_TEXT)
+        if( !mReader->read())
+            ThrowException( format() << "Unexpected end of file while reading beginning of <" << pName << "> element." );
+
+    if( mReader->getNodeType() != irr::io::EXN_ELEMENT || strcmp( mReader->getNodeName(), pName) != 0)
+        ThrowException( format() << "Expected start of <" << pName << "> element." );
+}
+
+// ------------------------------------------------------------------------------------------------
+// Tests for the closing tag of the given element, throws an exception if not found
+void ColladaParser::TestClosing( const char* pName)
+{
+    // check if we're already on the closing tag and return right away
+    if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END && strcmp( mReader->getNodeName(), pName) == 0)
+        return;
+
+    // if not, read some more
+    if( !mReader->read())
+        ThrowException( format() << "Unexpected end of file while reading end of <" << pName << "> element." );
+    // whitespace in front is ok, just read again if found
+    if( mReader->getNodeType() == irr::io::EXN_TEXT)
+        if( !mReader->read())
+            ThrowException( format() << "Unexpected end of file while reading end of <" << pName << "> element." );
+
+    // but this has the be the closing tag, or we're lost
+    if( mReader->getNodeType() != irr::io::EXN_ELEMENT_END || strcmp( mReader->getNodeName(), pName) != 0)
+        ThrowException( format() << "Expected end of <" << pName << "> element." );
+}
+
+// ------------------------------------------------------------------------------------------------
+// Returns the index of the named attribute or -1 if not found. Does not throw, therefore useful for optional attributes
+int ColladaParser::GetAttribute( const char* pAttr) const
+{
+    int index = TestAttribute( pAttr);
+    if( index != -1)
+        return index;
+
+    // attribute not found -> throw an exception
+    ThrowException( format() << "Expected attribute \"" << pAttr << "\" for element <" << mReader->getNodeName() << ">." );
+    return -1;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Tests the present element for the presence of one attribute, returns its index or throws an exception if not found
+int ColladaParser::TestAttribute( const char* pAttr) const
+{
+    for( int a = 0; a < mReader->getAttributeCount(); a++)
+        if( strcmp( mReader->getAttributeName( a), pAttr) == 0)
+            return a;
+
+    return -1;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the text contents of an element, throws an exception if not given. Skips leading whitespace.
+const char* ColladaParser::GetTextContent()
+{
+    const char* sz = TestTextContent();
+    if(!sz) {
+        ThrowException( "Invalid contents in element \"n\".");
+    }
+    return sz;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the text contents of an element, returns NULL if not given. Skips leading whitespace.
+const char* ColladaParser::TestTextContent()
+{
+    // present node should be the beginning of an element
+    if( mReader->getNodeType() != irr::io::EXN_ELEMENT || mReader->isEmptyElement())
+        return NULL;
+
+    // read contents of the element
+    if( !mReader->read() )
+        return NULL;
+    if( mReader->getNodeType() != irr::io::EXN_TEXT && mReader->getNodeType() != irr::io::EXN_CDATA)
+        return NULL;
+
+    // skip leading whitespace
+    const char* text = mReader->getNodeData();
+    SkipSpacesAndLineEnd( &text);
+
+    return text;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Calculates the resulting transformation fromm all the given transform steps
+aiMatrix4x4 ColladaParser::CalculateResultTransform( const std::vector<Transform>& pTransforms) const
+{
+    aiMatrix4x4 res;
+
+    for( std::vector<Transform>::const_iterator it = pTransforms.begin(); it != pTransforms.end(); ++it)
+    {
+        const Transform& tf = *it;
+        switch( tf.mType)
+        {
+            case TF_LOOKAT:
+      {
+        aiVector3D pos( tf.f[0], tf.f[1], tf.f[2]);
+        aiVector3D dstPos( tf.f[3], tf.f[4], tf.f[5]);
+        aiVector3D up = aiVector3D( tf.f[6], tf.f[7], tf.f[8]).Normalize();
+        aiVector3D dir = aiVector3D( dstPos - pos).Normalize();
+        aiVector3D right = (dir ^ up).Normalize();
+
+        res *= aiMatrix4x4(
+          right.x, up.x, -dir.x, pos.x,
+          right.y, up.y, -dir.y, pos.y,
+          right.z, up.z, -dir.z, pos.z,
+          0, 0, 0, 1);
+                break;
+      }
+            case TF_ROTATE:
+            {
+                aiMatrix4x4 rot;
+                ai_real angle = tf.f[3] * ai_real( AI_MATH_PI) / ai_real( 180.0 );
+                aiVector3D axis( tf.f[0], tf.f[1], tf.f[2]);
+                aiMatrix4x4::Rotation( angle, axis, rot);
+                res *= rot;
+                break;
+            }
+            case TF_TRANSLATE:
+            {
+                aiMatrix4x4 trans;
+                aiMatrix4x4::Translation( aiVector3D( tf.f[0], tf.f[1], tf.f[2]), trans);
+                res *= trans;
+                break;
+            }
+            case TF_SCALE:
+            {
+                aiMatrix4x4 scale( tf.f[0], 0.0f, 0.0f, 0.0f, 0.0f, tf.f[1], 0.0f, 0.0f, 0.0f, 0.0f, tf.f[2], 0.0f,
+                    0.0f, 0.0f, 0.0f, 1.0f);
+                res *= scale;
+                break;
+            }
+            case TF_SKEW:
+                // TODO: (thom)
+                ai_assert( false);
+                break;
+            case TF_MATRIX:
+            {
+                aiMatrix4x4 mat( tf.f[0], tf.f[1], tf.f[2], tf.f[3], tf.f[4], tf.f[5], tf.f[6], tf.f[7],
+                    tf.f[8], tf.f[9], tf.f[10], tf.f[11], tf.f[12], tf.f[13], tf.f[14], tf.f[15]);
+                res *= mat;
+                break;
+            }
+            default:
+                ai_assert( false);
+                break;
+        }
+    }
+
+    return res;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Determines the input data type for the given semantic string
+Collada::InputType ColladaParser::GetTypeForSemantic( const std::string& semantic)
+{
+    if ( semantic.empty() ) {
+        ASSIMP_LOG_WARN("Vertex input type is empty." );
+        return IT_Invalid;
+    }
+
+    if( semantic == "POSITION")
+        return IT_Position;
+    else if( semantic == "TEXCOORD")
+        return IT_Texcoord;
+    else if( semantic == "NORMAL")
+        return IT_Normal;
+    else if( semantic == "COLOR")
+        return IT_Color;
+    else if( semantic == "VERTEX")
+        return IT_Vertex;
+    else if( semantic == "BINORMAL" || semantic ==  "TEXBINORMAL")
+        return IT_Bitangent;
+    else if( semantic == "TANGENT" || semantic == "TEXTANGENT")
+        return IT_Tangent;
+
+    ASSIMP_LOG_WARN_F( "Unknown vertex input type \"", semantic, "\". Ignoring." );
+    return IT_Invalid;
+}
+
+#endif // !! ASSIMP_BUILD_NO_DAE_IMPORTER

+ 371 - 0
Engine/lib/assimp/code/ColladaParser.h

@@ -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 ColladaParser.h
+ *  @brief Defines the parser helper class for the collada loader
+ */
+
+#ifndef AI_COLLADAPARSER_H_INC
+#define AI_COLLADAPARSER_H_INC
+
+#include <assimp/irrXMLWrapper.h>
+#include "ColladaHelper.h"
+#include <assimp/ai_assert.h>
+#include <assimp/TinyFormatter.h>
+
+namespace Assimp
+{
+
+    // ------------------------------------------------------------------------------------------
+    /** Parser helper class for the Collada loader.
+     *
+     *  Does all the XML reading and builds internal data structures from it,
+     *  but leaves the resolving of all the references to the loader.
+     */
+    class ColladaParser
+    {
+        friend class ColladaLoader;
+
+    protected:
+        /** Constructor from XML file */
+        ColladaParser( IOSystem* pIOHandler, const std::string& pFile);
+
+        /** Destructor */
+        ~ColladaParser();
+
+        /** Reads the contents of the file */
+        void ReadContents();
+
+        /** Reads the structure of the file */
+        void ReadStructure();
+
+        /** Reads asset information such as coordinate system information and legal blah */
+        void ReadAssetInfo();
+
+        /** Reads the animation library */
+        void ReadAnimationLibrary();
+
+		/** Reads the animation clip library */
+		void ReadAnimationClipLibrary();
+
+        /** Unwrap controllers dependency hierarchy */
+        void PostProcessControllers();
+    
+		/** Re-build animations from animation clip library, if present, otherwise combine single-channel animations */
+		void PostProcessRootAnimations();
+
+        /** Reads an animation into the given parent structure */
+        void ReadAnimation( Collada::Animation* pParent);
+
+        /** Reads an animation sampler into the given anim channel */
+        void ReadAnimationSampler( Collada::AnimationChannel& pChannel);
+
+        /** Reads the skeleton controller library */
+        void ReadControllerLibrary();
+
+        /** Reads a controller into the given mesh structure */
+        void ReadController( Collada::Controller& pController);
+
+        /** Reads the joint definitions for the given controller */
+        void ReadControllerJoints( Collada::Controller& pController);
+
+        /** Reads the joint weights for the given controller */
+        void ReadControllerWeights( Collada::Controller& pController);
+
+        /** Reads the image library contents */
+        void ReadImageLibrary();
+
+        /** Reads an image entry into the given image */
+        void ReadImage( Collada::Image& pImage);
+
+        /** Reads the material library */
+        void ReadMaterialLibrary();
+
+        /** Reads a material entry into the given material */
+        void ReadMaterial( Collada::Material& pMaterial);
+
+        /** Reads the camera library */
+        void ReadCameraLibrary();
+
+        /** Reads a camera entry into the given camera */
+        void ReadCamera( Collada::Camera& pCamera);
+
+        /** Reads the light library */
+        void ReadLightLibrary();
+
+        /** Reads a light entry into the given light */
+        void ReadLight( Collada::Light& pLight);
+
+        /** Reads the effect library */
+        void ReadEffectLibrary();
+
+        /** Reads an effect entry into the given effect*/
+        void ReadEffect( Collada::Effect& pEffect);
+
+        /** Reads an COMMON effect profile */
+        void ReadEffectProfileCommon( Collada::Effect& pEffect);
+
+        /** Read sampler properties */
+        void ReadSamplerProperties( Collada::Sampler& pSampler);
+
+        /** Reads an effect entry containing a color or a texture defining that color */
+        void ReadEffectColor( aiColor4D& pColor, Collada::Sampler& pSampler);
+
+        /** Reads an effect entry containing a float */
+        void ReadEffectFloat( ai_real& pFloat);
+
+        /** Reads an effect parameter specification of any kind */
+        void ReadEffectParam( Collada::EffectParam& pParam);
+
+        /** Reads the geometry library contents */
+        void ReadGeometryLibrary();
+
+        /** Reads a geometry from the geometry library. */
+        void ReadGeometry( Collada::Mesh* pMesh);
+
+        /** Reads a mesh from the geometry library */
+        void ReadMesh( Collada::Mesh* pMesh);
+
+        /** Reads a source element - a combination of raw data and an accessor defining
+         * things that should not be redefinable. Yes, that's another rant.
+         */
+        void ReadSource();
+
+        /** Reads a data array holding a number of elements, and stores it in the global library.
+         * Currently supported are array of floats and arrays of strings.
+         */
+        void ReadDataArray();
+
+        /** Reads an accessor and stores it in the global library under the given ID -
+         * accessors use the ID of the parent <source> element
+         */
+        void ReadAccessor( const std::string& pID);
+
+        /** Reads input declarations of per-vertex mesh data into the given mesh */
+        void ReadVertexData( Collada::Mesh* pMesh);
+
+        /** Reads input declarations of per-index mesh data into the given mesh */
+        void ReadIndexData( Collada::Mesh* pMesh);
+
+        /** Reads a single input channel element and stores it in the given array, if valid */
+        void ReadInputChannel( std::vector<Collada::InputChannel>& poChannels);
+
+        /** Reads a <p> primitive index list and assembles the mesh data into the given mesh */
+        size_t ReadPrimitives( Collada::Mesh* pMesh, std::vector<Collada::InputChannel>& pPerIndexChannels,
+                              size_t pNumPrimitives, const std::vector<size_t>& pVCount, Collada::PrimitiveType pPrimType);
+
+        /** Copies the data for a single primitive into the mesh, based on the InputChannels */
+        void CopyVertex(size_t currentVertex, size_t numOffsets, size_t numPoints, size_t perVertexOffset,
+                        Collada::Mesh* pMesh, std::vector<Collada::InputChannel>& pPerIndexChannels,
+                        size_t currentPrimitive, const std::vector<size_t>& indices);
+
+        /** Reads one triangle of a tristrip into the mesh */
+        void ReadPrimTriStrips(size_t numOffsets, size_t perVertexOffset, Collada::Mesh* pMesh,
+                               std::vector<Collada::InputChannel>& pPerIndexChannels, size_t currentPrimitive, const std::vector<size_t>& indices);
+
+        /** Extracts a single object from an input channel and stores it in the appropriate mesh data array */
+        void ExtractDataObjectFromChannel( const Collada::InputChannel& pInput, size_t pLocalIndex, Collada::Mesh* pMesh);
+
+        /** Reads the library of node hierarchies and scene parts */
+        void ReadSceneLibrary();
+
+        /** Reads a scene node's contents including children and stores it in the given node */
+        void ReadSceneNode( Collada::Node* pNode);
+
+        /** Reads a node transformation entry of the given type and adds it to the given node's transformation list. */
+        void ReadNodeTransformation( Collada::Node* pNode, Collada::TransformType pType);
+
+        /** Reads a mesh reference in a node and adds it to the node's mesh list */
+        void ReadNodeGeometry( Collada::Node* pNode);
+
+        /** Reads the collada scene */
+        void ReadScene();
+
+        // Processes bind_vertex_input and bind elements
+        void ReadMaterialVertexInputBinding( Collada::SemanticMappingTable& tbl);
+
+    protected:
+        /** Aborts the file reading with an exception */
+        AI_WONT_RETURN void ThrowException( const std::string& pError) const AI_WONT_RETURN_SUFFIX;
+        void ReportWarning(const char* msg,...);
+
+        /** Skips all data until the end node of the current element */
+        void SkipElement();
+
+        /** Skips all data until the end node of the given element */
+        void SkipElement( const char* pElement);
+
+        /** Compares the current xml element name to the given string and returns true if equal */
+        bool IsElement( const char* pName) const;
+
+        /** Tests for the opening tag of the given element, throws an exception if not found */
+        void TestOpening( const char* pName);
+
+        /** Tests for the closing tag of the given element, throws an exception if not found */
+        void TestClosing( const char* pName);
+
+        /** Checks the present element for the presence of the attribute, returns its index
+         or throws an exception if not found */
+        int GetAttribute( const char* pAttr) const;
+
+        /** Returns the index of the named attribute or -1 if not found. Does not throw,
+         therefore useful for optional attributes */
+        int TestAttribute( const char* pAttr) const;
+
+        /** Reads the text contents of an element, throws an exception if not given.
+         Skips leading whitespace. */
+        const char* GetTextContent();
+
+        /** Reads the text contents of an element, returns NULL if not given.
+         Skips leading whitespace. */
+        const char* TestTextContent();
+
+        /** Reads a single bool from current text content */
+        bool ReadBoolFromTextContent();
+
+        /** Reads a single float from current text content */
+        ai_real ReadFloatFromTextContent();
+
+        /** Calculates the resulting transformation from all the given transform steps */
+        aiMatrix4x4 CalculateResultTransform( const std::vector<Collada::Transform>& pTransforms) const;
+
+        /** Determines the input data type for the given semantic string */
+        Collada::InputType GetTypeForSemantic( const std::string& pSemantic);
+
+        /** Finds the item in the given library by its reference, throws if not found */
+        template <typename Type> const Type& ResolveLibraryReference( const std::map<std::string, Type>& pLibrary, const std::string& pURL) const;
+
+    protected:
+        /** Filename, for a verbose error message */
+        std::string mFileName;
+
+        /** XML reader, member for everyday use */
+        irr::io::IrrXMLReader* mReader;
+
+        /** All data arrays found in the file by ID. Might be referred to by actually
+         everyone. Collada, you are a steaming pile of indirection. */
+        typedef std::map<std::string, Collada::Data> DataLibrary;
+        DataLibrary mDataLibrary;
+
+        /** Same for accessors which define how the data in a data array is accessed. */
+        typedef std::map<std::string, Collada::Accessor> AccessorLibrary;
+        AccessorLibrary mAccessorLibrary;
+
+        /** Mesh library: mesh by ID */
+        typedef std::map<std::string, Collada::Mesh*> MeshLibrary;
+        MeshLibrary mMeshLibrary;
+
+        /** node library: root node of the hierarchy part by ID */
+        typedef std::map<std::string, Collada::Node*> NodeLibrary;
+        NodeLibrary mNodeLibrary;
+
+        /** Image library: stores texture properties by ID */
+        typedef std::map<std::string, Collada::Image> ImageLibrary;
+        ImageLibrary mImageLibrary;
+
+        /** Effect library: surface attributes by ID */
+        typedef std::map<std::string, Collada::Effect> EffectLibrary;
+        EffectLibrary mEffectLibrary;
+
+        /** Material library: surface material by ID */
+        typedef std::map<std::string, Collada::Material> MaterialLibrary;
+        MaterialLibrary mMaterialLibrary;
+
+        /** Light library: surface light by ID */
+        typedef std::map<std::string, Collada::Light> LightLibrary;
+        LightLibrary mLightLibrary;
+
+        /** Camera library: surface material by ID */
+        typedef std::map<std::string, Collada::Camera> CameraLibrary;
+        CameraLibrary mCameraLibrary;
+
+        /** Controller library: joint controllers by ID */
+        typedef std::map<std::string, Collada::Controller> ControllerLibrary;
+        ControllerLibrary mControllerLibrary;
+
+		/** Animation library: animation references by ID */
+		typedef std::map<std::string, Collada::Animation*> AnimationLibrary;
+		AnimationLibrary mAnimationLibrary;
+
+		/** Animation clip library: clip animation references by ID */
+		typedef std::vector<std::pair<std::string, std::vector<std::string> > > AnimationClipLibrary;
+		AnimationClipLibrary mAnimationClipLibrary;
+
+        /** Pointer to the root node. Don't delete, it just points to one of
+         the nodes in the node library. */
+        Collada::Node* mRootNode;
+
+        /** Root animation container */
+        Collada::Animation mAnims;
+
+        /** Size unit: how large compared to a meter */
+        ai_real mUnitSize;
+
+        /** Which is the up vector */
+        enum { UP_X, UP_Y, UP_Z } mUpDirection;
+
+        /** Collada file format version */
+        Collada::FormatVersion mFormat;
+    };
+
+    // ------------------------------------------------------------------------------------------------
+    // Check for element match
+    inline bool ColladaParser::IsElement( const char* pName) const
+    {
+        ai_assert( mReader->getNodeType() == irr::io::EXN_ELEMENT);
+        return ::strcmp( mReader->getNodeName(), pName) == 0;
+    }
+
+    // ------------------------------------------------------------------------------------------------
+    // Finds the item in the given library by its reference, throws if not found
+    template <typename Type>
+    const Type& ColladaParser::ResolveLibraryReference( const std::map<std::string, Type>& pLibrary, const std::string& pURL) const
+    {
+        typename std::map<std::string, Type>::const_iterator it = pLibrary.find( pURL);
+        if( it == pLibrary.end())
+            ThrowException( Formatter::format() << "Unable to resolve library reference \"" << pURL << "\"." );
+        return it->second;
+    }
+
+} // end of namespace Assimp
+
+#endif // AI_COLLADAPARSER_H_INC

+ 506 - 0
Engine/lib/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
Engine/lib/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
Engine/lib/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
Engine/lib/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
Engine/lib/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

+ 57 - 0
Engine/lib/assimp/code/CreateAnimMesh.h

@@ -0,0 +1,57 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2017, 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 CreateAnimMesh.h
+ *  Create AnimMesh from Mesh
+ */
+#ifndef INCLUDED_AI_CREATE_ANIM_MESH_H
+#define INCLUDED_AI_CREATE_ANIM_MESH_H
+
+#include <assimp/mesh.h>
+
+namespace Assimp    {
+
+/** Create aiAnimMesh from aiMesh. */
+aiAnimMesh *aiCreateAnimMesh(const aiMesh *mesh);
+
+} // end of namespace Assimp
+#endif // INCLUDED_AI_CREATE_ANIM_MESH_H
+

+ 398 - 0
Engine/lib/assimp/code/D3MFExporter.cpp

@@ -0,0 +1,398 @@
+/*
+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_3MF_EXPORTER
+
+#include "D3MFExporter.h"
+
+#include <assimp/scene.h>
+#include <assimp/IOSystem.hpp>
+#include <assimp/IOStream.hpp>
+#include <assimp/Exporter.hpp>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/StringUtils.h>
+#include <assimp/Exceptional.h>
+
+#include "3MFXmlTags.h"
+#include "D3MFOpcPackage.h"
+
+#include <contrib/zip/src/zip.h>
+
+namespace Assimp {
+
+void ExportScene3MF( const char* pFile, IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/ ) {
+    if ( nullptr == pIOSystem ) {
+        throw DeadlyExportError( "Could not export 3MP archive: " + std::string( pFile ) );
+    }
+    D3MF::D3MFExporter myExporter( pFile, pScene );
+    if ( myExporter.validate() ) {
+        if ( pIOSystem->Exists( pFile ) ) {
+            if ( !pIOSystem->DeleteFile( pFile ) ) {
+                throw DeadlyExportError( "File exists, cannot override : " + std::string( pFile ) );
+            }
+        }
+        bool ok = myExporter.exportArchive(pFile);
+        if ( !ok ) {
+            throw DeadlyExportError( "Could not export 3MP archive: " + std::string( pFile ) );
+        }
+    }
+}
+
+namespace D3MF {
+
+D3MFExporter::D3MFExporter( const char* pFile, const aiScene* pScene )
+: mArchiveName( pFile )
+, m_zipArchive( nullptr )
+, mScene( pScene )
+, mModelOutput()
+, mRelOutput()
+, mContentOutput()
+, mBuildItems()
+, mRelations() {
+    // empty
+}
+
+D3MFExporter::~D3MFExporter() {
+    for ( size_t i = 0; i < mRelations.size(); ++i ) {
+        delete mRelations[ i ];
+    }
+    mRelations.clear();
+}
+
+bool D3MFExporter::validate() {
+    if ( mArchiveName.empty() ) {
+        return false;
+    }
+
+    if ( nullptr == mScene ) {
+        return false;
+    }
+
+    return true;
+}
+
+bool D3MFExporter::exportArchive( const char *file ) {
+    bool ok( true );
+
+    m_zipArchive = zip_open( file, ZIP_DEFAULT_COMPRESSION_LEVEL, 'w' );
+    if ( nullptr == m_zipArchive ) {
+        return false;
+    }
+
+    ok |= exportContentTypes();
+    ok |= export3DModel();
+    ok |= exportRelations();
+
+    zip_close( m_zipArchive );
+    m_zipArchive = nullptr;
+
+    return ok;
+}
+
+bool D3MFExporter::exportContentTypes() {
+    mContentOutput.clear();
+
+    mContentOutput << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
+    mContentOutput << std::endl;
+    mContentOutput << "<Types xmlns = \"http://schemas.openxmlformats.org/package/2006/content-types\">";
+    mContentOutput << std::endl;
+    mContentOutput << "<Default Extension = \"rels\" ContentType = \"application/vnd.openxmlformats-package.relationships+xml\" />";
+    mContentOutput << std::endl;
+    mContentOutput << "<Default Extension = \"model\" ContentType = \"application/vnd.ms-package.3dmanufacturing-3dmodel+xml\" />";
+    mContentOutput << std::endl;
+    mContentOutput << "</Types>";
+    mContentOutput << std::endl;
+    exportContentTyp( XmlTag::CONTENT_TYPES_ARCHIVE );
+
+    return true;
+}
+
+bool D3MFExporter::exportRelations() {
+    mRelOutput.clear();
+
+    mRelOutput << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
+    mRelOutput << std::endl;
+    mRelOutput << "<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">";
+
+    for ( size_t i = 0; i < mRelations.size(); ++i ) {
+        if ( mRelations[ i ]->target[ 0 ] == '/' ) {
+            mRelOutput << "<Relationship Target=\"" << mRelations[ i ]->target << "\" ";
+        } else {
+            mRelOutput << "<Relationship Target=\"/" << mRelations[ i ]->target << "\" ";
+        }
+        mRelOutput << "Id=\"" << mRelations[i]->id << "\" ";
+        mRelOutput << "Type=\"" << mRelations[ i ]->type << "\" />";
+        mRelOutput << std::endl;
+    }
+    mRelOutput << "</Relationships>";
+    mRelOutput << std::endl;
+
+    writeRelInfoToFile( "_rels", ".rels" );
+    mRelOutput.flush();
+
+    return true;
+}
+
+bool D3MFExporter::export3DModel() {
+    mModelOutput.clear();
+
+    writeHeader();
+    mModelOutput << "<" << XmlTag::model << " " << XmlTag::model_unit << "=\"millimeter\""
+            << "xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\">"
+            << std::endl;
+    mModelOutput << "<" << XmlTag::resources << ">";
+    mModelOutput << std::endl;
+
+    writeMetaData();
+
+    writeBaseMaterials();
+
+    writeObjects();
+
+
+    mModelOutput << "</" << XmlTag::resources << ">";
+    mModelOutput << std::endl;
+    writeBuild();
+
+    mModelOutput << "</" << XmlTag::model << ">\n";
+
+    OpcPackageRelationship *info = new OpcPackageRelationship;
+    info->id = "rel0";
+    info->target = "/3D/3DModel.model";
+    info->type = XmlTag::PACKAGE_START_PART_RELATIONSHIP_TYPE;
+    mRelations.push_back( info );
+
+    writeModelToArchive( "3D", "3DModel.model" );
+    mModelOutput.flush();
+
+    return true;
+}
+
+void D3MFExporter::writeHeader() {
+    mModelOutput << "<?xml version=\"1.0\" encoding=\"UTF - 8\"?>";
+    mModelOutput << std::endl;
+}
+
+void D3MFExporter::writeMetaData() {
+    if ( nullptr == mScene->mMetaData ) {
+        return;
+    }
+
+    const unsigned int numMetaEntries( mScene->mMetaData->mNumProperties );
+    if ( 0 == numMetaEntries ) {
+        return;
+    }
+
+	const aiString *key = nullptr;
+    const aiMetadataEntry *entry(nullptr);
+    for ( size_t i = 0; i < numMetaEntries; ++i ) {
+        mScene->mMetaData->Get( i, key, entry );
+        std::string k( key->C_Str() );
+        aiString value;
+        mScene->mMetaData->Get(  k, value );
+        mModelOutput << "<" << XmlTag::meta << " " << XmlTag::meta_name << "=\"" << key->C_Str() << "\">";
+        mModelOutput << value.C_Str();
+        mModelOutput << "</" << XmlTag::meta << ">" << std::endl;
+    }
+}
+
+void D3MFExporter::writeBaseMaterials() {
+    mModelOutput << "<basematerials id=\"1\">\n";
+    std::string strName, hexDiffuseColor , tmp;
+    for ( size_t i = 0; i < mScene->mNumMaterials; ++i ) {
+        aiMaterial *mat = mScene->mMaterials[ i ];
+        aiString name;
+        if ( mat->Get( AI_MATKEY_NAME, name ) != aiReturn_SUCCESS ) {
+            strName = "basemat_" + to_string( i );
+        } else {
+            strName = name.C_Str();
+        }
+        aiColor4D color;
+        if ( mat->Get( AI_MATKEY_COLOR_DIFFUSE, color ) == aiReturn_SUCCESS ) {
+            hexDiffuseColor.clear();
+            tmp.clear();
+            hexDiffuseColor = "#";
+            
+            tmp = DecimalToHexa( color.r );
+            hexDiffuseColor += tmp;
+            tmp = DecimalToHexa( color.g );
+            hexDiffuseColor += tmp;
+            tmp = DecimalToHexa( color.b );
+            hexDiffuseColor += tmp;
+            tmp = DecimalToHexa( color.a );
+            hexDiffuseColor += tmp;
+        } else {
+            hexDiffuseColor = "#FFFFFFFF";
+        }
+
+        mModelOutput << "<base name=\""+strName+"\" "+" displaycolor=\""+hexDiffuseColor+"\" />\n";
+    }
+    mModelOutput << "</basematerials>\n";
+}
+
+void D3MFExporter::writeObjects() {
+    if ( nullptr == mScene->mRootNode ) {
+        return;
+    }
+
+    aiNode *root = mScene->mRootNode;
+    for ( unsigned int i = 0; i < root->mNumChildren; ++i ) {
+        aiNode *currentNode( root->mChildren[ i ] );
+        if ( nullptr == currentNode ) {
+            continue;
+        }
+        mModelOutput << "<" << XmlTag::object << " id=\"" << currentNode->mName.C_Str() << "\" type=\"model\">";
+        mModelOutput << std::endl;
+        for ( unsigned int j = 0; j < currentNode->mNumMeshes; ++j ) {
+            aiMesh *currentMesh = mScene->mMeshes[ currentNode->mMeshes[ j ] ];
+            if ( nullptr == currentMesh ) {
+                continue;
+            }
+            writeMesh( currentMesh );
+        }
+        mBuildItems.push_back( i );
+
+        mModelOutput << "</" << XmlTag::object << ">";
+        mModelOutput << std::endl;
+    }
+}
+
+void D3MFExporter::writeMesh( aiMesh *mesh ) {
+    if ( nullptr == mesh ) {
+        return;
+    }
+
+    mModelOutput << "<" << XmlTag::mesh << ">" << std::endl;
+    mModelOutput << "<" << XmlTag::vertices << ">" << std::endl;
+    for ( unsigned int i = 0; i < mesh->mNumVertices; ++i ) {
+        writeVertex( mesh->mVertices[ i ] );
+    }
+    mModelOutput << "</" << XmlTag::vertices << ">" << std::endl;
+
+    const unsigned int matIdx( mesh->mMaterialIndex );
+
+    writeFaces( mesh, matIdx );
+
+    mModelOutput << "</" << XmlTag::mesh << ">" << std::endl;
+}
+
+void D3MFExporter::writeVertex( const aiVector3D &pos ) {
+    mModelOutput << "<" << XmlTag::vertex << " x=\"" << pos.x << "\" y=\"" << pos.y << "\" z=\"" << pos.z << "\" />";
+    mModelOutput << std::endl;
+}
+
+void D3MFExporter::writeFaces( aiMesh *mesh, unsigned int matIdx ) {
+    if ( nullptr == mesh ) {
+        return;
+    }
+
+    if ( !mesh->HasFaces() ) {
+        return;
+    }
+    mModelOutput << "<" << XmlTag::triangles << ">" << std::endl;
+    for ( unsigned int i = 0; i < mesh->mNumFaces; ++i ) {
+        aiFace &currentFace = mesh->mFaces[ i ];
+        mModelOutput << "<" << XmlTag::triangle << " v1=\"" << currentFace.mIndices[ 0 ] << "\" v2=\""
+                << currentFace.mIndices[ 1 ] << "\" v3=\"" << currentFace.mIndices[ 2 ]
+                << "\" pid=\"1\" p1=\""+to_string(matIdx)+"\" />";
+        mModelOutput << std::endl;
+    }
+    mModelOutput << "</" << XmlTag::triangles << ">";
+    mModelOutput << std::endl;
+}
+
+void D3MFExporter::writeBuild() {
+    mModelOutput << "<" << XmlTag::build << ">" << std::endl;
+
+    for ( size_t i = 0; i < mBuildItems.size(); ++i ) {
+        mModelOutput << "<" << XmlTag::item << " objectid=\"" << i + 1 << "\"/>";
+        mModelOutput << std::endl;
+    }
+    mModelOutput << "</" << XmlTag::build << ">";
+    mModelOutput << std::endl;
+}
+
+void D3MFExporter::exportContentTyp( const std::string &filename ) {
+    if ( nullptr == m_zipArchive ) {
+        throw DeadlyExportError( "3MF-Export: Zip archive not valid, nullptr." );
+    }
+    const std::string entry = filename;
+    zip_entry_open( m_zipArchive, entry.c_str() );
+
+    const std::string &exportTxt( mContentOutput.str() );
+    zip_entry_write( m_zipArchive, exportTxt.c_str(), exportTxt.size() );
+
+    zip_entry_close( m_zipArchive );
+}
+
+void D3MFExporter::writeModelToArchive( const std::string &folder, const std::string &modelName ) {
+    if ( nullptr == m_zipArchive ) {
+        throw DeadlyExportError( "3MF-Export: Zip archive not valid, nullptr." );
+    }
+    const std::string entry = folder + "/" + modelName;
+    zip_entry_open( m_zipArchive, entry.c_str() );
+
+    const std::string &exportTxt( mModelOutput.str() );
+    zip_entry_write( m_zipArchive, exportTxt.c_str(), exportTxt.size() );
+
+    zip_entry_close( m_zipArchive );
+}
+
+void D3MFExporter::writeRelInfoToFile( const std::string &folder, const std::string &relName ) {
+    if ( nullptr == m_zipArchive ) {
+        throw DeadlyExportError( "3MF-Export: Zip archive not valid, nullptr." );
+    }
+    const std::string entry = folder + "/" + relName;
+    zip_entry_open( m_zipArchive, entry.c_str() );
+
+    const std::string &exportTxt( mRelOutput.str() );
+    zip_entry_write( m_zipArchive, exportTxt.c_str(), exportTxt.size() );
+
+    zip_entry_close( m_zipArchive );
+}
+
+
+} // Namespace D3MF
+} // Namespace Assimp
+
+#endif // ASSIMP_BUILD_NO_3MF_EXPORTER
+#endif // ASSIMP_BUILD_NO_EXPORT

+ 106 - 0
Engine/lib/assimp/code/D3MFExporter.h

@@ -0,0 +1,106 @@
+/*
+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 <memory>
+#include <sstream>
+#include <vector>
+#include <assimp/vector3.h>
+
+struct aiScene;
+struct aiNode;
+struct aiMaterial;
+struct aiMesh;
+
+struct zip_t;
+
+namespace Assimp {
+
+class IOStream;
+
+namespace D3MF {
+
+#ifndef ASSIMP_BUILD_NO_EXPORT
+#ifndef ASSIMP_BUILD_NO_3MF_EXPORTER
+
+struct OpcPackageRelationship;
+
+class D3MFExporter {
+public:
+    D3MFExporter( const char* pFile, const aiScene* pScene );
+    ~D3MFExporter();
+    bool validate();
+    bool exportArchive( const char *file );
+    bool exportContentTypes();
+    bool exportRelations();
+    bool export3DModel();
+
+protected:
+    void writeHeader();
+    void writeMetaData();
+    void writeBaseMaterials();
+    void writeObjects();
+    void writeMesh( aiMesh *mesh );
+    void writeVertex( const aiVector3D &pos );
+    void writeFaces( aiMesh *mesh, unsigned int matIdx );
+    void writeBuild();
+    void exportContentTyp( const std::string &filename );
+    void writeModelToArchive( const std::string &folder, const std::string &modelName );
+    void writeRelInfoToFile( const std::string &folder, const std::string &relName );
+
+private:
+    std::string mArchiveName;
+    zip_t *m_zipArchive;
+    const aiScene *mScene;
+    std::ostringstream mModelOutput;
+    std::ostringstream mRelOutput;
+    std::ostringstream mContentOutput;
+    std::vector<unsigned int> mBuildItems;
+    std::vector<OpcPackageRelationship*> mRelations;
+};
+
+#endif // ASSIMP_BUILD_NO_3MF_EXPORTER
+#endif // ASSIMP_BUILD_NO_EXPORT
+
+} // Namespace D3MF
+} // Namespace Assimp
+

+ 483 - 0
Engine/lib/assimp/code/D3MFImporter.cpp

@@ -0,0 +1,483 @@
+/*
+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_3MF_IMPORTER
+
+#include "D3MFImporter.h"
+
+#include <assimp/scene.h>
+#include <assimp/IOSystem.hpp>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/importerdesc.h>
+#include <assimp/StringComparison.h>
+#include <assimp/StringUtils.h>
+
+#include <string>
+#include <vector>
+#include <map>
+#include <cassert>
+#include <memory>
+
+#include "D3MFOpcPackage.h"
+#include <unzip.h>
+#include <assimp/irrXMLWrapper.h>
+#include "3MFXmlTags.h"
+#include <assimp/fast_atof.h>
+
+#include <iomanip>
+
+namespace Assimp {
+namespace D3MF {
+
+class XmlSerializer {
+public:
+    using MatArray = std::vector<aiMaterial*>;
+    using MatId2MatArray = std::map<unsigned int, std::vector<unsigned int>>;
+
+    XmlSerializer(XmlReader* xmlReader)
+    : mMeshes()
+    , mMatArray()
+    , mActiveMatGroup( 99999999 )
+    , mMatId2MatArray()
+    , xmlReader(xmlReader){
+		// empty
+    }
+
+    ~XmlSerializer() {
+        // empty
+    }
+
+    void ImportXml(aiScene* scene) {
+        if ( nullptr == scene ) {
+            return;
+        }
+
+        scene->mRootNode = new aiNode();
+        std::vector<aiNode*> children;
+
+        std::string nodeName;
+        while(ReadToEndElement(D3MF::XmlTag::model)) {
+            nodeName = xmlReader->getNodeName();
+            if( nodeName == D3MF::XmlTag::object) {
+                children.push_back(ReadObject(scene));
+            } else if( nodeName == D3MF::XmlTag::build) {
+                // 
+            } else if ( nodeName == D3MF::XmlTag::basematerials ) {
+                ReadBaseMaterials();
+            } else if ( nodeName == D3MF::XmlTag::meta ) {
+                ReadMetadata();
+            }
+        }
+
+        if ( scene->mRootNode->mName.length == 0 ) {
+            scene->mRootNode->mName.Set( "3MF" );
+        }
+
+        // import the metadata
+        if ( !mMetaData.empty() ) {
+            const size_t numMeta( mMetaData.size() );
+            scene->mMetaData = aiMetadata::Alloc(static_cast<unsigned int>( numMeta ) );
+            for ( size_t i = 0; i < numMeta; ++i ) {
+                aiString val( mMetaData[ i ].value );
+                scene->mMetaData->Set(static_cast<unsigned int>( i ), mMetaData[ i ].name, val );
+            }
+        }
+
+        // import the meshes
+        scene->mNumMeshes = static_cast<unsigned int>( mMeshes.size());
+        scene->mMeshes = new aiMesh*[scene->mNumMeshes]();
+        std::copy( mMeshes.begin(), mMeshes.end(), scene->mMeshes);
+
+        // import the materials
+        scene->mNumMaterials = static_cast<unsigned int>( mMatArray.size() );
+        if ( 0 != scene->mNumMaterials ) {
+            scene->mMaterials = new aiMaterial*[ scene->mNumMaterials ];
+            std::copy( mMatArray.begin(), mMatArray.end(), scene->mMaterials );
+        }
+
+        // create the scenegraph
+        scene->mRootNode->mNumChildren = static_cast<unsigned int>(children.size());
+        scene->mRootNode->mChildren = new aiNode*[scene->mRootNode->mNumChildren]();
+        std::copy(children.begin(), children.end(), scene->mRootNode->mChildren);
+    }
+
+private:
+    aiNode* ReadObject(aiScene* scene) {
+        std::unique_ptr<aiNode> node(new aiNode());
+
+        std::vector<unsigned long> meshIds;
+
+        const char *attrib( nullptr );
+        std::string name, type;
+        attrib = xmlReader->getAttributeValue( D3MF::XmlTag::id.c_str() );
+        if ( nullptr != attrib ) {
+            name = attrib;
+        }
+        attrib = xmlReader->getAttributeValue( D3MF::XmlTag::type.c_str() );
+        if ( nullptr != attrib ) {
+            type = attrib;
+        }
+
+        node->mParent = scene->mRootNode;
+        node->mName.Set(name);
+
+        size_t meshIdx = mMeshes.size();
+
+        while(ReadToEndElement(D3MF::XmlTag::object)) {
+            if(xmlReader->getNodeName() == D3MF::XmlTag::mesh) {
+                auto mesh = ReadMesh();
+
+                mesh->mName.Set(name);
+                mMeshes.push_back(mesh);
+                meshIds.push_back(static_cast<unsigned long>(meshIdx));
+                ++meshIdx;
+            }
+        }
+
+        node->mNumMeshes = static_cast<unsigned int>(meshIds.size());
+
+        node->mMeshes = new unsigned int[node->mNumMeshes];
+
+        std::copy(meshIds.begin(), meshIds.end(), node->mMeshes);
+
+        return node.release();
+    }
+
+    aiMesh *ReadMesh() {
+        aiMesh* mesh = new aiMesh();
+        while(ReadToEndElement(D3MF::XmlTag::mesh)) {
+            if(xmlReader->getNodeName() == D3MF::XmlTag::vertices) {
+                ImportVertices(mesh);
+            } else if(xmlReader->getNodeName() == D3MF::XmlTag::triangles) {
+                ImportTriangles(mesh);
+            }
+        }
+
+        return mesh;
+    }
+
+    void ReadMetadata() {
+        const std::string name = xmlReader->getAttributeValue( D3MF::XmlTag::meta_name.c_str() );
+        xmlReader->read();
+        const std::string value = xmlReader->getNodeData();
+
+        if ( name.empty() ) {
+            return;
+        }
+
+        MetaEntry entry;
+        entry.name = name;
+        entry.value = value;
+        mMetaData.push_back( entry );
+    }
+
+    void ImportVertices(aiMesh* mesh) {
+        std::vector<aiVector3D> vertices;
+        while(ReadToEndElement(D3MF::XmlTag::vertices)) {
+            if(xmlReader->getNodeName() == D3MF::XmlTag::vertex) {
+                vertices.push_back(ReadVertex());
+            }
+        }
+        mesh->mNumVertices = static_cast<unsigned int>(vertices.size());
+        mesh->mVertices = new aiVector3D[mesh->mNumVertices];
+
+        std::copy(vertices.begin(), vertices.end(), mesh->mVertices);
+    }
+
+    aiVector3D ReadVertex() {
+        aiVector3D vertex;
+
+        vertex.x = ai_strtof(xmlReader->getAttributeValue(D3MF::XmlTag::x.c_str()), nullptr);
+        vertex.y = ai_strtof(xmlReader->getAttributeValue(D3MF::XmlTag::y.c_str()), nullptr);
+        vertex.z = ai_strtof(xmlReader->getAttributeValue(D3MF::XmlTag::z.c_str()), nullptr);
+
+        return vertex;
+    }
+
+    void ImportTriangles(aiMesh* mesh) {
+         std::vector<aiFace> faces;
+
+         while(ReadToEndElement(D3MF::XmlTag::triangles)) {
+             const std::string nodeName( xmlReader->getNodeName() );
+             if(xmlReader->getNodeName() == D3MF::XmlTag::triangle) {
+                 faces.push_back(ReadTriangle());
+                 const char *pidToken( xmlReader->getAttributeValue( D3MF::XmlTag::p1.c_str() ) );
+                 if ( nullptr != pidToken ) {
+                     int matIdx( std::atoi( pidToken ) );
+                     mesh->mMaterialIndex = matIdx;
+                 }
+             }
+         }
+
+        mesh->mNumFaces = static_cast<unsigned int>(faces.size());
+        mesh->mFaces = new aiFace[mesh->mNumFaces];
+        mesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
+
+        std::copy(faces.begin(), faces.end(), mesh->mFaces);
+    }
+
+    aiFace ReadTriangle() {
+        aiFace face;
+
+        face.mNumIndices = 3;
+        face.mIndices = new unsigned int[face.mNumIndices];
+        face.mIndices[0] = static_cast<unsigned int>(std::atoi(xmlReader->getAttributeValue(D3MF::XmlTag::v1.c_str())));
+        face.mIndices[1] = static_cast<unsigned int>(std::atoi(xmlReader->getAttributeValue(D3MF::XmlTag::v2.c_str())));
+        face.mIndices[2] = static_cast<unsigned int>(std::atoi(xmlReader->getAttributeValue(D3MF::XmlTag::v3.c_str())));
+
+        return face;
+    }
+
+    void ReadBaseMaterials() {
+        std::vector<unsigned int> MatIdArray;
+        const char *baseMaterialId( xmlReader->getAttributeValue( D3MF::XmlTag::basematerials_id.c_str() ) );
+        if ( nullptr != baseMaterialId ) {
+            unsigned int id = std::atoi( baseMaterialId );
+            const size_t newMatIdx( mMatArray.size() );
+            if ( id != mActiveMatGroup ) {
+                mActiveMatGroup = id;
+                MatId2MatArray::const_iterator it( mMatId2MatArray.find( id ) );
+                if ( mMatId2MatArray.end() == it ) {
+                    MatIdArray.clear();
+                    mMatId2MatArray[ id ] = MatIdArray;
+                } else {
+                    MatIdArray = it->second;
+                }
+            }
+            MatIdArray.push_back( static_cast<unsigned int>( newMatIdx ) );
+            mMatId2MatArray[ mActiveMatGroup ] = MatIdArray;
+        }
+
+        while ( ReadToEndElement( D3MF::XmlTag::basematerials ) ) {
+            mMatArray.push_back( readMaterialDef() );
+        }
+    }
+
+    bool parseColor( const char *color, aiColor4D &diffuse ) {
+        if ( nullptr == color ) {
+            return false;
+        }
+
+        //format of the color string: #RRGGBBAA or #RRGGBB (3MF Core chapter 5.1.1)
+        const size_t len( strlen( color ) );
+        if ( 9 != len && 7 != len) {
+            return false;
+        }
+
+        const char *buf( color );
+        if ( '#' != *buf ) {
+            return false;
+        }
+        ++buf;
+        char comp[ 3 ] = { 0,0,'\0' };
+
+        comp[ 0 ] = *buf;
+        ++buf;
+        comp[ 1 ] = *buf;
+        ++buf;
+        diffuse.r = static_cast<ai_real>( strtol( comp, NULL, 16 ) ) / ai_real(255.0);
+
+
+        comp[ 0 ] = *buf;
+        ++buf;
+        comp[ 1 ] = *buf;
+        ++buf;
+        diffuse.g = static_cast< ai_real >( strtol( comp, NULL, 16 ) ) / ai_real(255.0);
+
+        comp[ 0 ] = *buf;
+        ++buf;
+        comp[ 1 ] = *buf;
+        ++buf;
+        diffuse.b = static_cast< ai_real >( strtol( comp, NULL, 16 ) ) / ai_real(255.0);
+
+        if(7 == len)
+            return true;
+        comp[ 0 ] = *buf;
+        ++buf;
+        comp[ 1 ] = *buf;
+        ++buf;
+        diffuse.a = static_cast< ai_real >( strtol( comp, NULL, 16 ) ) / ai_real(255.0);
+
+        return true;
+    }
+
+    void assignDiffuseColor( aiMaterial *mat ) {
+        const char *color = xmlReader->getAttributeValue( D3MF::XmlTag::basematerials_displaycolor.c_str() );
+        aiColor4D diffuse;
+        if ( parseColor( color, diffuse ) ) {
+            mat->AddProperty<aiColor4D>( &diffuse, 1, AI_MATKEY_COLOR_DIFFUSE );
+        }
+
+    }
+    aiMaterial *readMaterialDef() {
+        aiMaterial *mat( nullptr );
+        const char *name( nullptr );
+        const std::string nodeName( xmlReader->getNodeName() );
+        if ( nodeName == D3MF::XmlTag::basematerials_base ) {
+            name = xmlReader->getAttributeValue( D3MF::XmlTag::basematerials_name.c_str() );
+            std::string stdMatName;
+            aiString matName;
+            std::string strId( to_string( mActiveMatGroup ) );
+            stdMatName += "id";
+            stdMatName += strId;
+            stdMatName += "_";
+            if ( nullptr != name ) {
+                stdMatName += std::string( name );
+            } else {
+                stdMatName += "basemat";
+            }
+            matName.Set( stdMatName );
+
+            mat = new aiMaterial;
+            mat->AddProperty( &matName, AI_MATKEY_NAME );
+
+            assignDiffuseColor( mat );
+        }
+
+        return mat;
+    }
+
+private:
+    bool ReadToStartElement(const std::string& startTag) {
+        while(xmlReader->read()) {
+            const std::string &nodeName( xmlReader->getNodeName() );
+            if (xmlReader->getNodeType() == irr::io::EXN_ELEMENT && nodeName == startTag) {
+                return true;
+            } else if (xmlReader->getNodeType() == irr::io::EXN_ELEMENT_END && nodeName == startTag) {
+                return false;
+            }
+        }
+
+        return false;
+    }
+
+    bool ReadToEndElement(const std::string& closeTag) {
+        while(xmlReader->read()) {
+            const std::string &nodeName( xmlReader->getNodeName() );
+            if (xmlReader->getNodeType() == irr::io::EXN_ELEMENT) {
+                return true;
+            } else if (xmlReader->getNodeType() == irr::io::EXN_ELEMENT_END && nodeName == closeTag) {
+                return false;
+            }
+        }
+        ASSIMP_LOG_ERROR("unexpected EOF, expected closing <" + closeTag + "> tag");
+
+        return false;
+    }
+
+private:
+    struct MetaEntry {
+        std::string name;
+        std::string value;
+    };
+    std::vector<MetaEntry> mMetaData;
+    std::vector<aiMesh*> mMeshes;
+    MatArray mMatArray;
+    unsigned int mActiveMatGroup;
+    MatId2MatArray mMatId2MatArray;
+    XmlReader* xmlReader;
+};
+
+} //namespace D3MF
+
+static const aiImporterDesc desc = {
+    "3mf Importer",
+    "",
+    "",
+    "http://3mf.io/",
+    aiImporterFlags_SupportBinaryFlavour | aiImporterFlags_SupportCompressedFlavour,
+    0,
+    0,
+    0,
+    0,
+    "3mf"
+};
+
+D3MFImporter::D3MFImporter()
+: BaseImporter() {
+    // empty
+}
+
+D3MFImporter::~D3MFImporter() {
+    // empty
+}
+
+bool D3MFImporter::CanRead(const std::string &filename, IOSystem *pIOHandler, bool checkSig) const {
+    const std::string extension( GetExtension( filename ) );
+    if(extension == desc.mFileExtensions ) {
+        return true;
+    } else if ( !extension.length() || checkSig ) {
+        if ( nullptr == pIOHandler ) {
+            return false;
+        }
+        if ( !D3MF::D3MFOpcPackage::isZipArchive( pIOHandler, filename ) ) {
+            return false;
+        }
+        D3MF::D3MFOpcPackage opcPackage( pIOHandler, filename );
+        return opcPackage.validate();
+    }
+
+    return false;
+}
+
+void D3MFImporter::SetupProperties(const Importer * /*pImp*/) {
+    // empty
+}
+
+const aiImporterDesc *D3MFImporter::GetInfo() const {
+    return &desc;
+}
+
+void D3MFImporter::InternReadFile( const std::string &filename, aiScene *pScene, IOSystem *pIOHandler ) {
+    D3MF::D3MFOpcPackage opcPackage(pIOHandler, filename);
+
+    std::unique_ptr<CIrrXML_IOStreamReader> xmlStream(new CIrrXML_IOStreamReader(opcPackage.RootStream()));
+    std::unique_ptr<D3MF::XmlReader> xmlReader(irr::io::createIrrXMLReader(xmlStream.get()));
+
+    D3MF::XmlSerializer xmlSerializer(xmlReader.get());
+
+    xmlSerializer.ImportXml(pScene);
+}
+
+} // Namespace Assimp
+
+#endif // ASSIMP_BUILD_NO_3MF_IMPORTER

+ 65 - 0
Engine/lib/assimp/code/D3MFImporter.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.
+
+----------------------------------------------------------------------
+*/
+
+#ifndef AI_D3MFLOADER_H_INCLUDED
+#define AI_D3MFLOADER_H_INCLUDED
+
+#include <assimp/BaseImporter.h>
+
+namespace Assimp {
+
+class D3MFImporter : public BaseImporter {
+public:
+    // BaseImporter interface
+    D3MFImporter();
+    ~D3MFImporter();
+    bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const;
+    void SetupProperties(const Importer *pImp);
+    const aiImporterDesc *GetInfo() const;
+
+protected:
+    void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler);
+};
+
+} // Namespace Assimp
+
+#endif // AI_D3MFLOADER_H_INCLUDED

+ 531 - 0
Engine/lib/assimp/code/D3MFOpcPackage.cpp

@@ -0,0 +1,531 @@
+/*
+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_3MF_IMPORTER
+
+#include "D3MFOpcPackage.h"
+#include <assimp/Exceptional.h>
+
+#include <assimp/IOStream.hpp>
+#include <assimp/IOSystem.hpp>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/ai_assert.h>
+
+#include <cstdlib>
+#include <memory>
+#include <vector>
+#include <map>
+#include <algorithm>
+#include <cassert>
+#include <unzip.h>
+#include "3MFXmlTags.h"
+
+namespace Assimp {
+
+namespace D3MF {
+
+class IOSystem2Unzip {
+public:
+    static voidpf open(voidpf opaque, const char* filename, int mode);
+    static uLong read(voidpf opaque, voidpf stream, void* buf, uLong size);
+    static uLong write(voidpf opaque, voidpf stream, const void* buf, uLong size);
+    static long tell(voidpf opaque, voidpf stream);
+    static long seek(voidpf opaque, voidpf stream, uLong offset, int origin);
+    static int close(voidpf opaque, voidpf stream);
+    static int testerror(voidpf opaque, voidpf stream);
+    static zlib_filefunc_def get(IOSystem* pIOHandler);
+};
+
+voidpf IOSystem2Unzip::open(voidpf opaque, const char* filename, int mode) {
+    IOSystem* io_system = reinterpret_cast<IOSystem*>(opaque);
+
+    const char* mode_fopen = NULL;
+    if((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ) {
+        mode_fopen = "rb";
+    } else {
+        if(mode & ZLIB_FILEFUNC_MODE_EXISTING) {
+            mode_fopen = "r+b";
+        } else {
+            if(mode & ZLIB_FILEFUNC_MODE_CREATE) {
+                mode_fopen = "wb";
+            }
+        }
+    }
+
+    return (voidpf) io_system->Open(filename, mode_fopen);
+}
+
+uLong IOSystem2Unzip::read(voidpf /*opaque*/, voidpf stream, void* buf, uLong size) {
+    IOStream* io_stream = (IOStream*) stream;
+
+    return static_cast<uLong>(io_stream->Read(buf, 1, size));
+}
+
+uLong IOSystem2Unzip::write(voidpf /*opaque*/, voidpf stream, const void* buf, uLong size) {
+    IOStream* io_stream = (IOStream*) stream;
+
+    return static_cast<uLong>(io_stream->Write(buf, 1, size));
+}
+
+long IOSystem2Unzip::tell(voidpf /*opaque*/, voidpf stream) {
+    IOStream* io_stream = (IOStream*) stream;
+
+    return static_cast<long>(io_stream->Tell());
+}
+
+long IOSystem2Unzip::seek(voidpf /*opaque*/, voidpf stream, uLong offset, int origin) {
+    IOStream* io_stream = (IOStream*) stream;
+
+    aiOrigin assimp_origin;
+    switch (origin) {
+        default:
+        case ZLIB_FILEFUNC_SEEK_CUR:
+            assimp_origin = aiOrigin_CUR;
+            break;
+        case ZLIB_FILEFUNC_SEEK_END:
+            assimp_origin = aiOrigin_END;
+            break;
+        case ZLIB_FILEFUNC_SEEK_SET:
+            assimp_origin = aiOrigin_SET;
+            break;
+    }
+
+    return (io_stream->Seek(offset, assimp_origin) == aiReturn_SUCCESS ? 0 : -1);
+}
+
+int IOSystem2Unzip::close(voidpf opaque, voidpf stream) {
+    IOSystem* io_system = (IOSystem*) opaque;
+    IOStream* io_stream = (IOStream*) stream;
+
+    io_system->Close(io_stream);
+
+    return 0;
+}
+
+int IOSystem2Unzip::testerror(voidpf /*opaque*/, voidpf /*stream*/) {
+    return 0;
+}
+
+zlib_filefunc_def IOSystem2Unzip::get(IOSystem* pIOHandler) {
+    zlib_filefunc_def mapping;
+
+    mapping.zopen_file = open;
+    mapping.zread_file = read;
+    mapping.zwrite_file = write;
+    mapping.ztell_file = tell;
+    mapping.zseek_file = seek;
+    mapping.zclose_file = close;
+    mapping.zerror_file = testerror;
+    mapping.opaque = reinterpret_cast<voidpf>(pIOHandler);
+
+    return mapping;
+}
+
+class ZipFile : public IOStream {
+    friend class D3MFZipArchive;
+
+public:
+    explicit ZipFile(size_t size);
+    virtual ~ZipFile();
+    size_t Read(void* pvBuffer, size_t pSize, size_t pCount );
+    size_t Write(const void* /*pvBuffer*/, size_t /*pSize*/, size_t /*pCount*/);
+    size_t FileSize() const;
+    aiReturn Seek(size_t /*pOffset*/, aiOrigin /*pOrigin*/);
+    size_t Tell() const;
+    void Flush();
+
+private:
+    void *m_Buffer;
+    size_t m_Size;
+};
+
+ZipFile::ZipFile(size_t size)
+: m_Buffer( nullptr )
+, m_Size(size) {
+    ai_assert(m_Size != 0);
+    m_Buffer = ::malloc(m_Size);
+}
+
+ZipFile::~ZipFile() {
+    ::free(m_Buffer);
+    m_Buffer = NULL;
+}
+
+size_t ZipFile::Read(void* pvBuffer, size_t pSize, size_t pCount) {
+    const size_t size = pSize * pCount;
+    ai_assert(size <= m_Size);
+
+    std::memcpy(pvBuffer, m_Buffer, size);
+
+    return size;
+}
+
+size_t ZipFile::Write(const void* pvBuffer, size_t size, size_t pCount ) {
+    const size_t size_to_write( size * pCount );
+    if ( 0 == size_to_write ) {
+        return 0U;
+    }
+    return 0U;
+}
+
+size_t ZipFile::FileSize() const {
+    return m_Size;
+}
+
+aiReturn ZipFile::Seek(size_t /*pOffset*/, aiOrigin /*pOrigin*/) {
+    return aiReturn_FAILURE;
+}
+
+size_t ZipFile::Tell() const {
+    return 0;
+}
+
+void ZipFile::Flush() {
+    // empty
+}
+
+class D3MFZipArchive : public IOSystem {
+public:
+    static const unsigned int FileNameSize = 256;
+
+    D3MFZipArchive(IOSystem* pIOHandler, const std::string & rFile);
+    ~D3MFZipArchive();
+    bool Exists(const char* pFile) const;
+    char getOsSeparator() const;
+    IOStream* Open(const char* pFile, const char* pMode = "rb");
+    void Close(IOStream* pFile);
+    bool isOpen() const;
+    void getFileList(std::vector<std::string> &rFileList);
+
+private:
+    bool mapArchive();
+
+private:
+    unzFile m_ZipFileHandle;
+    std::map<std::string, ZipFile*> m_ArchiveMap;
+};
+
+// ------------------------------------------------------------------------------------------------
+//  Constructor.
+D3MFZipArchive::D3MFZipArchive(IOSystem* pIOHandler, const std::string& rFile)
+: m_ZipFileHandle( nullptr )
+, m_ArchiveMap() {
+    if (! rFile.empty()) {                
+        zlib_filefunc_def mapping = IOSystem2Unzip::get(pIOHandler);            
+
+        m_ZipFileHandle = unzOpen2(rFile.c_str(), &mapping);
+        if(m_ZipFileHandle != nullptr ) {
+            mapArchive();
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+//  Destructor.
+D3MFZipArchive::~D3MFZipArchive() {
+    for(auto &file : m_ArchiveMap) {
+        delete file.second;
+    }
+    m_ArchiveMap.clear();
+
+    if(m_ZipFileHandle != nullptr) {
+        unzClose(m_ZipFileHandle);
+        m_ZipFileHandle = nullptr;
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+//  Returns true, if the archive is already open.
+bool D3MFZipArchive::isOpen() const {
+    return (m_ZipFileHandle != nullptr );
+}
+
+// ------------------------------------------------------------------------------------------------
+//  Returns true, if the filename is part of the archive.
+bool D3MFZipArchive::Exists(const char* pFile) const {
+    ai_assert(pFile != nullptr );
+
+    if ( pFile == nullptr ) {
+        return false;
+    }
+
+    std::string filename(pFile);
+    std::map<std::string, ZipFile*>::const_iterator it = m_ArchiveMap.find(filename);
+    bool exist( false );
+    if(it != m_ArchiveMap.end()) {
+        exist = true;
+    }
+
+    return exist;
+}
+
+// ------------------------------------------------------------------------------------------------
+//  Returns the separator delimiter.
+char D3MFZipArchive::getOsSeparator() const {
+#ifndef _WIN32
+    return '/';
+#else
+    return '\\';
+#endif
+}
+
+// ------------------------------------------------------------------------------------------------
+//  Opens a file, which is part of the archive.
+IOStream *D3MFZipArchive::Open(const char* pFile, const char* /*pMode*/) {
+    ai_assert(pFile != NULL);
+
+    IOStream* result = NULL;
+
+    std::map<std::string, ZipFile*>::iterator it = m_ArchiveMap.find(pFile);
+
+    if(it != m_ArchiveMap.end()) {
+        result = static_cast<IOStream*>(it->second);
+    }
+
+    return result;
+}
+
+// ------------------------------------------------------------------------------------------------
+//  Close a filestream.
+void D3MFZipArchive::Close(IOStream *pFile) {
+    (void)(pFile);
+    ai_assert(pFile != NULL);
+
+    // We don't do anything in case the file would be opened again in the future
+}
+// ------------------------------------------------------------------------------------------------
+//  Returns the file-list of the archive.
+void D3MFZipArchive::getFileList(std::vector<std::string> &rFileList) {
+    rFileList.clear();
+
+    for(const auto &file : m_ArchiveMap) {
+        rFileList.push_back(file.first);
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+//  Maps the archive content.
+bool D3MFZipArchive::mapArchive() {
+    bool success = false;
+
+    if(m_ZipFileHandle != NULL) {
+        if(m_ArchiveMap.empty()) {
+            //  At first ensure file is already open
+            if(unzGoToFirstFile(m_ZipFileHandle) == UNZ_OK) {
+                // Loop over all files
+                do {
+                    char filename[FileNameSize];
+                    unz_file_info fileInfo;
+
+                    if(unzGetCurrentFileInfo(m_ZipFileHandle, &fileInfo, filename, FileNameSize, NULL, 0, NULL, 0) == UNZ_OK) {
+                        // The file has EXACTLY the size of uncompressed_size. In C
+                        // you need to mark the last character with '\0', so add
+                        // another character
+                        if(fileInfo.uncompressed_size != 0 && unzOpenCurrentFile(m_ZipFileHandle) == UNZ_OK) {
+                            std::pair<std::map<std::string, ZipFile*>::iterator, bool> result = m_ArchiveMap.insert(std::make_pair(filename, new ZipFile(fileInfo.uncompressed_size)));
+
+                            if(unzReadCurrentFile(m_ZipFileHandle, result.first->second->m_Buffer, fileInfo.uncompressed_size) == (long int) fileInfo.uncompressed_size) {
+                                if(unzCloseCurrentFile(m_ZipFileHandle) == UNZ_OK) {
+                                    // Nothing to do anymore...
+                                }
+                            }
+                        }
+                    }
+                } while(unzGoToNextFile(m_ZipFileHandle) != UNZ_END_OF_LIST_OF_FILE);
+            }
+        }
+
+        success = true;
+    }
+
+    return success;
+}
+
+// ------------------------------------------------------------------------------------------------
+
+typedef std::shared_ptr<OpcPackageRelationship> OpcPackageRelationshipPtr;
+
+class OpcPackageRelationshipReader {
+public:
+    OpcPackageRelationshipReader(XmlReader* xmlReader) {        
+        while(xmlReader->read()) {
+            if(xmlReader->getNodeType() == irr::io::EXN_ELEMENT &&
+               xmlReader->getNodeName() == XmlTag::RELS_RELATIONSHIP_CONTAINER)
+            {
+                ParseRootNode(xmlReader);
+            }
+        }
+    }
+
+    void ParseRootNode(XmlReader* xmlReader)
+    {       
+        ParseAttributes(xmlReader);
+
+        while(xmlReader->read())
+        {
+            if(xmlReader->getNodeType() == irr::io::EXN_ELEMENT &&
+               xmlReader->getNodeName() == XmlTag::RELS_RELATIONSHIP_NODE)
+            {
+                ParseChildNode(xmlReader);
+            }
+        }
+    }
+
+    void ParseAttributes(XmlReader*) {
+        // empty
+    }
+
+    bool validateRels( OpcPackageRelationshipPtr &relPtr ) {
+        if ( relPtr->id.empty() || relPtr->type.empty() || relPtr->target.empty() ) {
+            return false;
+        }
+        return true;
+    }
+
+    void ParseChildNode(XmlReader* xmlReader) {        
+        OpcPackageRelationshipPtr relPtr(new OpcPackageRelationship());
+
+        relPtr->id = xmlReader->getAttributeValueSafe(XmlTag::RELS_ATTRIB_ID.c_str());
+        relPtr->type = xmlReader->getAttributeValueSafe(XmlTag::RELS_ATTRIB_TYPE.c_str());
+        relPtr->target = xmlReader->getAttributeValueSafe(XmlTag::RELS_ATTRIB_TARGET.c_str());
+        if ( validateRels( relPtr ) ) {
+            m_relationShips.push_back( relPtr );
+        }
+    }
+
+    std::vector<OpcPackageRelationshipPtr> m_relationShips;
+};
+
+// ------------------------------------------------------------------------------------------------
+D3MFOpcPackage::D3MFOpcPackage(IOSystem* pIOHandler, const std::string& rFile)
+: mRootStream(nullptr)
+, mZipArchive() {    
+    mZipArchive.reset( new D3MF::D3MFZipArchive( pIOHandler, rFile ) );    
+    if(!mZipArchive->isOpen()) {
+        throw DeadlyImportError("Failed to open file " + rFile+ ".");
+    }
+
+    std::vector<std::string> fileList;
+    mZipArchive->getFileList(fileList);
+
+    for (auto& file: fileList) {
+        if(file == D3MF::XmlTag::ROOT_RELATIONSHIPS_ARCHIVE) {
+            //PkgRelationshipReader pkgRelReader(file, archive);
+            ai_assert(mZipArchive->Exists(file.c_str()));
+
+            IOStream *fileStream = mZipArchive->Open(file.c_str());
+
+            ai_assert(fileStream != nullptr);
+
+            std::string rootFile = ReadPackageRootRelationship(fileStream);
+            if ( rootFile.size() > 0 && rootFile[ 0 ] == '/' ) {
+                rootFile = rootFile.substr( 1 );
+                if ( rootFile[ 0 ] == '/' ) {
+                    // deal with zip-bug
+                    rootFile = rootFile.substr( 1 );
+                }
+            }
+
+            ASSIMP_LOG_DEBUG(rootFile);
+
+            mRootStream = mZipArchive->Open(rootFile.c_str());
+            ai_assert( mRootStream != nullptr );
+            if ( nullptr == mRootStream ) {
+                throw DeadlyExportError( "Cannot open root-file in archive : " + rootFile );
+            }
+
+            mZipArchive->Close( fileStream );
+
+        } else if( file == D3MF::XmlTag::CONTENT_TYPES_ARCHIVE) {
+
+        }
+    }
+}
+
+D3MFOpcPackage::~D3MFOpcPackage() {
+    // empty
+}
+
+IOStream* D3MFOpcPackage::RootStream() const {
+    return mRootStream;
+}
+
+static const std::string ModelRef = "3D/3dmodel.model";
+
+bool D3MFOpcPackage::validate() {
+    if ( nullptr == mRootStream || nullptr == mZipArchive ) {
+        return false;
+    }
+
+    return mZipArchive->Exists( ModelRef.c_str() );
+}
+
+bool D3MFOpcPackage::isZipArchive( IOSystem* pIOHandler, const std::string& rFile ) {
+    D3MF::D3MFZipArchive ar( pIOHandler, rFile );
+    if ( !ar.isOpen() ) {
+        return false;
+    }
+
+    return true;
+}
+
+std::string D3MFOpcPackage::ReadPackageRootRelationship(IOStream* stream) {
+    std::unique_ptr<CIrrXML_IOStreamReader> xmlStream(new CIrrXML_IOStreamReader(stream));
+    std::unique_ptr<XmlReader> xml(irr::io::createIrrXMLReader(xmlStream.get()));
+
+    OpcPackageRelationshipReader reader(xml.get());
+
+    auto itr = std::find_if(reader.m_relationShips.begin(), reader.m_relationShips.end(), [](const OpcPackageRelationshipPtr& rel){
+        return rel->type == XmlTag::PACKAGE_START_PART_RELATIONSHIP_TYPE;
+    });
+
+    if ( itr == reader.m_relationShips.end() ) {
+        throw DeadlyImportError( "Cannot find " + XmlTag::PACKAGE_START_PART_RELATIONSHIP_TYPE );
+    }
+
+    return (*itr)->target;
+}
+
+} // Namespace D3MF
+} // Namespace Assimp
+
+#endif //ASSIMP_BUILD_NO_3MF_IMPORTER

+ 84 - 0
Engine/lib/assimp/code/D3MFOpcPackage.h

@@ -0,0 +1,84 @@
+/*
+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 D3MFOPCPACKAGE_H
+#define D3MFOPCPACKAGE_H
+
+#include <memory>
+
+#include <assimp/IOSystem.hpp>
+#include <assimp/irrXMLWrapper.h>
+
+namespace Assimp {
+namespace D3MF {
+
+using XmlReader = irr::io::IrrXMLReader ;
+using XmlReaderPtr = std::shared_ptr<XmlReader> ;
+
+struct OpcPackageRelationship {
+    std::string id;
+    std::string type;
+    std::string target;
+};
+
+class D3MFZipArchive;
+
+class D3MFOpcPackage {
+public:
+    D3MFOpcPackage( IOSystem* pIOHandler, const std::string& rFile );
+    ~D3MFOpcPackage();
+    IOStream* RootStream() const;
+    bool validate();
+    static bool isZipArchive( IOSystem* pIOHandler, const std::string& rFile );
+
+protected:
+    std::string ReadPackageRootRelationship(IOStream* stream);
+
+private:
+    IOStream* mRootStream;
+    std::unique_ptr<D3MFZipArchive> mZipArchive;
+};
+
+} // Namespace D3MF
+} // Namespace Assimp
+
+#endif // D3MFOPCPACKAGE_H

+ 222 - 0
Engine/lib/assimp/code/DXFHelper.h

@@ -0,0 +1,222 @@
+/*
+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  DXFHelper.h
+ *  @brief Internal utilities for the DXF loader.
+ */
+
+#ifndef INCLUDED_DXFHELPER_H
+#define INCLUDED_DXFHELPER_H
+
+#include <assimp/LineSplitter.h>
+#include <assimp/TinyFormatter.h>
+#include <assimp/StreamReader.h>
+#include <assimp/fast_atof.h>
+#include <vector>
+#include <assimp/DefaultLogger.hpp>
+
+namespace Assimp {
+namespace DXF {
+
+// read pairs of lines, parse group code and value and provide utilities
+// to convert the data to the target data type.
+// do NOT skip empty lines. In DXF files, they count as valid data.
+class LineReader {
+public:
+    LineReader(StreamReaderLE& reader)
+    : splitter(reader,false,true)
+    , groupcode( 0 )
+    , value()
+    , end() {
+        // empty
+    }
+
+    // -----------------------------------------
+    bool Is(int gc, const char* what) const {
+        return groupcode == gc && !strcmp(what,value.c_str());
+    }
+
+    // -----------------------------------------
+    bool Is(int gc) const {
+        return groupcode == gc;
+    }
+
+    // -----------------------------------------
+    int GroupCode() const {
+        return groupcode;
+    }
+
+    // -----------------------------------------
+    const std::string& Value() const {
+        return value;
+    }
+
+    // -----------------------------------------
+    bool End() const {
+        return !((bool)*this);
+    }
+
+    // -----------------------------------------
+    unsigned int ValueAsUnsignedInt() const {
+        return strtoul10(value.c_str());
+    }
+
+    // -----------------------------------------
+    int ValueAsSignedInt() const {
+        return strtol10(value.c_str());
+    }
+
+    // -----------------------------------------
+    float ValueAsFloat() const {
+        return fast_atof(value.c_str());
+    }
+
+    // -----------------------------------------
+    /** pseudo-iterator increment to advance to the next (groupcode/value) pair */
+    LineReader& operator++() {
+        if (end) {
+            if (end == 1) {
+                ++end;
+            }
+            return *this;
+        }
+
+        try {
+            groupcode = strtol10(splitter->c_str());
+            splitter++;
+
+            value = *splitter;
+            splitter++;
+
+            // automatically skip over {} meta blocks (these are for application use
+            // and currently not relevant for Assimp).
+            if (value.length() && value[0] == '{') {
+
+                size_t cnt = 0;
+                for(;splitter->length() && splitter->at(0) != '}'; splitter++, cnt++);
+
+                splitter++;
+                ASSIMP_LOG_DEBUG((Formatter::format("DXF: skipped over control group ("),cnt," lines)"));
+            }
+        } catch(std::logic_error&) {
+            ai_assert(!splitter);
+        }
+        if (!splitter) {
+            end = 1;
+        }
+        return *this;
+    }
+
+    // -----------------------------------------
+    LineReader& operator++(int) {
+        return ++(*this);
+    }
+
+
+    // -----------------------------------------
+    operator bool() const {
+        return end <= 1;
+    }
+
+private:
+    LineSplitter splitter;
+    int groupcode;
+    std::string value;
+    int end;
+};
+
+// represents a POLYLINE or a LWPOLYLINE. or even a 3DFACE The data is converted as needed.
+struct PolyLine {
+    PolyLine()
+    : flags() {
+        // empty
+    }
+
+    std::vector<aiVector3D> positions;
+    std::vector<aiColor4D>  colors;
+    std::vector<unsigned int> indices;
+    std::vector<unsigned int> counts;
+    unsigned int flags;
+
+    std::string layer;
+    std::string desc;
+};
+
+// reference to a BLOCK. Specifies its own coordinate system.
+struct InsertBlock {
+    InsertBlock()
+    : pos()
+    , scale(1.f,1.f,1.f)
+    , angle()
+    , name() {
+        // empty
+    }
+
+    aiVector3D pos;
+    aiVector3D scale;
+    float angle;
+
+    std::string name;
+};
+
+
+// keeps track of all geometry in a single BLOCK.
+struct Block
+{
+    std::vector< std::shared_ptr<PolyLine> > lines;
+    std::vector<InsertBlock> insertions;
+
+    std::string name;
+    aiVector3D base;
+};
+
+
+struct FileData
+{
+    // note: the LAST block always contains the stuff from ENTITIES.
+    std::vector<Block> blocks;
+};
+
+}
+} // Namespace Assimp
+
+#endif

+ 918 - 0
Engine/lib/assimp/code/DXFLoader.cpp

@@ -0,0 +1,918 @@
+/*
+---------------------------------------------------------------------------
+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  DXFLoader.cpp
+ *  @brief Implementation of the DXF importer class
+ */
+
+
+#ifndef ASSIMP_BUILD_NO_DXF_IMPORTER
+
+#include "DXFLoader.h"
+#include <assimp/ParsingUtils.h>
+#include "ConvertToLHProcess.h"
+#include <assimp/fast_atof.h>
+
+#include "DXFHelper.h"
+#include <assimp/IOSystem.hpp>
+#include <assimp/scene.h>
+#include <assimp/importerdesc.h>
+
+#include <numeric>
+
+using namespace Assimp;
+
+// AutoCAD Binary DXF<CR><LF><SUB><NULL>
+const std::string AI_DXF_BINARY_IDENT = std::string("AutoCAD Binary DXF\r\n\x1a\0");
+const size_t AI_DXF_BINARY_IDENT_LEN = 24u;
+
+// default vertex color that all uncolored vertices will receive
+const aiColor4D AI_DXF_DEFAULT_COLOR(aiColor4D(0.6f, 0.6f, 0.6f, 0.6f));
+
+// color indices for DXF - 16 are supported, the table is
+// taken directly from the DXF spec.
+static aiColor4D g_aclrDxfIndexColors[] = {
+    aiColor4D (0.6f, 0.6f, 0.6f, 1.0f),
+    aiColor4D (1.0f, 0.0f, 0.0f, 1.0f), // red
+    aiColor4D (0.0f, 1.0f, 0.0f, 1.0f), // green
+    aiColor4D (0.0f, 0.0f, 1.0f, 1.0f), // blue
+    aiColor4D (0.3f, 1.0f, 0.3f, 1.0f), // light green
+    aiColor4D (0.3f, 0.3f, 1.0f, 1.0f), // light blue
+    aiColor4D (1.0f, 0.3f, 0.3f, 1.0f), // light red
+    aiColor4D (1.0f, 0.0f, 1.0f, 1.0f), // pink
+    aiColor4D (1.0f, 0.6f, 0.0f, 1.0f), // orange
+    aiColor4D (0.6f, 0.3f, 0.0f, 1.0f), // dark orange
+    aiColor4D (1.0f, 1.0f, 0.0f, 1.0f), // yellow
+    aiColor4D (0.3f, 0.3f, 0.3f, 1.0f), // dark gray
+    aiColor4D (0.8f, 0.8f, 0.8f, 1.0f), // light gray
+    aiColor4D (0.0f, 00.f, 0.0f, 1.0f), // black
+    aiColor4D (1.0f, 1.0f, 1.0f, 1.0f), // white
+    aiColor4D (0.6f, 0.0f, 1.0f, 1.0f)  // violet
+};
+#define AI_DXF_NUM_INDEX_COLORS (sizeof(g_aclrDxfIndexColors)/sizeof(g_aclrDxfIndexColors[0]))
+#define AI_DXF_ENTITIES_MAGIC_BLOCK "$ASSIMP_ENTITIES_MAGIC"
+
+static const int GroupCode_Name  = 2;
+static const int GroupCode_XComp = 10;
+static const int GroupCode_YComp = 20;
+static const int GroupCode_ZComp = 30;
+
+static const aiImporterDesc desc = {
+    "Drawing Interchange Format (DXF) Importer",
+    "",
+    "",
+    "",
+    aiImporterFlags_SupportTextFlavour | aiImporterFlags_LimitedSupport,
+    0,
+    0,
+    0,
+    0,
+    "dxf"
+};
+
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by Importer
+DXFImporter::DXFImporter()
+: BaseImporter() {
+    // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+DXFImporter::~DXFImporter() {
+    // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+// Returns whether the class can handle the format of the given file.
+bool DXFImporter::CanRead( const std::string& filename, IOSystem* pIOHandler, bool checkSig ) const {
+    const std::string& extension = GetExtension( filename );
+    if ( extension == desc.mFileExtensions ) {
+        return true;
+    }
+
+    if ( extension.empty() || checkSig ) {
+        const char *pTokens[] = { "SECTION", "HEADER", "ENDSEC", "BLOCKS" };
+        return BaseImporter::SearchFileHeaderForToken(pIOHandler, filename, pTokens, 4, 32 );
+    }
+
+    return false;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Get a list of all supported file extensions
+const aiImporterDesc* DXFImporter::GetInfo () const {
+    return &desc;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Imports the given file into the given scene structure.
+void DXFImporter::InternReadFile( const std::string& filename, aiScene* pScene, IOSystem* pIOHandler) {
+    std::shared_ptr<IOStream> file = std::shared_ptr<IOStream>( pIOHandler->Open( filename) );
+
+    // Check whether we can read the file
+    if( file.get() == nullptr ) {
+        throw DeadlyImportError( "Failed to open DXF file " + filename + "");
+    }
+
+    // Check whether this is a binary DXF file - we can't read binary DXF files :-(
+    char buff[AI_DXF_BINARY_IDENT_LEN+1] = {0};
+    file->Read(buff,AI_DXF_BINARY_IDENT_LEN,1);
+
+    if (0 == strncmp(AI_DXF_BINARY_IDENT.c_str(),buff,AI_DXF_BINARY_IDENT_LEN)) {
+        throw DeadlyImportError("DXF: Binary files are not supported at the moment");
+    }
+
+    // DXF files can grow very large, so read them via the StreamReader,
+    // which will choose a suitable strategy.
+    file->Seek(0,aiOrigin_SET);
+    StreamReaderLE stream( file );
+
+    DXF::LineReader reader (stream);
+    DXF::FileData output;
+
+    // now get all lines of the file and process top-level sections
+    bool eof = false;
+    while(!reader.End()) {
+
+        // blocks table - these 'build blocks' are later (in ENTITIES)
+        // referenced an included via INSERT statements.
+        if (reader.Is(2,"BLOCKS")) {
+            ParseBlocks(reader,output);
+            continue;
+        }
+
+        // primary entity table
+        if (reader.Is(2,"ENTITIES")) {
+            ParseEntities(reader,output);
+            continue;
+        }
+
+        // skip unneeded sections entirely to avoid any problems with them
+        // altogether.
+        else if (reader.Is(2,"CLASSES") || reader.Is(2,"TABLES")) {
+            SkipSection(reader);
+            continue;
+        }
+
+        else if (reader.Is(2,"HEADER")) {
+            ParseHeader(reader,output);
+            continue;
+        }
+
+        // comments
+        else if (reader.Is(999)) {
+            ASSIMP_LOG_INFO_F("DXF Comment: ", reader.Value());
+        }
+
+        // don't read past the official EOF sign
+        else if (reader.Is(0,"EOF")) {
+            eof = true;
+            break;
+        }
+
+        ++reader;
+    }
+    if (!eof) {
+        ASSIMP_LOG_WARN("DXF: EOF reached, but did not encounter DXF EOF marker");
+    }
+
+    ConvertMeshes(pScene,output);
+
+    // Now rotate the whole scene by 90 degrees around the x axis to convert from AutoCAD's to Assimp's coordinate system
+    pScene->mRootNode->mTransformation = aiMatrix4x4(
+        1.f,0.f,0.f,0.f,
+        0.f,0.f,1.f,0.f,
+        0.f,-1.f,0.f,0.f,
+        0.f,0.f,0.f,1.f) * pScene->mRootNode->mTransformation;
+}
+
+// ------------------------------------------------------------------------------------------------
+void DXFImporter::ConvertMeshes(aiScene* pScene, DXF::FileData& output) {
+    // the process of resolving all the INSERT statements can grow the
+    // poly-count excessively, so log the original number.
+    // XXX Option to import blocks as separate nodes?
+    if (!DefaultLogger::isNullLogger()) {
+        unsigned int vcount = 0, icount = 0;
+        for (const DXF::Block& bl : output.blocks) {
+            for (std::shared_ptr<const DXF::PolyLine> pl : bl.lines) {
+                vcount += static_cast<unsigned int>(pl->positions.size());
+                icount += static_cast<unsigned int>(pl->counts.size());
+            }
+        }
+
+        ASSIMP_LOG_DEBUG_F("DXF: Unexpanded polycount is ", icount, ", vertex count is ", vcount);
+    }
+
+    if (! output.blocks.size()  ) {
+        throw DeadlyImportError("DXF: no data blocks loaded");
+    }
+
+    DXF::Block* entities( nullptr );
+
+    // index blocks by name
+    DXF::BlockMap blocks_by_name;
+    for (DXF::Block& bl : output.blocks) {
+        blocks_by_name[bl.name] = &bl;
+        if ( !entities && bl.name == AI_DXF_ENTITIES_MAGIC_BLOCK ) {
+            entities = &bl;
+        }
+    }
+
+    if (!entities) {
+        throw DeadlyImportError("DXF: no ENTITIES data block loaded");
+    }
+
+    typedef std::map<std::string, unsigned int> LayerMap;
+
+    LayerMap layers;
+    std::vector< std::vector< const DXF::PolyLine*> > corr;
+
+    // now expand all block references in the primary ENTITIES block
+    // XXX this involves heavy memory copying, consider a faster solution for future versions.
+    ExpandBlockReferences(*entities,blocks_by_name);
+
+    unsigned int cur = 0;
+    for (std::shared_ptr<const DXF::PolyLine> pl : entities->lines) {
+        if (pl->positions.size()) {
+
+            std::map<std::string, unsigned int>::iterator it = layers.find(pl->layer);
+            if (it == layers.end()) {
+                ++pScene->mNumMeshes;
+
+                layers[pl->layer] = cur++;
+
+                std::vector< const DXF::PolyLine* > pv;
+                pv.push_back(&*pl);
+
+                corr.push_back(pv);
+            }
+            else {
+                corr[(*it).second].push_back(&*pl);
+            }
+        }
+    }
+
+    if ( 0 == pScene->mNumMeshes) {
+        throw DeadlyImportError("DXF: this file contains no 3d data");
+    }
+
+    pScene->mMeshes = new aiMesh*[ pScene->mNumMeshes ] ();
+
+    for(const LayerMap::value_type& elem : layers){
+        aiMesh* const mesh =  pScene->mMeshes[elem.second] = new aiMesh();
+        mesh->mName.Set(elem.first);
+
+        unsigned int cvert = 0,cface = 0;
+        for(const DXF::PolyLine* pl : corr[elem.second]){
+            // sum over all faces since we need to 'verbosify' them.
+            cvert += std::accumulate(pl->counts.begin(),pl->counts.end(),0);
+            cface += static_cast<unsigned int>(pl->counts.size());
+        }
+
+        aiVector3D* verts = mesh->mVertices = new aiVector3D[cvert];
+        aiColor4D* colors = mesh->mColors[0] = new aiColor4D[cvert];
+        aiFace* faces = mesh->mFaces = new aiFace[cface];
+
+        mesh->mNumVertices = cvert;
+        mesh->mNumFaces = cface;
+
+        unsigned int prims = 0;
+        unsigned int overall_indices = 0;
+        for(const DXF::PolyLine* pl : corr[elem.second]){
+
+            std::vector<unsigned int>::const_iterator it = pl->indices.begin();
+            for(unsigned int facenumv : pl->counts) {
+                aiFace& face = *faces++;
+                face.mIndices = new unsigned int[face.mNumIndices = facenumv];
+
+                for (unsigned int i = 0; i < facenumv; ++i) {
+                    face.mIndices[i] = overall_indices++;
+
+                    ai_assert(pl->positions.size() == pl->colors.size());
+                    if (*it >= pl->positions.size()) {
+                        throw DeadlyImportError("DXF: vertex index out of bounds");
+                    }
+
+                    *verts++ = pl->positions[*it];
+                    *colors++ = pl->colors[*it++];
+                }
+
+                // set primitive flags now, this saves the extra pass in ScenePreprocessor.
+                switch(face.mNumIndices) {
+                    case 1:
+                        prims |= aiPrimitiveType_POINT;
+                        break;
+                    case 2:
+                        prims |= aiPrimitiveType_LINE;
+                        break;
+                    case 3:
+                        prims |= aiPrimitiveType_TRIANGLE;
+                        break;
+                    default:
+                        prims |= aiPrimitiveType_POLYGON;
+                        break;
+                }
+            }
+        }
+
+        mesh->mPrimitiveTypes = prims;
+        mesh->mMaterialIndex = 0;
+    }
+
+    GenerateHierarchy(pScene,output);
+    GenerateMaterials(pScene,output);
+}
+
+
+// ------------------------------------------------------------------------------------------------
+void DXFImporter::ExpandBlockReferences(DXF::Block& bl,const DXF::BlockMap& blocks_by_name) {
+    for (const DXF::InsertBlock& insert : bl.insertions) {
+
+        // first check if the referenced blocks exists ...
+        const DXF::BlockMap::const_iterator it = blocks_by_name.find(insert.name);
+        if (it == blocks_by_name.end()) {
+            ASSIMP_LOG_ERROR_F("DXF: Failed to resolve block reference: ", insert.name,"; skipping" );
+            continue;
+        }
+
+        // XXX this would be the place to implement recursive expansion if needed.
+        const DXF::Block& bl_src = *(*it).second;
+
+        for (std::shared_ptr<const DXF::PolyLine> pl_in : bl_src.lines) {
+            std::shared_ptr<DXF::PolyLine> pl_out = std::shared_ptr<DXF::PolyLine>(new DXF::PolyLine(*pl_in));
+
+            if (bl_src.base.Length() || insert.scale.x!=1.f || insert.scale.y!=1.f || insert.scale.z!=1.f || insert.angle || insert.pos.Length()) {
+                // manual coordinate system transformation
+                // XXX order
+                aiMatrix4x4 trafo, tmp;
+                aiMatrix4x4::Translation(-bl_src.base,trafo);
+                trafo *= aiMatrix4x4::Scaling(insert.scale,tmp);
+                trafo *= aiMatrix4x4::Translation(insert.pos,tmp);
+
+                // XXX rotation currently ignored - I didn't find an appropriate sample model.
+                if (insert.angle != 0.f) {
+                    ASSIMP_LOG_WARN("DXF: BLOCK rotation not currently implemented");
+                }
+
+                for (aiVector3D& v : pl_out->positions) {
+                    v *= trafo;
+                }
+            }
+
+            bl.lines.push_back(pl_out);
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void DXFImporter::GenerateMaterials(aiScene* pScene, DXF::FileData& /*output*/) {
+    // generate an almost-white default material. Reason:
+    // the default vertex color is GREY, so we are
+    // already at Assimp's usual default color.
+    // generate a default material
+    aiMaterial* pcMat = new aiMaterial();
+    aiString s;
+    s.Set(AI_DEFAULT_MATERIAL_NAME);
+    pcMat->AddProperty(&s, AI_MATKEY_NAME);
+
+    aiColor4D clrDiffuse(0.9f,0.9f,0.9f,1.0f);
+    pcMat->AddProperty(&clrDiffuse,1,AI_MATKEY_COLOR_DIFFUSE);
+
+    clrDiffuse = aiColor4D(1.0f,1.0f,1.0f,1.0f);
+    pcMat->AddProperty(&clrDiffuse,1,AI_MATKEY_COLOR_SPECULAR);
+
+    clrDiffuse = aiColor4D(0.05f,0.05f,0.05f,1.0f);
+    pcMat->AddProperty(&clrDiffuse,1,AI_MATKEY_COLOR_AMBIENT);
+
+    pScene->mNumMaterials = 1;
+    pScene->mMaterials = new aiMaterial*[1];
+    pScene->mMaterials[0] = pcMat;
+}
+
+// ------------------------------------------------------------------------------------------------
+void DXFImporter::GenerateHierarchy(aiScene* pScene, DXF::FileData& /*output*/) {
+    // generate the output scene graph, which is just the root node with a single child for each layer.
+    pScene->mRootNode = new aiNode();
+    pScene->mRootNode->mName.Set("<DXF_ROOT>");
+
+    if (1 == pScene->mNumMeshes)    {
+        pScene->mRootNode->mMeshes = new unsigned int[ pScene->mRootNode->mNumMeshes = 1 ];
+        pScene->mRootNode->mMeshes[0] = 0;
+    } else {
+        pScene->mRootNode->mChildren = new aiNode*[ pScene->mRootNode->mNumChildren = pScene->mNumMeshes ];
+        for (unsigned int m = 0; m < pScene->mRootNode->mNumChildren;++m)   {
+            aiNode* p = pScene->mRootNode->mChildren[m] = new aiNode();
+            p->mName = pScene->mMeshes[m]->mName;
+
+            p->mMeshes = new unsigned int[p->mNumMeshes = 1];
+            p->mMeshes[0] = m;
+            p->mParent = pScene->mRootNode;
+        }
+    }
+}
+
+
+// ------------------------------------------------------------------------------------------------
+void DXFImporter::SkipSection(DXF::LineReader& reader) {
+    for( ;!reader.End() && !reader.Is(0,"ENDSEC"); reader++);
+}
+
+// ------------------------------------------------------------------------------------------------
+void DXFImporter::ParseHeader(DXF::LineReader& reader, DXF::FileData& ) {
+    for( ;!reader.End() && !reader.Is(0,"ENDSEC"); reader++);
+}
+
+// ------------------------------------------------------------------------------------------------
+void DXFImporter::ParseBlocks(DXF::LineReader& reader, DXF::FileData& output) {
+    while( !reader.End() && !reader.Is(0,"ENDSEC")) {
+        if (reader.Is(0,"BLOCK")) {
+            ParseBlock(++reader,output);
+            continue;
+        }
+        ++reader;
+    }
+
+    ASSIMP_LOG_DEBUG_F("DXF: got ", output.blocks.size()," entries in BLOCKS" );
+}
+
+// ------------------------------------------------------------------------------------------------
+void DXFImporter::ParseBlock(DXF::LineReader& reader, DXF::FileData& output) {
+    // push a new block onto the stack.
+    output.blocks.push_back( DXF::Block() );
+    DXF::Block& block = output.blocks.back();
+
+    while( !reader.End() && !reader.Is(0,"ENDBLK")) {
+
+        switch(reader.GroupCode()) {
+            case GroupCode_Name:
+                block.name = reader.Value();
+                break;
+
+            case GroupCode_XComp:
+                block.base.x = reader.ValueAsFloat();
+                break;
+            case GroupCode_YComp:
+                block.base.y = reader.ValueAsFloat();
+                break;
+            case GroupCode_ZComp:
+                block.base.z = reader.ValueAsFloat();
+                break;
+        }
+
+        if (reader.Is(0,"POLYLINE")) {
+            ParsePolyLine(++reader,output);
+            continue;
+        }
+
+        // XXX is this a valid case?
+        if (reader.Is(0,"INSERT")) {
+            ASSIMP_LOG_WARN("DXF: INSERT within a BLOCK not currently supported; skipping");
+            for( ;!reader.End() && !reader.Is(0,"ENDBLK"); ++reader);
+            break;
+        }
+
+        else if (reader.Is(0,"3DFACE") || reader.Is(0,"LINE") || reader.Is(0,"3DLINE")) {
+            //http://sourceforge.net/tracker/index.php?func=detail&aid=2970566&group_id=226462&atid=1067632
+            Parse3DFace(++reader, output);
+            continue;
+        }
+        ++reader;
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void DXFImporter::ParseEntities(DXF::LineReader& reader, DXF::FileData& output) {
+    // Push a new block onto the stack.
+    output.blocks.push_back( DXF::Block() );
+    DXF::Block& block = output.blocks.back();
+
+    block.name = AI_DXF_ENTITIES_MAGIC_BLOCK;
+
+    while( !reader.End() && !reader.Is(0,"ENDSEC")) {
+        if (reader.Is(0,"POLYLINE")) {
+            ParsePolyLine(++reader,output);
+            continue;
+        }
+
+        else if (reader.Is(0,"INSERT")) {
+            ParseInsertion(++reader,output);
+            continue;
+        }
+
+        else if (reader.Is(0,"3DFACE") || reader.Is(0,"LINE") || reader.Is(0,"3DLINE")) {
+            //http://sourceforge.net/tracker/index.php?func=detail&aid=2970566&group_id=226462&atid=1067632
+            Parse3DFace(++reader, output);
+            continue;
+        }
+
+        ++reader;
+    }
+
+    ASSIMP_LOG_DEBUG_F( "DXF: got ", block.lines.size()," polylines and ", block.insertions.size(), 
+        " inserted blocks in ENTITIES" );
+}
+
+void DXFImporter::ParseInsertion(DXF::LineReader& reader, DXF::FileData& output) {
+    output.blocks.back().insertions.push_back( DXF::InsertBlock() );
+    DXF::InsertBlock& bl = output.blocks.back().insertions.back();
+
+    while( !reader.End() && !reader.Is(0)) {
+        switch(reader.GroupCode()) {
+            // name of referenced block
+        case GroupCode_Name:
+            bl.name = reader.Value();
+            break;
+
+            // translation
+        case GroupCode_XComp:
+            bl.pos.x = reader.ValueAsFloat();
+            break;
+        case GroupCode_YComp:
+            bl.pos.y = reader.ValueAsFloat();
+            break;
+        case GroupCode_ZComp:
+            bl.pos.z = reader.ValueAsFloat();
+            break;
+
+            // scaling
+        case 41:
+            bl.scale.x = reader.ValueAsFloat();
+            break;
+        case 42:
+            bl.scale.y = reader.ValueAsFloat();
+            break;
+        case 43:
+            bl.scale.z = reader.ValueAsFloat();
+            break;
+
+            // rotation angle
+        case 50:
+            bl.angle = reader.ValueAsFloat();
+            break;
+        }
+        reader++;
+    }
+}
+
+#define DXF_POLYLINE_FLAG_CLOSED        0x1
+#define DXF_POLYLINE_FLAG_3D_POLYLINE   0x8
+#define DXF_POLYLINE_FLAG_3D_POLYMESH   0x10
+#define DXF_POLYLINE_FLAG_POLYFACEMESH  0x40
+
+// ------------------------------------------------------------------------------------------------
+void DXFImporter::ParsePolyLine(DXF::LineReader& reader, DXF::FileData& output) {
+    output.blocks.back().lines.push_back( std::shared_ptr<DXF::PolyLine>( new DXF::PolyLine() ) );
+    DXF::PolyLine& line = *output.blocks.back().lines.back();
+
+    unsigned int iguess = 0, vguess = 0;
+    while( !reader.End() && !reader.Is(0,"ENDSEC")) {
+
+        if (reader.Is(0,"VERTEX")) {
+            ParsePolyLineVertex(++reader,line);
+            if (reader.Is(0,"SEQEND")) {
+                break;
+            }
+            continue;
+        }
+
+        switch(reader.GroupCode())
+        {
+        // flags --- important that we know whether it is a
+        // polyface mesh or 'just' a line.
+        case 70:
+            if (!line.flags)    {
+                line.flags = reader.ValueAsSignedInt();
+            }
+            break;
+
+        // optional number of vertices
+        case 71:
+            vguess = reader.ValueAsSignedInt();
+            line.positions.reserve(vguess);
+            break;
+
+        // optional number of faces
+        case 72:
+            iguess = reader.ValueAsSignedInt();
+            line.indices.reserve(iguess);
+            break;
+
+        // 8 specifies the layer on which this line is placed on
+        case 8:
+            line.layer = reader.Value();
+            break;
+        }
+
+        reader++;
+    }
+
+    //if (!(line.flags & DXF_POLYLINE_FLAG_POLYFACEMESH))   {
+    //  DefaultLogger::get()->warn((Formatter::format("DXF: polyline not currently supported: "),line.flags));
+    //  output.blocks.back().lines.pop_back();
+    //  return;
+    //}
+
+    if (vguess && line.positions.size() != vguess) {
+        ASSIMP_LOG_WARN_F("DXF: unexpected vertex count in polymesh: ",
+            line.positions.size(),", expected ", vguess );
+    }
+
+    if (line.flags & DXF_POLYLINE_FLAG_POLYFACEMESH ) {
+        if (line.positions.size() < 3 || line.indices.size() < 3)   {
+            ASSIMP_LOG_WARN("DXF: not enough vertices for polymesh; ignoring");
+            output.blocks.back().lines.pop_back();
+            return;
+        }
+
+        // if these numbers are wrong, parsing might have gone wild.
+        // however, the docs state that applications are not required
+        // to set the 71 and 72 fields, respectively, to valid values.
+        // So just fire a warning.
+        if (iguess && line.counts.size() != iguess) {
+            ASSIMP_LOG_WARN_F( "DXF: unexpected face count in polymesh: ", line.counts.size(),", expected ", iguess );
+        }
+    }
+    else if (!line.indices.size() && !line.counts.size()) {
+        // a poly-line - so there are no indices yet.
+        size_t guess = line.positions.size() + (line.flags & DXF_POLYLINE_FLAG_CLOSED ? 1 : 0);
+        line.indices.reserve(guess);
+
+        line.counts.reserve(guess/2);
+        for (unsigned int i = 0; i < line.positions.size()/2; ++i) {
+            line.indices.push_back(i*2);
+            line.indices.push_back(i*2+1);
+            line.counts.push_back(2);
+        }
+
+        // closed polyline?
+        if (line.flags & DXF_POLYLINE_FLAG_CLOSED) {
+            line.indices.push_back(static_cast<unsigned int>(line.positions.size()-1));
+            line.indices.push_back(0);
+            line.counts.push_back(2);
+        }
+    }
+}
+
+#define DXF_VERTEX_FLAG_PART_OF_POLYFACE 0x80
+#define DXF_VERTEX_FLAG_HAS_POSITIONS 0x40
+
+// ------------------------------------------------------------------------------------------------
+void DXFImporter::ParsePolyLineVertex(DXF::LineReader& reader, DXF::PolyLine& line) {
+    unsigned int cnti = 0, flags = 0;
+    unsigned int indices[4];
+
+    aiVector3D out;
+    aiColor4D clr = AI_DXF_DEFAULT_COLOR;
+
+    while( !reader.End() ) {
+
+        if (reader.Is(0)) { // SEQEND or another VERTEX
+            break;
+        }
+
+        switch (reader.GroupCode()) {
+        case 8:
+                // layer to which the vertex belongs to - assume that
+                // this is always the layer the top-level poly-line
+                // entity resides on as well.
+                if(reader.Value() != line.layer) {
+                    ASSIMP_LOG_WARN("DXF: expected vertex to be part of a poly-face but the 0x128 flag isn't set");
+                }
+                break;
+
+        case 70:
+                flags = reader.ValueAsUnsignedInt();
+                break;
+
+        // VERTEX COORDINATES
+        case GroupCode_XComp:
+            out.x = reader.ValueAsFloat();
+            break;
+
+        case GroupCode_YComp:
+            out.y = reader.ValueAsFloat();
+            break;
+
+        case GroupCode_ZComp:
+            out.z = reader.ValueAsFloat();
+            break;
+
+        // POLYFACE vertex indices
+        case 71:
+        case 72:
+        case 73:
+        case 74:
+            if (cnti == 4) {
+                ASSIMP_LOG_WARN("DXF: more than 4 indices per face not supported; ignoring");
+                break;
+            }
+            indices[cnti++] = reader.ValueAsUnsignedInt();
+            break;
+
+        // color
+        case 62:
+            clr = g_aclrDxfIndexColors[reader.ValueAsUnsignedInt() % AI_DXF_NUM_INDEX_COLORS];
+            break;
+        };
+
+        reader++;
+    }
+
+    if (line.flags & DXF_POLYLINE_FLAG_POLYFACEMESH && !(flags & DXF_VERTEX_FLAG_PART_OF_POLYFACE)) {
+        ASSIMP_LOG_WARN("DXF: expected vertex to be part of a polyface but the 0x128 flag isn't set");
+    }
+
+    if (cnti) {
+        line.counts.push_back(cnti);
+        for (unsigned int i = 0; i < cnti; ++i) {
+            // IMPORTANT NOTE: POLYMESH indices are ONE-BASED
+            if (indices[i] == 0) {
+                ASSIMP_LOG_WARN("DXF: invalid vertex index, indices are one-based.");
+                --line.counts.back();
+                // Workaround to fix issue 2229
+                if (line.counts.back() == 0) {
+                    line.counts.pop_back();
+                }
+                continue;
+            }
+            line.indices.push_back(indices[i]-1);
+        }
+    } else {
+        line.positions.push_back(out);
+        line.colors.push_back(clr);
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void DXFImporter::Parse3DFace(DXF::LineReader& reader, DXF::FileData& output)
+{
+    // (note) this is also used for for parsing line entities, so we
+    // must handle the vertex_count == 2 case as well.
+
+    output.blocks.back().lines.push_back( std::shared_ptr<DXF::PolyLine>( new DXF::PolyLine() )  );
+    DXF::PolyLine& line = *output.blocks.back().lines.back();
+
+    aiVector3D vip[4];
+    aiColor4D  clr = AI_DXF_DEFAULT_COLOR;
+
+    bool b[4] = {false,false,false,false};
+    while( !reader.End() ) {
+
+        // next entity with a groupcode == 0 is probably already the next vertex or polymesh entity
+        if (reader.GroupCode() == 0) {
+            break;
+        }
+        switch (reader.GroupCode())
+        {
+
+        // 8 specifies the layer
+        case 8:
+            line.layer = reader.Value();
+            break;
+
+        // x position of the first corner
+        case 10:
+            vip[0].x = reader.ValueAsFloat();
+            b[2] = true;
+            break;
+
+        // y position of the first corner
+        case 20:
+            vip[0].y = reader.ValueAsFloat();
+            b[2] = true;
+            break;
+
+        // z position of the first corner
+        case 30:
+            vip[0].z = reader.ValueAsFloat();
+            b[2] = true;
+            break;
+
+        // x position of the second corner
+        case 11:
+            vip[1].x = reader.ValueAsFloat();
+            b[3] = true;
+            break;
+
+        // y position of the second corner
+        case 21:
+            vip[1].y = reader.ValueAsFloat();
+            b[3] = true;
+            break;
+
+        // z position of the second corner
+        case 31:
+            vip[1].z = reader.ValueAsFloat();
+            b[3] = true;
+            break;
+
+        // x position of the third corner
+        case 12:
+            vip[2].x = reader.ValueAsFloat();
+            b[0] = true;
+            break;
+
+        // y position of the third corner
+        case 22:
+            vip[2].y = reader.ValueAsFloat();
+            b[0] = true;
+            break;
+
+        // z position of the third corner
+        case 32:
+            vip[2].z = reader.ValueAsFloat();
+            b[0] = true;
+            break;
+
+        // x position of the fourth corner
+        case 13:
+            vip[3].x = reader.ValueAsFloat();
+            b[1] = true;
+            break;
+
+        // y position of the fourth corner
+        case 23:
+            vip[3].y = reader.ValueAsFloat();
+            b[1] = true;
+            break;
+
+        // z position of the fourth corner
+        case 33:
+            vip[3].z = reader.ValueAsFloat();
+            b[1] = true;
+            break;
+
+        // color
+        case 62:
+            clr = g_aclrDxfIndexColors[reader.ValueAsUnsignedInt() % AI_DXF_NUM_INDEX_COLORS];
+            break;
+        };
+
+        ++reader;
+    }
+
+    // the fourth corner may even be identical to the third,
+    // in this case we treat it as if it didn't exist.
+    if (vip[3] == vip[2]) {
+        b[1] = false;
+    }
+
+    // sanity checks to see if we got something meaningful
+    if ((b[1] && !b[0]) || !b[2] || !b[3]) {
+        ASSIMP_LOG_WARN("DXF: unexpected vertex setup in 3DFACE/LINE/FACE entity; ignoring");
+        output.blocks.back().lines.pop_back();
+        return;
+    }
+
+    const unsigned int cnt = (2+(b[0]?1:0)+(b[1]?1:0));
+    line.counts.push_back(cnt);
+
+    for (unsigned int i = 0; i < cnt; ++i) {
+        line.indices.push_back(static_cast<unsigned int>(line.positions.size()));
+        line.positions.push_back(vip[i]);
+        line.colors.push_back(clr);
+    }
+}
+
+#endif // !! ASSIMP_BUILD_NO_DXF_IMPORTER

+ 149 - 0
Engine/lib/assimp/code/DXFLoader.h

@@ -0,0 +1,149 @@
+/*
+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  DXFLoader.h
+ *  @brief Declaration of the .dxf importer class.
+ */
+#ifndef AI_DXFLOADER_H_INCLUDED
+#define AI_DXFLOADER_H_INCLUDED
+
+#include <assimp/BaseImporter.h>
+#include <map>
+
+namespace Assimp    {
+
+// Forward declarations
+namespace DXF {
+    class LineReader;
+    struct FileData;
+    struct PolyLine;
+    struct Block;
+    struct InsertBlock;
+
+    typedef std::map<std::string, const DXF::Block*> BlockMap;
+}
+
+// ---------------------------------------------------------------------------
+/** 
+ *  @brief  DXF importer implementation.
+ */
+class DXFImporter : public BaseImporter {
+public:
+    DXFImporter();
+    ~DXFImporter();
+
+    // -------------------------------------------------------------------
+    /** Returns whether the class can handle the format of the given file.
+    * See BaseImporter::CanRead() for details.  */
+    bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
+        bool checkSig) const;
+
+protected:
+    // -------------------------------------------------------------------
+    /** Return importer meta information.
+     * See #BaseImporter::GetInfo for the details*/
+    const aiImporterDesc* GetInfo () const;
+
+    // -------------------------------------------------------------------
+    /** Imports the given file into the given scene structure.
+     * See BaseImporter::InternReadFile() for details */
+    void InternReadFile( const std::string& pFile,
+        aiScene* pScene,
+        IOSystem* pIOHandler);
+
+private:
+
+    // -----------------------------------------------------
+    void SkipSection(DXF::LineReader& reader);
+
+    // -----------------------------------------------------
+    void ParseHeader(DXF::LineReader& reader,
+        DXF::FileData& output);
+
+    // -----------------------------------------------------
+    void ParseEntities(DXF::LineReader& reader,
+        DXF::FileData& output);
+
+    // -----------------------------------------------------
+    void ParseBlocks(DXF::LineReader& reader,
+        DXF::FileData& output);
+
+    // -----------------------------------------------------
+    void ParseBlock(DXF::LineReader& reader,
+        DXF::FileData& output);
+
+    // -----------------------------------------------------
+    void ParseInsertion(DXF::LineReader& reader,
+        DXF::FileData& output);
+
+    // -----------------------------------------------------
+    void ParsePolyLine(DXF::LineReader& reader,
+        DXF::FileData& output);
+
+    // -----------------------------------------------------
+    void ParsePolyLineVertex(DXF::LineReader& reader,
+        DXF::PolyLine& line);
+
+    // -----------------------------------------------------
+    void Parse3DFace(DXF::LineReader& reader,
+        DXF::FileData& output);
+
+    // -----------------------------------------------------
+    void ConvertMeshes(aiScene* pScene,
+        DXF::FileData& output);
+
+    // -----------------------------------------------------
+    void GenerateHierarchy(aiScene* pScene,
+        DXF::FileData& output);
+
+    // -----------------------------------------------------
+    void GenerateMaterials(aiScene* pScene,
+        DXF::FileData& output);
+
+    // -----------------------------------------------------
+    void ExpandBlockReferences(DXF::Block& bl,
+        const DXF::BlockMap& blocks_by_name);
+};
+
+} // end of namespace Assimp
+
+#endif // AI_3DSIMPORTER_H_INC

+ 465 - 0
Engine/lib/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
Engine/lib/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
Engine/lib/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
Engine/lib/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
Engine/lib/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
Engine/lib/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
Engine/lib/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
Engine/lib/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

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