Selaa lähdekoodia

Merge branch 'master' into fix_obj_progress_reporting

Kim Kulling 6 vuotta sitten
vanhempi
commit
19c3cea0db

+ 3 - 0
code/CMakeLists.txt

@@ -966,6 +966,9 @@ if( MSVC )
   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

+ 5 - 2
code/ColladaLoader.cpp

@@ -728,8 +728,11 @@ aiMesh* ColladaLoader::CreateMesh( const ColladaParser& pParser, const Collada::
         std::vector<aiAnimMesh*> animMeshes;
         for (unsigned int i = 0; i < targetMeshes.size(); i++)
         {
-            aiAnimMesh *animMesh = aiCreateAnimMesh(targetMeshes.at(i));
-            animMesh->mWeight = targetWeights[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)

+ 63 - 3
code/ConvertToLHProcess.cpp

@@ -166,8 +166,9 @@ void MakeLeftHandedProcess::ProcessMesh( aiMesh* pMesh) {
     for( size_t a = 0; a < pMesh->mNumVertices; ++a)
     {
         pMesh->mVertices[a].z *= -1.0f;
-        if( pMesh->HasNormals())
+        if (pMesh->HasNormals()) {
             pMesh->mNormals[a].z *= -1.0f;
+        }
         if( pMesh->HasTangentsAndBitangents())
         {
             pMesh->mTangents[a].z *= -1.0f;
@@ -175,6 +176,23 @@ void MakeLeftHandedProcess::ProcessMesh( aiMesh* pMesh) {
         }
     }
 
+    // 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)
     {
@@ -346,8 +364,50 @@ void FlipWindingOrderProcess::ProcessMesh( aiMesh* pMesh)
     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]);
+        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]);
+                }
+            }
+        }
     }
 }
 

+ 12 - 0
code/DefaultIOSystem.cpp

@@ -76,6 +76,7 @@ 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) {
 
@@ -85,12 +86,15 @@ bool DefaultIOSystem::Exists( const char* pFile) const
             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)
@@ -110,14 +114,18 @@ IOStream* DefaultIOSystem::Open( const char* strFile, const char* 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
@@ -158,6 +166,7 @@ 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];
@@ -175,6 +184,7 @@ inline static void MakeAbsolutePath (const char* in, char* _out)
         }
 
     } else {
+#endif
         char* ret = :: _fullpath(_out, in, PATHLIMIT);
         if (!ret) {
             // preserve the input path, maybe someone else is able to fix
@@ -182,7 +192,9 @@ inline static void MakeAbsolutePath (const char* in, char* _out)
             ASSIMP_LOG_WARN_F("Invalid path: ", std::string(in));
             strcpy(_out, in);
         }
+#ifndef WindowsStore
     }
+#endif
 #else
     // use realpath
     char* ret = realpath(in, _out);

+ 2 - 2
code/FBXAnimation.cpp

