Browse Source

Merge branch 'master' into ps-devel-fix-contrib-zlib-static-compile-001

Kim Kulling 5 years ago
parent
commit
76f2e783c3

+ 3 - 3
.github/workflows/ccpp.yml

@@ -11,7 +11,7 @@ jobs:
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
     
     
     steps:
     steps:
-    - uses: actions/checkout@v1
+    - uses: actions/checkout@v2
     - name: configure
     - name: configure
       run: cmake CMakeLists.txt
       run: cmake CMakeLists.txt
     - name: build
     - name: build
@@ -23,7 +23,7 @@ jobs:
     runs-on: macos-latest
     runs-on: macos-latest
     
     
     steps:
     steps:
-    - uses: actions/checkout@v1
+    - uses: actions/checkout@v2
     - name: configure
     - name: configure
       run: cmake CMakeLists.txt
       run: cmake CMakeLists.txt
     - name: build
     - name: build
@@ -35,7 +35,7 @@ jobs:
     runs-on: windows-latest
     runs-on: windows-latest
     
     
     steps:
     steps:
-    - uses: actions/checkout@v1
+    - uses: actions/checkout@v2
     - name: configure
     - name: configure
       run: cmake CMakeLists.txt
       run: cmake CMakeLists.txt
     - name: build
     - name: build

+ 2 - 5
assimpTargets-debug.cmake.in

@@ -7,6 +7,8 @@ set(CMAKE_IMPORT_FILE_VERSION 1)
 
 
 set(ASSIMP_BUILD_SHARED_LIBS @BUILD_SHARED_LIBS@)
 set(ASSIMP_BUILD_SHARED_LIBS @BUILD_SHARED_LIBS@)
 
 
+get_property(LIB64 GLOBAL PROPERTY FIND_LIBRARY_USE_LIB64_PATHS)
+
 if(MSVC)
 if(MSVC)
   if(MSVC_TOOLSET_VERSION)
   if(MSVC_TOOLSET_VERSION)
     set(MSVC_PREFIX "vc${MSVC_TOOLSET_VERSION}")
     set(MSVC_PREFIX "vc${MSVC_TOOLSET_VERSION}")
@@ -35,8 +37,6 @@ if(MSVC)
   endif()
   endif()
   set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@-${MSVC_PREFIX}-mt" CACHE STRING "the suffix for the assimp windows library" )
   set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@-${MSVC_PREFIX}-mt" CACHE STRING "the suffix for the assimp windows library" )
 
 
-  file(TO_NATIVE_PATH "${_IMPORT_PREFIX}" _IMPORT_PREFIX)
-
   if(ASSIMP_BUILD_SHARED_LIBS)
   if(ASSIMP_BUILD_SHARED_LIBS)
     set(sharedLibraryName "assimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_SHARED_LIBRARY_SUFFIX@")
     set(sharedLibraryName "assimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_SHARED_LIBRARY_SUFFIX@")
     set(importLibraryName "assimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_IMPORT_LIBRARY_SUFFIX@")
     set(importLibraryName "assimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_IMPORT_LIBRARY_SUFFIX@")
@@ -89,9 +89,6 @@ else()
   endif()
   endif()
 endif()
 endif()
 
 
-
-
-
 # Commands beyond this point should not need to know the version.
 # Commands beyond this point should not need to know the version.
 set(CMAKE_IMPORT_FILE_VERSION)
 set(CMAKE_IMPORT_FILE_VERSION)
 
 

+ 3 - 3
assimpTargets-release.cmake.in

@@ -7,6 +7,8 @@ set(CMAKE_IMPORT_FILE_VERSION 1)
 
 
 set(ASSIMP_BUILD_SHARED_LIBS @BUILD_SHARED_LIBS@)
 set(ASSIMP_BUILD_SHARED_LIBS @BUILD_SHARED_LIBS@)
 
 
+get_property(LIB64 GLOBAL PROPERTY FIND_LIBRARY_USE_LIB64_PATHS)
+
 if(MSVC)
 if(MSVC)
   if(MSVC_TOOLSET_VERSION)
   if(MSVC_TOOLSET_VERSION)
     set(MSVC_PREFIX "vc${MSVC_TOOLSET_VERSION}")
     set(MSVC_PREFIX "vc${MSVC_TOOLSET_VERSION}")
@@ -34,8 +36,6 @@ if(MSVC)
     endif()
     endif()
   endif()
   endif()
   set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@-${MSVC_PREFIX}-mt" CACHE STRING "the suffix for the assimp windows library" )
   set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@-${MSVC_PREFIX}-mt" CACHE STRING "the suffix for the assimp windows library" )
-  	
-  file(TO_NATIVE_PATH "${_IMPORT_PREFIX}" _IMPORT_PREFIX)
 
 
   if(ASSIMP_BUILD_SHARED_LIBS)
   if(ASSIMP_BUILD_SHARED_LIBS)
     set(sharedLibraryName "assimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_SHARED_LIBRARY_SUFFIX@")
     set(sharedLibraryName "assimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_SHARED_LIBRARY_SUFFIX@")
