浏览代码

Merge branch 'master' into feature/clang-formatting

Kim Kulling 5 年之前
父节点
当前提交
2e84f5174d

+ 6 - 1
CMakeLists.txt

@@ -253,7 +253,7 @@ ELSEIF(MSVC)
   IF(MSVC12)
     ADD_COMPILE_OPTIONS(/wd4351)
   ENDIF()
-  SET(CMAKE_CXX_FLAGS_DEBUG "/D_DEBUG /MDd /Ob2 /DEBUG:FULL /Zi")
+  SET(CMAKE_CXX_FLAGS_DEBUG "/D_DEBUG /MDd /Ob2 /Zi")
 ELSEIF ( "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" )
   IF(NOT HUNTER_ENABLED)
     SET(CMAKE_CXX_FLAGS "-fPIC -std=c++11 ${CMAKE_CXX_FLAGS}")
@@ -391,6 +391,11 @@ IF(HUNTER_ENABLED)
   )
 ELSE(HUNTER_ENABLED)
   # cmake configuration files
+  if(${BUILD_SHARED_LIBS})
+    set(BUILD_LIB_TYPE SHARED)
+  else()
+    set(BUILD_LIB_TYPE STATIC)
+  endif()
   CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/assimp-config.cmake.in"         "${CMAKE_CURRENT_BINARY_DIR}/assimp-config.cmake" @ONLY IMMEDIATE)
   CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/assimpTargets.cmake.in"         "${CMAKE_CURRENT_BINARY_DIR}/assimpTargets.cmake" @ONLY IMMEDIATE)
   IF (is_multi_config)

+ 4 - 1
appveyor.yml

@@ -14,6 +14,7 @@ matrix:
   fast_finish: true
     
 image:
+  - Visual Studio 2013
   - Visual Studio 2015
   - Visual Studio 2017
   - Visual Studio 2019
@@ -29,11 +30,13 @@ install:
   - set PATH=C:\Ruby24-x64\bin;%PATH%
   - set CMAKE_DEFINES -DASSIMP_WERROR=ON
   - if [%COMPILER%]==[MinGW] set PATH=C:\MinGW\bin;%PATH%
+  - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2013" set CMAKE_GENERATOR_NAME=Visual Studio 12 2013
   - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2015" set CMAKE_GENERATOR_NAME=Visual Studio 14 2015
   - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" set CMAKE_GENERATOR_NAME=Visual Studio 15 2017
   - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2019" set CMAKE_GENERATOR_NAME=Visual Studio 16 2019
   - cmake %CMAKE_DEFINES% -G "%CMAKE_GENERATOR_NAME%" -A %platform% .
-  # Rename sh.exe as sh.exe in PATH interferes with MinGW
+  # Rename sh.exe as sh.exe in PATH interferes with MinGW  - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2015" set CMAKE_GENERATOR_NAME=Visual Studio 14 2015
+
   - rename "C:\Program Files\Git\usr\bin\sh.exe" "sh2.exe"
   - set PATH=%PATH%;"C:\\Program Files (x86)\\Inno Setup 5"
   - ps: Invoke-WebRequest -Uri https://download.microsoft.com/download/5/7/b/57b2947c-7221-4f33-b35e-2fc78cb10df4/vc_redist.x64.exe -OutFile .\packaging\windows-innosetup\vc_redist.x64.exe

+ 7 - 4
assimpTargets-debug.cmake.in

@@ -63,10 +63,13 @@ if(MSVC)
 else()
   set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@" CACHE STRING "the suffix for the assimp libraries" )
   if(ASSIMP_BUILD_SHARED_LIBS)