@@ -105,8 +105,8 @@ AnimationCurveNode::AnimationCurveNode(uint64_t id, const Element& element, cons
     const Scope& sc = GetRequiredScope(element);
 
     // find target node
-    const char* whitelist[] = {"Model","NodeAttribute"};
-    const std::vector<const Connection*>& conns = doc.GetConnectionsBySourceSequenced(ID(),whitelist,2);
+    const char* whitelist[] = {"Model","NodeAttribute","Deformer"};
+    const std::vector<const Connection*>& conns = doc.GetConnectionsBySourceSequenced(ID(),whitelist,3);
 
     for(const Connection* con : conns) {
 

+ 2858 - 2666
code/FBXConverter.cpp

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

+ 10 - 0
code/FBXConverter.h

@@ -63,6 +63,12 @@ struct aiScene;
 struct aiNode;
 struct aiMaterial;
 
+struct morphKeyData {
+    std::vector<unsigned int> values;
+    std::vector<float> weights;
+};
+typedef std::map<int64_t, morphKeyData*> morphAnimData;
+
 namespace Assimp {
 namespace FBX {
 
@@ -272,6 +278,7 @@ private:
     // the function is guaranteed to provide consistent results over multiple invocations
     // UNLESS RenameNode() is called for a particular node name.
     std::string FixNodeName(const std::string& name);
+    std::string FixAnimMeshName(const std::string& name);
 
     typedef std::map<const AnimationCurveNode*, const AnimationLayer*> LayerMap;
 
@@ -281,6 +288,9 @@ private:
     // ------------------------------------------------------------------------------------------------
     void ConvertAnimationStack(const AnimationStack& st);
 
+    // ------------------------------------------------------------------------------------------------
+    void ProcessMorphAnimDatas(std::map<std::string, morphAnimData*>* morphAnimDatas, const BlendShapeChannel* bsc, const AnimationCurveNode* node);
+
     // ------------------------------------------------------------------------------------------------
     void GenerateNodeAnimations(std::vector<aiNodeAnim*>& node_anims,
         const std::string& fixed_name,

+ 47 - 0
code/FBXDeformer.cpp

@@ -48,6 +48,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #include "FBXParser.h"
 #include "FBXDocument.h"
+#include "FBXMeshGeometry.h"
 #include "FBXImporter.h"
 #include "FBXDocumentUtil.h"
 
@@ -158,9 +159,55 @@ Skin::~Skin()
 {
 
 }
+// ------------------------------------------------------------------------------------------------
+BlendShape::BlendShape(uint64_t id, const Element& element, const Document& doc, const std::string& name)
+    : Deformer(id, element, doc, name)
+{
+    const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(ID(), "Deformer");
+    blendShapeChannels.reserve(conns.size());
+    for (const Connection* con : conns) {
+        const BlendShapeChannel* const bspc = ProcessSimpleConnection<BlendShapeChannel>(*con, false, "BlendShapeChannel -> BlendShape", element);
+        if (bspc) {
+            blendShapeChannels.push_back(bspc);
+            continue;
+        }
+    }
+}
+// ------------------------------------------------------------------------------------------------
+BlendShape::~BlendShape()
+{
 
 }
+// ------------------------------------------------------------------------------------------------
+BlendShapeChannel::BlendShapeChannel(uint64_t id, const Element& element, const Document& doc, const std::string& name)
+    : Deformer(id, element, doc, name)
+{
+    const Scope& sc = GetRequiredScope(element);
+    const Element* const DeformPercent = sc["DeformPercent"];
+    if (DeformPercent) {
+        percent = ParseTokenAsFloat(GetRequiredToken(*DeformPercent, 0));
+    }
+    const Element* const FullWeights = sc["FullWeights"];
+    if (FullWeights) {
+        ParseVectorDataArray(fullWeights, *FullWeights);
+    }
+    const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(ID(), "Geometry");
+    shapeGeometries.reserve(conns.size());
+    for (const Connection* con : conns) {
+        const ShapeGeometry* const sg = ProcessSimpleConnection<ShapeGeometry>(*con, false, "Shape -> BlendShapeChannel", element);
+        if (sg) {
+            shapeGeometries.push_back(sg);
+            continue;
+        }
+    }
 }
+// ------------------------------------------------------------------------------------------------
+BlendShapeChannel::~BlendShapeChannel()
+{
 
+}
+// ------------------------------------------------------------------------------------------------
+}
+}
 #endif
 

+ 9 - 0
code/FBXDocument.cpp

@@ -146,6 +146,9 @@ const Object* LazyObject::Get(bool dieOnError)
             if (!strcmp(classtag.c_str(),"Mesh")) {
                 object.reset(new MeshGeometry(id,element,name,doc));
             }
+            if (!strcmp(classtag.c_str(), "Shape")) {
+                object.reset(new ShapeGeometry(id, element, name, doc));
+            }
         }
         else if (!strncmp(obtype,"NodeAttribute",length)) {
             if (!strcmp(classtag.c_str(),"Camera")) {
@@ -171,6 +174,12 @@ const Object* LazyObject::Get(bool dieOnError)
             else if (!strcmp(classtag.c_str(),"Skin")) {
                 object.reset(new Skin(id,element,doc,name));
             }
+            else if (!strcmp(classtag.c_str(), "BlendShape")) {
+                object.reset(new BlendShape(id, element, doc, name));
+            }
+            else if (!strcmp(classtag.c_str(), "BlendShapeChannel")) {
+                object.reset(new BlendShapeChannel(id, element, doc, name));
+            }
         }
         else if ( !strncmp( obtype, "Model", length ) ) {
             // FK and IK effectors are not supported

+ 43 - 0
code/FBXDocument.h

@@ -65,6 +65,7 @@ struct ImportSettings;
 class PropertyTable;
 class Document;
 class Material;
+class ShapeGeometry;
 class Geometry;
 
 class Video;
@@ -74,6 +75,8 @@ class AnimationCurveNode;
 class AnimationLayer;
 class AnimationStack;
 
+class BlendShapeChannel;
+class BlendShape;
 class Skin;
 class Cluster;
 
@@ -869,6 +872,46 @@ private:
 typedef std::vector<float> WeightArray;
 typedef std::vector<unsigned int> WeightIndexArray;
 
+
+/** DOM class for BlendShapeChannel deformers */
+class BlendShapeChannel : public Deformer
+{
+public:
+    BlendShapeChannel(uint64_t id, const Element& element, const Document& doc, const std::string& name);
+    virtual ~BlendShapeChannel();
+
+    float DeformPercent() const {
+        return percent;
+    }
+
+    const WeightArray& GetFullWeights() const {
+        return fullWeights;
+    }
+
+    const std::vector<const ShapeGeometry*>& GetShapeGeometries() const {
+        return shapeGeometries;
+    }
+private:
+    float percent;
+    WeightArray fullWeights;
+    std::vector<const ShapeGeometry*> shapeGeometries;
+};
+
+/** DOM class for BlendShape deformers */
+class BlendShape : public Deformer
+{
+public:
+    BlendShape(uint64_t id, const Element& element, const Document& doc, const std::string& name);
+    virtual ~BlendShape();
+
+    const std::vector<const BlendShapeChannel*>& BlendShapeChannels() const {
+        return blendShapeChannels;
+    }
+
+private:
+    std::vector<const BlendShapeChannel*> blendShapeChannels;
+};
+
 /** DOM class for skin deformer clusters (aka subdeformers) */
 class Cluster : public Deformer
 {

+ 42 - 5
code/FBXMeshGeometry.cpp

@@ -62,7 +62,7 @@ using namespace Util;
 
 // ------------------------------------------------------------------------------------------------
 Geometry::Geometry(uint64_t id, const Element& element, const std::string& name, const Document& doc)
-    : Object(id, element,name)
+    : Object(id, element, name)
     , skin()
 {
     const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(ID(),"Deformer");
@@ -70,18 +70,26 @@ Geometry::Geometry(uint64_t id, const Element& element, const std::string& name,
         const Skin* const sk = ProcessSimpleConnection<Skin>(*con, false, "Skin -> Geometry", element);
         if(sk) {
             skin = sk;
-            break;
+        }
+        const BlendShape* const bsp = ProcessSimpleConnection<BlendShape>(*con, false, "BlendShape -> Geometry", element);
+        if (bsp) {
+            blendShapes.push_back(bsp);
         }
     }
 }
 
-
 // ------------------------------------------------------------------------------------------------
 Geometry::~Geometry()
 {
     // empty
 }
 
+// ------------------------------------------------------------------------------------------------
+const std::vector<const BlendShape*>& Geometry::GetBlendShapes() const {
+    return blendShapes;
+}
+
+// ------------------------------------------------------------------------------------------------
 const Skin* Geometry::DeformerSkin() const {
     return skin;
 }
@@ -232,7 +240,6 @@ const std::vector<aiColor4D>& MeshGeometry::GetVertexColors( unsigned int index
 const MatIndexArray& MeshGeometry::GetMaterialIndices() const {
     return m_materials;
 }
-
 // ------------------------------------------------------------------------------------------------
 const unsigned int* MeshGeometry::ToOutputVertexIndex( unsigned int in_index, unsigned int& count ) const {
     if ( in_index >= m_mapping_counts.size() ) {
@@ -640,9 +647,39 @@ void MeshGeometry::ReadVertexDataMaterials(std::vector<int>& materials_out, cons
             << MappingInformationType << "," << ReferenceInformationType);
     }
 }
+// ------------------------------------------------------------------------------------------------
+ShapeGeometry::ShapeGeometry(uint64_t id, const Element& element, const std::string& name, const Document& doc)
+    : Geometry(id, element, name, doc)
+{
+    const Scope* sc = element.Compound();
+    if (!sc) {
+        DOMError("failed to read Geometry object (class: Shape), no data scope found");
+    }
+    const Element& Indexes = GetRequiredElement(*sc, "Indexes", &element);
+    const Element& Normals = GetRequiredElement(*sc, "Normals", &element);
+    const Element& Vertices = GetRequiredElement(*sc, "Vertices", &element);
+    ParseVectorDataArray(m_indices, Indexes);
+    ParseVectorDataArray(m_vertices, Vertices);
+    ParseVectorDataArray(m_normals, Normals);
+}
 
+// ------------------------------------------------------------------------------------------------
+ShapeGeometry::~ShapeGeometry() {
+    // empty
+}
+// ------------------------------------------------------------------------------------------------
+const std::vector<aiVector3D>& ShapeGeometry::GetVertices() const {
+    return m_vertices;
+}
+// ------------------------------------------------------------------------------------------------
+const std::vector<aiVector3D>& ShapeGeometry::GetNormals() const {
+    return m_normals;
+}
+// ------------------------------------------------------------------------------------------------
+const std::vector<unsigned int>& ShapeGeometry::GetIndices() const {
+    return m_indices;
+}
 } // !FBX
 } // !Assimp
-
 #endif
 

+ 33 - 1
code/FBXMeshGeometry.h

@@ -64,8 +64,13 @@ public:
     /** Get the Skin attached to this geometry or NULL */
     const Skin* DeformerSkin() const;
 
+    /** Get the BlendShape attached to this geometry or NULL */
+    const std::vector<const BlendShape*>& GetBlendShapes() const;
+
 private:
     const Skin* skin;
+    std::vector<const BlendShape*> blendShapes;
+
 };
 
 typedef std::vector<int> MatIndexArray;
@@ -125,7 +130,6 @@ public:
     /** Determine the face to which a particular output vertex index belongs.
     *  This mapping is always unique. */
     unsigned int FaceForVertexIndex( unsigned int in_index ) const;
-
 private:
     void ReadLayer( const Scope& layer );
     void ReadLayerElement( const Scope& layerElement );
@@ -174,6 +178,34 @@ private:
     std::vector<unsigned int> m_mappings;
 };
 
+/**
+*  DOM class for FBX geometry of type "Shape"
+*/
+class ShapeGeometry : public Geometry
+{
+public:
+    /** The class constructor */
+    ShapeGeometry(uint64_t id, const Element& element, const std::string& name, const Document& doc);
+
+    /** The class destructor */
+    virtual ~ShapeGeometry();
+
+    /** Get a list of all vertex points, non-unique*/
+    const std::vector<aiVector3D>& GetVertices() const;
+
+    /** Get a list of all vertex normals or an empty array if
+    *  no normals are specified. */
+    const std::vector<aiVector3D>& GetNormals() const;
+
+    /** Return list of vertex indices. */
+    const std::vector<unsigned int>& GetIndices() const;
+
+private:
+    std::vector<aiVector3D> m_vertices;
+    std::vector<aiVector3D> m_normals;
+    std::vector<unsigned int> m_indices;
+};
+
 }
 }
 