@@ -56,7 +56,7 @@ if(MSVC)
     # Import target "assimp::assimp" for configuration "Release"
     # Import target "assimp::assimp" for configuration "Release"
     set_property(TARGET assimp::assimp APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
     set_property(TARGET assimp::assimp APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
     set_target_properties(assimp::assimp PROPERTIES
     set_target_properties(assimp::assimp PROPERTIES
-      IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/${staticLibraryName}"
+      IMPORTED_LOCATION_RELEASE "@CMAKE_INSTALL_FULL_LIBDIR@/${staticLibraryName}"
     )
     )
     list(APPEND _IMPORT_CHECK_TARGETS assimp::assimp )
     list(APPEND _IMPORT_CHECK_TARGETS assimp::assimp )
     list(APPEND _IMPORT_CHECK_FILES_FOR_assimp::assimp "@CMAKE_INSTALL_FULL_LIBDIR@/${staticLibraryName}")
     list(APPEND _IMPORT_CHECK_FILES_FOR_assimp::assimp "@CMAKE_INSTALL_FULL_LIBDIR@/${staticLibraryName}")

+ 26 - 14
code/3MF/D3MFExporter.cpp

@@ -181,7 +181,7 @@ bool D3MFExporter::export3DModel() {
 
 
     writeHeader();
     writeHeader();
     mModelOutput << "<" << XmlTag::model << " " << XmlTag::model_unit << "=\"millimeter\""
     mModelOutput << "<" << XmlTag::model << " " << XmlTag::model_unit << "=\"millimeter\""
-            << "xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\">"
+            << " xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\">"
             << std::endl;
             << std::endl;
     mModelOutput << "<" << XmlTag::resources << ">";
     mModelOutput << "<" << XmlTag::resources << ">";
     mModelOutput << std::endl;
     mModelOutput << std::endl;
@@ -212,7 +212,7 @@ bool D3MFExporter::export3DModel() {
 }
 }
 
 
 void D3MFExporter::writeHeader() {
 void D3MFExporter::writeHeader() {
-    mModelOutput << "<?xml version=\"1.0\" encoding=\"UTF - 8\"?>";
+    mModelOutput << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
     mModelOutput << std::endl;
     mModelOutput << std::endl;
 }
 }
 
 
@@ -254,16 +254,28 @@ void D3MFExporter::writeBaseMaterials() {
         if ( mat->Get( AI_MATKEY_COLOR_DIFFUSE, color ) == aiReturn_SUCCESS ) {
         if ( mat->Get( AI_MATKEY_COLOR_DIFFUSE, color ) == aiReturn_SUCCESS ) {
             hexDiffuseColor.clear();
             hexDiffuseColor.clear();
             tmp.clear();
             tmp.clear();
-            hexDiffuseColor = "#";
-            
-            tmp = DecimalToHexa( (ai_real) color.r );
-            hexDiffuseColor += tmp;
-            tmp = DecimalToHexa((ai_real)color.g);
-            hexDiffuseColor += tmp;
-            tmp = DecimalToHexa((ai_real)color.b);
-            hexDiffuseColor += tmp;
-            tmp = DecimalToHexa((ai_real)color.a);
-            hexDiffuseColor += tmp;
+            // rgbs %
+    		if(color.r <= 1 && color.g <= 1 && color.b <= 1 && color.a <= 1){
+    	
+    			 hexDiffuseColor = Rgba2Hex(
+    			 	(int)((ai_real)color.r)*255,
+    			 	(int)((ai_real)color.g)*255,
+    			 	(int)((ai_real)color.b)*255,
+    			 	(int)((ai_real)color.a)*255,
+    			 	true
+    			 );
+    			 
+    		}else{
+    			hexDiffuseColor = "#";
+            	tmp = DecimalToHexa( (ai_real) color.r );
+            	hexDiffuseColor += tmp;
+            	tmp = DecimalToHexa((ai_real)color.g);
+            	hexDiffuseColor += tmp;
+            	tmp = DecimalToHexa((ai_real)color.b);
+            	hexDiffuseColor += tmp;
+            	tmp = DecimalToHexa((ai_real)color.a);
+            	hexDiffuseColor += tmp;
+    		}
         } else {
         } else {
             hexDiffuseColor = "#FFFFFFFF";
             hexDiffuseColor = "#FFFFFFFF";
         }
         }
@@ -284,7 +296,7 @@ void D3MFExporter::writeObjects() {
         if ( nullptr == currentNode ) {
         if ( nullptr == currentNode ) {
             continue;
             continue;
         }
         }
-        mModelOutput << "<" << XmlTag::object << " id=\"" << currentNode->mName.C_Str() << "\" type=\"model\">";
+        mModelOutput << "<" << XmlTag::object << " id=\"" << i + 2 << "\" type=\"model\">";
         mModelOutput << std::endl;
         mModelOutput << std::endl;
         for ( unsigned int j = 0; j < currentNode->mNumMeshes; ++j ) {
         for ( unsigned int j = 0; j < currentNode->mNumMeshes; ++j ) {
             aiMesh *currentMesh = mScene->mMeshes[ currentNode->mMeshes[ j ] ];
             aiMesh *currentMesh = mScene->mMeshes[ currentNode->mMeshes[ j ] ];
@@ -348,7 +360,7 @@ void D3MFExporter::writeBuild() {
     mModelOutput << "<" << XmlTag::build << ">" << std::endl;
     mModelOutput << "<" << XmlTag::build << ">" << std::endl;
 
 
     for ( size_t i = 0; i < mBuildItems.size(); ++i ) {
     for ( size_t i = 0; i < mBuildItems.size(); ++i ) {
-        mModelOutput << "<" << XmlTag::item << " objectid=\"" << i + 1 << "\"/>";
+        mModelOutput << "<" << XmlTag::item << " objectid=\"" << i + 2 << "\"/>";
         mModelOutput << std::endl;
         mModelOutput << std::endl;
     }
     }
     mModelOutput << "</" << XmlTag::build << ">";
     mModelOutput << "</" << XmlTag::build << ">";

+ 1 - 0
code/CMakeLists.txt

@@ -121,6 +121,7 @@ SET( PUBLIC_HEADERS
   ${HEADER_PATH}/GenericProperty.h
   ${HEADER_PATH}/GenericProperty.h
   ${HEADER_PATH}/SpatialSort.h
   ${HEADER_PATH}/SpatialSort.h
   ${HEADER_PATH}/SkeletonMeshBuilder.h
   ${HEADER_PATH}/SkeletonMeshBuilder.h
+  ${HEADER_PATH}/SmallVector.h
   ${HEADER_PATH}/SmoothingGroups.h
   ${HEADER_PATH}/SmoothingGroups.h
   ${HEADER_PATH}/SmoothingGroups.inl
   ${HEADER_PATH}/SmoothingGroups.inl
   ${HEADER_PATH}/StandardShapes.h
   ${HEADER_PATH}/StandardShapes.h

+ 32 - 3
code/Common/Exporter.cpp

@@ -83,32 +83,61 @@ namespace Assimp {
 void GetPostProcessingStepInstanceList(std::vector< BaseProcess* >& out);
 void GetPostProcessingStepInstanceList(std::vector< BaseProcess* >& out);
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-// Exporter worker function prototypes. Should not be necessary to #ifndef them, it's just a prototype
-// do not use const, because some exporter need to convert the scene temporary
+// Exporter worker function prototypes. Do not use const, because some exporter need to convert 
+// the scene temporary
+#ifndef ASSIMP_BUILD_NO_COLLADA_EXPORTER
 void ExportSceneCollada(const char*,IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneCollada(const char*,IOSystem*, const aiScene*, const ExportProperties*);
+#endif
+#ifndef ASSIMP_BUILD_NO_X_EXPORTER
 void ExportSceneXFile(const char*,IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneXFile(const char*,IOSystem*, const aiScene*, const ExportProperties*);
+#endif
+#ifndef ASSIMP_BUILD_NO_STEP_EXPORTER
 void ExportSceneStep(const char*,IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneStep(const char*,IOSystem*, const aiScene*, const ExportProperties*);
+#endif
+#ifndef ASSIMP_BUILD_NO_OBJ_EXPORTER
 void ExportSceneObj(const char*,IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneObj(const char*,IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneObjNoMtl(const char*,IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneObjNoMtl(const char*,IOSystem*, const aiScene*, const ExportProperties*);
+#endif
+#ifndef ASSIMP_BUILD_NO_STL_EXPORTER
 void ExportSceneSTL(const char*,IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneSTL(const char*,IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneSTLBinary(const char*,IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneSTLBinary(const char*,IOSystem*, const aiScene*, const ExportProperties*);
+#endif
+#ifndef ASSIMP_BUILD_NO_PLY_EXPORTER
 void ExportScenePly(const char*,IOSystem*, const aiScene*, const ExportProperties*);
 void ExportScenePly(const char*,IOSystem*, const aiScene*, const ExportProperties*);
 void ExportScenePlyBinary(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportScenePlyBinary(const char*, IOSystem*, const aiScene*, const ExportProperties*);
+#endif
+#ifndef ASSIMP_BUILD_NO_3DS_EXPORTER
 void ExportScene3DS(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportScene3DS(const char*, IOSystem*, const aiScene*, const ExportProperties*);
+#endif
+#ifndef ASSIMP_BUILD_NO_GLTF_EXPORTER
 void ExportSceneGLTF(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneGLTF(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneGLB(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneGLB(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneGLTF2(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneGLTF2(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneGLB2(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneGLB2(const char*, IOSystem*, const aiScene*, const ExportProperties*);
+#endif
+#ifndef ASSIMP_BUILD_NO_ASSBIN_EXPORTER
 void ExportSceneAssbin(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneAssbin(const char*, IOSystem*, const aiScene*, const ExportProperties*);
+#endif
+#ifndef ASSIMP_BUILD_NO_ASSXML_EXPORTER
 void ExportSceneAssxml(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneAssxml(const char*, IOSystem*, const aiScene*, const ExportProperties*);
+#endif
+#ifndef ASSIMP_BUILD_NO_X3D_EXPORTER
 void ExportSceneX3D(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneX3D(const char*, IOSystem*, const aiScene*, const ExportProperties*);
+#endif
+#ifndef ASSIMP_BUILD_NO_FBX_EXPORTER
 void ExportSceneFBX(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneFBX(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneFBXA(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneFBXA(const char*, IOSystem*, const aiScene*, const ExportProperties*);
+#endif
+#ifndef ASSIMP_BUILD_NO_3MF_EXPORTER
 void ExportScene3MF( const char*, IOSystem*, const aiScene*, const ExportProperties* );
 void ExportScene3MF( const char*, IOSystem*, const aiScene*, const ExportProperties* );
+#endif
+#ifndef ASSIMP_BUILD_NO_M3D_EXPORTER
 void ExportSceneM3D(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneM3D(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneM3DA(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneM3DA(const char*, IOSystem*, const aiScene*, const ExportProperties*);
+#endif
+#ifndef ASSIMP_BUILD_NO_ASSJSON_EXPORTER
 void ExportAssimp2Json(const char* , IOSystem*, const aiScene* , const Assimp::ExportProperties*);
 void ExportAssimp2Json(const char* , IOSystem*, const aiScene* , const Assimp::ExportProperties*);
-
+#endif
 
 
 static void setupExporterArray(std::vector<Exporter::ExportFormatEntry> &exporters) {
 static void setupExporterArray(std::vector<Exporter::ExportFormatEntry> &exporters) {
 #ifndef ASSIMP_BUILD_NO_COLLADA_EXPORTER
 #ifndef ASSIMP_BUILD_NO_COLLADA_EXPORTER

+ 57 - 66
code/PostProcessing/LimitBoneWeightsProcess.cpp

@@ -44,6 +44,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 
 
 #include "LimitBoneWeightsProcess.h"
 #include "LimitBoneWeightsProcess.h"
+#include <assimp/SmallVector.h>
 #include <assimp/StringUtils.h>
 #include <assimp/StringUtils.h>
 #include <assimp/postprocess.h>
 #include <assimp/postprocess.h>
 #include <assimp/DefaultLogger.hpp>
 #include <assimp/DefaultLogger.hpp>
@@ -52,7 +53,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 using namespace Assimp;
 using namespace Assimp;
 
 
-
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
 // Constructor to be privately used by Importer
 LimitBoneWeightsProcess::LimitBoneWeightsProcess()
 LimitBoneWeightsProcess::LimitBoneWeightsProcess()
@@ -76,10 +76,12 @@ bool LimitBoneWeightsProcess::IsActive( unsigned int pFlags) const
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Executes the post processing step on the given imported data.
 // Executes the post processing step on the given imported data.
-void LimitBoneWeightsProcess::Execute( aiScene* pScene) {
+void LimitBoneWeightsProcess::Execute( aiScene* pScene)
+{
     ASSIMP_LOG_DEBUG("LimitBoneWeightsProcess begin");
     ASSIMP_LOG_DEBUG("LimitBoneWeightsProcess begin");
-    for (unsigned int a = 0; a < pScene->mNumMeshes; ++a ) {
-        ProcessMesh(pScene->mMeshes[a]);
+
+    for (unsigned int m = 0; m < pScene->mNumMeshes; ++m) {
+        ProcessMesh(pScene->mMeshes[m]);
     }
     }
 
 
     ASSIMP_LOG_DEBUG("LimitBoneWeightsProcess end");
     ASSIMP_LOG_DEBUG("LimitBoneWeightsProcess end");
@@ -95,107 +97,96 @@ void LimitBoneWeightsProcess::SetupProperties(const Importer* pImp)
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Unites identical vertices in the given mesh
 // Unites identical vertices in the given mesh
-void LimitBoneWeightsProcess::ProcessMesh( aiMesh* pMesh)
+void LimitBoneWeightsProcess::ProcessMesh(aiMesh* pMesh)
 {
 {
-    if( !pMesh->HasBones())
+    if (!pMesh->HasBones())
         return;
         return;
 
 
     // collect all bone weights per vertex
     // collect all bone weights per vertex
-    typedef std::vector< std::vector< Weight > > WeightsPerVertex;
-    WeightsPerVertex vertexWeights( pMesh->mNumVertices);
+    typedef SmallVector<Weight,8> VertexWeightArray;
+    typedef std::vector<VertexWeightArray> WeightsPerVertex;
+    WeightsPerVertex vertexWeights(pMesh->mNumVertices);
+    size_t maxVertexWeights = 0;
 
 
-    // collect all weights per vertex
-    for( unsigned int a = 0; a < pMesh->mNumBones; a++)
+    for (unsigned int b = 0; b < pMesh->mNumBones; ++b)
     {
     {
-        const aiBone* bone = pMesh->mBones[a];
-        for( unsigned int b = 0; b < bone->mNumWeights; b++)
+        const aiBone* bone = pMesh->mBones[b];
+        for (unsigned int w = 0; w < bone->mNumWeights; ++w)
         {
         {
-            const aiVertexWeight& w = bone->mWeights[b];
-            vertexWeights[w.mVertexId].push_back( Weight( a, w.mWeight));
+            const aiVertexWeight& vw = bone->mWeights[w];
+            vertexWeights[vw.mVertexId].push_back(Weight(b, vw.mWeight));
+            maxVertexWeights = std::max(maxVertexWeights, vertexWeights[vw.mVertexId].size());
         }
         }
     }
     }
 
 
+    if (maxVertexWeights <= mMaxWeights)
+        return;
+
     unsigned int removed = 0, old_bones = pMesh->mNumBones;
     unsigned int removed = 0, old_bones = pMesh->mNumBones;
 
 
     // now cut the weight count if it exceeds the maximum
     // now cut the weight count if it exceeds the maximum
-    bool bChanged = false;
-    for( WeightsPerVertex::iterator vit = vertexWeights.begin(); vit != vertexWeights.end(); ++vit)
+    for (WeightsPerVertex::iterator vit = vertexWeights.begin(); vit != vertexWeights.end(); ++vit)
     {
     {
-        if( vit->size() <= mMaxWeights)
+        if (vit->size() <= mMaxWeights)
             continue;
             continue;
 
 
-        bChanged = true;
-
         // more than the defined maximum -> first sort by weight in descending order. That's
         // more than the defined maximum -> first sort by weight in descending order. That's
         // why we defined the < operator in such a weird way.
         // why we defined the < operator in such a weird way.
-        std::sort( vit->begin(), vit->end());
+        std::sort(vit->begin(), vit->end());
 
 
         // now kill everything beyond the maximum count
         // now kill everything beyond the maximum count
         unsigned int m = static_cast<unsigned int>(vit->size());
         unsigned int m = static_cast<unsigned int>(vit->size());
-        vit->erase( vit->begin() + mMaxWeights, vit->end());
-        removed += static_cast<unsigned int>(m-vit->size());
+        vit->resize(mMaxWeights);
+        removed += static_cast<unsigned int>(m - vit->size());
 
 
         // and renormalize the weights
         // and renormalize the weights
         float sum = 0.0f;
         float sum = 0.0f;
-        for( std::vector<Weight>::const_iterator it = vit->begin(); it != vit->end(); ++it ) {
+        for(const Weight* it = vit->begin(); it != vit->end(); ++it) {
             sum += it->mWeight;
             sum += it->mWeight;
         }
         }
-        if( 0.0f != sum ) {
+        if (0.0f != sum) {
             const float invSum = 1.0f / sum;
             const float invSum = 1.0f / sum;
-            for( std::vector<Weight>::iterator it = vit->begin(); it != vit->end(); ++it ) {
+            for(Weight* it = vit->begin(); it != vit->end(); ++it) {
                 it->mWeight *= invSum;
                 it->mWeight *= invSum;
             }
             }
         }
         }
     }
     }
 
 
-    if (bChanged)   {
-        // rebuild the vertex weight array for all bones
-        typedef std::vector< std::vector< aiVertexWeight > > WeightsPerBone;
-        WeightsPerBone boneWeights( pMesh->mNumBones);
-        for( unsigned int a = 0; a < vertexWeights.size(); a++)
+    // clear weight count for all bone
+    for (unsigned int a = 0; a < pMesh->mNumBones; ++a)
+    {
+        pMesh->mBones[a]->mNumWeights = 0;
+    }
+
+    // rebuild the vertex weight array for all bones
+    for (unsigned int a = 0; a < vertexWeights.size(); ++a)
+    {
+        const VertexWeightArray& vw = vertexWeights[a];
+        for (const Weight* it = vw.begin(); it != vw.end(); ++it)
         {
         {
-            const std::vector<Weight>& vw = vertexWeights[a];
-            for( std::vector<Weight>::const_iterator it = vw.begin(); it != vw.end(); ++it)
-                boneWeights[it->mBone].push_back( aiVertexWeight( a, it->mWeight));
+            aiBone* bone = pMesh->mBones[it->mBone];
+            bone->mWeights[bone->mNumWeights++] = aiVertexWeight(a, it->mWeight);
         }
         }
+    }
 
 
-        // and finally copy the vertex weight list over to the mesh's bones
-        std::vector<bool> abNoNeed(pMesh->mNumBones,false);
-        bChanged = false;
+    // remove empty bones
+    unsigned int writeBone = 0;
 
 
-        for( unsigned int a = 0; a < pMesh->mNumBones; a++)
+    for (unsigned int readBone = 0; readBone< pMesh->mNumBones; ++readBone)
+    {
+        aiBone* bone = pMesh->mBones[readBone];
+        if (bone->mNumWeights > 0)
         {
         {
-            const std::vector<aiVertexWeight>& bw = boneWeights[a];
-            aiBone* bone = pMesh->mBones[a];
-
-            if ( bw.empty() )
-            {
-                abNoNeed[a] = bChanged = true;
-                continue;
-            }
-
-            // copy the weight list. should always be less weights than before, so we don't need a new allocation
-            ai_assert( bw.size() <= bone->mNumWeights);
-            bone->mNumWeights = static_cast<unsigned int>( bw.size() );
-            ::memcpy( bone->mWeights, &bw[0], bw.size() * sizeof( aiVertexWeight));
+            pMesh->mBones[writeBone++] = bone;
         }
         }
-
-        if (bChanged)   {
-            // the number of new bones is smaller than before, so we can reuse the old array
-            aiBone** ppcCur = pMesh->mBones;aiBone** ppcSrc = ppcCur;
-
-            for (std::vector<bool>::const_iterator iter  = abNoNeed.begin();iter != abNoNeed.end()  ;++iter)    {
-                if (*iter)  {
-                    delete *ppcSrc;
-                    --pMesh->mNumBones;
-                }
-                else *ppcCur++ = *ppcSrc;
-                ++ppcSrc;
-            }
+        else
+        {
+            delete bone;
         }
         }
+    }
+    pMesh->mNumBones = writeBone;
 
 
-        if (!DefaultLogger::isNullLogger()) {
-            ASSIMP_LOG_INFO_F("Removed ", removed, " weights. Input bones: ", old_bones, ". Output bones: ", pMesh->mNumBones );
-        }
+    if (!DefaultLogger::isNullLogger()) {
+        ASSIMP_LOG_INFO_F("Removed ", removed, " weights. Input bones: ", old_bones, ". Output bones: ", pMesh->mNumBones);
     }
     }
 }
 }

+ 10 - 2
code/glTF2/glTF2Asset.h

@@ -58,6 +58,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <algorithm>
 #include <algorithm>
 #include <list>
 #include <list>
 #include <map>
 #include <map>
+#include <set>
 #include <stdexcept>
 #include <stdexcept>
 #include <string>
 #include <string>
 #include <vector>
 #include <vector>
@@ -81,14 +82,18 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #define ASSIMP_GLTF_USE_UNORDERED_MULTIMAP
 #define ASSIMP_GLTF_USE_UNORDERED_MULTIMAP
 #else
 #else
 #define gltf_unordered_map map
 #define gltf_unordered_map map
+#define gltf_unordered_set set
 #endif
 #endif
 
 
 #ifdef ASSIMP_GLTF_USE_UNORDERED_MULTIMAP
 #ifdef ASSIMP_GLTF_USE_UNORDERED_MULTIMAP
 #include <unordered_map>
 #include <unordered_map>
+#include <unordered_set>
 #if _MSC_VER > 1600
 #if _MSC_VER > 1600
 #define gltf_unordered_map unordered_map
 #define gltf_unordered_map unordered_map
+#define gltf_unordered_set unordered_set
 #else
 #else
 #define gltf_unordered_map tr1::unordered_map
 #define gltf_unordered_map tr1::unordered_map
+#define gltf_unordered_set tr1::unordered_set
 #endif
 #endif
 #endif
 #endif
 
 
@@ -375,8 +380,8 @@ struct Accessor : public Object {
 
 
     inline uint8_t *GetPointer();
     inline uint8_t *GetPointer();
 
 
-    template <class T>
-    bool ExtractData(T *&outData);
+    template<class T>
+    void ExtractData(T *&outData);
 
 
     void WriteData(size_t count, const void *src_buffer, size_t src_stride);
     void WriteData(size_t count, const void *src_buffer, size_t src_stride);
 
 
@@ -720,6 +725,7 @@ struct Mesh : public Object {
     std::vector<Primitive> primitives;
     std::vector<Primitive> primitives;
 
 
     std::vector<float> weights;
     std::vector<float> weights;
+    std::vector<std::string> targetNames;
 
 
     Mesh() {}
     Mesh() {}
 
 
@@ -874,6 +880,8 @@ class LazyDict : public LazyDictBase {
     Value *mDict; //! JSON dictionary object
     Value *mDict; //! JSON dictionary object
     Asset &mAsset; //! The asset instance
     Asset &mAsset; //! The asset instance
 
 
+    std::gltf_unordered_set<unsigned int> mRecursiveReferenceCheck;  //! Used by Retrieve to prevent recursive lookups
+
     void AttachToDocument(Document &doc);
     void AttachToDocument(Document &doc);
     void DetachFromDocument();
     void DetachFromDocument();
 
 

+ 28 - 7
code/glTF2/glTF2Asset.inl

@@ -280,6 +280,11 @@ Ref<T> LazyDict<T>::Retrieve(unsigned int i) {
         throw DeadlyImportError("GLTF: Object at index \"" + to_string(i) + "\" is not a JSON object");
         throw DeadlyImportError("GLTF: Object at index \"" + to_string(i) + "\" is not a JSON object");
     }
     }
 
 
+    if (mRecursiveReferenceCheck.find(i) != mRecursiveReferenceCheck.end()) {
+        throw DeadlyImportError("GLTF: Object at index \"" + to_string(i) + "\" has recursive reference to itself");
+    }
+    mRecursiveReferenceCheck.insert(i);
+
     // Unique ptr prevents memory leak in case of Read throws an exception
     // Unique ptr prevents memory leak in case of Read throws an exception
     auto inst = std::unique_ptr<T>(new T());
     auto inst = std::unique_ptr<T>(new T());
     inst->id = std::string(mDictId) + "_" + to_string(i);
     inst->id = std::string(mDictId) + "_" + to_string(i);
@@ -287,7 +292,9 @@ Ref<T> LazyDict<T>::Retrieve(unsigned int i) {
     ReadMember(obj, "name", inst->name);
     ReadMember(obj, "name", inst->name);
     inst->Read(obj, mAsset);
     inst->Read(obj, mAsset);
 
 
-    return Add(inst.release());
+    Ref<T> result = Add(inst.release());
+    mRecursiveReferenceCheck.erase(i);
+    return result;
 }
 }
 
 
 template <class T>
 template <class T>
@@ -613,10 +620,13 @@ inline void CopyData(size_t count,
 }
 }
 } // namespace
 } // namespace
 
 
-template <class T>
-bool Accessor::ExtractData(T *&outData) {
-    uint8_t *data = GetPointer();
-    if (!data) return false;
+template<class T>
+void Accessor::ExtractData(T *&outData)
+{
+    uint8_t* data = GetPointer();
+    if (!data) {
+        throw DeadlyImportError("GLTF: data is NULL");
+    }
 
 
     const size_t elemSize = GetElementSize();
     const size_t elemSize = GetElementSize();
     const size_t totalSize = elemSize * count;
     const size_t totalSize = elemSize * count;
@@ -636,8 +646,6 @@ bool Accessor::ExtractData(T *&outData) {
             memcpy(outData + i, data + i * stride, elemSize);
             memcpy(outData + i, data + i * stride, elemSize);
         }
         }
     }
     }
-
-    return true;
 }
 }
 
 
 inline void Accessor::WriteData(size_t _count, const void *src_buffer, size_t src_stride) {
 inline void Accessor::WriteData(size_t _count, const void *src_buffer, size_t src_stride) {
@@ -1026,6 +1034,19 @@ inline void Mesh::Read(Value &pJSON_Object, Asset &pAsset_Root) {
             }
             }
         }
         }
     }
     }
+
+    Value *extras = FindObject(pJSON_Object, "extras");
+    if (nullptr != extras ) {
+        if (Value* curTargetNames = FindArray(*extras, "targetNames")) {
+            this->targetNames.resize(curTargetNames->Size());
+            for (unsigned int i = 0; i < curTargetNames->Size(); ++i) {
+                Value& targetNameValue = (*curTargetNames)[i];
+                if (targetNameValue.IsString()) {
+                    this->targetNames[i] = targetNameValue.GetString();
+                }
+            }
+        }
+    }
 }
 }
 
 
 inline void Camera::Read(Value &obj, Asset & /*r*/) {
 inline void Camera::Read(Value &obj, Asset & /*r*/) {

+ 3 - 0
code/glTF2/glTF2Importer.cpp

@@ -472,6 +472,9 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) {
 					if (mesh.weights.size() > i) {
 					if (mesh.weights.size() > i) {
 						aiAnimMesh.mWeight = mesh.weights[i];
 						aiAnimMesh.mWeight = mesh.weights[i];
 					}
 					}
+					if (mesh.targetNames.size() > i) {
+						aiAnimMesh.mName = mesh.targetNames[i];
+					}
 				}
 				}
 			}
 			}
 
 

+ 28 - 13
include/assimp/MathFunctions.h

@@ -55,36 +55,51 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 namespace Assimp {
 namespace Assimp {
 namespace Math {
 namespace Math {
 
 
-// TODO: use binary GCD for unsigned integers ....
-template < typename IntegerType >
-inline
-IntegerType gcd( IntegerType a, IntegerType b ) {
+/// @brief  Will return the greatest common divisor.
+/// @param  a   [in] Value a.
+/// @param  b   [in] Value b.
+/// @return The greatest common divisor.
+template <typename IntegerType>
+inline IntegerType gcd( IntegerType a, IntegerType b ) {
 	const IntegerType zero = (IntegerType)0;
 	const IntegerType zero = (IntegerType)0;
 	while ( true ) {
 	while ( true ) {
-		if ( a == zero )
+		if ( a == zero ) {
 			return b;
 			return b;
+        }
 		b %= a;
 		b %= a;
 
 
-		if ( b == zero )
+		if ( b == zero ) {
 			return a;
 			return a;
+        }
 		a %= b;
 		a %= b;
 	}
 	}
 }
 }
 
 
+/// @brief  Will return the greatest common divisor.
+/// @param  a   [in] Value a.
+/// @param  b   [in] Value b.
+/// @return The greatest common divisor.
 template < typename IntegerType >
 template < typename IntegerType >
-inline
-IntegerType lcm( IntegerType a, IntegerType b ) {
+inline IntegerType lcm( IntegerType a, IntegerType b ) {
 	const IntegerType t = gcd (a,b);
 	const IntegerType t = gcd (a,b);
-	if (!t)
+	if (!t) {
         return t;
         return t;
+    }
 	return a / t * b;
 	return a / t * b;
 }
 }
-
+/// @brief  Will return the smallest epsilon-value for the requested type. 
+/// @return The numercical limit epsilon depending on its type.
 template<class T>
 template<class T>
-inline
-T getEpsilon() {
+inline T getEpsilon() {
     return std::numeric_limits<T>::epsilon();
     return std::numeric_limits<T>::epsilon();
 }
 }
 
 
+/// @brief  Will return the constant PI for the requested type.
+/// @return Pi
+template<class T>
+inline T PI() {
+    return static_cast<T>(3.14159265358979323846);
 }
 }
-}
+
+} // namespace Math
+} // namespace Assimp

+ 164 - 0
include/assimp/SmallVector.h

@@ -0,0 +1,164 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2020, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file Defines small vector with inplace storage.
+Based on CppCon 2016: Chandler Carruth "High Performance Code 201: Hybrid Data Structures" */
+
+#pragma once
+#ifndef AI_SMALLVECTOR_H_INC
+#define AI_SMALLVECTOR_H_INC
+
+#ifdef __GNUC__
+#   pragma GCC system_header
+#endif
+
+namespace Assimp {
+
+// --------------------------------------------------------------------------------------------
+/// @brief Small vector with inplace storage. 
+///
+/// Reduces heap allocations when list is shorter. It uses a small array for a dedicated size.
+/// When the growing gets bigger than this small cache a dynamic growing algorithm will be 
+/// used.
+// --------------------------------------------------------------------------------------------
+template<typename T, unsigned int Capacity>
+class SmallVector {
+public:
+    /// @brief  The default class constructor.
+    SmallVector() : 
+            mStorage(mInplaceStorage),
+            mSize(0),
+            mCapacity(Capacity) {
+        // empty
+    }
+
+    /// @brief  The class destructor.
+    ~SmallVector() {
+        if (mStorage != mInplaceStorage) {
+            delete [] mStorage;
+        }
+    }
+
+    /// @brief  Will push a new item. The capacity will grow in case of a too small capacity.
+    /// @param  item    [in] The item to push at the end of the vector.
+    void push_back(const T& item) {
+        if (mSize < mCapacity) {
+            mStorage[mSize++] = item;
+            return;
+        }
+        
+        push_back_and_grow(item);
+    }
+
+    /// @brief  Will resize the vector.
+    /// @param  newSize     [in] The new size.
+    void resize(size_t newSize) {
+        if (newSize > mCapacity) {
+            grow(newSize);
+        }
+        mSize = newSize;
+    }
+
+    /// @brief  Returns the current size of the vector.
+    /// @return The current size.
+    size_t size() const {
+        return mSize;
+    }
+
+    /// @brief  Returns a pointer to the first item.
+    /// @return The first item as a pointer.
+    T* begin() {
+        return mStorage;
+    }
+
+    /// @brief  Returns a pointer to the end.
+    /// @return The end as a pointer.
+    T* end() {
+        return &mStorage[mSize];
+    }
+
+    /// @brief  Returns a const pointer to the first item.
+    /// @return The first item as a const pointer.
+    T* begin() const {
+        return mStorage;
+    }
+
+    /// @brief  Returns a const pointer to the end.
+    /// @return The end as a const pointer.
+    T* end() const {
+        return &mStorage[mSize];
+    }
+
+    SmallVector(const SmallVector &) = delete;
+    SmallVector(SmallVector &&) = delete;
+    SmallVector &operator = (const SmallVector &) = delete;
+    SmallVector &operator = (SmallVector &&) = delete;
+
+private:
+    void grow( size_t newCapacity) {
+        T* oldStorage = mStorage;
+        T* newStorage = new T[newCapacity];
+
+        std::memcpy(newStorage, oldStorage, mSize * sizeof(T));
+
+        mStorage = newStorage;
+        mCapacity = newCapacity;
+
+        if (oldStorage != mInplaceStorage) {
+            delete [] oldStorage;
+        }
+    }
+
+    void push_back_and_grow(const T& item) {
+        grow(mCapacity + Capacity);
+
+        mStorage[mSize++] = item;
+    }
+
+    T* mStorage;
+    size_t mSize;
+    size_t mCapacity;
+    T mInplaceStorage[Capacity];
+};
+
+} // end namespace Assimp
+
+#endif // !! AI_SMALLVECTOR_H_INC

+ 17 - 0
include/assimp/StringUtils.h

@@ -145,4 +145,21 @@ std::string DecimalToHexa( T toConvert ) {
     return result;
     return result;
 }
 }
 
 
+///	@brief	translate RGBA to String
+///	@param	r   aiColor.r
+///	@param	g   aiColor.g
+///	@param	b   aiColor.b
+///	@param	a   aiColor.a
+///	@param	with_head   # 
+///	@return	The hexadecimal string, is empty in case of an error.
+AI_FORCE_INLINE std::string Rgba2Hex(int r, int g, int b, int a, bool with_head) {
+	std::stringstream ss;
+	if (with_head) {
+		ss << "#";
+    }
+	ss << std::hex << (r << 24 | g << 16 | b << 8 | a);
+	
+    return ss.str();
+}
+
 #endif // INCLUDED_AI_STRINGUTILS_H
 #endif // INCLUDED_AI_STRINGUTILS_H

+ 4 - 4
packaging/windows-innosetup/script_x64.iss

@@ -2,7 +2,7 @@
 
 
 [Setup]
 [Setup]
 AppName=Open Asset Import Library - SDK
 AppName=Open Asset Import Library - SDK
-AppVerName=Open Asset Import Library - SDK (v5.0.0)
+AppVerName=Open Asset Import Library - SDK (v5.0.1)
 DefaultDirName={pf}\Assimp
 DefaultDirName={pf}\Assimp
 DefaultGroupName=Assimp
 DefaultGroupName=Assimp
 UninstallDisplayIcon={app}\bin\x64\assimp.exe
 UninstallDisplayIcon={app}\bin\x64\assimp.exe
@@ -12,9 +12,9 @@ SetupIconFile=..\..\tools\shared\assimp_tools_icon.ico
 WizardImageFile=compiler:WizModernImage-IS.BMP
 WizardImageFile=compiler:WizModernImage-IS.BMP
 WizardSmallImageFile=compiler:WizModernSmallImage-IS.BMP
 WizardSmallImageFile=compiler:WizModernSmallImage-IS.BMP
 LicenseFile=License.rtf
 LicenseFile=License.rtf
-OutputBaseFileName=assimp-sdk-5.0.0-setup
-VersionInfoVersion=5.0.0.0
-VersionInfoTextVersion=5.0.0
+OutputBaseFileName=assimp-sdk-5.0.1-setup
+VersionInfoVersion=5.0.1.0
+VersionInfoTextVersion=5.0.1
 VersionInfoCompany=Assimp Development Team
 VersionInfoCompany=Assimp Development Team
 ArchitecturesInstallIn64BitMode=x64
 ArchitecturesInstallIn64BitMode=x64
 
 

+ 4 - 4
packaging/windows-innosetup/script_x86.iss

@@ -2,7 +2,7 @@
 
 
 [Setup]
 [Setup]
 AppName=Open Asset Import Library - SDK
 AppName=Open Asset Import Library - SDK
-AppVerName=Open Asset Import Library - SDK (v5.0.0)
+AppVerName=Open Asset Import Library - SDK (v5.0.1)
 DefaultDirName={pf}\Assimp
 DefaultDirName={pf}\Assimp
 DefaultGroupName=Assimp
 DefaultGroupName=Assimp
 UninstallDisplayIcon={app}\bin\x86\assimp.exe
 UninstallDisplayIcon={app}\bin\x86\assimp.exe
@@ -12,9 +12,9 @@ SetupIconFile=..\..\tools\shared\assimp_tools_icon.ico
 WizardImageFile=compiler:WizModernImage-IS.BMP
 WizardImageFile=compiler:WizModernImage-IS.BMP
 WizardSmallImageFile=compiler:WizModernSmallImage-IS.BMP
 WizardSmallImageFile=compiler:WizModernSmallImage-IS.BMP
 LicenseFile=License.rtf
 LicenseFile=License.rtf
-OutputBaseFileName=assimp-sdk-5.0.0-setup
-VersionInfoVersion=4.1.0.0
-VersionInfoTextVersion=4.1.0
+OutputBaseFileName=assimp-sdk-5.0.1-setup
+VersionInfoVersion=5.0.1.0
+VersionInfoTextVersion=5.0.1
 VersionInfoCompany=Assimp Development Team
 VersionInfoCompany=Assimp Development Team
 ;ArchitecturesInstallIn64BitMode=x64
 ;ArchitecturesInstallIn64BitMode=x64
 
 

+ 12 - 6
samples/SimpleOpenGL/CMakeLists.txt

@@ -1,3 +1,5 @@
+SET(SAMPLE_PROJECT_NAME assimp_simpleogl)
+
 FIND_PACKAGE(OpenGL)
 FIND_PACKAGE(OpenGL)
 FIND_PACKAGE(GLUT)
 FIND_PACKAGE(GLUT)
 IF ( MSVC )
 IF ( MSVC )
@@ -16,6 +18,10 @@ IF ( NOT GLUT_FOUND )
   ENDIF ()
   ENDIF ()
 ENDIF ()
 ENDIF ()
 
 
+# Used for usage and error messages in the program.
+ADD_COMPILE_DEFINITIONS(ASSIMP_VERSION="${ASSIMP_VERSION}")
+ADD_COMPILE_DEFINITIONS(PROJECT_NAME="${SAMPLE_PROJECT_NAME}")
+
 if ( MSVC )
 if ( MSVC )
   ADD_DEFINITIONS( -D_SCL_SECURE_NO_WARNINGS )
   ADD_DEFINITIONS( -D_SCL_SECURE_NO_WARNINGS )
   ADD_DEFINITIONS( -D_CRT_SECURE_NO_WARNINGS )
   ADD_DEFINITIONS( -D_CRT_SECURE_NO_WARNINGS )
@@ -34,17 +40,17 @@ LINK_DIRECTORIES(
   ${Assimp_BINARY_DIR}/lib
   ${Assimp_BINARY_DIR}/lib
 )
 )
 
 
-ADD_EXECUTABLE( assimp_simpleogl
+ADD_EXECUTABLE( ${SAMPLE_PROJECT_NAME}
   Sample_SimpleOpenGL.c
   Sample_SimpleOpenGL.c
 )
 )
 
 
-SET_PROPERTY(TARGET assimp_simpleogl PROPERTY DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX})
+SET_PROPERTY(TARGET ${SAMPLE_PROJECT_NAME} PROPERTY DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX})
 
 
-TARGET_LINK_LIBRARIES( assimp_simpleogl assimp ${OPENGL_LIBRARIES} ${GLUT_LIBRARIES} ${M_LIB} )
-SET_TARGET_PROPERTIES( assimp_simpleogl PROPERTIES
-  OUTPUT_NAME assimp_simpleogl
+TARGET_LINK_LIBRARIES( ${SAMPLE_PROJECT_NAME} assimp ${OPENGL_LIBRARIES} ${GLUT_LIBRARIES} ${M_LIB} )
+SET_TARGET_PROPERTIES( ${SAMPLE_PROJECT_NAME} PROPERTIES
+  OUTPUT_NAME ${SAMPLE_PROJECT_NAME}
 )
 )
 
 
-INSTALL( TARGETS assimp_simpleogl
+INSTALL( TARGETS ${SAMPLE_PROJECT_NAME}
   DESTINATION "${ASSIMP_BIN_INSTALL_DIR}" COMPONENT assimp-dev
   DESTINATION "${ASSIMP_BIN_INSTALL_DIR}" COMPONENT assimp-dev
 )
 )

+ 72 - 12
samples/SimpleOpenGL/Sample_SimpleOpenGL.c

@@ -15,9 +15,9 @@
 #include <stdio.h>
 #include <stdio.h>
 
 
 #ifdef __APPLE__
 #ifdef __APPLE__
-#include <glut.h>
+#include <freeglut.h>
 #else
 #else
-#include <GL/glut.h>
+#include <GL/freeglut.h>
 #endif
 #endif
 
 
 /* assimp include files. These three are usually needed. */
 /* assimp include files. These three are usually needed. */
@@ -25,6 +25,39 @@
 #include <assimp/scene.h>
 #include <assimp/scene.h>
 #include <assimp/postprocess.h>
 #include <assimp/postprocess.h>
 
 
+#define COMMAND_USAGE "--usage"
+
+/* ---------------------------------------------------------------------------- */
+inline static void print_run_command(const char* command_name) {
+	printf("Run '%s %s' for more information.\n", 
+		PROJECT_NAME, command_name);
+}
+
+/* ---------------------------------------------------------------------------- */
+inline static void print_error(const char* msg) {
+	printf("ERROR: %s\n", msg);
+}
+
+#define NEW_LINE "\n"
+#define DOUBLE_NEW_LINE NEW_LINE NEW_LINE
+
+/* ---------------------------------------------------------------------------- */
+inline static void print_usage() {
+	static const char* usage_format = 
+		"Usage: "
+		PROJECT_NAME
+		" <file>"	 DOUBLE_NEW_LINE
+		"where:"	 DOUBLE_NEW_LINE
+		"  %-10s %s" DOUBLE_NEW_LINE
+		"options:"	 DOUBLE_NEW_LINE
+		"  %-10s %s" DOUBLE_NEW_LINE;
+	printf(usage_format,
+		// where
+		"file", "The input model file to load.",
+		// options
+		COMMAND_USAGE, "Display usage.");
+}
+
 /* the global Assimp scene object */
 /* the global Assimp scene object */
 const C_STRUCT aiScene* scene = NULL;
 const C_STRUCT aiScene* scene = NULL;
 GLuint scene_list = 0;
 GLuint scene_list = 0;
@@ -245,7 +278,7 @@ void do_motion (void)
 	static int frames = 0;
 	static int frames = 0;
 
 
 	int time = glutGet(GLUT_ELAPSED_TIME);
 	int time = glutGet(GLUT_ELAPSED_TIME);
-	angle += (time-prev_time)*0.01f;
+	angle += static_cast<float>((time-prev_time)*0.01);
 	prev_time = time;
 	prev_time = time;
 
 
 	frames += 1;
 	frames += 1;
@@ -324,12 +357,42 @@ int loadasset (const char* path)
 /* ---------------------------------------------------------------------------- */
 /* ---------------------------------------------------------------------------- */
 int main(int argc, char **argv)
 int main(int argc, char **argv)
 {
 {
+	const char* model_file = NULL;
 	C_STRUCT aiLogStream stream;
 	C_STRUCT aiLogStream stream;
 
 
+	if (argc < 2) {
+		print_error("No input model file specifed.");
+		print_run_command(COMMAND_USAGE);
+		return EXIT_FAILURE;
+	}
+
+	// Find and execute available commands entered by the user.
+	for (int i = 1; i < argc; ++i) {
+		if (!strncmp(argv[i], COMMAND_USAGE, strlen(COMMAND_USAGE))) {
+			print_usage();
+			return EXIT_SUCCESS;
+		}
+	}
+
+	// Check and validate the specified model file extension.
+	model_file = argv[1];
+	const char* extension = strchr(model_file, '.');
+	if (!extension) {
+		print_error("Please provide a file with a valid extension.");
+		return EXIT_FAILURE;
+	}
+
+	if (AI_FALSE == aiIsExtensionSupported(extension)) {
+		print_error("The specified model file extension is currently "
+			"unsupported in Assimp " ASSIMP_VERSION ".");
+		return EXIT_FAILURE;
+	}
+
 	glutInitWindowSize(900,600);
 	glutInitWindowSize(900,600);
 	glutInitWindowPosition(100,100);
 	glutInitWindowPosition(100,100);
 	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
 	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
 	glutInit(&argc, argv);
 	glutInit(&argc, argv);
+	glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS);
 
 
 	glutCreateWindow("Assimp - Very simple OpenGL sample");
 	glutCreateWindow("Assimp - Very simple OpenGL sample");
 	glutDisplayFunc(display);
 	glutDisplayFunc(display);
@@ -346,14 +409,11 @@ int main(int argc, char **argv)
 	stream = aiGetPredefinedLogStream(aiDefaultLogStream_FILE,"assimp_log.txt");
 	stream = aiGetPredefinedLogStream(aiDefaultLogStream_FILE,"assimp_log.txt");
 	aiAttachLogStream(&stream);
 	aiAttachLogStream(&stream);
 
 
-	/* the model name can be specified on the command line. If none
-	  is specified, we try to locate one of the more expressive test
-	  models from the repository (/models-nonbsd may be missing in
-	  some distributions so we need a fallback from /models!). */
-	if( 0 != loadasset( argc >= 2 ? argv[1] : "../../test/models-nonbsd/X/dwarf.x")) {
-		if( argc != 1 || (0 != loadasset( "../../../../test/models-nonbsd/X/dwarf.x") && 0 != loadasset( "../../test/models/X/Testwuson.X"))) {
-			return -1;
-		}
+	// Load the model file.
+	if(0 != loadasset(model_file)) {
+		print_error("Failed to load model. Please ensure that the specified file exists.");
+		aiDetachAllLogStreams();
+		return EXIT_FAILURE;
 	}
 	}
 
 
 	glClearColor(0.1f,0.1f,0.1f,1.f);
 	glClearColor(0.1f,0.1f,0.1f,1.f);
@@ -384,5 +444,5 @@ int main(int argc, char **argv)
 	   again. This will definitely release the last resources allocated
 	   again. This will definitely release the last resources allocated
 	   by Assimp.*/
 	   by Assimp.*/
 	aiDetachAllLogStreams();
 	aiDetachAllLogStreams();
-	return 0;
+	return EXIT_SUCCESS;
 }
 }

+ 1 - 1
samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/ModelLoader.cpp

@@ -22,7 +22,7 @@ bool ModelLoader::Load(HWND hwnd, ID3D11Device * dev, ID3D11DeviceContext * devc
 		aiProcess_Triangulate |
 		aiProcess_Triangulate |
 		aiProcess_ConvertToLeftHanded);
 		aiProcess_ConvertToLeftHanded);
 
 
-	if (pScene == NULL)
+	if (pScene == nullptr)
 		return false;
 		return false;
 
 
 	this->directory_ = filename.substr(0, filename.find_last_of("/\\"));
 	this->directory_ = filename.substr(0, filename.find_last_of("/\\"));

+ 19 - 19
samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/main.cpp

@@ -126,7 +126,7 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/,
 	int argc;
 	int argc;
 	LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);
 	LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);
 	if (!argv) {
 	if (!argv) {
-		MessageBox(NULL, 
+		MessageBox(nullptr, 
 			TEXT("An error occured while reading command line arguments."),
 			TEXT("An error occured while reading command line arguments."),
 			TEXT("Error!"),
 			TEXT("Error!"),
 			MB_ICONERROR | MB_OK);
 			MB_ICONERROR | MB_OK);
@@ -143,7 +143,7 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/,
 
 
 	// Ensure that a model file has been specified.
 	// Ensure that a model file has been specified.
 	if (argc < 2) {
 	if (argc < 2) {
-		MessageBox(NULL, 
+		MessageBox(nullptr, 
 			TEXT("No model file specified. The program will now close."), 
 			TEXT("No model file specified. The program will now close."), 
 			TEXT("Error!"),
 			TEXT("Error!"),
 			MB_ICONERROR | MB_OK);
 			MB_ICONERROR | MB_OK);
@@ -165,16 +165,16 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/,
 	wc.cbClsExtra = 0;
 	wc.cbClsExtra = 0;
 	wc.cbWndExtra = 0;
 	wc.cbWndExtra = 0;
 	wc.hInstance = hInstance;
 	wc.hInstance = hInstance;
-	wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
-	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
-	wc.hbrBackground = NULL;
-	wc.lpszMenuName = NULL;
+	wc.hIcon = LoadIcon(nullptr, IDI_APPLICATION);
+	wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
+	wc.hbrBackground = nullptr;
+	wc.lpszMenuName = nullptr;
 	wc.lpszClassName = g_szClassName;
 	wc.lpszClassName = g_szClassName;
-	wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
+	wc.hIconSm = LoadIcon(nullptr, IDI_APPLICATION);
 
 
 	if (!RegisterClassEx(&wc))
 	if (!RegisterClassEx(&wc))
 	{
 	{
-		MessageBox(NULL, "Window Registration Failed!", "Error!",
+		MessageBox(nullptr, "Window Registration Failed!", "Error!",
 			MB_ICONEXCLAMATION | MB_OK);
 			MB_ICONEXCLAMATION | MB_OK);
 		return 0;
 		return 0;
 	}
 	}
@@ -188,12 +188,12 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/,
 		" Simple Textured Directx11 Sample ",
 		" Simple Textured Directx11 Sample ",
 		WS_OVERLAPPEDWINDOW,
 		WS_OVERLAPPEDWINDOW,
 		CW_USEDEFAULT, CW_USEDEFAULT, wr.right - wr.left, wr.bottom - wr.top,
 		CW_USEDEFAULT, CW_USEDEFAULT, wr.right - wr.left, wr.bottom - wr.top,
-		NULL, NULL, hInstance, NULL
+		nullptr, nullptr, hInstance, nullptr
 	);
 	);
 
 
-	if (g_hwnd == NULL)
+	if (g_hwnd == nullptr)
 	{
 	{
-		MessageBox(NULL, "Window Creation Failed!", "Error!",
+		MessageBox(nullptr, "Window Creation Failed!", "Error!",
 			MB_ICONEXCLAMATION | MB_OK);
 			MB_ICONEXCLAMATION | MB_OK);
 		return 0;
 		return 0;
 	}
 	}
@@ -210,7 +210,7 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/,
 		while (true)
 		while (true)
 		{
 		{
 
 
-			if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
+			if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
 			{
 			{
 				TranslateMessage(&msg);
 				TranslateMessage(&msg);
 				DispatchMessage(&msg);
 				DispatchMessage(&msg);
@@ -372,7 +372,7 @@ void InitD3D(HINSTANCE /*hinstance*/, HWND hWnd)
 	ID3D11Texture2D *pBackBuffer;
 	ID3D11Texture2D *pBackBuffer;
 	swapchain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);
 	swapchain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);
 
 
-	dev->CreateRenderTargetView(pBackBuffer, NULL, &backbuffer);
+	dev->CreateRenderTargetView(pBackBuffer, nullptr, &backbuffer);
 	pBackBuffer->Release();
 	pBackBuffer->Release();
 
 
 	D3D11_TEXTURE2D_DESC descDepth;
 	D3D11_TEXTURE2D_DESC descDepth;
@@ -440,7 +440,7 @@ void InitD3D(HINSTANCE /*hinstance*/, HWND hWnd)
 void CleanD3D(void)
 void CleanD3D(void)
 {
 {
 	if (swapchain)
 	if (swapchain)
-		swapchain->SetFullscreenState(FALSE, NULL);
+		swapchain->SetFullscreenState(FALSE, nullptr);
 
 
 	if (ourModel) {
 	if (ourModel) {
 		ourModel->Close();
 		ourModel->Close();
@@ -513,8 +513,8 @@ void InitPipeline()
 	if(FAILED(CompileShaderFromFile(SHADER_PATH PIXEL_SHADER_FILE, 0, "main", "ps_4_0", &PS)))
 	if(FAILED(CompileShaderFromFile(SHADER_PATH PIXEL_SHADER_FILE, 0, "main", "ps_4_0", &PS)))
 		Throwanerror(UTFConverter(L"Failed to compile shader from file " PIXEL_SHADER_FILE).c_str());
 		Throwanerror(UTFConverter(L"Failed to compile shader from file " PIXEL_SHADER_FILE).c_str());
 
 
-	dev->CreateVertexShader(VS->GetBufferPointer(), VS->GetBufferSize(), NULL, &pVS);
-	dev->CreatePixelShader(PS->GetBufferPointer(), PS->GetBufferSize(), NULL, &pPS);
+	dev->CreateVertexShader(VS->GetBufferPointer(), VS->GetBufferSize(), nullptr, &pVS);
+	dev->CreatePixelShader(PS->GetBufferPointer(), PS->GetBufferSize(), nullptr, &pPS);
 
 
 	D3D11_INPUT_ELEMENT_DESC ied[] =
 	D3D11_INPUT_ELEMENT_DESC ied[] =
 	{
 	{
@@ -576,16 +576,16 @@ HRESULT	CompileShaderFromFile(LPCWSTR pFileName, const D3D_SHADER_MACRO* pDefine
 	compileFlags |= D3DCOMPILE_DEBUG;
 	compileFlags |= D3DCOMPILE_DEBUG;
 #endif
 #endif
 
 
-	ID3DBlob* pErrorBlob = NULL;
+	ID3DBlob* pErrorBlob = nullptr;
 
 
 	HRESULT result = D3DCompileFromFile(pFileName, pDefines, D3D_COMPILE_STANDARD_FILE_INCLUDE, pEntryPoint, pShaderModel, compileFlags, 0, ppBytecodeBlob, &pErrorBlob);
 	HRESULT result = D3DCompileFromFile(pFileName, pDefines, D3D_COMPILE_STANDARD_FILE_INCLUDE, pEntryPoint, pShaderModel, compileFlags, 0, ppBytecodeBlob, &pErrorBlob);
 	if (FAILED(result))
 	if (FAILED(result))
 	{
 	{
-		if (pErrorBlob != NULL)
+		if (pErrorBlob != nullptr)
 			OutputDebugStringA((LPCSTR)pErrorBlob->GetBufferPointer());
 			OutputDebugStringA((LPCSTR)pErrorBlob->GetBufferPointer());
 	}
 	}
 
 
-	if (pErrorBlob != NULL)
+	if (pErrorBlob != nullptr)
 		pErrorBlob->Release();
 		pErrorBlob->Release();
 
 
 	return result;
 	return result;

+ 8 - 0
test/CMakeLists.txt

@@ -61,6 +61,14 @@ SET( COMMON
   unit/utIssues.cpp
   unit/utIssues.cpp
   unit/utAnim.cpp
   unit/utAnim.cpp
   unit/AssimpAPITest.cpp
   unit/AssimpAPITest.cpp
+  unit/AssimpAPITest_aiMatrix3x3.cpp
+  unit/AssimpAPITest_aiMatrix4x4.cpp
+  unit/AssimpAPITest_aiQuaternion.cpp
+  unit/AssimpAPITest_aiVector2D.cpp
+  unit/AssimpAPITest_aiVector3D.cpp
+  unit/MathTest.cpp
+  unit/MathTest.h
+  unit/RandomNumberGeneration.h
   unit/utBatchLoader.cpp
   unit/utBatchLoader.cpp
   unit/utDefaultIOStream.cpp
   unit/utDefaultIOStream.cpp
   unit/utFastAtof.cpp
   unit/utFastAtof.cpp

+ 25 - 0
test/models/glTF2/RecursiveNodes/RecursiveNodes.gltf

@@ -0,0 +1,25 @@
+{
+    "asset": {
+        "version": "2.0"
+    },
+    "scene": 0,
+    "scenes": [
+        {
+            "nodes": [
+                0
+            ]
+        }
+    ],
+    "nodes": [
+        {
+            "children": [
+                1
+            ]
+        },
+        {
+            "children": [
+                0
+            ]
+        }
+    ]
+}

+ 151 - 0
test/unit/AssimpAPITest_aiMatrix3x3.cpp

@@ -0,0 +1,151 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2020, assimp team
+
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+#include "UnitTestPCH.h"
+#include "MathTest.h"
+
+using namespace Assimp;
+
+class AssimpAPITest_aiMatrix3x3 : public AssimpMathTest {
+protected:
+    virtual void SetUp() {
+        result_c = result_cpp = aiMatrix3x3();
+    }
+
+    aiMatrix3x3 result_c, result_cpp;
+};
+
+TEST_F(AssimpAPITest_aiMatrix3x3, aiIdentityMatrix3Test) {
+    // Force a non-identity matrix.
+    result_c = aiMatrix3x3(0,0,0,0,0,0,0,0,0);
+    aiIdentityMatrix3(&result_c);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiMatrix3x3, aiMatrix3FromMatrix4Test) {
+    const auto m = random_mat4();
+    result_cpp = aiMatrix3x3(m);
+    aiMatrix3FromMatrix4(&result_c, &m);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiMatrix3x3, aiMatrix3FromQuaternionTest) {
+    const auto q = random_quat();
+    result_cpp = q.GetMatrix();
+    aiMatrix3FromQuaternion(&result_c, &q);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiMatrix3x3, aiMatrix3AreEqualTest) {
+    result_c = result_cpp = random_mat3();
+    EXPECT_EQ(result_cpp == result_c,
+        (bool)aiMatrix3AreEqual(&result_cpp, &result_c));
+}
+
+TEST_F(AssimpAPITest_aiMatrix3x3, aiMatrix3AreEqualEpsilonTest) {
+    result_c = result_cpp = random_mat3();
+    EXPECT_EQ(result_cpp.Equal(result_c, Epsilon),
+        (bool)aiMatrix3AreEqualEpsilon(&result_cpp, &result_c, Epsilon));
+}
+
+TEST_F(AssimpAPITest_aiMatrix3x3, aiMultiplyMatrix3Test) {
+    const auto m = random_mat3();
+    result_c = result_cpp = random_mat3();
+    result_cpp *= m;
+    aiMultiplyMatrix3(&result_c, &m);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiMatrix3x3, aiTransposeMatrix3Test) {
+    result_c = result_cpp = random_mat3();
+    result_cpp.Transpose();
+    aiTransposeMatrix3(&result_c);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiMatrix3x3, aiMatrix3InverseTest) {
+    // Use a predetermined matrix to prevent arbitrary
+    // cases where it could have a null determinant.
+    result_c = result_cpp = aiMatrix3x3(
+        5, 2, 7,
+        4, 6, 9,
+        1, 8, 3);
+    result_cpp.Inverse();
+    aiMatrix3Inverse(&result_c);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiMatrix3x3, aiMatrix3DeterminantTest) {
+    result_c = result_cpp = random_mat3();
+    EXPECT_EQ(result_cpp.Determinant(),
+        aiMatrix3Determinant(&result_c));
+}
+
+TEST_F(AssimpAPITest_aiMatrix3x3, aiMatrix3RotationZTest) {
+    const float angle(RandPI.next());
+    aiMatrix3x3::RotationZ(angle, result_cpp);
+    aiMatrix3RotationZ(&result_c, angle);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiMatrix3x3, aiMatrix3FromRotationAroundAxisTest) {
+    const float angle(RandPI.next());
+    const auto axis = random_unit_vec3();
+    aiMatrix3x3::Rotation(angle, axis, result_cpp);
+    aiMatrix3FromRotationAroundAxis(&result_c, &axis, angle);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiMatrix3x3, aiMatrix3TranslationTest) {
+    const auto axis = random_vec2();
+    aiMatrix3x3::Translation(axis, result_cpp);
+    aiMatrix3Translation(&result_c, &axis);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiMatrix3x3, aiMatrix3FromToTest) {
+    // Use predetermined vectors to prevent running into division by zero.
+    const auto from = aiVector3D(1,2,1).Normalize(), to = aiVector3D(-1,1,1).Normalize();
+    aiMatrix3x3::FromToMatrix(from, to, result_cpp);
+    aiMatrix3FromTo(&result_c, &from, &to);
+    EXPECT_EQ(result_cpp, result_c);
+}

+ 259 - 0
test/unit/AssimpAPITest_aiMatrix4x4.cpp

@@ -0,0 +1,259 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2020, assimp team
+
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+#include "UnitTestPCH.h"
+#include "MathTest.h"
+
+using namespace Assimp;
+
+class AssimpAPITest_aiMatrix4x4 : public AssimpMathTest {
+protected:
+    virtual void SetUp() {
+        result_c = result_cpp = aiMatrix4x4();
+    }
+
+    /* Generates a predetermined transformation matrix to use
+       for the aiDecompose functions to prevent running into
+       division by zero. */
+    aiMatrix4x4 get_predetermined_transformation_matrix_for_decomposition() const {
+        aiMatrix4x4 t, r;
+        aiMatrix4x4::Translation(aiVector3D(14,-25,-8), t);
+        aiMatrix4x4::Rotation(Math::PI<float>() / 4.0f, aiVector3D(1).Normalize(), r);
+        return t * r;
+    }
+
+    aiMatrix4x4 result_c, result_cpp;
+};
+
+TEST_F(AssimpAPITest_aiMatrix4x4, aiIdentityMatrix4Test) {
+    // Force a non-identity matrix.
+    result_c = aiMatrix4x4(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
+    aiIdentityMatrix4(&result_c);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiMatrix4x4, aiMatrix4FromMatrix3Test) {
+    aiMatrix3x3 m = random_mat3();
+    result_cpp = aiMatrix4x4(m);
+    aiMatrix4FromMatrix3(&result_c, &m);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiMatrix4x4, aiMatrix4FromScalingQuaternionPositionTest) {
+    const aiVector3D s = random_vec3();
+    const aiQuaternion q = random_quat();
+    const aiVector3D t = random_vec3();
+    result_cpp = aiMatrix4x4(s, q, t);
+    aiMatrix4FromScalingQuaternionPosition(&result_c, &s, &q, &t);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiMatrix4x4, aiMatrix4AddTest) {
+    const aiMatrix4x4 temp = random_mat4();
+    result_c = result_cpp = random_mat4();
+    result_cpp = result_cpp + temp;
+    aiMatrix4Add(&result_c, &temp);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiMatrix4x4, aiMatrix4AreEqualTest) {
+    result_c = result_cpp = random_mat4();
+    EXPECT_EQ(result_cpp == result_c,
+        (bool)aiMatrix4AreEqual(&result_cpp, &result_c));
+}
+
+TEST_F(AssimpAPITest_aiMatrix4x4, aiMatrix4AreEqualEpsilonTest) {
+    result_c = result_cpp = random_mat4();
+    EXPECT_EQ(result_cpp.Equal(result_c, Epsilon),
+        (bool)aiMatrix4AreEqualEpsilon(&result_cpp, &result_c, Epsilon));
+}
+
+TEST_F(AssimpAPITest_aiMatrix4x4, aiMultiplyMatrix4Test) {
+    const auto m = random_mat4();
+    result_c = result_cpp = random_mat4();
+    result_cpp *= m;
+    aiMultiplyMatrix4(&result_c, &m);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiMatrix4x4, aiTransposeMatrix4Test) {
+    result_c = result_cpp = random_mat4();
+    result_cpp.Transpose();
+    aiTransposeMatrix4(&result_c);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiMatrix4x4, aiMatrix4InverseTest) {
+    // Use a predetermined matrix to prevent arbitrary
+    // cases where it could have a null determinant.
+    result_c = result_cpp = aiMatrix4x4(
+        6, 10, 15, 3,
+        14, 2, 12, 8,
+        9, 13, 5, 16,
+        4, 7, 11, 1);
+    result_cpp.Inverse();
+    aiMatrix4Inverse(&result_c);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiMatrix4x4, aiMatrix4DeterminantTest) {
+    result_c = result_cpp = random_mat4();
+    EXPECT_EQ(result_cpp.Determinant(),
+        aiMatrix4Determinant(&result_c));
+}
+
+TEST_F(AssimpAPITest_aiMatrix4x4, aiMatrix4IsIdentityTest) {
+    EXPECT_EQ(result_cpp.IsIdentity(),
+        (bool)aiMatrix4IsIdentity(&result_c));
+}
+
+TEST_F(AssimpAPITest_aiMatrix4x4, aiDecomposeMatrixTest) {
+    aiVector3D scaling_c, scaling_cpp,
+        position_c, position_cpp;
+    aiQuaternion rotation_c, rotation_cpp;
+
+    result_c = result_cpp = get_predetermined_transformation_matrix_for_decomposition();
+    result_cpp.Decompose(scaling_cpp, rotation_cpp, position_cpp);
+    aiDecomposeMatrix(&result_c, &scaling_c, &rotation_c, &position_c);
+    EXPECT_EQ(scaling_cpp, scaling_c);
+    EXPECT_EQ(position_cpp, position_c);
+    EXPECT_EQ(rotation_cpp, rotation_c);
+}
+
+TEST_F(AssimpAPITest_aiMatrix4x4, aiMatrix4DecomposeIntoScalingEulerAnglesPositionTest) {
+    aiVector3D scaling_c, scaling_cpp,
+        rotation_c, rotation_cpp,
+        position_c, position_cpp;
+
+    result_c = result_cpp = get_predetermined_transformation_matrix_for_decomposition();
+    result_cpp.Decompose(scaling_cpp, rotation_cpp, position_cpp);
+    aiMatrix4DecomposeIntoScalingEulerAnglesPosition(&result_c, &scaling_c, &rotation_c, &position_c);
+    EXPECT_EQ(scaling_cpp, scaling_c);
+    EXPECT_EQ(position_cpp, position_c);
+    EXPECT_EQ(rotation_cpp, rotation_c);
+}
+
+TEST_F(AssimpAPITest_aiMatrix4x4, aiMatrix4DecomposeIntoScalingAxisAnglePositionTest) {
+    aiVector3D scaling_c, scaling_cpp,
+        axis_c, axis_cpp,
+        position_c, position_cpp;
+    float angle_c, angle_cpp;
+
+    result_c = result_cpp = get_predetermined_transformation_matrix_for_decomposition();
+    result_cpp.Decompose(scaling_cpp, axis_cpp, angle_cpp, position_cpp);
+    aiMatrix4DecomposeIntoScalingAxisAnglePosition(&result_c, &scaling_c, &axis_c, &angle_c, &position_c);
+    EXPECT_EQ(scaling_cpp, scaling_c);
+    EXPECT_EQ(axis_cpp, axis_c);
+    EXPECT_EQ(angle_cpp, angle_c);
+    EXPECT_EQ(position_cpp, position_c);
+}
+
+TEST_F(AssimpAPITest_aiMatrix4x4, aiMatrix4DecomposeNoScalingTest) {
+    aiVector3D position_c, position_cpp;
+    aiQuaternion rotation_c, rotation_cpp;
+
+    result_c = result_cpp = get_predetermined_transformation_matrix_for_decomposition();
+    result_cpp.DecomposeNoScaling(rotation_cpp, position_cpp);
+    aiMatrix4DecomposeNoScaling(&result_c, &rotation_c, &position_c);
+    EXPECT_EQ(position_cpp, position_c);
+    EXPECT_EQ(rotation_cpp, rotation_c);
+}
+
+TEST_F(AssimpAPITest_aiMatrix4x4, aiMatrix4FromEulerAnglesTest) {
+    const float x(RandPI.next()),
+        y(RandPI.next()),
+        z(RandPI.next());
+    result_cpp.FromEulerAnglesXYZ(x, y, z);
+    aiMatrix4FromEulerAngles(&result_c, x, y, z);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiMatrix4x4, aiMatrix4RotationXTest) {
+    const float angle(RandPI.next());
+    aiMatrix4x4::RotationX(angle, result_cpp);
+    aiMatrix4RotationX(&result_c, angle);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiMatrix4x4, aiMatrix4RotationYTest) {
+    const float angle(RandPI.next());
+    aiMatrix4x4::RotationY(angle, result_cpp);
+    aiMatrix4RotationY(&result_c, angle);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiMatrix4x4, aiMatrix4RotationZTest) {
+    const float angle(RandPI.next());
+    aiMatrix4x4::RotationZ(angle, result_cpp);
+    aiMatrix4RotationZ(&result_c, angle);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiMatrix4x4, aiMatrix4FromRotationAroundAxisTest) {
+    const float angle(RandPI.next());
+    const auto axis = random_unit_vec3();
+    aiMatrix4x4::Rotation(angle, axis, result_cpp);
+    aiMatrix4FromRotationAroundAxis(&result_c, &axis, angle);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiMatrix4x4, aiMatrix4TranslationTest) {
+    const auto axis = random_vec3();
+    aiMatrix4x4::Translation(axis, result_cpp);
+    aiMatrix4Translation(&result_c, &axis);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiMatrix4x4, aiMatrix4ScalingTest) {
+    const auto scaling = random_vec3();
+    aiMatrix4x4::Scaling(scaling, result_cpp);
+    aiMatrix4Scaling(&result_c, &scaling);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiMatrix4x4, aiMatrix4FromToTest) {
+    // Use predetermined vectors to prevent running into division by zero.
+    const auto from = aiVector3D(1,2,1).Normalize(), to = aiVector3D(-1,1,1).Normalize();
+    aiMatrix4x4::FromToMatrix(from, to, result_cpp);
+    aiMatrix4FromTo(&result_c, &from, &to);
+    EXPECT_EQ(result_cpp, result_c);
+}

+ 135 - 0
test/unit/AssimpAPITest_aiQuaternion.cpp

@@ -0,0 +1,135 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2020, assimp team
+
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+#include "UnitTestPCH.h"
+#include "MathTest.h"
+
+using namespace Assimp;
+
+class AssimpAPITest_aiQuaternion : public AssimpMathTest {
+protected:
+    virtual void SetUp() {
+        result_c = result_cpp = aiQuaternion();
+    }
+
+    aiQuaternion result_c, result_cpp;
+};
+
+TEST_F(AssimpAPITest_aiQuaternion, aiCreateQuaternionFromMatrixTest) {
+    // Use a predetermined transformation matrix
+    // to prevent running into division by zero.
+    aiMatrix3x3 m, r;
+    aiMatrix3x3::Translation(aiVector2D(14,-25), m);
+    aiMatrix3x3::RotationZ(Math::PI<float>() / 4.0f, r);
+    m = m * r;
+
+    result_cpp = aiQuaternion(m);
+    aiCreateQuaternionFromMatrix(&result_c, &m);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiQuaternion, aiQuaternionFromEulerAnglesTest) {
+    const float x(RandPI.next()),
+        y(RandPI.next()),
+        z(RandPI.next());
+    result_cpp = aiQuaternion(x, y, z);
+    aiQuaternionFromEulerAngles(&result_c, x, y, z);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiQuaternion, aiQuaternionFromAxisAngleTest) {
+    const float angle(RandPI.next());
+    const aiVector3D axis(random_unit_vec3());
+    result_cpp = aiQuaternion(axis, angle);
+    aiQuaternionFromAxisAngle(&result_c, &axis, angle);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiQuaternion, aiQuaternionFromNormalizedQuaternionTest) {
+    const auto qvec3 = random_unit_vec3();
+    result_cpp = aiQuaternion(qvec3);
+    aiQuaternionFromNormalizedQuaternion(&result_c, &qvec3);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiQuaternion, aiQuaternionAreEqualTest) {
+    result_c = result_cpp = random_quat();
+    EXPECT_EQ(result_cpp == result_c,
+        (bool)aiQuaternionAreEqual(&result_cpp, &result_c));
+}
+
+TEST_F(AssimpAPITest_aiQuaternion, aiQuaternionAreEqualEpsilonTest) {
+    result_c = result_cpp = random_quat();
+    EXPECT_EQ(result_cpp.Equal(result_c, Epsilon),
+        (bool)aiQuaternionAreEqualEpsilon(&result_cpp, &result_c, Epsilon));
+}
+
+TEST_F(AssimpAPITest_aiQuaternion, aiQuaternionNormalizeTest) {
+    result_c = result_cpp = random_quat();
+    aiQuaternionNormalize(&result_c);
+    EXPECT_EQ(result_cpp.Normalize(), result_c);
+}
+
+TEST_F(AssimpAPITest_aiQuaternion, aiQuaternionConjugateTest) {
+    result_c = result_cpp = random_quat();
+    aiQuaternionConjugate(&result_c);
+    EXPECT_EQ(result_cpp.Conjugate(), result_c);
+}
+
+TEST_F(AssimpAPITest_aiQuaternion, aiQuaternionMultiplyTest) {
+    const aiQuaternion temp = random_quat();
+    result_c = result_cpp = random_quat();
+    result_cpp = result_cpp * temp;
+    aiQuaternionMultiply(&result_c, &temp);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiQuaternion, aiQuaternionInterpolateTest) {
+    // Use predetermined quaternions to prevent division by zero
+    // during slerp calculations.
+    const float INTERPOLATION(0.5f);
+    const auto q1 = aiQuaternion(aiVector3D(-1,1,1).Normalize(), Math::PI<float>() / 4.0f);
+    const auto q2 = aiQuaternion(aiVector3D(1,2,1).Normalize(), Math::PI<float>() / 2.0f);
+    aiQuaternion::Interpolate(result_cpp, q1, q2, INTERPOLATION);
+    aiQuaternionInterpolate(&result_c, &q1, &q2, INTERPOLATION);
+    EXPECT_EQ(result_cpp, result_c);
+}

+ 140 - 0
test/unit/AssimpAPITest_aiVector2D.cpp

@@ -0,0 +1,140 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2020, assimp team
+
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+#include "UnitTestPCH.h"
+#include "MathTest.h"
+
+using namespace Assimp;
+
+class AssimpAPITest_aiVector2D : public AssimpMathTest {
+protected:
+    virtual void SetUp() {
+        result_c = result_cpp = aiVector2D();
+        temp = random_vec2(); // Generates a random 2D vector != null vector.
+    }
+
+    aiVector2D result_c, result_cpp, temp;
+};
+
+TEST_F(AssimpAPITest_aiVector2D, aiVector2AreEqualTest) {
+    result_c = result_cpp = random_vec2();
+    EXPECT_EQ(result_cpp == result_c,
+        (bool)aiVector2AreEqual(&result_cpp, &result_c));
+}
+
+TEST_F(AssimpAPITest_aiVector2D, aiVector2AreEqualEpsilonTest) {
+    result_c = result_cpp = random_vec2();
+    EXPECT_EQ(result_cpp.Equal(result_c, Epsilon),
+        (bool)aiVector2AreEqualEpsilon(&result_cpp, &result_c, Epsilon));
+}
+
+TEST_F(AssimpAPITest_aiVector2D, aiVector2AddTest) {
+    result_c = result_cpp = random_vec2();
+    result_cpp += temp;
+    aiVector2Add(&result_c, &temp);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiVector2D, aiVector2SubtractTest) {
+    result_c = result_cpp = random_vec2();
+    result_cpp -= temp;
+    aiVector2Subtract(&result_c, &temp);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiVector2D, aiVector2ScaleTest) {
+    const float FACTOR = RandNonZero.next();
+    result_c = result_cpp = random_vec2();
+    result_cpp *= FACTOR;
+    aiVector2Scale(&result_c, FACTOR);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiVector2D, aiVector2SymMulTest) {
+    result_c = result_cpp = random_vec2();
+    result_cpp = result_cpp.SymMul(temp);
+    aiVector2SymMul(&result_c, &temp);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiVector2D, aiVector2DivideByScalarTest) {
+    const float DIVISOR = RandNonZero.next();
+    result_c = result_cpp = random_vec2();
+    result_cpp /= DIVISOR;
+    aiVector2DivideByScalar(&result_c, DIVISOR);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiVector2D, aiVector2DivideByVectorTest) {
+    result_c = result_cpp = random_vec2();
+    result_cpp = result_cpp / temp;
+    aiVector2DivideByVector(&result_c, &temp);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiVector2D, aiVector2LengthTest) {
+    result_c = result_cpp = random_vec2();
+    EXPECT_EQ(result_cpp.Length(), aiVector2Length(&result_c));
+}
+
+TEST_F(AssimpAPITest_aiVector2D, aiVector2SquareLengthTest) {
+    result_c = result_cpp = random_vec2();
+    EXPECT_EQ(result_cpp.SquareLength(), aiVector2SquareLength(&result_c));
+}
+
+TEST_F(AssimpAPITest_aiVector2D, aiVector2NegateTest) {
+    result_c = result_cpp = random_vec2();
+    aiVector2Negate(&result_c);
+    EXPECT_EQ(-result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiVector2D, aiVector2DotProductTest) {
+    result_c = result_cpp = random_vec2();
+    EXPECT_EQ(result_cpp * result_c,
+        aiVector2DotProduct(&result_cpp, &result_c));
+}
+
+TEST_F(AssimpAPITest_aiVector2D, aiVector2NormalizeTest) {
+    result_c = result_cpp = random_vec2();
+    aiVector2Normalize(&result_c);
+    EXPECT_EQ(result_cpp.Normalize(), result_c);
+}

+ 185 - 0
test/unit/AssimpAPITest_aiVector3D.cpp

@@ -0,0 +1,185 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2020, assimp team
+
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+#include "UnitTestPCH.h"
+#include "MathTest.h"
+
+using namespace Assimp;
+
+class AssimpAPITest_aiVector3D : public AssimpMathTest {
+protected:
+    virtual void SetUp() {
+        result_c = result_cpp = aiVector3D();
+        temp = random_vec3(); // Generates a random 3D vector != null vector.
+    }
+
+    aiVector3D result_c, result_cpp, temp;
+};
+
+TEST_F(AssimpAPITest_aiVector3D, aiVector3AreEqualTest) {
+    result_c = result_cpp = random_vec3();
+    EXPECT_EQ(result_cpp == result_c,
+        (bool)aiVector3AreEqual(&result_cpp, &result_c));
+}
+
+TEST_F(AssimpAPITest_aiVector3D, aiVector3AreEqualEpsilonTest) {
+    result_c = result_cpp = random_vec3();
+    EXPECT_EQ(result_cpp.Equal(result_c, Epsilon),
+        (bool)aiVector3AreEqualEpsilon(&result_cpp, &result_c, Epsilon));
+}
+
+TEST_F(AssimpAPITest_aiVector3D, aiVector3LessThanTest) {
+    result_c = result_cpp = random_vec3();
+    EXPECT_EQ(result_cpp < temp,
+        (bool)aiVector3LessThan(&result_c, &temp));
+}
+
+TEST_F(AssimpAPITest_aiVector3D, aiVector3AddTest) {
+    result_c = result_cpp = random_vec3();
+    result_cpp += temp;
+    aiVector3Add(&result_c, &temp);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiVector3D, aiVector3SubtractTest) {
+    result_c = result_cpp = random_vec3();
+    result_cpp -= temp;
+    aiVector3Subtract(&result_c, &temp);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiVector3D, aiVector3ScaleTest) {
+    const float FACTOR = RandNonZero.next();
+    result_c = result_cpp = random_vec3();
+    result_cpp *= FACTOR;
+    aiVector3Scale(&result_c, FACTOR);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiVector3D, aiVector3SymMulTest) {
+    result_c = result_cpp = random_vec3();
+    result_cpp = result_cpp.SymMul(temp);
+    aiVector3SymMul(&result_c, &temp);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiVector3D, aiVector3DivideByScalarTest) {
+    const float DIVISOR = RandNonZero.next();
+    result_c = result_cpp = random_vec3();
+    result_cpp /= DIVISOR;
+    aiVector3DivideByScalar(&result_c, DIVISOR);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiVector3D, aiVector3DivideByVectorTest) {
+    result_c = result_cpp = random_vec3();
+    result_cpp = result_cpp / temp;
+    aiVector3DivideByVector(&result_c, &temp);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiVector3D, aiVector3LengthTest) {
+    result_c = result_cpp = random_vec3();
+    EXPECT_EQ(result_cpp.Length(), aiVector3Length(&result_c));
+}
+
+TEST_F(AssimpAPITest_aiVector3D, aiVector3SquareLengthTest) {
+    result_c = result_cpp = random_vec3();
+    EXPECT_EQ(result_cpp.SquareLength(), aiVector3SquareLength(&result_c));
+}
+
+TEST_F(AssimpAPITest_aiVector3D, aiVector3NegateTest) {
+    result_c = result_cpp = random_vec3();
+    aiVector3Negate(&result_c);
+    EXPECT_EQ(-result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiVector3D, aiVector3DotProductTest) {
+    result_c = result_cpp = random_vec3();
+    EXPECT_EQ(result_cpp * result_c,
+        aiVector3DotProduct(&result_cpp, &result_c));
+}
+
+TEST_F(AssimpAPITest_aiVector3D, aiVector3CrossProductTest) {
+    result_c = result_cpp = random_vec3();
+    result_cpp = result_cpp ^ temp;
+    aiVector3CrossProduct(&result_c, &result_c, &temp);
+    EXPECT_EQ(result_cpp, result_c);
+}
+
+TEST_F(AssimpAPITest_aiVector3D, aiVector3NormalizeTest) {
+    result_c = result_cpp = random_vec3();
+    aiVector3Normalize(&result_c);
+    EXPECT_EQ(result_cpp.Normalize(), result_c);
+}
+
+TEST_F(AssimpAPITest_aiVector3D, aiVector3NormalizeSafeTest) {
+    result_c = result_cpp = random_vec3();
+    aiVector3NormalizeSafe(&result_c);
+    EXPECT_EQ(result_cpp.NormalizeSafe(), result_c);
+}
+
+TEST_F(AssimpAPITest_aiVector3D, aiVector3RotateByQuaternionTest) {
+    aiVector3D v_c, v_cpp;
+    v_c = v_cpp = random_vec3();
+    const auto q = random_quat();
+    aiVector3RotateByQuaternion(&v_c, &q);
+    EXPECT_EQ(q.Rotate(v_cpp), v_c);
+}
+
+TEST_F(AssimpAPITest_aiVector3D, aiTransformVecByMatrix3Test) {
+    const auto m = random_mat3();
+    aiVector3D v_c, v_cpp;
+    v_c = v_cpp = random_vec3();
+    v_cpp *= m;
+    aiTransformVecByMatrix3(&v_c, &m);
+    EXPECT_EQ(v_cpp, v_c);
+}
+
+TEST_F(AssimpAPITest_aiVector3D, aiTransformVecByMatrix4Test) {
+    const auto m = random_mat4();
+    aiVector3D v_c, v_cpp;
+    v_c = v_cpp = random_vec3();
+    v_cpp *= m;
+    aiTransformVecByMatrix4(&v_c, &m);
+    EXPECT_EQ(v_cpp, v_c);
+}

+ 56 - 0
test/unit/MathTest.cpp

@@ -0,0 +1,56 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2020, assimp team
+
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+#include "MathTest.h"
+
+namespace Assimp {
+
+// Initialize epsilon value.
+const float AssimpMathTest::Epsilon = Math::getEpsilon<float>();
+
+// Initialize with an interval of [1,100].
+RandomUniformFloatGenerator AssimpMathTest::RandNonZero(1.0f, 100.0f);
+
+// Initialize with an interval of [-PI,PI] inclusively.
+RandomUniformFloatGenerator AssimpMathTest::RandPI(-Math::PI<float>(), Math::PI<float>());
+
+}

+ 103 - 0
test/unit/MathTest.h

@@ -0,0 +1,103 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2020, assimp team
+
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+
+#ifndef ASSIMP_MATH_TEST_H
+#define ASSIMP_MATH_TEST_H
+
+#include "UnitTestPCH.h"
+#include <assimp/types.h>
+#include "RandomNumberGeneration.h"
+
+namespace Assimp {
+
+/** Custom test class providing several math related utilities. */
+class AssimpMathTest : public ::testing::Test {
+public:
+    /** Return a random non-null 2D vector. */
+    inline static aiVector2D random_vec2() {
+        return aiVector2D(RandNonZero.next(), RandNonZero.next());
+    }
+
+    /** Return a random non-null 3D vector. */
+    inline static aiVector3D random_vec3() {
+        return aiVector3D(RandNonZero.next(), RandNonZero.next(),RandNonZero.next());
+    }
+
+    /** Return a random unit 3D vector. */
+    inline static aiVector3D random_unit_vec3() {
+        return random_vec3().NormalizeSafe();
+    }
+
+    /** Return a quaternion with random orientation and
+      * rotation angle around axis. */
+    inline static aiQuaternion random_quat() {
+        return aiQuaternion(random_unit_vec3(), RandPI.next());
+    }
+
+    /** Return a random non-null 3x3 matrix. */
+    inline static aiMatrix3x3 random_mat3() {
+        return aiMatrix3x3(
+            RandNonZero.next(), RandNonZero.next(),RandNonZero.next(),
+            RandNonZero.next(), RandNonZero.next(),RandNonZero.next(),
+            RandNonZero.next(), RandNonZero.next(),RandNonZero.next());
+    }
+
+    /** Return a random non-null 4x4 matrix. */
+    inline static aiMatrix4x4 random_mat4() {
+        return aiMatrix4x4(
+            RandNonZero.next(), RandNonZero.next(),RandNonZero.next(), RandNonZero.next(),
+            RandNonZero.next(), RandNonZero.next(),RandNonZero.next(), RandNonZero.next(),
+            RandNonZero.next(), RandNonZero.next(),RandNonZero.next(), RandNonZero.next(),
+            RandNonZero.next(), RandNonZero.next(),RandNonZero.next(), RandNonZero.next());
+    }
+
+    /** Epsilon value to use in tests. */
+    static const float Epsilon;
+
+    /** Random number generators. */
+    static RandomUniformFloatGenerator RandNonZero, RandPI;
+};
+
+}
+
+#endif // ASSIMP_MATH_TEST_H

+ 82 - 0
test/unit/RandomNumberGeneration.h

@@ -0,0 +1,82 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2020, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+
+#ifndef ASSIMP_RANDOM_NUMBER_GENERATION_H
+#define ASSIMP_RANDOM_NUMBER_GENERATION_H
+
+#include <random>
+
+namespace Assimp {
+
+/** Helper class to use for generating pseudo-random
+ *  real numbers, with a uniform distribution. */
+template<typename T>
+class RandomUniformRealGenerator {
+public:
+    RandomUniformRealGenerator() :
+            dist_(),
+            rd_(), 
+            re_(rd_())  {
+        // empty
+    }
+    
+    RandomUniformRealGenerator(T min, T max) :
+            dist_(min, max),
+            rd_(),
+            re_(rd_())  {
+        // empty
+    }
+
+    inline T next() {
+        return dist_(re_);
+    }
+
+private:
+    std::uniform_real_distribution<T> dist_;
+    std::random_device rd_;
+    std::default_random_engine re_;
+};
+
+using RandomUniformFloatGenerator = RandomUniformRealGenerator<float>;
+
+}
+
+#endif // ASSIMP_RANDOM_NUMBER_GENERATION_H

+ 6 - 0
test/unit/utglTF2ImportExport.cpp

@@ -491,6 +491,12 @@ TEST_F(utglTF2ImportExport, texcoords) {
     EXPECT_EQ(uvIndex, 1);
     EXPECT_EQ(uvIndex, 1);
 }
 }
 
 
+TEST_F(utglTF2ImportExport, recursive_nodes) {
+    Assimp::Importer importer;
+    const aiScene* scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/RecursiveNodes/RecursiveNodes.gltf", aiProcess_ValidateDataStructure);
+    EXPECT_EQ(nullptr, scene);
+}
+
 TEST_F(utglTF2ImportExport, norootnode_noscene) {
 TEST_F(utglTF2ImportExport, norootnode_noscene) {
     Assimp::Importer importer;
     Assimp::Importer importer;
     const aiScene* scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/TestNoRootNode/NoScene.gltf", aiProcess_ValidateDataStructure);
     const aiScene* scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/TestNoRootNode/NoScene.gltf", aiProcess_ValidateDataStructure);