-    if(APPLE)
-        set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@.@ASSIMP_VERSION_MAJOR@@CMAKE_SHARED_LIBRARY_SUFFIX@")
-    else(APPLE)
-        set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_SHARED_LIBRARY_SUFFIX@.@ASSIMP_VERSION_MAJOR@")
+    if(WIN32)
+      # Handle MinGW compiler.
+      set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_SHARED_LIBRARY_SUFFIX@@CMAKE_STATIC_LIBRARY_SUFFIX@")
+    elseif(APPLE)
+      set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@.@ASSIMP_VERSION_MAJOR@@CMAKE_SHARED_LIBRARY_SUFFIX@")
+    else()
+      set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_SHARED_LIBRARY_SUFFIX@.@ASSIMP_VERSION_MAJOR@")
     endif()
     set_target_properties(assimp::assimp PROPERTIES
       IMPORTED_SONAME_DEBUG "${sharedLibraryName}"

+ 7 - 4
assimpTargets-release.cmake.in

@@ -63,10 +63,13 @@ if(MSVC)
 else()
   set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@" CACHE STRING "the suffix for the assimp libraries" )
   if(ASSIMP_BUILD_SHARED_LIBS)
-    if(APPLE)
-        set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}.@ASSIMP_VERSION_MAJOR@@CMAKE_SHARED_LIBRARY_SUFFIX@")
-    else(APPLE)
-        set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_SHARED_LIBRARY_SUFFIX@.@ASSIMP_VERSION_MAJOR@")
+    if(WIN32)
+      # Handle MinGW compiler.
+      set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_SHARED_LIBRARY_SUFFIX@@CMAKE_STATIC_LIBRARY_SUFFIX@")
+    elseif(APPLE)
+      set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}.@ASSIMP_VERSION_MAJOR@@CMAKE_SHARED_LIBRARY_SUFFIX@")
+    else()
+      set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_SHARED_LIBRARY_SUFFIX@.@ASSIMP_VERSION_MAJOR@")
     endif()
     set_target_properties(assimp::assimp PROPERTIES
       IMPORTED_SONAME_RELEASE "${sharedLibraryName}"

+ 1 - 5
assimpTargets.cmake.in

@@ -54,11 +54,7 @@ if(_IMPORT_PREFIX STREQUAL "/")
 endif()
 
 # Create imported target assimp::assimp
-if(@BUILD_SHARED_LIBS@)
-  add_library(assimp::assimp SHARED IMPORTED)
-else()
-  add_library(assimp::assimp STATIC IMPORTED)
-endif()
+add_library(assimp::assimp @BUILD_LIB_TYPE@ IMPORTED)
 
 set_target_properties(assimp::assimp PROPERTIES
   COMPATIBLE_INTERFACE_STRING "assimp_MAJOR_VERSION"

+ 1 - 1
code/3DS/3DSConverter.cpp

@@ -72,7 +72,7 @@ void Discreet3DSImporter::ReplaceDefaultMaterial()
     unsigned int idx( NotSet );
     for (unsigned int i = 0; i < mScene->mMaterials.size();++i)
     {
-        std::string &s = mScene->mMaterials[i].mName;
+        std::string s = mScene->mMaterials[i].mName;
         for ( std::string::iterator it = s.begin(); it != s.end(); ++it ) {
             *it = static_cast< char >( ::tolower( *it ) );
         }

+ 2 - 0
code/CMakeLists.txt

@@ -670,6 +670,8 @@ SET( PostProcessing_SRCS
   PostProcessing/MakeVerboseFormat.h
   PostProcessing/ScaleProcess.cpp
   PostProcessing/ScaleProcess.h
+  PostProcessing/ArmaturePopulate.cpp
+  PostProcessing/ArmaturePopulate.h
   PostProcessing/GenBoundingBoxesProcess.cpp
   PostProcessing/GenBoundingBoxesProcess.h
 )

+ 1 - 1
code/Collada/ColladaHelper.h

@@ -583,7 +583,7 @@ struct Image
     /** Embedded image data */
     std::vector<uint8_t> mImageData;
 
-    /** File format hint ofembedded image data */
+    /** File format hint of embedded image data */
     std::string mEmbeddedFormat;
 };
 

+ 3 - 53
code/Collada/ColladaLoader.cpp

@@ -1735,6 +1735,7 @@ void ColladaLoader::BuildMaterials(ColladaParser& pParser, aiScene* /*pScene*/)
 
 // ------------------------------------------------------------------------------------------------
 // Resolves the texture name for the given effect texture entry
+// and loads the texture data 
 aiString ColladaLoader::FindFilenameForEffectTexture(const ColladaParser& pParser,
     const Collada::Effect& pEffect, const std::string& pName)
 {
@@ -1762,7 +1763,7 @@ aiString ColladaLoader::FindFilenameForEffectTexture(const ColladaParser& pParse
 
         //set default texture file name
         result.Set(name + ".jpg");
-        ConvertPath(result);
+        ColladaParser::UriDecodePath(result);
         return result;
     }
 
@@ -1781,7 +1782,7 @@ aiString ColladaLoader::FindFilenameForEffectTexture(const ColladaParser& pParse
 
 
         // setup format hint
-        if (imIt->second.mEmbeddedFormat.length() > 3) {
+        if (imIt->second.mEmbeddedFormat.length() >= HINTMAXTEXTURELEN) {
             ASSIMP_LOG_WARN("Collada: texture format hint is too long, truncating to 3 characters");
         }
         strncpy(tex->achFormatHint, imIt->second.mEmbeddedFormat.c_str(), 3);
@@ -1802,61 +1803,10 @@ aiString ColladaLoader::FindFilenameForEffectTexture(const ColladaParser& pParse
         }
 
         result.Set(imIt->second.mFileName);
-        ConvertPath(result);
     }
     return result;
 }
 
-// ------------------------------------------------------------------------------------------------
-// Convert a path read from a collada file to the usual representation
-void ColladaLoader::ConvertPath(aiString& ss)
-{
-    // TODO: collada spec, p 22. Handle URI correctly.
-    // For the moment we're just stripping the file:// away to make it work.
-    // Windows doesn't seem to be able to find stuff like
-    // 'file://..\LWO\LWO2\MappingModes\earthSpherical.jpg'
-    if (0 == strncmp(ss.data, "file://", 7))
-    {
-        ss.length -= 7;
-        memmove(ss.data, ss.data + 7, ss.length);
-        ss.data[ss.length] = '\0';
-    }
-
-    // Maxon Cinema Collada Export writes "file:///C:\andsoon" with three slashes...
-    // I need to filter it without destroying linux paths starting with "/somewhere"
-#if defined( _MSC_VER )
-    if (ss.data[0] == '/' && isalpha((unsigned char)ss.data[1]) && ss.data[2] == ':') {
-#else
-    if (ss.data[0] == '/' && isalpha(ss.data[1]) && ss.data[2] == ':') {
-#endif
-        --ss.length;
-        ::memmove(ss.data, ss.data + 1, ss.length);
-        ss.data[ss.length] = 0;
-    }
-
-    // find and convert all %xy special chars
-    char* out = ss.data;
-    for (const char* it = ss.data; it != ss.data + ss.length; /**/)
-    {
-        if (*it == '%' && (it + 3) < ss.data + ss.length)
-        {
-            // separate the number to avoid dragging in chars from behind into the parsing
-            char mychar[3] = { it[1], it[2], 0 };
-            size_t nbr = strtoul16(mychar);
-            it += 3;
-            *out++ = (char)(nbr & 0xFF);
-        }
-        else
-        {
-            *out++ = *it++;
-        }
-    }
-
-    // adjust length and terminator of the shortened string
-    *out = 0;
-    ss.length = (ptrdiff_t)(out - ss.data);
-}
-
 // ------------------------------------------------------------------------------------------------
 // Reads a float value from an accessor and its data array.
 ai_real ColladaLoader::ReadFloat(const Collada::Accessor& pAccessor, const Collada::Data& pData, size_t pIndex, size_t pOffset) const

+ 1 - 4
code/Collada/ColladaLoader.h

@@ -94,7 +94,7 @@ public:
 public:
     /** Returns whether the class can handle the format of the given file.
      * See BaseImporter::CanRead() for details. */
-    bool CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const override;
+    bool CanRead(const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const override;
 
 protected:
     /** Return importer meta information.
@@ -184,9 +184,6 @@ protected:
     aiString FindFilenameForEffectTexture( const ColladaParser& pParser,
         const Collada::Effect& pEffect, const std::string& pName);
 
-    /** Converts a path read from a collada file to the usual representation */
-    void ConvertPath( aiString& ss);
-
     /** Reads a float value from an accessor and its data array.
      * @param pAccessor The accessor to use for reading
      * @param pData The data array to read from

+ 68 - 4
code/Collada/ColladaParser.cpp

@@ -183,13 +183,67 @@ std::string ColladaParser::ReadZaeManifest(ZipArchiveIOSystem &zip_archive) {
                 if (filepath == nullptr)
                     return std::string();
 
-                return std::string(filepath);
+                aiString ai_str(filepath);
+                UriDecodePath(ai_str);
+
+                return std::string(ai_str.C_Str());
             }
         }
     }
     return std::string();
 }
 
+// ------------------------------------------------------------------------------------------------
+// Convert a path read from a collada file to the usual representation
+void ColladaParser::UriDecodePath(aiString& ss)
+{
+    // TODO: collada spec, p 22. Handle URI correctly.
+    // For the moment we're just stripping the file:// away to make it work.
+    // Windows doesn't seem to be able to find stuff like
+    // 'file://..\LWO\LWO2\MappingModes\earthSpherical.jpg'
+    if (0 == strncmp(ss.data, "file://", 7))
+    {
+        ss.length -= 7;
+        memmove(ss.data, ss.data + 7, ss.length);
+        ss.data[ss.length] = '\0';
+    }
+
+    // Maxon Cinema Collada Export writes "file:///C:\andsoon" with three slashes...
+    // I need to filter it without destroying linux paths starting with "/somewhere"
+#if defined( _MSC_VER )
+    if (ss.data[0] == '/' && isalpha((unsigned char)ss.data[1]) && ss.data[2] == ':') {
+#else
+    if (ss.data[0] == '/' && isalpha(ss.data[1]) && ss.data[2] == ':') {
+#endif
+        --ss.length;
+        ::memmove(ss.data, ss.data + 1, ss.length);
+        ss.data[ss.length] = 0;
+    }
+
+    // find and convert all %xy special chars
+    char* out = ss.data;
+    for (const char* it = ss.data; it != ss.data + ss.length; /**/)
+    {
+        if (*it == '%' && (it + 3) < ss.data + ss.length)
+        {
+            // separate the number to avoid dragging in chars from behind into the parsing
+            char mychar[3] = { it[1], it[2], 0 };
+            size_t nbr = strtoul16(mychar);
+            it += 3;
+            *out++ = (char)(nbr & 0xFF);
+        }
+        else
+        {
+            *out++ = *it++;
+        }
+    }
+
+    // adjust length and terminator of the shortened string
+    *out = 0;
+    ai_assert(out > ss.data);
+    ss.length = static_cast<ai_uint32>(out - ss.data);
+}
+
 // ------------------------------------------------------------------------------------------------
 // Read bool from text contents of current element
 bool ColladaParser::ReadBoolFromTextContent()
@@ -1120,7 +1174,12 @@ void ColladaParser::ReadImage(Collada::Image& pImage)
                     if (!mReader->isEmptyElement()) {
                         // element content is filename - hopefully
                         const char* sz = TestTextContent();
-                        if (sz)pImage.mFileName = sz;
+                        if (sz)
+                        {
+                            aiString filepath(sz);
+                            UriDecodePath(filepath);
+                            pImage.mFileName = filepath.C_Str();
+                        }
                         TestClosing("init_from");
                     }
                     if (!pImage.mFileName.length()) {
@@ -1153,7 +1212,12 @@ void ColladaParser::ReadImage(Collada::Image& pImage)
                 {
                     // element content is filename - hopefully
                     const char* sz = TestTextContent();
-                    if (sz)pImage.mFileName = sz;
+                    if (sz)
+                    {
+                        aiString filepath(sz);
+                        UriDecodePath(filepath);
+                        pImage.mFileName = filepath.C_Str();
+                    }
                     TestClosing("ref");
                 }
                 else if (IsElement("hex") && !pImage.mFileName.length())
@@ -3056,7 +3120,7 @@ void ColladaParser::ReadMaterialVertexInputBinding(Collada::SemanticMappingTable
     }
 }
 
-void Assimp::ColladaParser::ReadEmbeddedTextures(ZipArchiveIOSystem& zip_archive)
+void ColladaParser::ReadEmbeddedTextures(ZipArchiveIOSystem& zip_archive)
 {
     // Attempt to load any undefined Collada::Image in ImageLibrary
     for (ImageLibrary::iterator it = mImageLibrary.begin(); it != mImageLibrary.end(); ++it) {

+ 4 - 1
code/Collada/ColladaParser.h

@@ -66,12 +66,15 @@ namespace Assimp
     {
         friend class ColladaLoader;
 
+        /** Converts a path read from a collada file to the usual representation */
+        static void UriDecodePath(aiString& ss);
+
     protected:
         /** Map for generic metadata as aiString */
         typedef std::map<std::string, aiString> StringMetaData;
 
         /** Constructor from XML file */
-        ColladaParser( IOSystem* pIOHandler, const std::string& pFile);
+        ColladaParser(IOSystem* pIOHandler, const std::string& pFile);
 
         /** Destructor */
         ~ColladaParser();

+ 7 - 0
code/Common/PostStepRegistry.cpp

@@ -131,11 +131,15 @@ corresponding preprocessor flag to selectively disable steps.
 #if (!defined ASSIMP_BUILD_NO_GLOBALSCALE_PROCESS)
 #   include "PostProcessing/ScaleProcess.h"
 #endif
+#if (!defined ASSIMP_BUILD_NO_ARMATUREPOPULATE_PROCESS)
+#   include "PostProcessing/ArmaturePopulate.h"
+#endif
 #if (!defined ASSIMP_BUILD_NO_GENBOUNDINGBOXES_PROCESS)
 #   include "PostProcessing/GenBoundingBoxesProcess.h"
 #endif
 
 
+
 namespace Assimp {
 
 // ------------------------------------------------------------------------------------------------
@@ -180,6 +184,9 @@ void GetPostProcessingStepInstanceList(std::vector< BaseProcess* >& out)
 #if (!defined ASSIMP_BUILD_NO_GLOBALSCALE_PROCESS)
     out.push_back( new ScaleProcess());
 #endif
+#if (!defined ASSIMP_BUILD_NO_ARMATUREPOPULATE_PROCESS)
+    out.push_back( new ArmaturePopulate());
+#endif
 #if (!defined ASSIMP_BUILD_NO_PRETRANSFORMVERTICES_PROCESS)
     out.push_back( new PretransformVertices());
 #endif

+ 5 - 9
code/Common/Version.cpp

@@ -46,8 +46,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/scene.h>
 #include "ScenePrivate.h"
 
-static const unsigned int MajorVersion = 5;
-static const unsigned int MinorVersion = 0;
+#include "revision.h"
 
 // --------------------------------------------------------------------------------
 // Legal information string - don't remove this.
@@ -56,9 +55,9 @@ static const char* LEGAL_INFORMATION =
 "Open Asset Import Library (Assimp).\n"
 "A free C/C++ library to import various 3D file formats into applications\n\n"
 
-"(c) 2008-2017, assimp team\n"
+"(c) 2006-2019, assimp team\n"
 "License under the terms and conditions of the 3-clause BSD license\n"
-"http://assimp.sourceforge.net\n"
+"http://assimp.org\n"
 ;
 
 // ------------------------------------------------------------------------------------------------
@@ -70,13 +69,13 @@ ASSIMP_API const char*  aiGetLegalString  ()    {
 // ------------------------------------------------------------------------------------------------
 // Get Assimp minor version
 ASSIMP_API unsigned int aiGetVersionMinor ()    {
-    return MinorVersion;
+    return VER_MINOR;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Get Assimp major version
 ASSIMP_API unsigned int aiGetVersionMajor ()    {
-    return MajorVersion;
+    return VER_MAJOR;
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -104,9 +103,6 @@ ASSIMP_API unsigned int aiGetCompileFlags ()    {
     return flags;
 }
 
-// include current build revision, which is even updated from time to time -- :-)
-#include "revision.h"
-
 // ------------------------------------------------------------------------------------------------
 ASSIMP_API unsigned int aiGetVersionRevision() {
     return GitVersion;

+ 9 - 9
code/Common/scene.cpp

@@ -44,23 +44,23 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 aiNode::aiNode()
 : mName("")
-, mParent(NULL)
+, mParent(nullptr)
 , mNumChildren(0)
-, mChildren(NULL)
+, mChildren(nullptr)
 , mNumMeshes(0)
-, mMeshes(NULL)
-, mMetaData(NULL) {
+, mMeshes(nullptr)
+, mMetaData(nullptr) {
     // empty
 }
 
 aiNode::aiNode(const std::string& name)
 : mName(name)
-, mParent(NULL)
+, mParent(nullptr)
 , mNumChildren(0)
-, mChildren(NULL)
+, mChildren(nullptr)
 , mNumMeshes(0)
-, mMeshes(NULL)
-, mMetaData(NULL) {
+, mMeshes(nullptr)
+, mMetaData(nullptr) {
     // empty
 }
 
@@ -68,7 +68,7 @@ aiNode::aiNode(const std::string& name)
 aiNode::~aiNode() {
     // delete all children recursively
     // to make sure we won't crash if the data is invalid ...
-    if (mChildren && mNumChildren)
+    if (mNumChildren && mChildren)
     {
         for (unsigned int a = 0; a < mNumChildren; a++)
             delete mChildren[a];

+ 8 - 0
code/FBX/FBXCompileConfig.h

@@ -47,6 +47,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #define INCLUDED_AI_FBX_COMPILECONFIG_H
 
 #include <map>
+#include <set>
 
 //
 #if _MSC_VER > 1500 || (defined __GNUC___)
@@ -54,16 +55,23 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #   else
 #   define fbx_unordered_map map
 #   define fbx_unordered_multimap multimap
+#   define fbx_unordered_set set
+#   define fbx_unordered_multiset multiset
 #endif
 
 #ifdef ASSIMP_FBX_USE_UNORDERED_MULTIMAP
 #   include <unordered_map>
+#   include <unordered_set>
 #   if _MSC_VER > 1600
 #       define fbx_unordered_map unordered_map
 #       define fbx_unordered_multimap unordered_multimap
+#       define fbx_unordered_set unordered_set
+#       define fbx_unordered_multiset unordered_multiset
 #   else
 #       define fbx_unordered_map tr1::unordered_map
 #       define fbx_unordered_multimap tr1::unordered_multimap
+#       define fbx_unordered_set tr1::unordered_set
+#       define fbx_unordered_multiset tr1::unordered_multiset
 #   endif
 #endif
 

+ 225 - 113
code/FBX/FBXConverter.cpp

@@ -68,7 +68,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <sstream>
 #include <iomanip>
 #include <cstdint>
-
+#include <iostream>
+#include <stdlib.h>
 
 namespace Assimp {
     namespace FBX {
@@ -77,7 +78,7 @@ namespace Assimp {
 
 #define MAGIC_NODE_TAG "_$AssimpFbx$"
 
-#define CONVERT_FBX_TIME(time) static_cast<double>(time) / 46186158000L
+#define CONVERT_FBX_TIME(time) static_cast<double>(time) / 46186158000LL
 
         FBXConverter::FBXConverter(aiScene* out, const Document& doc, bool removeEmptyBones )
         : defaultMaterialIndex()
@@ -96,6 +97,14 @@ namespace Assimp {
             // populate the node_anim_chain_bits map, which is needed
             // to determine which nodes need to be generated.
             ConvertAnimations();
+            // Embedded textures in FBX could be connected to nothing but to itself,
+            // for instance Texture -> Video connection only but not to the main graph,
+            // The idea here is to traverse all objects to find these Textures and convert them,
+            // so later during material conversion it will find converted texture in the textures_converted array.
+            if (doc.Settings().readTextures)
+            {
+                ConvertOrphantEmbeddedTextures();
+            }
             ConvertRootNode();
 
             if (doc.Settings().readAllMaterials) {
@@ -145,7 +154,7 @@ namespace Assimp {
             out->mRootNode->mName.Set(unique_name);
 
             // root has ID 0
-            ConvertNodes(0L, *out->mRootNode);
+            ConvertNodes(0L, out->mRootNode, out->mRootNode);
         }
 
         static std::string getAncestorBaseName(const aiNode* node)
@@ -179,8 +188,11 @@ namespace Assimp {
             GetUniqueName(original_name, unique_name);
             return unique_name;
         }
-
-        void FBXConverter::ConvertNodes(uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform) {
+        /// todo: pre-build node hierarchy
+        /// todo: get bone from stack
+        /// todo: make map of aiBone* to aiNode*
+        /// then update convert clusters to the new format
+        void FBXConverter::ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node) {
             const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(id, "Model");
 
             std::vector<aiNode*> nodes;
@@ -191,62 +203,69 @@ namespace Assimp {
 
             try {
                 for (const Connection* con : conns) {
-
                     // ignore object-property links
                     if (con->PropertyName().length()) {
-                        continue;
+                        // really important we document why this is ignored.
+                        FBXImporter::LogInfo("ignoring property link - no docs on why this is ignored");
+                        continue; //?
                     }
 
+                    // convert connection source object into Object base class
                     const Object* const object = con->SourceObject();
                     if (nullptr == object) {
-                        FBXImporter::LogWarn("failed to convert source object for Model link");
+                        FBXImporter::LogError("failed to convert source object for Model link");
                         continue;
                     }
 
+                    // FBX Model::Cube, Model::Bone001, etc elements
+                    // This detects if we can cast the object into this model structure.
                     const Model* const model = dynamic_cast<const Model*>(object);
 
                     if (nullptr != model) {
                         nodes_chain.clear();
                         post_nodes_chain.clear();
 
-                        aiMatrix4x4 new_abs_transform = parent_transform;
-
-                        std::string unique_name = MakeUniqueNodeName(model, parent);
-
+                        aiMatrix4x4 new_abs_transform = parent->mTransformation;
+                        std::string node_name = FixNodeName(model->Name());
                         // 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.
-                        const bool need_additional_node = GenerateTransformationNodeChain(*model, unique_name, nodes_chain, post_nodes_chain);
 
+
+                        // generate node transforms - this includes pivot data
+                        // if need_additional_node is true then you t
+                        const bool need_additional_node = GenerateTransformationNodeChain(*model, node_name, nodes_chain, post_nodes_chain);
+
+                        // assert that for the current node we must have at least a single transform
                         ai_assert(nodes_chain.size());
 
                         if (need_additional_node) {
-                            nodes_chain.push_back(new aiNode(unique_name));
+                            nodes_chain.push_back(new aiNode(node_name));
                         }
 
                         //setup metadata on newest node
                         SetupNodeMetadata(*model, *nodes_chain.back());
 
                         // link all nodes in a row
-                        aiNode* last_parent = &parent;
-                        for (aiNode* prenode : nodes_chain) {
-                            ai_assert(prenode);
+                        aiNode* last_parent = parent;
+                        for (aiNode* child : nodes_chain) {
+                            ai_assert(child);
 
-                            if (last_parent != &parent) {
+                            if (last_parent != parent) {
                                 last_parent->mNumChildren = 1;
                                 last_parent->mChildren = new aiNode*[1];
-                                last_parent->mChildren[0] = prenode;
+                                last_parent->mChildren[0] = child;
                             }
 
-                            prenode->mParent = last_parent;
-                            last_parent = prenode;
+                            child->mParent = last_parent;
+                            last_parent = child;
 
-                            new_abs_transform *= prenode->mTransformation;
+                            new_abs_transform *= child->mTransformation;
                         }
 
                         // attach geometry
-                        ConvertModel(*model, *nodes_chain.back(), new_abs_transform);
+                        ConvertModel(*model, nodes_chain.back(), root_node, new_abs_transform);
 
                         // check if there will be any child nodes
                         const std::vector<const Connection*>& child_conns
@@ -258,7 +277,7 @@ namespace Assimp {
                             for (aiNode* postnode : post_nodes_chain) {
                                 ai_assert(postnode);
 
-                                if (last_parent != &parent) {
+                                if (last_parent != parent) {
                                     last_parent->mNumChildren = 1;
                                     last_parent->mChildren = new aiNode*[1];
                                     last_parent->mChildren[0] = postnode;
@@ -280,15 +299,15 @@ namespace Assimp {
                             );
                         }
 
-                        // attach sub-nodes (if any)
-                        ConvertNodes(model->ID(), *last_parent, new_abs_transform);
+                        // recursion call - child nodes
+                        ConvertNodes(model->ID(), last_parent, root_node);
 
                         if (doc.Settings().readLights) {
-                            ConvertLights(*model, unique_name);
+                            ConvertLights(*model, node_name);
                         }
 
                         if (doc.Settings().readCameras) {
-                            ConvertCameras(*model, unique_name);
+                            ConvertCameras(*model, node_name);
                         }
 
                         nodes.push_back(nodes_chain.front());
@@ -297,11 +316,17 @@ namespace Assimp {
                 }
 
                 if (nodes.size()) {
-                    parent.mChildren = new aiNode*[nodes.size()]();
-                    parent.mNumChildren = static_cast<unsigned int>(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);
+                    std::swap_ranges(nodes.begin(), nodes.end(), parent->mChildren);
                 }
+                else
+                {
+                    parent->mNumChildren = 0;
+                    parent->mChildren = nullptr;
+                }
+                
             }
             catch (std::exception&) {
                 Util::delete_fun<aiNode> deleter;
@@ -803,7 +828,7 @@ namespace Assimp {
             // 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) == ((chainBits & chainMaskComplex) != 0));
+            //ai_assert(NeedsComplexTransformationChain(model) == ((chainBits & chainMaskComplex) != 0));
 
             // now, if we have more than just Translation, Scaling and Rotation,
             // we need to generate a full node chain to accommodate for assimp's
@@ -905,7 +930,8 @@ namespace Assimp {
             }
         }
 
-        void FBXConverter::ConvertModel(const Model& model, aiNode& nd, const aiMatrix4x4& node_global_transform)
+        void FBXConverter::ConvertModel(const Model &model, aiNode *parent, aiNode *root_node,
+                                        const aiMatrix4x4 &absolute_transform)
         {
             const std::vector<const Geometry*>& geos = model.GetGeometry();
 
@@ -917,11 +943,12 @@ namespace Assimp {
                 const MeshGeometry* const mesh = dynamic_cast<const MeshGeometry*>(geo);
                 const LineGeometry* const line = dynamic_cast<const LineGeometry*>(geo);
                 if (mesh) {
-                    const std::vector<unsigned int>& indices = ConvertMesh(*mesh, model, node_global_transform, nd);
+                    const std::vector<unsigned int>& indices = ConvertMesh(*mesh, model, parent, root_node,
+                                                                           absolute_transform);
                     std::copy(indices.begin(), indices.end(), std::back_inserter(meshes));
                 }
                 else if (line) {
-                    const std::vector<unsigned int>& indices = ConvertLine(*line, model, node_global_transform, nd);
+                    const std::vector<unsigned int>& indices = ConvertLine(*line, model, parent, root_node);
                     std::copy(indices.begin(), indices.end(), std::back_inserter(meshes));
                 }
                 else {
@@ -930,15 +957,16 @@ namespace Assimp {
             }
 
             if (meshes.size()) {
-                nd.mMeshes = new unsigned int[meshes.size()]();
-                nd.mNumMeshes = static_cast<unsigned int>(meshes.size());
+                parent->mMeshes = new unsigned int[meshes.size()]();
+                parent->mNumMeshes = static_cast<unsigned int>(meshes.size());
 
-                std::swap_ranges(meshes.begin(), meshes.end(), nd.mMeshes);
+                std::swap_ranges(meshes.begin(), meshes.end(), parent->mMeshes);
             }
         }
 
-        std::vector<unsigned int> FBXConverter::ConvertMesh(const MeshGeometry& mesh, const Model& model,
-            const aiMatrix4x4& node_global_transform, aiNode& nd)
+        std::vector<unsigned int>
+        FBXConverter::ConvertMesh(const MeshGeometry &mesh, const Model &model, aiNode *parent, aiNode *root_node,
+                                  const aiMatrix4x4 &absolute_transform)
         {
             std::vector<unsigned int> temp;
 
@@ -962,18 +990,18 @@ namespace Assimp {
                 const MatIndexArray::value_type base = mindices[0];
                 for (MatIndexArray::value_type index : mindices) {
                     if (index != base) {
-                        return ConvertMeshMultiMaterial(mesh, model, node_global_transform, nd);
+                        return ConvertMeshMultiMaterial(mesh, model, parent, root_node, absolute_transform);
                     }
                 }
             }
 
             // faster code-path, just copy the data
-            temp.push_back(ConvertMeshSingleMaterial(mesh, model, node_global_transform, nd));
+            temp.push_back(ConvertMeshSingleMaterial(mesh, model, absolute_transform, parent, root_node));
             return temp;
         }
 
         std::vector<unsigned int> FBXConverter::ConvertLine(const LineGeometry& line, const Model& model,
-            const aiMatrix4x4& node_global_transform, aiNode& nd)
+                                                            aiNode *parent, aiNode *root_node)
         {
             std::vector<unsigned int> temp;
 
@@ -984,7 +1012,7 @@ namespace Assimp {
                 return temp;
             }
 
-            aiMesh* const out_mesh = SetupEmptyMesh(line, nd);
+            aiMesh* const out_mesh = SetupEmptyMesh(line, root_node);
             out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE;
 
             // copy vertices
@@ -1019,7 +1047,7 @@ namespace Assimp {
             return temp;
         }
 
-        aiMesh* FBXConverter::SetupEmptyMesh(const Geometry& mesh, aiNode& nd)
+        aiMesh* FBXConverter::SetupEmptyMesh(const Geometry& mesh, aiNode *parent)
         {
             aiMesh* const out_mesh = new aiMesh();
             meshes.push_back(out_mesh);
@@ -1036,17 +1064,18 @@ namespace Assimp {
             }
             else
             {
-                out_mesh->mName = nd.mName;
+                out_mesh->mName = parent->mName;
             }
 
             return out_mesh;
         }
 
-        unsigned int FBXConverter::ConvertMeshSingleMaterial(const MeshGeometry& mesh, const Model& model,
-            const aiMatrix4x4& node_global_transform, aiNode& nd)
+        unsigned int FBXConverter::ConvertMeshSingleMaterial(const MeshGeometry &mesh, const Model &model,
+                                                             const aiMatrix4x4 &absolute_transform, aiNode *parent,
+                                                             aiNode *root_node)
         {
             const MatIndexArray& mindices = mesh.GetMaterialIndices();
-            aiMesh* const out_mesh = SetupEmptyMesh(mesh, nd);
+            aiMesh* const out_mesh = SetupEmptyMesh(mesh, parent);
 
             const std::vector<aiVector3D>& vertices = mesh.GetVertices();
             const std::vector<unsigned int>& faces = mesh.GetFaceIndexCounts();
@@ -1113,7 +1142,7 @@ namespace Assimp {
                         binormals = &tempBinormals;
                     }
                     else {
-                        binormals = NULL;
+                        binormals = nullptr;
                     }
                 }
 
@@ -1163,8 +1192,9 @@ namespace Assimp {
                 ConvertMaterialForMesh(out_mesh, model, mesh, mindices[0]);
             }
 
-            if (doc.Settings().readWeights && mesh.DeformerSkin() != NULL) {
-                ConvertWeights(out_mesh, model, mesh, node_global_transform, NO_MATERIAL_SEPARATION);
+            if (doc.Settings().readWeights && mesh.DeformerSkin() != nullptr) {
+                ConvertWeights(out_mesh, model, mesh, absolute_transform, parent, root_node, NO_MATERIAL_SEPARATION,
+                               nullptr);
             }
 
             std::vector<aiAnimMesh*> animMeshes;
@@ -1209,8 +1239,10 @@ namespace Assimp {
             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)
+        std::vector<unsigned int>
+        FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model, aiNode *parent,
+                                               aiNode *root_node,
+                                               const aiMatrix4x4 &absolute_transform)
         {
             const MatIndexArray& mindices = mesh.GetMaterialIndices();
             ai_assert(mindices.size());
@@ -1221,7 +1253,7 @@ namespace Assimp {
             for (MatIndexArray::value_type index : mindices) {
                 if (had.find(index) == had.end()) {
 
-                    indices.push_back(ConvertMeshMultiMaterial(mesh, model, index, node_global_transform, nd));
+                    indices.push_back(ConvertMeshMultiMaterial(mesh, model, index, parent, root_node, absolute_transform));
                     had.insert(index);
                 }
             }
@@ -1229,18 +1261,18 @@ namespace Assimp {
             return indices;
         }
 
-        unsigned int FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model,
-            MatIndexArray::value_type index,
-            const aiMatrix4x4& node_global_transform,
-            aiNode& nd)
+        unsigned int FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model,
+                                                            MatIndexArray::value_type index,
+                                                            aiNode *parent, aiNode *root_node,
+                                                            const aiMatrix4x4 &absolute_transform)
         {
-            aiMesh* const out_mesh = SetupEmptyMesh(mesh, nd);
+            aiMesh* const out_mesh = SetupEmptyMesh(mesh, parent);
 
             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;
+            const bool process_weights = doc.Settings().readWeights && mesh.DeformerSkin() != nullptr;
 
             unsigned int count_faces = 0;
             unsigned int count_vertices = 0;
@@ -1300,7 +1332,7 @@ namespace Assimp {
                         binormals = &tempBinormals;
                     }
                     else {
-                        binormals = NULL;
+                        binormals = nullptr;
                     }
                 }
 
@@ -1399,7 +1431,7 @@ namespace Assimp {
             ConvertMaterialForMesh(out_mesh, model, mesh, index);
 
             if (process_weights) {
-                ConvertWeights(out_mesh, model, mesh, node_global_transform, index, &reverseMapping);
+                ConvertWeights(out_mesh, model, mesh, absolute_transform, parent, root_node, index, &reverseMapping);
             }
 
             std::vector<aiAnimMesh*> animMeshes;
@@ -1449,10 +1481,10 @@ namespace Assimp {
             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)
+        void FBXConverter::ConvertWeights(aiMesh *out, const Model &model, const MeshGeometry &geo,
+                                          const aiMatrix4x4 &absolute_transform,
+                                          aiNode *parent, aiNode *root_node, unsigned int materialIndex,
+                                          std::vector<unsigned int> *outputVertStartIndices)
         {
             ai_assert(geo.DeformerSkin());
 
@@ -1463,13 +1495,12 @@ namespace Assimp {
             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 {
-
+                // iterate over the sub deformers
                 for (const Cluster* cluster : sk.Clusters()) {
                     ai_assert(cluster);
 
@@ -1483,15 +1514,16 @@ namespace Assimp {
                     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
+                        // ToOutputVertexIndex only returns nullptr if index is out of bounds
                         // which should never happen
-                        ai_assert(out_idx != NULL);
+                        ai_assert(out_idx != nullptr);
 
                         index_out_indices.push_back(no_index_sentinel);
                         count_out_indices.push_back(0);
@@ -1520,68 +1552,107 @@ namespace Assimp {
                             }
                         }
                     }
-                    
+
                     // 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.
-                    ConvertCluster(bones, model, *cluster, out_indices, index_out_indices,
-                            count_out_indices, node_global_transform);
+                    ConvertCluster(bones, cluster, out_indices, index_out_indices,
+                                   count_out_indices, absolute_transform, parent, root_node);
                 }
+
+                bone_map.clear();
             }
-            catch (std::exception&) {
+            catch (std::exception&e) {
                 std::for_each(bones.begin(), bones.end(), Util::delete_fun<aiBone>());
                 throw;
             }
 
             if (bones.empty()) {
+                out->mBones = nullptr;
+                out->mNumBones = 0;
                 return;
-            }
-
-            out->mBones = new aiBone*[bones.size()]();
-            out->mNumBones = static_cast<unsigned int>(bones.size());
+            } else {
+                out->mBones = new aiBone *[bones.size()]();
+                out->mNumBones = static_cast<unsigned int>(bones.size());
 
-            std::swap_ranges(bones.begin(), bones.end(), out->mBones);
+                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)
+        const aiNode* FBXConverter::GetNodeByName( const aiString& name, aiNode *current_node )
         {
+            aiNode * iter = current_node;
+            //printf("Child count: %d", iter->mNumChildren);
+            return iter;
+        }
 
-            aiBone* const bone = new aiBone();
-            bones.push_back(bone);
+        void FBXConverter::ConvertCluster(std::vector<aiBone *> &local_mesh_bones, 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 &absolute_transform,
+                                          aiNode *parent, aiNode *root_node) {
+            ai_assert(cl); // make sure cluster valid
+            std::string deformer_name = cl->TargetNode()->Name();
+            aiString bone_name = aiString(FixNodeName(deformer_name));
 
-            bone->mName = FixNodeName(cl.TargetNode()->Name());
+            aiBone *bone = nullptr;
 
-            bone->mOffsetMatrix = cl.TransformLink();
-            bone->mOffsetMatrix.Inverse();
+            if (bone_map.count(deformer_name)) {
+                std::cout << "retrieved bone from lookup " << bone_name.C_Str() << ". Deformer: " << deformer_name
+                          << std::endl;
+                bone = bone_map[deformer_name];
+            } else {
+                std::cout << "created new bone " << bone_name.C_Str() << ". Deformer: " << deformer_name << std::endl;
+                bone = new aiBone();
+                bone->mName = bone_name;
 
-            bone->mOffsetMatrix = bone->mOffsetMatrix * node_global_transform;
+                // store local transform link for post processing
+                bone->mOffsetMatrix = cl->TransformLink();
+                bone->mOffsetMatrix.Inverse();
 
-            bone->mNumWeights = static_cast<unsigned int>(out_indices.size());
-            aiVertexWeight* cursor = bone->mWeights = new aiVertexWeight[out_indices.size()];
+                aiMatrix4x4 matrix = (aiMatrix4x4)absolute_transform;
 
-            const size_t no_index_sentinel = std::numeric_limits<size_t>::max();
-            const WeightArray& weights = cl.GetWeights();
+                bone->mOffsetMatrix = bone->mOffsetMatrix * matrix; // * mesh_offset
 
-            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;
-                }
+                //
+                // Now calculate the aiVertexWeights
+                //
+
+                aiVertexWeight *cursor = nullptr;
+
+                bone->mNumWeights = static_cast<unsigned int>(out_indices.size());
+                cursor = bone->mWeights = new aiVertexWeight[out_indices.size()];
 
-                const size_t cc = count_out_indices[i];
-                for (size_t j = 0; j < cc; ++j) {
-                    aiVertexWeight& out_weight = *cursor++;
+                const size_t no_index_sentinel = std::numeric_limits<size_t>::max();
+                const WeightArray& weights = cl->GetWeights();
 
-                    out_weight.mVertexId = static_cast<unsigned int>(out_indices[index_index + j]);
-                    out_weight.mWeight = weights[i];
+                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) {
+                        // cursor runs from first element relative to the start
+                        // or relative to the start of the next indexes.
+                        aiVertexWeight& out_weight = *cursor++;
+
+                        out_weight.mVertexId = static_cast<unsigned int>(out_indices[index_index + j]);
+                        out_weight.mWeight = weights[i];
+                    }
                 }
+
+                bone_map.insert(std::pair<const std::string, aiBone *>(deformer_name, bone));
             }
+
+            std::cout << "bone research: Indicies size: " << out_indices.size() << std::endl;
+
+            // lookup must be populated in case something goes wrong
+            // this also allocates bones to mesh instance outside
+            local_mesh_bones.push_back(bone);
         }
 
         void FBXConverter::ConvertMaterialForMesh(aiMesh* out, const Model& model, const MeshGeometry& geo,
@@ -1711,7 +1782,7 @@ namespace Assimp {
                 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);
+                VideoMap::const_iterator it = textures_converted.find(*media);
                 if (it != textures_converted.end()) {
                     index = (*it).second;
                     textureReady = true;
@@ -1719,7 +1790,7 @@ namespace Assimp {
                 else {
                     if (media->ContentLength() > 0) {
                         index = ConvertVideo(*media);
-                        textures_converted[media] = index;
+                        textures_converted[*media] = index;
                         textureReady = true;
                     }
                 }
@@ -2243,13 +2314,13 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa
             if (media != nullptr && media->ContentLength() > 0) {
                 unsigned int index;
 
-                VideoMap::const_iterator it = textures_converted.find(media);
+                VideoMap::const_iterator it = textures_converted.find(*media);
                 if (it != textures_converted.end()) {
                     index = (*it).second;
                 }
                 else {
                     index = ConvertVideo(*media);
-                    textures_converted[media] = index;
+                    textures_converted[*media] = index;
                 }
 
                 // setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture)
@@ -2677,7 +2748,7 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa
         // sanity check whether the input is ok
         static void validateAnimCurveNodes(const std::vector<const AnimationCurveNode*>& curves,
             bool strictMode) {
-            const Object* target(NULL);
+            const Object* target(nullptr);
             for (const AnimationCurveNode* node : curves) {
                 if (!target) {
                     target = node->Target();
@@ -2708,7 +2779,7 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa
 #ifdef ASSIMP_BUILD_DEBUG
             validateAnimCurveNodes(curves, doc.Settings().strictMode);
 #endif
-            const AnimationCurveNode* curve_node = NULL;
+            const AnimationCurveNode* curve_node = nullptr;
             for (const AnimationCurveNode* node : curves) {
                 ai_assert(node);
 
@@ -3556,7 +3627,7 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa
             ai_assert(!out->mMeshes);
             ai_assert(!out->mNumMeshes);
 
-            // note: the trailing () ensures initialization with NULL - not
+            // note: the trailing () ensures initialization with nullptr - not
             // many C++ users seem to know this, so pointing it out to avoid
             // confusion why this code works.
 
@@ -3603,6 +3674,47 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa
             }
         }
 
+        void FBXConverter::ConvertOrphantEmbeddedTextures()
+        {
+            // in C++14 it could be:
+            // for (auto&& [id, object] : objects)
+            for (auto&& id_and_object : doc.Objects())
+            {
+                auto&& id = std::get<0>(id_and_object);
+                auto&& object = std::get<1>(id_and_object);
+                // If an object doesn't have parent
+                if (doc.ConnectionsBySource().count(id) == 0)
+                {
+                    const Texture* realTexture = nullptr;
+                    try
+                    {
+                        const auto& element = object->GetElement();
+                        const Token& key = element.KeyToken();
+                        const char* obtype = key.begin();
+                        const size_t length = static_cast<size_t>(key.end() - key.begin());
+                        if (strncmp(obtype, "Texture", length) == 0)
+                        {
+                            const Texture* texture = static_cast<const Texture*>(object->Get());
+                            if (texture->Media() && texture->Media()->ContentLength() > 0)
+                            {
+                                realTexture = texture;
+                            }
+                        }
+                    }
+                    catch (...)
+                    {
+                        // do nothing
+                    }
+                    if (realTexture)
+                    {
+                        const Video* media = realTexture->Media();
+                        unsigned int index = ConvertVideo(*media);
+                        textures_converted[*media] = index;
+                    }
+                }
+            }
+        }
+
         // ------------------------------------------------------------------------------------------------
         void ConvertToAssimpScene(aiScene* out, const Document& doc, bool removeEmptyBones)
         {

+ 55 - 28
code/FBX/FBXConverter.h

@@ -123,7 +123,7 @@ private:
 
     // ------------------------------------------------------------------------------------------------
     // collect and assign child nodes
-    void ConvertNodes(uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform = aiMatrix4x4());
+    void ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node);
 
     // ------------------------------------------------------------------------------------------------
     void ConvertLights(const Model& model, const std::string &orig_name );
@@ -179,32 +179,35 @@ private:
     void SetupNodeMetadata(const Model& model, aiNode& nd);
 
     // ------------------------------------------------------------------------------------------------
-    void ConvertModel(const Model& model, aiNode& nd, const aiMatrix4x4& node_global_transform);
+    void ConvertModel(const Model &model, aiNode *parent, aiNode *root_node,
+                      const aiMatrix4x4 &absolute_transform);
     
     // ------------------------------------------------------------------------------------------------
     // MeshGeometry -> aiMesh, return mesh index + 1 or 0 if the conversion failed
-    std::vector<unsigned int> ConvertMesh(const MeshGeometry& mesh, const Model& model,
-        const aiMatrix4x4& node_global_transform, aiNode& nd);
+    std::vector<unsigned int>
+    ConvertMesh(const MeshGeometry &mesh, const Model &model, aiNode *parent, aiNode *root_node,
+                const aiMatrix4x4 &absolute_transform);
 
     // ------------------------------------------------------------------------------------------------
     std::vector<unsigned int> ConvertLine(const LineGeometry& line, const Model& model,
-        const aiMatrix4x4& node_global_transform, aiNode& nd);
+                                          aiNode *parent, aiNode *root_node);
 
     // ------------------------------------------------------------------------------------------------
-    aiMesh* SetupEmptyMesh(const Geometry& mesh, aiNode& nd);
+    aiMesh* SetupEmptyMesh(const Geometry& mesh, aiNode *parent);
 
     // ------------------------------------------------------------------------------------------------
-    unsigned int ConvertMeshSingleMaterial(const MeshGeometry& mesh, const Model& model,
-        const aiMatrix4x4& node_global_transform, aiNode& nd);
+    unsigned int ConvertMeshSingleMaterial(const MeshGeometry &mesh, const Model &model,
+                                           const aiMatrix4x4 &absolute_transform, aiNode *parent,
+                                           aiNode *root_node);
 
     // ------------------------------------------------------------------------------------------------
-    std::vector<unsigned int> ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model,
-        const aiMatrix4x4& node_global_transform, aiNode& nd);
+    std::vector<unsigned int>
+    ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model, aiNode *parent, aiNode *root_node,
+                             const aiMatrix4x4 &absolute_transform);
 
     // ------------------------------------------------------------------------------------------------
-    unsigned int ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model,
-        MatIndexArray::value_type index,
-        const aiMatrix4x4& node_global_transform, aiNode& nd);
+    unsigned int ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model, MatIndexArray::value_type index,
+                                          aiNode *parent, aiNode *root_node, const aiMatrix4x4 &absolute_transform);
 
     // ------------------------------------------------------------------------------------------------
     static const unsigned int NO_MATERIAL_SEPARATION = /* std::numeric_limits<unsigned int>::max() */
@@ -217,17 +220,17 @@ private:
     *  - outputVertStartIndices is only used when a material index is specified, it gives for
     *    each output vertex the DOM index it maps to.
     */
-    void ConvertWeights(aiMesh* out, const Model& model, const MeshGeometry& geo,
-        const aiMatrix4x4& node_global_transform = aiMatrix4x4(),
-        unsigned int materialIndex = NO_MATERIAL_SEPARATION,
-        std::vector<unsigned int>* outputVertStartIndices = NULL);
-
+    void ConvertWeights(aiMesh *out, const Model &model, const MeshGeometry &geo, const aiMatrix4x4 &absolute_transform,
+                        aiNode *parent = NULL, aiNode *root_node = NULL,
+                        unsigned int materialIndex = NO_MATERIAL_SEPARATION,
+                        std::vector<unsigned int> *outputVertStartIndices = NULL);
+    // lookup
+    static const aiNode* GetNodeByName( const aiString& name, aiNode *current_node );
     // ------------------------------------------------------------------------------------------------
-    void ConvertCluster(std::vector<aiBone*>& bones, const Model& /*model*/, const Cluster& cl,
-        std::vector<size_t>& out_indices,
-        std::vector<size_t>& index_out_indices,
-        std::vector<size_t>& count_out_indices,
-        const aiMatrix4x4& node_global_transform);
+    void ConvertCluster(std::vector<aiBone *> &local_mesh_bones, 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 &absolute_transform,
+                        aiNode *parent, aiNode *root_node);
 
     // ------------------------------------------------------------------------------------------------
     void ConvertMaterialForMesh(aiMesh* out, const Model& model, const MeshGeometry& geo,
@@ -424,6 +427,10 @@ private:
     // copy generated meshes, animations, lights, cameras and textures to the output scene
     void TransferDataToScene();
 
+    // ------------------------------------------------------------------------------------------------
+    // FBX file could have embedded textures not connected to anything
+    void ConvertOrphantEmbeddedTextures();
+
 private:
     // 0: not assigned yet, others: index is value - 1
     unsigned int defaultMaterialIndex;
@@ -435,27 +442,47 @@ private:
     std::vector<aiCamera*> cameras;
     std::vector<aiTexture*> textures;
 
-    using MaterialMap = std::map<const Material*, unsigned int>;
+    using MaterialMap = std::fbx_unordered_map<const Material*, unsigned int>;
     MaterialMap materials_converted;
 
-    using VideoMap = std::map<const Video*, unsigned int>;
+    using VideoMap = std::fbx_unordered_map<const Video, unsigned int>;
     VideoMap textures_converted;
 
-    using MeshMap = std::map<const Geometry*, std::vector<unsigned int> >;
+    using MeshMap = std::fbx_unordered_map<const Geometry*, std::vector<unsigned int> >;
     MeshMap meshes_converted;
 
     // fixed node name -> which trafo chain components have animations?
-    using NodeAnimBitMap = std::map<std::string, unsigned int> ;
+    using NodeAnimBitMap = std::fbx_unordered_map<std::string, unsigned int> ;
     NodeAnimBitMap node_anim_chain_bits;
 
     // number of nodes with the same name
-    using NodeNameCache = std::unordered_map<std::string, unsigned int>;
+    using NodeNameCache = std::fbx_unordered_map<std::string, unsigned int>;
     NodeNameCache mNodeNames;
 
+    // Deformer name is not the same as a bone name - it does contain the bone name though :)
+    // Deformer names in FBX are always unique in an FBX file.
+    std::map<const std::string, aiBone *> bone_map;
+
     double anim_fps;
 
     aiScene* const out;
     const FBX::Document& doc;
+
+    static void BuildBoneList(aiNode *current_node, const aiNode *root_node, const aiScene *scene,
+                             std::vector<aiBone*>& bones);
+
+    void BuildBoneStack(aiNode *current_node, const aiNode *root_node, const aiScene *scene,
+                   const std::vector<aiBone *> &bones,
+                   std::map<aiBone *, aiNode *> &bone_stack,
+                   std::vector<aiNode*> &node_stack );
+
+    static void BuildNodeList(aiNode *current_node, std::vector<aiNode *> &nodes);
+
+    static aiNode *GetNodeFromStack(const aiString &node_name, std::vector<aiNode *> &nodes);
+
+    static aiNode *GetArmatureRoot(aiNode *bone_node, std::vector<aiBone*> &bone_list);
+
+    static bool IsBoneNode(const aiString &bone_name, std::vector<aiBone *> &bones);
 };
 
 }

+ 37 - 2
code/FBX/FBXDocument.h

@@ -637,6 +637,20 @@ public:
         return ptr;
     }
 
+    bool operator==(const Video& other) const
+    {
+        return (
+               type == other.type
+            && relativeFileName == other.relativeFileName
+            && fileName == other.fileName
+        );
+    }
+
+    bool operator<(const Video& other) const
+    {
+        return std::tie(type, relativeFileName, fileName) < std::tie(other.type, other.relativeFileName, other.fileName);
+    }
+
 private:
     std::string type;
     std::string relativeFileName;
@@ -1005,10 +1019,10 @@ public:
 // during their entire lifetime (Document). FBX files have
 // up to many thousands of objects (most of which we never use),
 // so the memory overhead for them should be kept at a minimum.
-typedef std::map<uint64_t, LazyObject*> ObjectMap;
+typedef std::fbx_unordered_map<uint64_t, LazyObject*> ObjectMap;
 typedef std::fbx_unordered_map<std::string, std::shared_ptr<const PropertyTable> > PropertyTemplateMap;
 
-typedef std::multimap<uint64_t, const Connection*> ConnectionMap;
+typedef std::fbx_unordered_multimap<uint64_t, const Connection*> ConnectionMap;
 
 /** DOM class for global document settings, a single instance per document can
  *  be accessed via Document.Globals(). */
@@ -1177,4 +1191,25 @@ private:
 } // Namespace FBX
 } // Namespace Assimp
 
+namespace std
+{
+    template <>
+    struct hash<const Assimp::FBX::Video>
+    {
+        std::size_t operator()(const Assimp::FBX::Video& video) const
+        {
+            using std::size_t;
+            using std::hash;
+            using std::string;
+
+            size_t res = 17;
+            res = res * 31 + hash<string>()(video.Name());
+            res = res * 31 + hash<string>()(video.RelativeFilename());
+            res = res * 31 + hash<string>()(video.Type());
+
+            return res;
+        }
+    };
+}
+
 #endif // INCLUDED_AI_FBX_DOCUMENT_H

+ 102 - 110
code/FBX/FBXImporter.cpp

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

+ 268 - 0
code/PostProcessing/ArmaturePopulate.cpp

@@ -0,0 +1,268 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+#include "ArmaturePopulate.h"
+
+#include <assimp/BaseImporter.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/postprocess.h>
+#include <assimp/scene.h>
+#include <iostream>
+
+namespace Assimp {
+
+/// The default class constructor.
+ArmaturePopulate::ArmaturePopulate() : BaseProcess()
+{}
+
+/// The class destructor.
+ArmaturePopulate::~ArmaturePopulate() 
+{}
+
+bool ArmaturePopulate::IsActive(unsigned int pFlags) const {
+  return (pFlags & aiProcess_PopulateArmatureData) != 0;
+}
+
+void ArmaturePopulate::SetupProperties(const Importer *pImp) {
+  // do nothing
+}
+
+void ArmaturePopulate::Execute(aiScene *out) {
+
+  // Now convert all bone positions to the correct mOffsetMatrix
+  std::vector<aiBone *> bones;
+  std::vector<aiNode *> nodes;
+  std::map<aiBone *, aiNode *> bone_stack;
+  BuildBoneList(out->mRootNode, out->mRootNode, out, bones);
+  BuildNodeList(out->mRootNode, nodes);
+
+  BuildBoneStack(out->mRootNode, out->mRootNode, out, bones, bone_stack, nodes);
+
+  ASSIMP_LOG_DEBUG_F("Bone stack size: ", bone_stack.size());
+
+  for (std::pair<aiBone *, aiNode *> kvp : bone_stack) {
+    aiBone *bone = kvp.first;
+    aiNode *bone_node = kvp.second;
+    ASSIMP_LOG_DEBUG_F("active node lookup: ", bone->mName.C_Str());
+    // lcl transform grab - done in generate_nodes :)
+
+    // bone->mOffsetMatrix = bone_node->mTransformation;
+    aiNode *armature = GetArmatureRoot(bone_node, bones);
+
+    ai_assert(armature);
+
+    // set up bone armature id
+    bone->mArmature = armature;
+
+    // set this bone node to be referenced properly
+    ai_assert(bone_node);
+    bone->mNode = bone_node;
+  }
+}
+
+
+/* Reprocess all nodes to calculate bone transforms properly based on the REAL
+ * mOffsetMatrix not the local. */
+/* Before this would use mesh transforms which is wrong for bone transforms */
+/* Before this would work for simple character skeletons but not complex meshes
+ * with multiple origins */
+/* Source: sketch fab log cutter fbx */
+void ArmaturePopulate::BuildBoneList(aiNode *current_node,
+                                     const aiNode *root_node,
+                                     const aiScene *scene,
+                                     std::vector<aiBone *> &bones) {
+  ai_assert(scene);
+  for (unsigned int nodeId = 0; nodeId < current_node->mNumChildren; ++nodeId) {
+    aiNode *child = current_node->mChildren[nodeId];
+    ai_assert(child);
+
+    // check for bones
+    for (unsigned int meshId = 0; meshId < child->mNumMeshes; ++meshId) {
+      ai_assert(child->mMeshes);
+      unsigned int mesh_index = child->mMeshes[meshId];
+      aiMesh *mesh = scene->mMeshes[mesh_index];
+      ai_assert(mesh);
+
+      for (unsigned int boneId = 0; boneId < mesh->mNumBones; ++boneId) {
+        aiBone *bone = mesh->mBones[boneId];
+        ai_assert(bone);
+
+        // duplicate meshes exist with the same bones sometimes :)
+        // so this must be detected
+        if (std::find(bones.begin(), bones.end(), bone) == bones.end()) {
+          // add the element once
+          bones.push_back(bone);
+        }
+      }
+
+      // find mesh and get bones
+      // then do recursive lookup for bones in root node hierarchy
+    }
+
+    BuildBoneList(child, root_node, scene, bones);
+  }
+}
+
+/* Prepare flat node list which can be used for non recursive lookups later */
+void ArmaturePopulate::BuildNodeList(const aiNode *current_node,
+                                     std::vector<aiNode *> &nodes) {
+  ai_assert(current_node);
+
+  for (unsigned int nodeId = 0; nodeId < current_node->mNumChildren; ++nodeId) {
+    aiNode *child = current_node->mChildren[nodeId];
+    ai_assert(child);
+
+    nodes.push_back(child);
+
+    BuildNodeList(child, nodes);
+  }
+}
+
+/* A bone stack allows us to have multiple armatures, with the same bone names
+ * A bone stack allows us also to retrieve bones true transform even with
+ * duplicate names :)
+ */
+void ArmaturePopulate::BuildBoneStack(aiNode *current_node,
+                                      const aiNode *root_node,
+                                      const aiScene *scene,
+                                      const std::vector<aiBone *> &bones,
+                                      std::map<aiBone *, aiNode *> &bone_stack,
+                                      std::vector<aiNode *> &node_stack) {
+  ai_assert(scene);
+  ai_assert(root_node);
+  ai_assert(!node_stack.empty());
+
+  for (aiBone *bone : bones) {
+    ai_assert(bone);
+    aiNode *node = GetNodeFromStack(bone->mName, node_stack);
+    if (node == nullptr) {
+      node_stack.clear();
+      BuildNodeList(root_node, node_stack);
+      ASSIMP_LOG_DEBUG_F("Resetting bone stack: nullptr element ", bone->mName.C_Str());
+
+      node = GetNodeFromStack(bone->mName, node_stack);
+
+      if (!node) {
+        ASSIMP_LOG_ERROR("serious import issue node for bone was not detected");
+        continue;
+      }
+    }
+
+    ASSIMP_LOG_DEBUG_F("Successfully added bone[", bone->mName.C_Str(), "] to stack and bone node is: ", node->mName.C_Str());
+
+    bone_stack.insert(std::pair<aiBone *, aiNode *>(bone, node));
+  }
+}
+
+
+/* Returns the armature root node */
+/* This is required to be detected for a bone initially, it will recurse up
+ * until it cannot find another bone and return the node No known failure
+ * points. (yet)
+ */
+aiNode *ArmaturePopulate::GetArmatureRoot(aiNode *bone_node,
+                                          std::vector<aiBone *> &bone_list) {
+  while (bone_node) {
+    if (!IsBoneNode(bone_node->mName, bone_list)) {
+      ASSIMP_LOG_DEBUG_F("GetArmatureRoot() Found valid armature: ", bone_node->mName.C_Str());
+      return bone_node;
+    }
+
+    bone_node = bone_node->mParent;
+  }
+  
+  ASSIMP_LOG_ERROR("GetArmatureRoot() can't find armature!");
+  
+  return nullptr;
+}
+
+
+
+/* Simple IsBoneNode check if this could be a bone */
+bool ArmaturePopulate::IsBoneNode(const aiString &bone_name,
+                                  std::vector<aiBone *> &bones) {
+  for (aiBone *bone : bones) {
+    if (bone->mName == bone_name) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+/* Pop this node by name from the stack if found */
+/* Used in multiple armature situations with duplicate node / bone names */
+/* Known flaw: cannot have nodes with bone names, will be fixed in later release
+ */
+/* (serious to be fixed) Known flaw: nodes which have more than one bone could
+ * be prematurely dropped from stack */
+aiNode *ArmaturePopulate::GetNodeFromStack(const aiString &node_name,
+                                           std::vector<aiNode *> &nodes) {
+  std::vector<aiNode *>::iterator iter;
+  aiNode *found = nullptr;
+  for (iter = nodes.begin(); iter < nodes.end(); ++iter) {
+    aiNode *element = *iter;
+    ai_assert(element);
+    // node valid and node name matches
+    if (element->mName == node_name) {
+      found = element;
+      break;
+    }
+  }
+
+  if (found != nullptr) {
+    ASSIMP_LOG_INFO_F("Removed node from stack: ", found->mName.C_Str());
+    // now pop the element from the node list
+    nodes.erase(iter);
+
+    return found;
+  }
+
+  // unique names can cause this problem
+  ASSIMP_LOG_ERROR("[Serious] GetNodeFromStack() can't find node from stack!");
+
+  return nullptr;
+}
+
+
+
+
+} // Namespace Assimp

+ 112 - 0
code/PostProcessing/ArmaturePopulate.h

@@ -0,0 +1,112 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+#ifndef ARMATURE_POPULATE_H_
+#define ARMATURE_POPULATE_H_
+
+#include "Common/BaseProcess.h"
+#include <assimp/BaseImporter.h>
+#include <vector>
+#include <map>
+
+
+struct aiNode;
+struct aiBone;
+
+namespace Assimp {
+
+// ---------------------------------------------------------------------------
+/** Armature Populate: This is a post process designed
+ * To save you time when importing models into your game engines
+ * This was originally designed only for fbx but will work with other formats
+ * it is intended to auto populate aiBone data with armature and the aiNode
+ * This is very useful when dealing with skinned meshes
+ * or when dealing with many different skeletons
+ * It's off by default but recommend that you try it and use it
+ * It should reduce down any glue code you have in your
+ * importers
+ * You can contact RevoluPowered <[email protected]>
+ * For more info about this
+*/
+class ASSIMP_API ArmaturePopulate : public BaseProcess {
+public:
+    /// The default class constructor.
+    ArmaturePopulate();
+
+    /// The class destructor.
+    virtual ~ArmaturePopulate();
+
+    /// Overwritten, @see BaseProcess
+    virtual bool IsActive( unsigned int pFlags ) const;
+
+    /// Overwritten, @see BaseProcess
+    virtual void SetupProperties( const Importer* pImp );
+
+    /// Overwritten, @see BaseProcess
+    virtual void Execute( aiScene* pScene );
+
+    static aiNode *GetArmatureRoot(aiNode *bone_node,
+                                      std::vector<aiBone *> &bone_list);
+
+    static bool IsBoneNode(const aiString &bone_name,
+                              std::vector<aiBone *> &bones);
+
+    static aiNode *GetNodeFromStack(const aiString &node_name,
+                                       std::vector<aiNode *> &nodes);
+
+    static void BuildNodeList(const aiNode *current_node,
+                                 std::vector<aiNode *> &nodes);
+
+    static void BuildBoneList(aiNode *current_node, const aiNode *root_node,
+                                 const aiScene *scene,
+                                 std::vector<aiBone *> &bones);                        
+
+    static void BuildBoneStack(aiNode *current_node, const aiNode *root_node,
+                                  const aiScene *scene,
+                                  const std::vector<aiBone *> &bones,
+                                  std::map<aiBone *, aiNode *> &bone_stack,
+                                  std::vector<aiNode *> &node_stack);
+};
+
+} // Namespace Assimp
+
+
+#endif // SCALE_PROCESS_H_

+ 1 - 1
code/glTF2/glTF2AssetWriter.inl

@@ -358,7 +358,7 @@ namespace glTF2 {
             WriteVec(pbrSpecularGlossiness, pbrSG.specularFactor, "specularFactor", defaultSpecularFactor, w.mAl);
 
             if (pbrSG.glossinessFactor != 1) {
-                WriteFloat(obj, pbrSG.glossinessFactor, "glossinessFactor", w.mAl);
+                WriteFloat(pbrSpecularGlossiness, pbrSG.glossinessFactor, "glossinessFactor", w.mAl);
             }
 
             WriteTex(pbrSpecularGlossiness, pbrSG.diffuseTexture, "diffuseTexture", w.mAl);

+ 3 - 1
code/glTF2/glTF2Exporter.cpp

@@ -320,7 +320,9 @@ void glTF2Exporter::GetMatTex(const aiMaterial* mat, Ref<Texture>& texture, aiTe
                     if (path[0] == '*') { // embedded
                         aiTexture* tex = mScene->mTextures[atoi(&path[1])];
 
-                        uint8_t* data = reinterpret_cast<uint8_t*>(tex->pcData);
+                        // copy data since lifetime control is handed over to the asset
+                        uint8_t* data = new uint8_t[tex->mWidth];
+                        memcpy(data, tex->pcData, tex->mWidth);
                         texture->source->SetData(data, tex->mWidth, *mAsset);
 
                         if (tex->achFormatHint[0]) {

+ 8 - 2
contrib/irrXML/CXMLReaderImpl.h

@@ -15,6 +15,9 @@
 #include <cstdint>
 //using namespace Assimp;
 
+// For locale independent number conversion
+#include <sstream>
+#include <locale>
 
 #ifdef _DEBUG
 #define IRR_DEBUGPRINT(x) printf((x));
@@ -178,8 +181,11 @@ public:
 			return 0;
 
 		core::stringc c = attrvalue;
-        return static_cast<float>(atof(c.c_str()));
-		//return fast_atof(c.c_str());
+		std::istringstream sstr(c.c_str());
+		sstr.imbue(std::locale("C")); // Locale free number convert
+		float fNum;
+		sstr >> fNum;
+		return fNum;
 	}
 
 

+ 13 - 8
include/assimp/defs.h

@@ -128,16 +128,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  * GENBOUNDINGBOXES */
 //////////////////////////////////////////////////////////////////////////
 
-#ifdef _MSC_VER
+#ifdef _WIN32
 #   undef ASSIMP_API
-
     //////////////////////////////////////////////////////////////////////////
     /* Define 'ASSIMP_BUILD_DLL_EXPORT' to build a DLL of the library */
     //////////////////////////////////////////////////////////////////////////
 #   ifdef ASSIMP_BUILD_DLL_EXPORT
 #       define ASSIMP_API __declspec(dllexport)
 #       define ASSIMP_API_WINONLY __declspec(dllexport)
-#       pragma warning (disable : 4251)
 
     //////////////////////////////////////////////////////////////////////////
     /* Define 'ASSIMP_DLL' before including Assimp to link to ASSIMP in
@@ -150,7 +148,19 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #       define ASSIMP_API
 #       define ASSIMP_API_WINONLY
 #   endif
+#elif defined(SWIG)
+
+    /* Do nothing, the relevant defines are all in AssimpSwigPort.i */
+
+#else
+#   define ASSIMP_API __attribute__ ((visibility("default")))
+#   define ASSIMP_API_WINONLY
+#endif
 
+#ifdef _MSC_VER
+#   ifdef ASSIMP_BUILD_DLL_EXPORT
+#       pragma warning (disable : 4251)
+#   endif
     /* Force the compiler to inline a function, if possible
      */
 #   define AI_FORCE_INLINE __forceinline
@@ -158,17 +168,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     /* Tells the compiler that a function never returns. Used in code analysis
      * to skip dead paths (e.g. after an assertion evaluated to false). */
 #   define AI_WONT_RETURN __declspec(noreturn)
-
 #elif defined(SWIG)
 
     /* Do nothing, the relevant defines are all in AssimpSwigPort.i */
 
 #else
-
 #   define AI_WONT_RETURN
-
-#   define ASSIMP_API __attribute__ ((visibility("default")))
-#   define ASSIMP_API_WINONLY
 #   define AI_FORCE_INLINE inline
 #endif // (defined _MSC_VER)
 

+ 26 - 10
include/assimp/mesh.h

@@ -252,6 +252,9 @@ struct aiVertexWeight {
 };
 
 
+// Forward declare aiNode (pointer use only)
+struct aiNode;
+
 // ---------------------------------------------------------------------------
 /** @brief A single bone of a mesh.
  *
@@ -268,6 +271,16 @@ struct aiBone {
     //! The maximum value for this member is #AI_MAX_BONE_WEIGHTS.
     unsigned int mNumWeights;
 
+#ifndef ASSIMP_BUILD_NO_ARMATUREPOPULATE_PROCESS
+    // The bone armature node - used for skeleton conversion
+    // you must enable aiProcess_PopulateArmatureData to populate this
+    C_STRUCT aiNode* mArmature;
+
+    // The bone node in the scene - used for skeleton conversion
+    // you must enable aiProcess_PopulateArmatureData to populate this
+    C_STRUCT aiNode* mNode;
+
+#endif
     //! The influence weights of this bone, by vertex index.
     C_STRUCT aiVertexWeight* mWeights;
 
@@ -422,11 +435,11 @@ struct aiAnimMesh
     /**Anim Mesh name */
     C_STRUCT aiString mName;
 
-    /** Replacement for aiMesh::mVertices. If this array is non-NULL,
+    /** Replacement for aiMesh::mVertices. If this array is non-nullptr,
      *  it *must* contain mNumVertices entries. The corresponding
-     *  array in the host mesh must be non-NULL as well - animation
+     *  array in the host mesh must be non-nullptr as well - animation
      *  meshes may neither add or nor remove vertex components (if
-     *  a replacement array is NULL and the corresponding source
+     *  a replacement array is nullptr and the corresponding source
      *  array is not, the source data is taken instead)*/
     C_STRUCT aiVector3D* mVertices;
 
@@ -600,7 +613,7 @@ struct aiMesh
     C_STRUCT aiVector3D* mVertices;
 
     /** Vertex normals.
-    * The array contains normalized vectors, NULL if not present.
+    * The array contains normalized vectors, nullptr if not present.
     * The array is mNumVertices in size. Normals are undefined for
     * point and line primitives. A mesh consisting of points and
     * lines only may not have normal vectors. Meshes with mixed
@@ -623,7 +636,7 @@ struct aiMesh
 
     /** Vertex tangents.
     * The tangent of a vertex points in the direction of the positive
-    * X texture axis. The array contains normalized vectors, NULL if
+    * X texture axis. The array contains normalized vectors, nullptr if
     * not present. The array is mNumVertices in size. A mesh consisting
     * of points and lines only may not have normal vectors. Meshes with
     * mixed primitive types (i.e. lines and triangles) may have
@@ -637,7 +650,7 @@ struct aiMesh
 
     /** Vertex bitangents.
     * The bitangent of a vertex points in the direction of the positive
-    * Y texture axis. The array contains normalized vectors, NULL if not
+    * Y texture axis. The array contains normalized vectors, nullptr if not
     * present. The array is mNumVertices in size.
     * @note If the mesh contains tangents, it automatically also contains
     * bitangents.
@@ -646,14 +659,14 @@ struct aiMesh
 
     /** Vertex color sets.
     * A mesh may contain 0 to #AI_MAX_NUMBER_OF_COLOR_SETS vertex
-    * colors per vertex. NULL if not present. Each array is
+    * colors per vertex. nullptr if not present. Each array is
     * mNumVertices in size if present.
     */
     C_STRUCT aiColor4D* mColors[AI_MAX_NUMBER_OF_COLOR_SETS];
 
     /** Vertex texture coords, also known as UV channels.
     * A mesh may contain 0 to AI_MAX_NUMBER_OF_TEXTURECOORDS per
-    * vertex. NULL if not present. The array is mNumVertices in size.
+    * vertex. nullptr if not present. The array is mNumVertices in size.
     */
     C_STRUCT aiVector3D* mTextureCoords[AI_MAX_NUMBER_OF_TEXTURECOORDS];
 
@@ -675,7 +688,7 @@ struct aiMesh
     C_STRUCT aiFace* mFaces;
 
     /** The number of bones this mesh contains.
-    * Can be 0, in which case the mBones array is NULL.
+    * Can be 0, in which case the mBones array is nullptr.
     */
     unsigned int mNumBones;
 
@@ -773,7 +786,10 @@ struct aiMesh
         // DO NOT REMOVE THIS ADDITIONAL CHECK
         if (mNumBones && mBones)    {
             for( unsigned int a = 0; a < mNumBones; a++) {
-                delete mBones[a];
+                if(mBones[a])
+                {
+                    delete mBones[a];
+                }
             }
             delete [] mBones;
         }

+ 15 - 0
include/assimp/postprocess.h

@@ -320,6 +320,19 @@ enum aiPostProcessSteps
     */
     aiProcess_FixInfacingNormals = 0x2000,
 
+
+
+    // -------------------------------------------------------------------------
+    /** 
+     * This step generically populates aiBone->mArmature and aiBone->mNode generically
+     * The point of these is it saves you later having to calculate these elements
+     * This is useful when handling rest information or skin information
+     * If you have multiple armatures on your models we strongly recommend enabling this 
+     * Instead of writing your own multi-root, multi-armature lookups we have done the 
+     * hard work for you :)
+   */
+    aiProcess_PopulateArmatureData = 0x4000,
+
     // -------------------------------------------------------------------------
     /** <hr>This step splits meshes with more than one primitive type in
      *  homogeneous sub-meshes.
@@ -537,6 +550,8 @@ enum aiPostProcessSteps
     */
     aiProcess_Debone  = 0x4000000,
 
+
+
     // -------------------------------------------------------------------------
     /** <hr>This step will perform a global scale of the model.
     *

+ 11 - 11
include/assimp/scene.h

@@ -110,13 +110,13 @@ struct ASSIMP_API aiNode
     /** The transformation relative to the node's parent. */
     C_STRUCT aiMatrix4x4 mTransformation;
 
-    /** Parent node. NULL if this node is the root node. */
+    /** Parent node. nullptr if this node is the root node. */
     C_STRUCT aiNode* mParent;
 
     /** The number of child nodes of this node. */
     unsigned int mNumChildren;
 
-    /** The child nodes of this node. NULL if mNumChildren is 0. */
+    /** The child nodes of this node. nullptr if mNumChildren is 0. */
     C_STRUCT aiNode** mChildren;
 
     /** The number of meshes of this node. */
@@ -127,7 +127,7 @@ struct ASSIMP_API aiNode
       */
     unsigned int* mMeshes;
 
-    /** Metadata associated with this node or NULL if there is no metadata.
+    /** Metadata associated with this node or nullptr if there is no metadata.
       *  Whether any metadata is generated depends on the source file format. See the
       * @link importer_notes @endlink page for more information on every source file
       * format. Importers that don't document any metadata don't write any.
@@ -149,7 +149,7 @@ struct ASSIMP_API aiNode
      *  of the scene.
      *
      *  @param name Name to search for
-     *  @return NULL or a valid Node if the search was successful.
+     *  @return nullptr or a valid Node if the search was successful.
      */
     inline 
     const aiNode* FindNode(const aiString& name) const {
@@ -344,7 +344,7 @@ struct aiScene
 
 #ifdef __cplusplus
 
-    //! Default constructor - set everything to 0/NULL
+    //! Default constructor - set everything to 0/nullptr
     ASSIMP_API aiScene();
 
     //! Destructor
@@ -353,33 +353,33 @@ struct aiScene
     //! Check whether the scene contains meshes
     //! Unless no special scene flags are set this will always be true.
     inline bool HasMeshes() const { 
-        return mMeshes != NULL && mNumMeshes > 0; 
+        return mMeshes != nullptr && mNumMeshes > 0; 
     }
 
     //! Check whether the scene contains materials
     //! Unless no special scene flags are set this will always be true.
     inline bool HasMaterials() const { 
-        return mMaterials != NULL && mNumMaterials > 0; 
+        return mMaterials != nullptr && mNumMaterials > 0; 
     }
 
     //! Check whether the scene contains lights
     inline bool HasLights() const { 
-        return mLights != NULL && mNumLights > 0; 
+        return mLights != nullptr && mNumLights > 0; 
     }
 
     //! Check whether the scene contains textures
     inline bool HasTextures() const {
-        return mTextures != NULL && mNumTextures > 0; 
+        return mTextures != nullptr && mNumTextures > 0; 
     }
 
     //! Check whether the scene contains cameras
     inline bool HasCameras() const {
-        return mCameras != NULL && mNumCameras > 0; 
+        return mCameras != nullptr && mNumCameras > 0; 
     }
 
     //! Check whether the scene contains animations
     inline bool HasAnimations() const { 
-        return mAnimations != NULL && mNumAnimations > 0; 
+        return mAnimations != nullptr && mNumAnimations > 0; 
     }
 
     //! Returns a short filename from a full path

+ 1 - 0
test/CMakeLists.txt

@@ -148,6 +148,7 @@ SET( POST_PROCESSES
   unit/utRemoveRedundantMaterials.cpp
   unit/utRemoveVCProcess.cpp
   unit/utScaleProcess.cpp
+  unit/utArmaturePopulate.cpp
   unit/utJoinVertices.cpp
   unit/utRemoveComments.cpp
   unit/utRemoveComponent.cpp

二进制
test/models/Collada/duck.zae


文件差异内容过多而无法显示
+ 544 - 0
test/models/FBX/box_orphant_embedded_texture.fbx


二进制
test/models/FBX/huesitos.fbx


+ 1 - 1
test/unit/Main.cpp

@@ -16,7 +16,7 @@ int main(int argc, char* argv[])
 
     // create a logger from both CPP
     Assimp::DefaultLogger::create("AssimpLog_Cpp.txt",Assimp::Logger::VERBOSE,
-        aiDefaultLogStream_DEBUGGER | aiDefaultLogStream_FILE);
+        aiDefaultLogStream_STDOUT | aiDefaultLogStream_DEBUGGER | aiDefaultLogStream_FILE);
 
     // .. and C. They should smoothly work together
     aiEnableVerboseLogging(AI_TRUE);

+ 83 - 0
test/unit/utArmaturePopulate.cpp

@@ -0,0 +1,83 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+#include "UnitTestPCH.h"
+#include "TestModelFactory.h"
+
+
+#include "SceneDiffer.h"
+#include "AbstractImportExportBase.h"
+
+#include <assimp/Importer.hpp>
+#include <assimp/postprocess.h>
+#include <assimp/material.h>
+#include <assimp/scene.h>
+#include <assimp/types.h>
+
+#include "PostProcessing/ArmaturePopulate.h"
+
+namespace Assimp {
+namespace UnitTest {
+
+class utArmaturePopulate : public ::testing::Test {
+    // empty
+};
+
+TEST_F( utArmaturePopulate, importCheckForArmatureTest) {
+    Assimp::Importer importer;
+    unsigned int mask = aiProcess_PopulateArmatureData | aiProcess_ValidateDataStructure;
+    const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/FBX/huesitos.fbx", mask);
+    EXPECT_NE( nullptr, scene );
+    EXPECT_EQ(scene->mNumMeshes, 1u);
+    aiMesh* mesh = scene->mMeshes[0];
+    EXPECT_EQ(mesh->mNumFaces, 68u);
+    EXPECT_EQ(mesh->mNumVertices, 256u);
+    EXPECT_GT(mesh->mNumBones, 0u);
+
+    aiBone* exampleBone = mesh->mBones[0];
+    EXPECT_NE(exampleBone, nullptr);
+    EXPECT_NE(exampleBone->mArmature, nullptr);
+    EXPECT_NE(exampleBone->mNode, nullptr);
+}
+
+} // Namespace UnitTest
+} // Namespace Assimp

+ 45 - 5
test/unit/utColladaImportExport.cpp

@@ -44,6 +44,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "AbstractImportExportBase.h"
 
 #include <assimp/Importer.hpp>
+#include <assimp/scene.h>
 #include <assimp/postprocess.h>
 
 using namespace Assimp;
@@ -52,8 +53,19 @@ class utColladaImportExport : public AbstractImportExportBase {
 public:
     virtual bool importerTest() {
         Assimp::Importer importer;
-        const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/Collada/duck.dae", aiProcess_ValidateDataStructure );
-        return nullptr != scene;
+        const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/duck.dae", aiProcess_ValidateDataStructure);
+        if (scene == nullptr)
+            return false;
+
+        // Expected number of items
+        EXPECT_EQ(scene->mNumMeshes, 1u);
+        EXPECT_EQ(scene->mNumMaterials, 1u);
+        EXPECT_EQ(scene->mNumAnimations, 0u);
+        EXPECT_EQ(scene->mNumTextures, 0u);
+        EXPECT_EQ(scene->mNumLights, 1u);
+        EXPECT_EQ(scene->mNumCameras, 1u);
+
+        return true;
     }
 };
 
@@ -64,9 +76,37 @@ TEST_F(utColladaImportExport, importBlenFromFileTest) {
 class utColladaZaeImportExport : public AbstractImportExportBase {
 public:
     virtual bool importerTest() {
-        Assimp::Importer importer;
-        const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/duck.zae", aiProcess_ValidateDataStructure);
-        return nullptr != scene;
+        {
+            Assimp::Importer importer;
+            const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/duck.zae", aiProcess_ValidateDataStructure);
+            if (scene == nullptr)
+                return false;
+
+            // Expected number of items
+            EXPECT_EQ(scene->mNumMeshes, 1u);
+            EXPECT_EQ(scene->mNumMaterials, 1u);
+            EXPECT_EQ(scene->mNumAnimations, 0u);
+            EXPECT_EQ(scene->mNumTextures, 1u);
+            EXPECT_EQ(scene->mNumLights, 1u);
+            EXPECT_EQ(scene->mNumCameras, 1u);
+        }
+
+        {
+            Assimp::Importer importer;
+            const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/duck_nomanifest.zae", aiProcess_ValidateDataStructure);
+            if (scene == nullptr)
+                return false;
+
+            // Expected number of items
+            EXPECT_EQ(scene->mNumMeshes, 1u);
+            EXPECT_EQ(scene->mNumMaterials, 1u);
+            EXPECT_EQ(scene->mNumAnimations, 0u);
+            EXPECT_EQ(scene->mNumTextures, 1u);
+            EXPECT_EQ(scene->mNumLights, 1u);
+            EXPECT_EQ(scene->mNumCameras, 1u);
+        }
+
+        return true;
     }
 };
 

+ 30 - 29
test/unit/utFBXImporterExporter.cpp

@@ -76,6 +76,7 @@ TEST_F( utFBXImporterExporter, importBareBoxWithoutColorsAndTextureCoords ) {
     EXPECT_EQ(mesh->mNumVertices, 36u);
 }
 
+
 TEST_F(utFBXImporterExporter, importCubesWithNoNames) {
     Assimp::Importer importer;
     const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/FBX/cubes_nonames.fbx", aiProcess_ValidateDataStructure);
@@ -86,26 +87,6 @@ TEST_F(utFBXImporterExporter, importCubesWithNoNames) {
     ASSERT_STREQ(root->mName.C_Str(), "RootNode");
     ASSERT_TRUE(root->mChildren);
     ASSERT_EQ(root->mNumChildren, 2u);
-
-    const auto child0 = root->mChildren[0];
-    ASSERT_TRUE(child0);
-    ASSERT_STREQ(child0->mName.C_Str(), "RootNode001");
-    ASSERT_TRUE(child0->mChildren);
-    ASSERT_EQ(child0->mNumChildren, 1u);
-
-    const auto child00 = child0->mChildren[0];
-    ASSERT_TRUE(child00);
-    ASSERT_STREQ(child00->mName.C_Str(), "RootNode001001");
-
-    const auto child1 = root->mChildren[1];
-    ASSERT_TRUE(child1);
-    ASSERT_STREQ(child1->mName.C_Str(), "RootNode002");
-    ASSERT_TRUE(child1->mChildren);
-    ASSERT_EQ(child1->mNumChildren, 1u);
-
-    const auto child10 = child1->mChildren[0];
-    ASSERT_TRUE(child10);
-    ASSERT_STREQ(child10->mName.C_Str(), "RootNode002001");
 }
 
 TEST_F(utFBXImporterExporter, importCubesWithUnicodeDuplicatedNames) {
@@ -137,7 +118,7 @@ TEST_F(utFBXImporterExporter, importCubesWithUnicodeDuplicatedNames) {
 
     const auto child10 = child1->mChildren[0];
     ASSERT_TRUE(child10);
-    ASSERT_STREQ(child10->mName.C_Str(), "\xd0\x9a\xd1\x83\xd0\xb1\x31""001");
+    ASSERT_STREQ(child10->mName.C_Str(), "\xd0\x9a\xd1\x83\xd0\xb1\x31");
 }
 
 TEST_F(utFBXImporterExporter, importCubesComplexTransform) {
@@ -168,14 +149,14 @@ TEST_F(utFBXImporterExporter, importCubesComplexTransform) {
     auto parent = child1;
     const size_t chain_length = 8u;
     const char* chainStr[chain_length] = {
-        "Cube1001_$AssimpFbx$_Translation",
-        "Cube1001_$AssimpFbx$_RotationPivot",
-        "Cube1001_$AssimpFbx$_RotationPivotInverse",
-        "Cube1001_$AssimpFbx$_ScalingOffset",
-        "Cube1001_$AssimpFbx$_ScalingPivot",
-        "Cube1001_$AssimpFbx$_Scaling",
-        "Cube1001_$AssimpFbx$_ScalingPivotInverse",
-        "Cube1001"
+        "Cube1_$AssimpFbx$_Translation",
+        "Cube1_$AssimpFbx$_RotationPivot",
+        "Cube1_$AssimpFbx$_RotationPivotInverse",
+        "Cube1_$AssimpFbx$_ScalingOffset",
+        "Cube1_$AssimpFbx$_ScalingPivot",
+        "Cube1_$AssimpFbx$_Scaling",
+        "Cube1_$AssimpFbx$_ScalingPivotInverse",
+        "Cube1"
     };
     for (size_t i = 0; i < chain_length; ++i) {
         ASSERT_TRUE(parent->mChildren);
@@ -282,3 +263,23 @@ TEST_F(utFBXImporterExporter, fbxTokenizeTestTest) {
     //const aiScene* scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/FBX/transparentTest2.fbx", aiProcess_ValidateDataStructure);
     //EXPECT_NE(nullptr, scene);
 }
+
+TEST_F(utFBXImporterExporter, importOrphantEmbeddedTextureTest) {
+    // see https://github.com/assimp/assimp/issues/1957
+    Assimp::Importer importer;
+    const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/FBX/box_orphant_embedded_texture.fbx", aiProcess_ValidateDataStructure);
+    EXPECT_NE(nullptr, scene);
+
+    EXPECT_EQ(1u, scene->mNumMaterials);
+    aiMaterial *mat = scene->mMaterials[0];
+    ASSERT_NE(nullptr, mat);
+
+    aiString path;
+    aiTextureMapMode modes[2];
+    ASSERT_EQ(aiReturn_SUCCESS, mat->GetTexture(aiTextureType_DIFFUSE, 0, &path, nullptr, nullptr, nullptr, nullptr, modes));
+    ASSERT_STREQ(path.C_Str(), "..\\Primitives\\GridGrey.tga");
+
+    ASSERT_EQ(1u, scene->mNumTextures);
+    ASSERT_TRUE(scene->mTextures[0]->pcData);
+    ASSERT_EQ(9026u, scene->mTextures[0]->mWidth) << "FBX ASCII base64 compression used for a texture.";
+}

+ 1 - 1
test/unit/utVersion.cpp

@@ -48,7 +48,7 @@ TEST_F( utVersion, aiGetLegalStringTest ) {
     EXPECT_NE( lv, nullptr );
     std::string text( lv );
 
-    size_t pos( text.find( std::string( "2017" ) ) );
+    size_t pos( text.find( std::string( "2019" ) ) );
     EXPECT_NE( pos, std::string::npos );
 }
 

部分文件因为文件数量过多而无法显示