+ 14 - 3
code/Importer/IFC/IFCLoader.cpp

@@ -207,10 +207,21 @@ void IFCImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOS
                 }
                 uint8_t* buff = new uint8_t[fileInfo.uncompressed_size];
                 LogInfo("Decompressing IFCZIP file");
-                unzOpenCurrentFile( zip  );
-                const int ret = unzReadCurrentFile( zip, buff, fileInfo.uncompressed_size);
+                unzOpenCurrentFile(zip);
+                size_t total = 0;
+                int read = 0;
+                do {
+                    int bufferSize = fileInfo.uncompressed_size < INT16_MAX ? fileInfo.uncompressed_size : INT16_MAX;
+                    void* buffer = malloc(bufferSize);
+                    read = unzReadCurrentFile(zip, buffer, bufferSize);
+                    if (read > 0) {
+                        memcpy((char*)buff + total, buffer, read);
+                        total += read;
+                    }
+                    free(buffer);
+                } while (read > 0);
                 size_t filesize = fileInfo.uncompressed_size;
-                if ( ret < 0 || size_t(ret) != filesize )
+                if (total == 0 || size_t(total) != filesize)
                 {
                     delete[] buff;
                     ThrowException("Failed to decompress IFC ZIP file");

+ 1 - 1
code/ObjFileParser.cpp

@@ -118,7 +118,7 @@ void ObjFileParser::parseFile( IOStreamBuffer<char> &streamBuffer ) {
     size_t lastFilePos( 0 );
 
     std::vector<char> buffer;
-    while ( streamBuffer.getNextDataLine( buffer, '\\' ) ) {
+    while ( streamBuffer.getNextDataLine( buffer, '\0' ) ) {
         m_DataIt = buffer.begin();
         m_DataItEnd = buffer.end();
 

+ 8 - 5
code/glTF2Importer.cpp

@@ -438,13 +438,16 @@ void glTF2Importer::ImportMeshes(glTF2::Asset& r)
                 }
             }
 
-            for (size_t tc = 0; tc < attr.texcoord.size() && tc < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++tc) {
-                if (!attr.texcoord[tc]) {
-                    DefaultLogger::get()->warn("NULL texcoord encountered in mesh \"" + mesh.name +
-                        "\" and will be ignored");
+            for (size_t c = 0; c < attr.color.size() && c < AI_MAX_NUMBER_OF_COLOR_SETS; ++c) {
+                if (attr.color[c]->count != aim->mNumVertices) {
+                    DefaultLogger::get()->warn("Color stream size in mesh \"" + mesh.name +
+                        "\" does not match the vertex count");
                     continue;
                 }
-
+                aim->mColors[c] = new aiColor4D[attr.color[c]->count];
+                attr.color[c]->ExtractData(aim->mColors[c]);
+            }
+            for (size_t tc = 0; tc < attr.texcoord.size() && tc < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++tc) {
                 if (attr.texcoord[tc]->count != aim->mNumVertices) {
                     DefaultLogger::get()->warn("Texcoord stream size in mesh \"" + mesh.name +
                                                "\" does not match the vertex count");

+ 4 - 1
include/assimp/IOStreamBuffer.h

@@ -243,7 +243,7 @@ template<class T>
 inline
 bool IOStreamBuffer<T>::getNextDataLine( std::vector<T> &buffer, T continuationToken ) {
     buffer.resize( m_cacheSize );
-    if ( m_cachePos == m_cacheSize || 0 == m_filePos ) {
+    if ( m_cachePos >= m_cacheSize || 0 == m_filePos ) {
         if ( !readNextBlock() ) {
             return false;
         }
@@ -273,6 +273,9 @@ bool IOStreamBuffer<T>::getNextDataLine( std::vector<T> &buffer, T continuationT
         buffer[ i ] = m_cache[ m_cachePos ];
         ++m_cachePos;
         ++i;
+        if (m_cachePos >= size()) {
+            break;
+        }
         if ( m_cachePos >= m_cacheSize ) {
             if ( !readNextBlock() ) {
                 return false;

+ 4 - 1
include/assimp/mesh.h

@@ -402,7 +402,7 @@ enum aiPrimitiveType
 
 
 // ---------------------------------------------------------------------------
-/** @brief NOT CURRENTLY IN USE. An AnimMesh is an attachment to an #aiMesh stores per-vertex
+/** @brief An AnimMesh is an attachment to an #aiMesh stores per-vertex
  *  animations for a particular frame.
  *
  *  You may think of an #aiAnimMesh as a `patch` for the host mesh, which
@@ -414,6 +414,9 @@ enum aiPrimitiveType
 */
 struct aiAnimMesh
 {
+    /**Anim Mesh name */
+    C_STRUCT aiString mName;
+
     /** Replacement for aiMesh::mVertices. If this array is non-NULL,
      *  it *must* contain mNumVertices entries. The corresponding
      *  array in the host mesh must be non-NULL as well - animation

+ 26 - 2
port/jassimp/jassimp-native/src/jassimp.cpp

@@ -1,6 +1,7 @@
 #include "jassimp.h"
 
 #include <assimp/Importer.hpp>
+#include <assimp/ProgressHandler.hpp>
 #include <assimp/scene.h>
 #include <assimp/IOStream.hpp>
 #include <assimp/IOSystem.hpp>
@@ -248,7 +249,7 @@ static bool call(JNIEnv *env, jobject object, const char* typeName, const char*
 		return false;
 	}
 
-	jboolean jReturnValue = env->CallBooleanMethod(object, mid, params[0].l);
+	jboolean jReturnValue = env->CallBooleanMethodA(object, mid, params);
 
 	return (bool)jReturnValue;
 }
@@ -591,6 +592,24 @@ class JavaIOSystem : public Assimp::IOSystem {
 	
 };
 
+class JavaProgressHandler : public Assimp::ProgressHandler {
+	private:
+    JNIEnv* mJniEnv;
+	jobject& mJavaProgressHandler;
+	
+	public:
+	JavaProgressHandler(JNIEnv* env, jobject& javaProgressHandler) :
+		mJniEnv(env),
+		mJavaProgressHandler(javaProgressHandler)
+	{};
+	
+    bool Update(float percentage)
+    {
+    	jvalue params[1];
+		params[0].f = percentage;
+	    return call(mJniEnv, mJavaProgressHandler, "jassimp/AiProgressHandler", "update", "(F)Z", params);
+    }
+};
 
 static bool loadMeshes(JNIEnv *env, const aiScene* cScene, jobject& jScene)
 {
@@ -1880,7 +1899,7 @@ JNIEXPORT jstring JNICALL Java_jassimp_Jassimp_getErrorString
 
 
 JNIEXPORT jobject JNICALL Java_jassimp_Jassimp_aiImportFile
-  (JNIEnv *env, jclass jClazz, jstring jFilename, jlong postProcess, jobject ioSystem)
+  (JNIEnv *env, jclass jClazz, jstring jFilename, jlong postProcess, jobject ioSystem, jobject progressHandler)
 {
 	jobject jScene = NULL; 
 
@@ -1896,6 +1915,11 @@ JNIEXPORT jobject JNICALL Java_jassimp_Jassimp_aiImportFile
 		lprintf("Created aiFileIO\n");
 	}
 	
+	if(progressHandler != NULL)
+	{
+		imp.SetProgressHandler(new JavaProgressHandler(env, progressHandler));
+	}
+	
 	lprintf("opening file: %s\n", cFilename);
 
 	/* do import */

+ 1 - 1
port/jassimp/jassimp-native/src/jassimp.h

@@ -39,7 +39,7 @@ JNIEXPORT jstring JNICALL Java_jassimp_Jassimp_getErrorString
  * Signature: (Ljava/lang/String;J)Ljassimp/AiScene;
  */
 JNIEXPORT jobject JNICALL Java_jassimp_Jassimp_aiImportFile
-  (JNIEnv *, jclass, jstring, jlong, jobject);
+  (JNIEnv *, jclass, jstring, jlong, jobject, jobject);
 
 #ifdef __cplusplus
 }

+ 1 - 1
port/jassimp/jassimp/src/jassimp/AiMaterial.java

@@ -457,7 +457,7 @@ public final class AiMaterial {
          * 
          * @return the data
          */
-        private Object getData() {
+        public Object getData() {
             return m_data;
         }
     }

+ 46 - 0
port/jassimp/jassimp/src/jassimp/AiProgressHandler.java

@@ -0,0 +1,46 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library - Java Binding (jassimp)
+---------------------------------------------------------------------------
+
+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.
+---------------------------------------------------------------------------
+*/
+package jassimp;
+
+public interface AiProgressHandler
+{
+    boolean update(float percentage);
+}

+ 6 - 7
port/jassimp/jassimp/src/jassimp/AiSceneFlag.java

@@ -47,11 +47,6 @@ import java.util.Set;
  * Status flags for {@link AiScene}s.
  */
 public enum AiSceneFlag {
-    /**
-     * The mapped c/c++ integer enum value.
-     */
-    private final int m_rawValue;
-
     /**
      * Specifies that the scene data structure that was imported is not 
      * complete.<p>
@@ -119,8 +114,12 @@ public enum AiSceneFlag {
      * you actually need to render it).
      */
     TERRAIN(0x10);
-    
-    
+
+    /**
+     * The mapped c/c++ integer enum value.
+     */
+    private final int m_rawValue;
+
     /**
      * Utility method for converting from c/c++ based integer enums to java 
      * enums.<p>

+ 22 - 6
port/jassimp/jassimp/src/jassimp/Jassimp.java

@@ -68,8 +68,9 @@ public final class Jassimp {
      * @return the loaded scene, or null if an error occurred
      * @throws IOException if an error occurs
      */
-    private static native AiScene aiImportFile(String filename, 
-            long postProcessing, AiIOSystem<?> ioSystem) throws IOException;
+    private static native AiScene aiImportFile(String filename,
+            long postProcessing, AiIOSystem<?> ioSystem,
+                AiProgressHandler progressHandler) throws IOException;
     
     
     /**
@@ -158,11 +159,26 @@ public final class Jassimp {
     public static AiScene importFile(String filename, 
             Set<AiPostProcessSteps> postProcessing, AiIOSystem<?> ioSystem) 
                   throws IOException {
-        
-       loadLibrary();
-       
+        return importFile(filename, postProcessing, ioSystem, null);
+    }
+
+    /**
+     * Imports a file via assimp.
+     *
+     * @param filename the file to import
+     * @param postProcessing post processing flags
+     * @param ioSystem ioSystem to load files, or null for default
+     * @return the loaded scene, or null if an error occurred
+     * @throws IOException if an error occurs
+     */
+    public static AiScene importFile(String filename,
+            Set<AiPostProcessSteps> postProcessing, AiIOSystem<?> ioSystem,
+            AiProgressHandler progressHandler) throws IOException {
+
+        loadLibrary();
+
         return aiImportFile(filename, AiPostProcessSteps.toRawValue(
-                postProcessing), ioSystem);
+                postProcessing), ioSystem, progressHandler);
     }