Răsfoiți Sursa

Merge branch 'master' into fixTexcoord

Malcolm Tyrrell 5 ani în urmă
părinte
comite
b6553b8a78

+ 1 - 1
CMakeLists.txt

@@ -253,7 +253,7 @@ ELSEIF(MSVC)
   IF(MSVC12)
     ADD_COMPILE_OPTIONS(/wd4351)
   ENDIF()
-  SET(CMAKE_CXX_FLAGS_DEBUG "/D_DEBUG /MDd /Ob2 /Zi /O0")
+  SET(CMAKE_CXX_FLAGS_DEBUG "/D_DEBUG /MDd /Zi /Od")
 ELSEIF ( "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" )
   IF(NOT HUNTER_ENABLED)
     SET(CMAKE_CXX_FLAGS "-fPIC -std=c++11 ${CMAKE_CXX_FLAGS}")

+ 94 - 93
code/B3D/B3DImporter.cpp

@@ -5,8 +5,6 @@ Open Asset Import Library (assimp)
 
 Copyright (c) 2006-2019, assimp team
 
-
-
 All rights reserved.
 
 Redistribution and use of this software in source and binary forms,
@@ -78,7 +76,6 @@ static const aiImporterDesc desc = {
     "b3d"
 };
 
-// (fixme, Aramis) quick workaround to get rid of all those signed to unsigned warnings
 #ifdef _MSC_VER
 #	pragma warning (disable: 4018)
 #endif
@@ -86,10 +83,8 @@ static const aiImporterDesc desc = {
 //#define DEBUG_B3D
 
 template<typename T>
-void DeleteAllBarePointers(std::vector<T>& x)
-{
-    for(auto p : x)
-    {
+void DeleteAllBarePointers(std::vector<T>& x) {
+    for(auto p : x) {
         delete p;
     }
 }
@@ -102,10 +97,14 @@ B3DImporter::~B3DImporter()
 bool B3DImporter::CanRead( const std::string& pFile, IOSystem* /*pIOHandler*/, bool /*checkSig*/) const{
 
     size_t pos=pFile.find_last_of( '.' );
-    if( pos==string::npos ) return false;
+    if( pos==string::npos ) {
+        return false;
+    }
 
     string ext=pFile.substr( pos+1 );
-    if( ext.size()!=3 ) return false;
+    if( ext.size()!=3 ) {
+        return false;
+    }
 
     return (ext[0]=='b' || ext[0]=='B') && (ext[1]=='3') && (ext[2]=='d' || ext[2]=='D');
 }
@@ -117,30 +116,21 @@ const aiImporterDesc* B3DImporter::GetInfo () const
     return &desc;
 }
 
-#ifdef DEBUG_B3D
-    extern "C"{ void _stdcall AllocConsole(); }
-#endif
 // ------------------------------------------------------------------------------------------------
 void B3DImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler){
-
-#ifdef DEBUG_B3D
-    AllocConsole();
-    freopen( "conin$","r",stdin );
-    freopen( "conout$","w",stdout );
-    freopen( "conout$","w",stderr );
-    cout<<"Hello world from the B3DImporter!"<<endl;
-#endif
-
     std::unique_ptr<IOStream> file( pIOHandler->Open( pFile));
 
     // Check whether we can read from the file
-    if( file.get() == NULL)
+    if( file.get() == nullptr) {
         throw DeadlyImportError( "Failed to open B3D file " + pFile + ".");
+    }
 
     // check whether the .b3d file is large enough to contain
     // at least one chunk.
     size_t fileSize = file->FileSize();
-    if( fileSize<8 ) throw DeadlyImportError( "B3D File is too small.");
+    if( fileSize<8 ) {
+        throw DeadlyImportError( "B3D File is too small.");
+    }
 
     _pos=0;
     _buf.resize( fileSize );
@@ -158,14 +148,17 @@ AI_WONT_RETURN void B3DImporter::Oops(){
 // ------------------------------------------------------------------------------------------------
 AI_WONT_RETURN void B3DImporter::Fail( string str ){
 #ifdef DEBUG_B3D
-    cout<<"Error in B3D file data: "<<str<<endl;
+    ASSIMP_LOG_ERROR_F("Error in B3D file data: ", str);
 #endif
     throw DeadlyImportError( "B3D Importer - error in B3D file data: "+str );
 }
 
 // ------------------------------------------------------------------------------------------------
 int B3DImporter::ReadByte(){
-    if( _pos<_buf.size() ) return _buf[_pos++];
+    if( _pos<_buf.size() ) {
+        return _buf[_pos++];
+    }
+    
     Fail( "EOF" );
     return 0;
 }
@@ -224,7 +217,9 @@ string B3DImporter::ReadString(){
     string str;
     while( _pos<_buf.size() ){
         char c=(char)ReadByte();
-        if( !c ) return str;
+        if( !c ) {
+            return str;
+        }
         str+=c;
     }
     Fail( "EOF" );
@@ -238,7 +233,7 @@ string B3DImporter::ReadChunk(){
         tag+=char( ReadByte() );
     }
 #ifdef DEBUG_B3D
-//	cout<<"ReadChunk:"<<tag<<endl;
+    ASSIMP_LOG_DEBUG_F("ReadChunk: ", tag);
 #endif
     unsigned sz=(unsigned)ReadInt();
     _stack.push_back( _pos+sz );
@@ -269,7 +264,6 @@ T *B3DImporter::to_array( const vector<T> &v ){
     return p;
 }
 
-
 // ------------------------------------------------------------------------------------------------
 template<class T>
 T **unique_to_array( vector<std::unique_ptr<T> > &v ){
@@ -283,7 +277,6 @@ T **unique_to_array( vector<std::unique_ptr<T> > &v ){
     return p;
 }
 
-
 // ------------------------------------------------------------------------------------------------
 void B3DImporter::ReadTEXS(){
     while( ChunkSize() ){
@@ -376,9 +369,13 @@ void B3DImporter::ReadVRTS(){
 
         v.vertex=ReadVec3();
 
-        if( _vflags & 1 ) v.normal=ReadVec3();
+        if( _vflags & 1 ) {
+            v.normal=ReadVec3();
+        }
 
-        if( _vflags & 2 ) ReadQuat();	//skip v 4bytes...
+        if( _vflags & 2 ) {
+            ReadQuat();	//skip v 4bytes...
+        }
 
         for( int i=0;i<_tcsets;++i ){
             float t[4]={0,0,0,0};
@@ -386,53 +383,55 @@ void B3DImporter::ReadVRTS(){
                 t[j]=ReadFloat();
             }
             t[1]=1-t[1];
-            if( !i ) v.texcoords=aiVector3D( t[0],t[1],t[2] );
+            if( !i ) {
+                v.texcoords=aiVector3D( t[0],t[1],t[2] );
+            }
         }
     }
 }
 
 // ------------------------------------------------------------------------------------------------
-void B3DImporter::ReadTRIS( int v0 ){
-    int matid=ReadInt();
-    if( matid==-1 ){
-        matid=0;
-    }else if( matid<0 || matid>=(int)_materials.size() ){
+void B3DImporter::ReadTRIS(int v0) {
+	int matid = ReadInt();
+	if (matid == -1) {
+		matid = 0;
+	} else if (matid < 0 || matid >= (int)_materials.size()) {
 #ifdef DEBUG_B3D
-        cout<<"material id="<<matid<<endl;
+		ASSIMP_LOG_ERROR_F("material id=", matid);
 #endif
-        Fail( "Bad material id" );
-    }
+		Fail("Bad material id");
+	}
 
-    std::unique_ptr<aiMesh> mesh(new aiMesh);
+	std::unique_ptr<aiMesh> mesh(new aiMesh);
 
-    mesh->mMaterialIndex=matid;
-    mesh->mNumFaces=0;
-    mesh->mPrimitiveTypes=aiPrimitiveType_TRIANGLE;
+	mesh->mMaterialIndex = matid;
+	mesh->mNumFaces = 0;
+	mesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
 
-    int n_tris=ChunkSize()/12;
-    aiFace *face=mesh->mFaces=new aiFace[n_tris];
+	int n_tris = ChunkSize() / 12;
+	aiFace *face = mesh->mFaces = new aiFace[n_tris];
 
-    for( int i=0;i<n_tris;++i ){
-        int i0=ReadInt()+v0;
-        int i1=ReadInt()+v0;
-        int i2=ReadInt()+v0;
-        if( i0<0 || i0>=(int)_vertices.size() || i1<0 || i1>=(int)_vertices.size() || i2<0 || i2>=(int)_vertices.size() ){
+	for (int i = 0; i < n_tris; ++i) {
+		int i0 = ReadInt() + v0;
+		int i1 = ReadInt() + v0;
+		int i2 = ReadInt() + v0;
+		if (i0 < 0 || i0 >= (int)_vertices.size() || i1 < 0 || i1 >= (int)_vertices.size() || i2 < 0 || i2 >= (int)_vertices.size()) {
 #ifdef DEBUG_B3D
-            cout<<"Bad triangle index: i0="<<i0<<", i1="<<i1<<", i2="<<i2<<endl;
+			ASSIMP_LOG_ERROR_F("Bad triangle index: i0=", i0, ", i1=", i1, ", i2=", i2);
 #endif
-            Fail( "Bad triangle index" );
-            continue;
-        }
-        face->mNumIndices=3;
-        face->mIndices=new unsigned[3];
-        face->mIndices[0]=i0;
-        face->mIndices[1]=i1;
-        face->mIndices[2]=i2;
-        ++mesh->mNumFaces;
-        ++face;
-    }
-
-    _meshes.emplace_back( std::move(mesh) );
+			Fail("Bad triangle index");
+			continue;
+		}
+		face->mNumIndices = 3;
+		face->mIndices = new unsigned[3];
+		face->mIndices[0] = i0;
+		face->mIndices[1] = i1;
+		face->mIndices[2] = i2;
+		++mesh->mNumFaces;
+		++face;
+	}
+
+	_meshes.emplace_back(std::move(mesh));
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -453,29 +452,23 @@ void B3DImporter::ReadMESH(){
 }
 
 // ------------------------------------------------------------------------------------------------
-void B3DImporter::ReadBONE( int id ){
-    while( ChunkSize() ){
-        int vertex=ReadInt();
-        float weight=ReadFloat();
-        if( vertex<0 || vertex>=(int)_vertices.size() ){
-            Fail( "Bad vertex index" );
-        }
-
-        Vertex &v=_vertices[vertex];
-        int i;
-        for( i=0;i<4;++i ){
-            if( !v.weights[i] ){
-                v.bones[i]=id;
-                v.weights[i]=weight;
-                break;
-            }
-        }
-#ifdef DEBUG_B3D
-        if( i==4 ){
-            cout<<"Too many bone weights"<<endl;
-        }
-#endif
-    }
+void B3DImporter::ReadBONE(int id) {
+	while (ChunkSize()) {
+		int vertex = ReadInt();
+		float weight = ReadFloat();
+		if (vertex < 0 || vertex >= (int)_vertices.size()) {
+			Fail("Bad vertex index");
+		}
+
+		Vertex &v = _vertices[vertex];
+		for (int i = 0; i < 4; ++i) {
+			if (!v.weights[i]) {
+				v.bones[i] = id;
+				v.weights[i] = weight;
+				break;
+			}
+		}
+	}
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -633,11 +626,15 @@ void B3DImporter::ReadBB3D( aiScene *scene ){
     }
     ExitChunk();
 
-    if( !_nodes.size() ) Fail( "No nodes" );
+    if( !_nodes.size() ) {
+        Fail( "No nodes" );
+    }
 
-    if( !_meshes.size() ) Fail( "No meshes" );
+    if( !_meshes.size() ) {
+        Fail( "No meshes" );
+    }
 
-    //Fix nodes/meshes/bones
+    // Fix nodes/meshes/bones
     for(size_t i=0;i<_nodes.size();++i ){
         aiNode *node=_nodes[i];
 
@@ -648,8 +645,12 @@ void B3DImporter::ReadBB3D( aiScene *scene ){
             int n_verts=mesh->mNumVertices=n_tris * 3;
 
             aiVector3D *mv=mesh->mVertices=new aiVector3D[ n_verts ],*mn=0,*mc=0;
-            if( _vflags & 1 ) mn=mesh->mNormals=new aiVector3D[ n_verts ];
-            if( _tcsets ) mc=mesh->mTextureCoords[0]=new aiVector3D[ n_verts ];
+            if( _vflags & 1 ) {
+                mn=mesh->mNormals=new aiVector3D[ n_verts ];
+            }
+            if( _tcsets ) {
+                mc=mesh->mTextureCoords[0]=new aiVector3D[ n_verts ];
+            }
 
             aiFace *face=mesh->mFaces;
 

+ 2 - 0
code/CMakeLists.txt

@@ -411,6 +411,8 @@ ADD_ASSIMP_IMPORTER( M3D
   M3D/M3DMaterials.h
   M3D/M3DImporter.h
   M3D/M3DImporter.cpp
+  M3D/M3DWrapper.h
+  M3D/M3DWrapper.cpp
   M3D/m3d.h
 )
 

+ 157 - 125
code/Common/Importer.cpp

@@ -5,8 +5,6 @@ Open Asset Import Library (assimp)
 
 Copyright (c) 2006-2019, assimp team
 
-
-
 All rights reserved.
 
 Redistribution and use of this software in source and binary forms,
@@ -78,6 +76,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/TinyFormatter.h>
 #include <assimp/Exceptional.h>
 #include <assimp/Profiler.h>
+#include <assimp/commonMetaData.h>
+
 #include <set>
 #include <memory>
 #include <cctype>
@@ -119,7 +119,7 @@ void* AllocateFromAssimpHeap::operator new ( size_t num_bytes, const std::nothro
         return AllocateFromAssimpHeap::operator new( num_bytes );
     }
     catch( ... )    {
-        return NULL;
+        return nullptr;
     }
 }
 
@@ -134,9 +134,8 @@ void* AllocateFromAssimpHeap::operator new[] ( size_t num_bytes)    {
 void* AllocateFromAssimpHeap::operator new[] ( size_t num_bytes, const std::nothrow_t& ) throw() {
     try {
         return AllocateFromAssimpHeap::operator new[]( num_bytes );
-    }
-    catch( ... )    {
-        return NULL;
+    } catch( ... )    {
+        return nullptr;
     }
 }
 
@@ -148,7 +147,7 @@ void AllocateFromAssimpHeap::operator delete[] ( void* data)    {
 // Importer constructor.
 Importer::Importer()
  : pimpl( new ImporterPimpl ) {
-    pimpl->mScene = NULL;
+    pimpl->mScene = nullptr;
     pimpl->mErrorString = "";
 
     // Allocate a default IO handler
@@ -174,14 +173,14 @@ Importer::Importer()
 
 // ------------------------------------------------------------------------------------------------
 // Destructor of Importer
-Importer::~Importer()
-{
+Importer::~Importer() {
     // Delete all import plugins
 	DeleteImporterInstanceList(pimpl->mImporter);
 
     // Delete all post-processing plug-ins
-    for( unsigned int a = 0; a < pimpl->mPostProcessingSteps.size(); a++)
+    for( unsigned int a = 0; a < pimpl->mPostProcessingSteps.size(); ++a ) {
         delete pimpl->mPostProcessingSteps[a];
+    }
 
     // Delete the assigned IO and progress handler
     delete pimpl->mIOHandler;
@@ -199,9 +198,9 @@ Importer::~Importer()
 
 // ------------------------------------------------------------------------------------------------
 // Register a custom post-processing step
-aiReturn Importer::RegisterPPStep(BaseProcess* pImp)
-{
-    ai_assert(NULL != pImp);
+aiReturn Importer::RegisterPPStep(BaseProcess* pImp) {
+    ai_assert( nullptr != pImp );
+    
     ASSIMP_BEGIN_EXCEPTION_REGION();
 
         pimpl->mPostProcessingSteps.push_back(pImp);
@@ -213,9 +212,9 @@ aiReturn Importer::RegisterPPStep(BaseProcess* pImp)
 
 // ------------------------------------------------------------------------------------------------
 // Register a custom loader plugin
-aiReturn Importer::RegisterLoader(BaseImporter* pImp)
-{
-    ai_assert(NULL != pImp);
+aiReturn Importer::RegisterLoader(BaseImporter* pImp) {
+    ai_assert(nullptr != pImp);
+    
     ASSIMP_BEGIN_EXCEPTION_REGION();
 
     // --------------------------------------------------------------------
@@ -242,13 +241,13 @@ aiReturn Importer::RegisterLoader(BaseImporter* pImp)
     pimpl->mImporter.push_back(pImp);
     ASSIMP_LOG_INFO_F("Registering custom importer for these file extensions: ", baked);
     ASSIMP_END_EXCEPTION_REGION(aiReturn);
+    
     return AI_SUCCESS;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Unregister a custom loader plugin
-aiReturn Importer::UnregisterLoader(BaseImporter* pImp)
-{
+aiReturn Importer::UnregisterLoader(BaseImporter* pImp) {
     if(!pImp) {
         // unregistering a NULL importer is no problem for us ... really!
         return AI_SUCCESS;
@@ -265,13 +264,13 @@ aiReturn Importer::UnregisterLoader(BaseImporter* pImp)
     }
     ASSIMP_LOG_WARN("Unable to remove custom importer: I can't find you ...");
     ASSIMP_END_EXCEPTION_REGION(aiReturn);
+
     return AI_FAILURE;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Unregister a custom loader plugin
-aiReturn Importer::UnregisterPPStep(BaseProcess* pImp)
-{
+aiReturn Importer::UnregisterPPStep(BaseProcess* pImp) {
     if(!pImp) {
         // unregistering a NULL ppstep is no problem for us ... really!
         return AI_SUCCESS;
@@ -288,24 +287,22 @@ aiReturn Importer::UnregisterPPStep(BaseProcess* pImp)
     }
     ASSIMP_LOG_WARN("Unable to remove custom post-processing step: I can't find you ..");
     ASSIMP_END_EXCEPTION_REGION(aiReturn);
+
     return AI_FAILURE;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Supplies a custom IO handler to the importer to open and access files.
-void Importer::SetIOHandler( IOSystem* pIOHandler)
-{
+void Importer::SetIOHandler( IOSystem* pIOHandler) {
+    ai_assert(nullptr != pimpl);
+    
     ASSIMP_BEGIN_EXCEPTION_REGION();
     // If the new handler is zero, allocate a default IO implementation.
-    if (!pIOHandler)
-    {
+    if (!pIOHandler) {
         // Release pointer in the possession of the caller
         pimpl->mIOHandler = new DefaultIOSystem();
         pimpl->mIsDefaultHandler = true;
-    }
-    // Otherwise register the custom handler
-    else if (pimpl->mIOHandler != pIOHandler)
-    {
+    } else if (pimpl->mIOHandler != pIOHandler) { // Otherwise register the custom handler
         delete pimpl->mIOHandler;
         pimpl->mIOHandler = pIOHandler;
         pimpl->mIsDefaultHandler = false;
@@ -316,29 +313,32 @@ void Importer::SetIOHandler( IOSystem* pIOHandler)
 // ------------------------------------------------------------------------------------------------
 // Get the currently set IO handler
 IOSystem* Importer::GetIOHandler() const {
+    ai_assert(nullptr != pimpl);
+    
     return pimpl->mIOHandler;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Check whether a custom IO handler is currently set
 bool Importer::IsDefaultIOHandler() const {
+    ai_assert(nullptr != pimpl);
+    
     return pimpl->mIsDefaultHandler;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Supplies a custom progress handler to get regular callbacks during importing
 void Importer::SetProgressHandler ( ProgressHandler* pHandler ) {
+    ai_assert(nullptr != pimpl);
+    
     ASSIMP_BEGIN_EXCEPTION_REGION();
+    
     // If the new handler is zero, allocate a default implementation.
-    if (!pHandler)
-    {
+    if (!pHandler) {
         // Release pointer in the possession of the caller
         pimpl->mProgressHandler = new DefaultProgressHandler();
         pimpl->mIsDefaultProgressHandler = true;
-    }
-    // Otherwise register the custom handler
-    else if (pimpl->mProgressHandler != pHandler)
-    {
+    } else if (pimpl->mProgressHandler != pHandler) { // Otherwise register the custom handler
         delete pimpl->mProgressHandler;
         pimpl->mProgressHandler = pHandler;
         pimpl->mIsDefaultProgressHandler = false;
@@ -349,19 +349,22 @@ void Importer::SetProgressHandler ( ProgressHandler* pHandler ) {
 // ------------------------------------------------------------------------------------------------
 // Get the currently set progress handler
 ProgressHandler* Importer::GetProgressHandler() const {
+    ai_assert(nullptr != pimpl);
+    
     return pimpl->mProgressHandler;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Check whether a custom progress handler is currently set
 bool Importer::IsDefaultProgressHandler() const {
+    ai_assert(nullptr != pimpl);
+    
     return pimpl->mIsDefaultProgressHandler;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Validate post process step flags
-bool _ValidateFlags(unsigned int pFlags)
-{
+bool _ValidateFlags(unsigned int pFlags) {
     if (pFlags & aiProcess_GenSmoothNormals && pFlags & aiProcess_GenNormals)   {
         ASSIMP_LOG_ERROR("#aiProcess_GenSmoothNormals and #aiProcess_GenNormals are incompatible");
         return false;
@@ -375,12 +378,13 @@ bool _ValidateFlags(unsigned int pFlags)
 
 // ------------------------------------------------------------------------------------------------
 // Free the current scene
-void Importer::FreeScene( )
-{
+void Importer::FreeScene( ) {
+    ai_assert(nullptr != pimpl);
+    
     ASSIMP_BEGIN_EXCEPTION_REGION();
 
     delete pimpl->mScene;
-    pimpl->mScene = NULL;
+    pimpl->mScene = nullptr;
 
     pimpl->mErrorString = "";
     ASSIMP_END_EXCEPTION_REGION(void);
@@ -388,44 +392,48 @@ void Importer::FreeScene( )
 
 // ------------------------------------------------------------------------------------------------
 // Get the current error string, if any
-const char* Importer::GetErrorString() const
-{
-     /* Must remain valid as long as ReadFile() or FreeFile() are not called */
+const char* Importer::GetErrorString() const {
+    ai_assert(nullptr != pimpl);
+    
+    // Must remain valid as long as ReadFile() or FreeFile() are not called
     return pimpl->mErrorString.c_str();
 }
 
 // ------------------------------------------------------------------------------------------------
 // Enable extra-verbose mode
-void Importer::SetExtraVerbose(bool bDo)
-{
+void Importer::SetExtraVerbose(bool bDo) {
+    ai_assert(nullptr != pimpl);
+    
     pimpl->bExtraVerbose = bDo;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Get the current scene
-const aiScene* Importer::GetScene() const
-{
+const aiScene* Importer::GetScene() const {
+    ai_assert(nullptr != pimpl);
+    
     return pimpl->mScene;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Orphan the current scene and return it.
-aiScene* Importer::GetOrphanedScene()
-{
+aiScene* Importer::GetOrphanedScene() {
+    ai_assert(nullptr != pimpl);
+    
     aiScene* s = pimpl->mScene;
 
     ASSIMP_BEGIN_EXCEPTION_REGION();
-    pimpl->mScene = NULL;
+    pimpl->mScene = nullptr;
 
-    pimpl->mErrorString = ""; /* reset error string */
+    pimpl->mErrorString = ""; // reset error string
     ASSIMP_END_EXCEPTION_REGION(aiScene*);
+    
     return s;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Validate post-processing flags
-bool Importer::ValidateFlags(unsigned int pFlags) const
-{
+bool Importer::ValidateFlags(unsigned int pFlags) const {
     ASSIMP_BEGIN_EXCEPTION_REGION();
     // run basic checks for mutually exclusive flags
     if(!_ValidateFlags(pFlags)) {
@@ -467,8 +475,9 @@ bool Importer::ValidateFlags(unsigned int pFlags) const
 const aiScene* Importer::ReadFileFromMemory( const void* pBuffer,
     size_t pLength,
     unsigned int pFlags,
-    const char* pHint /*= ""*/)
-{
+    const char* pHint /*= ""*/) {
+    ai_assert(nullptr != pimpl);
+    
     ASSIMP_BEGIN_EXCEPTION_REGION();
     if (!pHint) {
         pHint = "";
@@ -476,12 +485,12 @@ const aiScene* Importer::ReadFileFromMemory( const void* pBuffer,
 
     if (!pBuffer || !pLength || strlen(pHint) > MaxLenHint ) {
         pimpl->mErrorString = "Invalid parameters passed to ReadFileFromMemory()";
-        return NULL;
+        return nullptr;
     }
 
     // prevent deletion of the previous IOHandler
     IOSystem* io = pimpl->mIOHandler;
-    pimpl->mIOHandler = NULL;
+    pimpl->mIOHandler = nullptr;
 
     SetIOHandler(new MemoryIOSystem((const uint8_t*)pBuffer,pLength,io));
 
@@ -498,8 +507,8 @@ const aiScene* Importer::ReadFileFromMemory( const void* pBuffer,
 }
 
 // ------------------------------------------------------------------------------------------------
-void WriteLogOpening(const std::string& file)
-{
+void WriteLogOpening(const std::string& file) {
+    
     ASSIMP_LOG_INFO_F("Load ", file);
 
     // print a full version dump. This is nice because we don't
@@ -550,8 +559,9 @@ void WriteLogOpening(const std::string& file)
 
 // ------------------------------------------------------------------------------------------------
 // Reads the given file and returns its contents if successful.
-const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags)
-{
+const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags) {
+    ai_assert(nullptr != pimpl);
+    
     ASSIMP_BEGIN_EXCEPTION_REGION();
     const std::string pFile(_pFile);
 
@@ -580,7 +590,7 @@ const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags)
 
             pimpl->mErrorString = "Unable to open file \"" + pFile + "\".";
             ASSIMP_LOG_ERROR(pimpl->mErrorString);
-            return NULL;
+            return nullptr;
         }
 
         std::unique_ptr<Profiler> profiler(GetPropertyInteger(AI_CONFIG_GLOB_MEASURE_TIME,0)?new Profiler():NULL);
@@ -589,7 +599,7 @@ const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags)
         }
 
         // Find an worker class which can handle the file
-        BaseImporter* imp = NULL;
+        BaseImporter* imp = nullptr;
         SetPropertyInteger("importerIndex", -1);
         for( unsigned int a = 0; a < pimpl->mImporter.size(); a++)  {
 
@@ -617,7 +627,7 @@ const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags)
             if( !imp)   {
                 pimpl->mErrorString = "No suitable reader found for the file format of file \"" + pFile + "\".";
                 ASSIMP_LOG_ERROR(pimpl->mErrorString);
-                return NULL;
+                return nullptr;
             }
         }
 
@@ -633,7 +643,7 @@ const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags)
         // Dispatch the reading to the worker class for this format
         const aiImporterDesc *desc( imp->GetInfo() );
         std::string ext( "unknown" );
-        if ( NULL != desc ) {
+        if ( nullptr != desc ) {
             ext = desc->mName;
         }
         ASSIMP_LOG_INFO("Found a matching importer for this file format: " + ext + "." );
@@ -654,15 +664,20 @@ const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags)
 
         // If successful, apply all active post processing steps to the imported data
         if( pimpl->mScene)  {
+            if (!pimpl->mScene->mMetaData || !pimpl->mScene->mMetaData->HasKey(AI_METADATA_SOURCE_FORMAT)) {
+                if (!pimpl->mScene->mMetaData) {
+                    pimpl->mScene->mMetaData = new aiMetadata;
+                }
+                pimpl->mScene->mMetaData->Add(AI_METADATA_SOURCE_FORMAT, aiString(ext));
+            }
 
 #ifndef ASSIMP_BUILD_NO_VALIDATEDS_PROCESS
             // The ValidateDS process is an exception. It is executed first, even before ScenePreprocessor is called.
-            if (pFlags & aiProcess_ValidateDataStructure)
-            {
+            if (pFlags & aiProcess_ValidateDataStructure) {
                 ValidateDSProcess ds;
                 ds.ExecuteOnScene (this);
                 if (!pimpl->mScene) {
-                    return NULL;
+                    return nullptr;
                 }
             }
 #endif // no validation
@@ -695,8 +710,7 @@ const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags)
         }
     }
 #ifdef ASSIMP_CATCH_GLOBAL_EXCEPTIONS
-    catch (std::exception &e)
-    {
+    catch (std::exception &e) {
 #if (defined _MSC_VER) &&   (defined _CPPRTTI)
         // if we have RTTI get the full name of the exception that occurred
         pimpl->mErrorString = std::string(typeid( e ).name()) + ": " + e.what();
@@ -705,24 +719,26 @@ const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags)
 #endif
 
         ASSIMP_LOG_ERROR(pimpl->mErrorString);
-        delete pimpl->mScene; pimpl->mScene = NULL;
+        delete pimpl->mScene; pimpl->mScene = nullptr;
     }
 #endif // ! ASSIMP_CATCH_GLOBAL_EXCEPTIONS
 
     // either successful or failure - the pointer expresses it anyways
     ASSIMP_END_EXCEPTION_REGION_WITH_ERROR_STRING(const aiScene*, pimpl->mErrorString);
+    
     return pimpl->mScene;
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // Apply post-processing to the currently bound scene
-const aiScene* Importer::ApplyPostProcessing(unsigned int pFlags)
-{
+const aiScene* Importer::ApplyPostProcessing(unsigned int pFlags) {
+    ai_assert(nullptr != pimpl);
+    
     ASSIMP_BEGIN_EXCEPTION_REGION();
     // Return immediately if no scene is active
     if (!pimpl->mScene) {
-        return NULL;
+        return nullptr;
     }
 
     // If no flags are given, return the current scene with no further action
@@ -737,12 +753,11 @@ const aiScene* Importer::ApplyPostProcessing(unsigned int pFlags)
 #ifndef ASSIMP_BUILD_NO_VALIDATEDS_PROCESS
     // The ValidateDS process plays an exceptional role. It isn't contained in the global
     // list of post-processing steps, so we need to call it manually.
-    if (pFlags & aiProcess_ValidateDataStructure)
-    {
+    if (pFlags & aiProcess_ValidateDataStructure) {
         ValidateDSProcess ds;
         ds.ExecuteOnScene (this);
         if (!pimpl->mScene) {
-            return NULL;
+            return nullptr;
         }
     }
 #endif // no validation
@@ -762,11 +777,9 @@ const aiScene* Importer::ApplyPostProcessing(unsigned int pFlags)
 
     std::unique_ptr<Profiler> profiler(GetPropertyInteger(AI_CONFIG_GLOB_MEASURE_TIME,0)?new Profiler():NULL);
     for( unsigned int a = 0; a < pimpl->mPostProcessingSteps.size(); a++)   {
-
         BaseProcess* process = pimpl->mPostProcessingSteps[a];
         pimpl->mProgressHandler->UpdatePostProcess(static_cast<int>(a), static_cast<int>(pimpl->mPostProcessingSteps.size()) );
         if( process->IsActive( pFlags)) {
-
             if (profiler) {
                 profiler->BeginRegion("postprocess");
             }
@@ -803,24 +816,28 @@ const aiScene* Importer::ApplyPostProcessing(unsigned int pFlags)
         static_cast<int>(pimpl->mPostProcessingSteps.size()) );
 
     // update private scene flags
-    if( pimpl->mScene )
+    if( pimpl->mScene ) {
       ScenePriv(pimpl->mScene)->mPPStepsApplied |= pFlags;
+    }
 
     // clear any data allocated by post-process steps
     pimpl->mPPShared->Clean();
     ASSIMP_LOG_INFO("Leaving post processing pipeline");
 
     ASSIMP_END_EXCEPTION_REGION(const aiScene*);
+    
     return pimpl->mScene;
 }
 
 // ------------------------------------------------------------------------------------------------
 const aiScene* Importer::ApplyCustomizedPostProcessing( BaseProcess *rootProcess, bool requestValidation ) {
+    ai_assert(nullptr != pimpl);
+    
     ASSIMP_BEGIN_EXCEPTION_REGION();
 
     // Return immediately if no scene is active
-    if ( NULL == pimpl->mScene ) {
-        return NULL;
+    if ( nullptr == pimpl->mScene ) {
+        return nullptr;
     }
 
     // If no flags are given, return the current scene with no further action
@@ -839,7 +856,7 @@ const aiScene* Importer::ApplyCustomizedPostProcessing( BaseProcess *rootProcess
         ValidateDSProcess ds;
         ds.ExecuteOnScene( this );
         if ( !pimpl->mScene ) {
-            return NULL;
+            return nullptr;
         }
     }
 #endif // no validation
@@ -890,46 +907,50 @@ const aiScene* Importer::ApplyCustomizedPostProcessing( BaseProcess *rootProcess
 
 // ------------------------------------------------------------------------------------------------
 // Helper function to check whether an extension is supported by ASSIMP
-bool Importer::IsExtensionSupported(const char* szExtension) const
-{
+bool Importer::IsExtensionSupported(const char* szExtension) const {
     return nullptr != GetImporter(szExtension);
 }
 
 // ------------------------------------------------------------------------------------------------
-size_t Importer::GetImporterCount() const
-{
+size_t Importer::GetImporterCount() const {
+    ai_assert(nullptr != pimpl);
+    
     return pimpl->mImporter.size();
 }
 
 // ------------------------------------------------------------------------------------------------
-const aiImporterDesc* Importer::GetImporterInfo(size_t index) const
-{
+const aiImporterDesc* Importer::GetImporterInfo(size_t index) const {
+    ai_assert(nullptr != pimpl);
+    
     if (index >= pimpl->mImporter.size()) {
-        return NULL;
+        return nullptr;
     }
     return pimpl->mImporter[index]->GetInfo();
 }
 
 
 // ------------------------------------------------------------------------------------------------
-BaseImporter* Importer::GetImporter (size_t index) const
-{
+BaseImporter* Importer::GetImporter (size_t index) const {
+    ai_assert(nullptr != pimpl);
+    
     if (index >= pimpl->mImporter.size()) {
-        return NULL;
+        return nullptr;
     }
     return pimpl->mImporter[index];
 }
 
 // ------------------------------------------------------------------------------------------------
 // Find a loader plugin for a given file extension
-BaseImporter* Importer::GetImporter (const char* szExtension) const
-{
+BaseImporter* Importer::GetImporter (const char* szExtension) const {
+    ai_assert(nullptr != pimpl);
+    
     return GetImporter(GetImporterIndex(szExtension));
 }
 
 // ------------------------------------------------------------------------------------------------
 // Find a loader plugin for a given file extension
 size_t Importer::GetImporterIndex (const char* szExtension) const {
+    ai_assert(nullptr != pimpl);
     ai_assert(nullptr != szExtension);
 
     ASSIMP_BEGIN_EXCEPTION_REGION();
@@ -960,8 +981,9 @@ size_t Importer::GetImporterIndex (const char* szExtension) const {
 
 // ------------------------------------------------------------------------------------------------
 // Helper function to build a list of all file extensions supported by ASSIMP
-void Importer::GetExtensionList(aiString& szOut) const
-{
+void Importer::GetExtensionList(aiString& szOut) const {
+    ai_assert(nullptr != pimpl);
+    
     ASSIMP_BEGIN_EXCEPTION_REGION();
     std::set<std::string> str;
     for (std::vector<BaseImporter*>::const_iterator i =  pimpl->mImporter.begin();i != pimpl->mImporter.end();++i)  {
@@ -985,8 +1007,9 @@ void Importer::GetExtensionList(aiString& szOut) const
 
 // ------------------------------------------------------------------------------------------------
 // Set a configuration property
-bool Importer::SetPropertyInteger(const char* szName, int iValue)
-{
+bool Importer::SetPropertyInteger(const char* szName, int iValue) {
+    ai_assert(nullptr != pimpl);
+    
     bool existing;
     ASSIMP_BEGIN_EXCEPTION_REGION();
         existing = SetGenericProperty<int>(pimpl->mIntProperties, szName,iValue);
@@ -996,8 +1019,9 @@ bool Importer::SetPropertyInteger(const char* szName, int iValue)
 
 // ------------------------------------------------------------------------------------------------
 // Set a configuration property
-bool Importer::SetPropertyFloat(const char* szName, ai_real iValue)
-{
+bool Importer::SetPropertyFloat(const char* szName, ai_real iValue) {
+    ai_assert(nullptr != pimpl);
+    
     bool existing;
     ASSIMP_BEGIN_EXCEPTION_REGION();
         existing = SetGenericProperty<ai_real>(pimpl->mFloatProperties, szName,iValue);
@@ -1007,8 +1031,9 @@ bool Importer::SetPropertyFloat(const char* szName, ai_real iValue)
 
 // ------------------------------------------------------------------------------------------------
 // Set a configuration property
-bool Importer::SetPropertyString(const char* szName, const std::string& value)
-{
+bool Importer::SetPropertyString(const char* szName, const std::string& value) {
+    ai_assert(nullptr != pimpl);
+    
     bool existing;
     ASSIMP_BEGIN_EXCEPTION_REGION();
         existing = SetGenericProperty<std::string>(pimpl->mStringProperties, szName,value);
@@ -1018,8 +1043,9 @@ bool Importer::SetPropertyString(const char* szName, const std::string& value)
 
 // ------------------------------------------------------------------------------------------------
 // Set a configuration property
-bool Importer::SetPropertyMatrix(const char* szName, const aiMatrix4x4& value)
-{
+bool Importer::SetPropertyMatrix(const char* szName, const aiMatrix4x4& value) {
+    ai_assert(nullptr != pimpl);
+    
     bool existing;
     ASSIMP_BEGIN_EXCEPTION_REGION();
         existing = SetGenericProperty<aiMatrix4x4>(pimpl->mMatrixProperties, szName,value);
@@ -1029,40 +1055,43 @@ bool Importer::SetPropertyMatrix(const char* szName, const aiMatrix4x4& value)
 
 // ------------------------------------------------------------------------------------------------
 // Get a configuration property
-int Importer::GetPropertyInteger(const char* szName,
-    int iErrorReturn /*= 0xffffffff*/) const
-{
+int Importer::GetPropertyInteger(const char* szName, int iErrorReturn /*= 0xffffffff*/) const {
+    ai_assert(nullptr != pimpl);
+    
     return GetGenericProperty<int>(pimpl->mIntProperties,szName,iErrorReturn);
 }
 
 // ------------------------------------------------------------------------------------------------
 // Get a configuration property
-ai_real Importer::GetPropertyFloat(const char* szName,
-    ai_real iErrorReturn /*= 10e10*/) const
-{
+ai_real Importer::GetPropertyFloat(const char* szName, ai_real iErrorReturn /*= 10e10*/) const {
+    ai_assert(nullptr != pimpl);
+    
     return GetGenericProperty<ai_real>(pimpl->mFloatProperties,szName,iErrorReturn);
 }
 
 // ------------------------------------------------------------------------------------------------
 // Get a configuration property
-const std::string Importer::GetPropertyString(const char* szName,
-    const std::string& iErrorReturn /*= ""*/) const
-{
+const std::string Importer::GetPropertyString(const char* szName, const std::string& iErrorReturn /*= ""*/) const {
+    ai_assert(nullptr != pimpl);
+    
     return GetGenericProperty<std::string>(pimpl->mStringProperties,szName,iErrorReturn);
 }
 
 // ------------------------------------------------------------------------------------------------
 // Get a configuration property
-const aiMatrix4x4 Importer::GetPropertyMatrix(const char* szName,
-    const aiMatrix4x4& iErrorReturn /*= aiMatrix4x4()*/) const
-{
+const aiMatrix4x4 Importer::GetPropertyMatrix(const char* szName, const aiMatrix4x4& iErrorReturn /*= aiMatrix4x4()*/) const {
+    ai_assert(nullptr != pimpl);
+    
     return GetGenericProperty<aiMatrix4x4>(pimpl->mMatrixProperties,szName,iErrorReturn);
 }
 
 // ------------------------------------------------------------------------------------------------
 // Get the memory requirements of a single node
-inline void AddNodeWeight(unsigned int& iScene,const aiNode* pcNode)
-{
+inline 
+void AddNodeWeight(unsigned int& iScene,const aiNode* pcNode) {
+    if ( nullptr == pcNode ) {
+        return;
+    }
     iScene += sizeof(aiNode);
     iScene += sizeof(unsigned int) * pcNode->mNumMeshes;
     iScene += sizeof(void*) * pcNode->mNumChildren;
@@ -1074,8 +1103,9 @@ inline void AddNodeWeight(unsigned int& iScene,const aiNode* pcNode)
 
 // ------------------------------------------------------------------------------------------------
 // Get the memory requirements of the scene
-void Importer::GetMemoryRequirements(aiMemoryInfo& in) const
-{
+void Importer::GetMemoryRequirements(aiMemoryInfo& in) const {
+    ai_assert(nullptr != pimpl);
+    
     in = aiMemoryInfo();
     aiScene* mScene = pimpl->mScene;
 
@@ -1087,8 +1117,7 @@ void Importer::GetMemoryRequirements(aiMemoryInfo& in) const
     in.total = sizeof(aiScene);
 
     // add all meshes
-    for (unsigned int i = 0; i < mScene->mNumMeshes;++i)
-    {
+    for (unsigned int i = 0; i < mScene->mNumMeshes;++i) {
         in.meshes += sizeof(aiMesh);
         if (mScene->mMeshes[i]->HasPositions()) {
             in.meshes += sizeof(aiVector3D) * mScene->mMeshes[i]->mNumVertices;
@@ -1105,14 +1134,16 @@ void Importer::GetMemoryRequirements(aiMemoryInfo& in) const
         for (unsigned int a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS;++a) {
             if (mScene->mMeshes[i]->HasVertexColors(a)) {
                 in.meshes += sizeof(aiColor4D) * mScene->mMeshes[i]->mNumVertices;
+            } else {
+                break;
             }
-            else break;
         }
         for (unsigned int a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS;++a) {
             if (mScene->mMeshes[i]->HasTextureCoords(a)) {
                 in.meshes += sizeof(aiVector3D) * mScene->mMeshes[i]->mNumVertices;
+            } else {
+                break;
             }
-            else break;
         }
         if (mScene->mMeshes[i]->HasBones()) {
             in.meshes += sizeof(void*) * mScene->mMeshes[i]->mNumBones;
@@ -1131,8 +1162,9 @@ void Importer::GetMemoryRequirements(aiMemoryInfo& in) const
         in.textures += sizeof(aiTexture);
         if (pc->mHeight) {
             in.textures += 4 * pc->mHeight * pc->mWidth;
+        } else {
+            in.textures += pc->mWidth;
         }
-        else in.textures += pc->mWidth;
     }
     in.total += in.textures;
 

+ 24 - 9
code/FBX/FBXConverter.cpp

@@ -60,6 +60,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/scene.h>
 
 #include <assimp/CreateAnimMesh.h>
+#include <assimp/commonMetaData.h>
+#include <assimp/StringUtils.h>
 
 #include <tuple>
 #include <memory>
@@ -1597,12 +1599,11 @@ namespace Assimp {
             aiBone *bone = nullptr;
 
             if (bone_map.count(deformer_name)) {
-                std::cout << "retrieved bone from lookup " << bone_name.C_Str() << ". Deformer: " << deformer_name
-                          << std::endl;
-                bone = bone_map[deformer_name];
-            } else {
-                std::cout << "created new bone " << bone_name.C_Str() << ". Deformer: " << deformer_name << std::endl;
-                bone = new aiBone();
+				ASSIMP_LOG_DEBUG_F("retrieved bone from lookup ", bone_name.C_Str(), ". Deformer:", deformer_name);
+				bone = bone_map[deformer_name];
+			} else {
+				ASSIMP_LOG_DEBUG_F("created new bone ", bone_name.C_Str(), ". Deformer: ", deformer_name);
+				bone = new aiBone();
                 bone->mName = bone_name;
 
                 // store local transform link for post processing
@@ -1648,7 +1649,7 @@ namespace Assimp {
                 bone_map.insert(std::pair<const std::string, aiBone *>(deformer_name, bone));
             }
 
-            std::cout << "bone research: Indicies size: " << out_indices.size() << std::endl;
+            ASSIMP_LOG_DEBUG_F("bone research: Indicies size: ", out_indices.size());
 
             // lookup must be populated in case something goes wrong
             // this also allocates bones to mesh instance outside
@@ -2087,7 +2088,14 @@ namespace Assimp {
             TrySetTextureProperties(out_mat, textures, "Maya|TEX_emissive_map|file", aiTextureType_EMISSION_COLOR, mesh);
             TrySetTextureProperties(out_mat, textures, "Maya|TEX_metallic_map|file", aiTextureType_METALNESS, mesh);
             TrySetTextureProperties(out_mat, textures, "Maya|TEX_roughness_map|file", aiTextureType_DIFFUSE_ROUGHNESS, mesh);
-            TrySetTextureProperties(out_mat, textures, "Maya|TEX_ao_map|file", aiTextureType_AMBIENT_OCCLUSION, mesh);            
+            TrySetTextureProperties(out_mat, textures, "Maya|TEX_ao_map|file", aiTextureType_AMBIENT_OCCLUSION, mesh);
+
+            // 3DSMax PBR
+            TrySetTextureProperties(out_mat, textures, "3dsMax|Parameters|base_color_map", aiTextureType_BASE_COLOR, mesh);
+            TrySetTextureProperties(out_mat, textures, "3dsMax|Parameters|bump_map", aiTextureType_NORMAL_CAMERA, mesh);
+            TrySetTextureProperties(out_mat, textures, "3dsMax|Parameters|emission_map", aiTextureType_EMISSION_COLOR, mesh);
+            TrySetTextureProperties(out_mat, textures, "3dsMax|Parameters|metalness_map", aiTextureType_METALNESS, mesh);
+            TrySetTextureProperties(out_mat, textures, "3dsMax|Parameters|roughness_map", aiTextureType_DIFFUSE_ROUGHNESS, mesh);
         }
 
         void FBXConverter::SetTextureProperties(aiMaterial* out_mat, const LayeredTextureMap& layeredTextures, const MeshGeometry* const mesh)
@@ -3604,7 +3612,9 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa
                 return;
             }
 
-            out->mMetaData = aiMetadata::Alloc(15);
+            const bool hasGenerator = !doc.Creator().empty();
+
+            out->mMetaData = aiMetadata::Alloc(16 + (hasGenerator ? 1 : 0));
             out->mMetaData->Set(0, "UpAxis", doc.GlobalSettings().UpAxis());
             out->mMetaData->Set(1, "UpAxisSign", doc.GlobalSettings().UpAxisSign());
             out->mMetaData->Set(2, "FrontAxis", doc.GlobalSettings().FrontAxis());
@@ -3620,6 +3630,11 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa
             out->mMetaData->Set(12, "TimeSpanStart", doc.GlobalSettings().TimeSpanStart());
             out->mMetaData->Set(13, "TimeSpanStop", doc.GlobalSettings().TimeSpanStop());
             out->mMetaData->Set(14, "CustomFrameRate", doc.GlobalSettings().CustomFrameRate());
+            out->mMetaData->Set(15, AI_METADATA_SOURCE_FORMAT_VERSION, aiString(to_string(doc.FBXVersion())));
+            if (hasGenerator)
+            {
+                out->mMetaData->Set(16, AI_METADATA_SOURCE_GENERATOR, aiString(doc.Creator()));
+            }
         }
 
         void FBXConverter::TransferDataToScene()

+ 2 - 0
code/FBX/FBXConverter.h

@@ -421,6 +421,8 @@ private:
         double& minTime,
         Model::RotOrder order);
 
+    // ------------------------------------------------------------------------------------------------
+    // Copy global geometric data and some information about the source asset into scene metadata.
     void ConvertGlobalSettings();
 
     // ------------------------------------------------------------------------------------------------

+ 3 - 2
code/Importer/IFC/IFCGeometry.cpp

@@ -138,8 +138,9 @@ void ProcessPolygonBoundaries(TempMesh& result, const TempMesh& inmesh, size_t m
             }
         }
     }
-
-    ai_assert(outer_polygon_it != end);
+	if (outer_polygon_it == end) {
+		return;
+	}
 
     const size_t outer_polygon_size = *outer_polygon_it;
     const IfcVector3& master_normal = normals[std::distance(begin, outer_polygon_it)];

+ 0 - 2
code/LWO/LWOLoader.cpp

@@ -586,7 +586,6 @@ void LWOImporter::GenerateNodeGraph(std::map<uint16_t,aiNode*>& apcNodes)
     root->mName.Set("<LWORoot>");
 
     //Set parent of all children, inserting pivots
-    //std::cout << "Set parent of all children" << std::endl;
     std::map<uint16_t, aiNode*> mapPivot;
     for (auto itapcNodes = apcNodes.begin(); itapcNodes != apcNodes.end(); ++itapcNodes) {
 
@@ -618,7 +617,6 @@ void LWOImporter::GenerateNodeGraph(std::map<uint16_t,aiNode*>& apcNodes)
     }
 
     //Merge pivot map into node map
-    //std::cout << "Merge pivot map into node map" << std::endl;
     for (auto itMapPivot = mapPivot.begin(); itMapPivot != mapPivot.end(); ++itMapPivot) {
         apcNodes[itMapPivot->first] = itMapPivot->second;
     }

+ 310 - 318
code/M3D/M3DExporter.cpp

@@ -55,17 +55,19 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <string>
 #include <vector>
 
-#include <assimp/version.h> // aiGetVersion
-#include <assimp/IOSystem.hpp>
-#include <assimp/Exporter.hpp>
-#include <assimp/DefaultLogger.hpp>
-#include <assimp/StreamWriter.h> // StreamWriterLE
 #include <assimp/Exceptional.h> // DeadlyExportError
+#include <assimp/StreamWriter.h> // StreamWriterLE
 #include <assimp/material.h> // aiTextureType
-#include <assimp/scene.h>
 #include <assimp/mesh.h>
+#include <assimp/scene.h>
+#include <assimp/version.h> // aiGetVersion
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/Exporter.hpp>
+#include <assimp/IOSystem.hpp>
+
 #include "M3DExporter.h"
 #include "M3DMaterials.h"
+#include "M3DWrapper.h"
 
 // RESOURCES:
 // https://gitlab.com/bztsrc/model3d/blob/master/docs/m3d_format.md
@@ -80,341 +82,331 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *  - aiAnimation -> m3d_action (frame with timestamp and list of bone id, position, orientation
  *      triplets, instead of per bone timestamp + lists)
  */
-using namespace Assimp;
-
-namespace Assimp {
-
-    // ---------------------------------------------------------------------
-    // Worker function for exporting a scene to binary M3D.
-    // Prototyped and registered in Exporter.cpp
-    void ExportSceneM3D (
-        const char* pFile,
-        IOSystem* pIOSystem,
-        const aiScene* pScene,
-        const ExportProperties* pProperties
-    ){
-        // initialize the exporter
-        M3DExporter exporter(pScene, pProperties);
-
-        // perform binary export
-        exporter.doExport(pFile, pIOSystem, false);
-    }
-
-    // ---------------------------------------------------------------------
-    // Worker function for exporting a scene to ASCII A3D.
-    // Prototyped and registered in Exporter.cpp
-    void ExportSceneA3D (
-        const char* pFile,
-        IOSystem* pIOSystem,
-        const aiScene* pScene,
-        const ExportProperties* pProperties
-
-    ){
-        // initialize the exporter
-        M3DExporter exporter(pScene, pProperties);
-
-        // perform ascii export
-        exporter.doExport(pFile, pIOSystem, true);
-    }
-
-} // end of namespace Assimp
-
-// ------------------------------------------------------------------------------------------------
-M3DExporter::M3DExporter ( const aiScene* pScene, const ExportProperties* pProperties )
-: mScene(pScene)
-, mProperties(pProperties)
-, outfile()
-, m3d(nullptr) { }
 
 // ------------------------------------------------------------------------------------------------
-void M3DExporter::doExport (
-    const char* pFile,
-    IOSystem* pIOSystem,
-    bool toAscii
-){
-    // TODO: convert mProperties into M3D_EXP_* flags
-    (void)mProperties;
-
-    // open the indicated file for writing (in binary / ASCII mode)
-    outfile.reset(pIOSystem->Open(pFile, toAscii ? "wt" : "wb"));
-    if (!outfile) {
-        throw DeadlyExportError( "could not open output .m3d file: " + std::string(pFile) );
-    }
-
-    // use malloc() here because m3d_free() will call free()
-    m3d = (m3d_t*)calloc(1, sizeof(m3d_t));
-    if(!m3d) {
-        throw DeadlyExportError( "memory allocation error" );
-    }
-    m3d->name = _m3d_safestr((char*)&mScene->mRootNode->mName.data, 2);
-
-    // Create a model from assimp structures
-    aiMatrix4x4 m;
-    NodeWalk(mScene->mRootNode, m);
-
-    // serialize the structures
-    unsigned int size;
-    unsigned char *output = m3d_save(m3d, M3D_EXP_FLOAT,
-        M3D_EXP_EXTRA | (toAscii ? M3D_EXP_ASCII : 0), &size);
-    m3d_free(m3d);
-    if(!output || size < 8) {
-        throw DeadlyExportError( "unable to serialize into Model 3D" );
-    }
-
-    // Write out serialized model
-    outfile->Write(output, size, 1);
-
-    // explicitly release file pointer,
-    // so we don't have to rely on class destruction.
-    outfile.reset();
-}
-
-
+// Conversion functions
 // ------------------------------------------------------------------------------------------------
 // helper to add a vertex (private to NodeWalk)
-m3dv_t *M3DExporter::AddVrtx(m3dv_t *vrtx, uint32_t *numvrtx, m3dv_t *v, uint32_t *idx)
-{
-    if(v->x == (M3D_FLOAT)-0.0) v->x = (M3D_FLOAT)0.0;
-    if(v->y == (M3D_FLOAT)-0.0) v->y = (M3D_FLOAT)0.0;
-    if(v->z == (M3D_FLOAT)-0.0) v->z = (M3D_FLOAT)0.0;
-    if(v->w == (M3D_FLOAT)-0.0) v->w = (M3D_FLOAT)0.0;
-    vrtx = (m3dv_t*)M3D_REALLOC(vrtx, ((*numvrtx) + 1) * sizeof(m3dv_t));
-    memcpy(&vrtx[*numvrtx], v, sizeof(m3dv_t));
-    *idx = *numvrtx;
-    (*numvrtx)++;
-    return vrtx;
+m3dv_t *AddVrtx(m3dv_t *vrtx, uint32_t *numvrtx, m3dv_t *v, uint32_t *idx) {
+	if (v->x == (M3D_FLOAT)-0.0) v->x = (M3D_FLOAT)0.0;
+	if (v->y == (M3D_FLOAT)-0.0) v->y = (M3D_FLOAT)0.0;
+	if (v->z == (M3D_FLOAT)-0.0) v->z = (M3D_FLOAT)0.0;
+	if (v->w == (M3D_FLOAT)-0.0) v->w = (M3D_FLOAT)0.0;
+	vrtx = (m3dv_t *)M3D_REALLOC(vrtx, ((*numvrtx) + 1) * sizeof(m3dv_t));
+	memcpy(&vrtx[*numvrtx], v, sizeof(m3dv_t));
+	*idx = *numvrtx;
+	(*numvrtx)++;
+	return vrtx;
 }
 
 // ------------------------------------------------------------------------------------------------
 // helper to add a tmap (private to NodeWalk)
-m3dti_t *M3DExporter::AddTmap(m3dti_t *tmap, uint32_t *numtmap, m3dti_t *ti, uint32_t *idx)
-{
-    tmap = (m3dti_t*)M3D_REALLOC(tmap, ((*numtmap) + 1) * sizeof(m3dti_t));
-    memcpy(&tmap[*numtmap], ti, sizeof(m3dti_t));
-    *idx = *numtmap;
-    (*numtmap)++;
-    return tmap;
+m3dti_t *AddTmap(m3dti_t *tmap, uint32_t *numtmap, m3dti_t *ti, uint32_t *idx) {
+	tmap = (m3dti_t *)M3D_REALLOC(tmap, ((*numtmap) + 1) * sizeof(m3dti_t));
+	memcpy(&tmap[*numtmap], ti, sizeof(m3dti_t));
+	*idx = *numtmap;
+	(*numtmap)++;
+	return tmap;
 }
 
 // ------------------------------------------------------------------------------------------------
-// recursive node walker
-void M3DExporter::NodeWalk(const aiNode* pNode, aiMatrix4x4 m)
-{
-    aiMatrix4x4 nm = m * pNode->mTransformation;
-
-    for(unsigned int i = 0; i < pNode->mNumMeshes; i++) {
-        const aiMesh *mesh = mScene->mMeshes[pNode->mMeshes[i]];
-        unsigned int mi = (M3D_INDEX)-1U;
-        if(mScene->mMaterials) {
-            // get the material for this mesh
-            mi = addMaterial(mScene->mMaterials[mesh->mMaterialIndex]);
-        }
-        // iterate through the mesh faces
-        for(unsigned int j = 0; j < mesh->mNumFaces; j++) {
-            unsigned int n;
-            const aiFace* face = &(mesh->mFaces[j]);
-            // only triangle meshes supported for now
-            if(face->mNumIndices != 3) {
-                throw DeadlyExportError( "use aiProcess_Triangulate before export" );
-            }
-            // add triangle to the output
-            n = m3d->numface++;
-            m3d->face = (m3df_t*)M3D_REALLOC(m3d->face,
-                m3d->numface * sizeof(m3df_t));
-            if(!m3d->face) {
-                throw DeadlyExportError( "memory allocation error" );
-            }
-            /* set all index to -1 by default */
-            m3d->face[n].vertex[0] = m3d->face[n].vertex[1] = m3d->face[n].vertex[2] =
-            m3d->face[n].normal[0] = m3d->face[n].normal[1] = m3d->face[n].normal[2] =
-            m3d->face[n].texcoord[0] = m3d->face[n].texcoord[1] = m3d->face[n].texcoord[2] = -1U;
-            m3d->face[n].materialid = mi;
-            for(unsigned int k = 0; k < face->mNumIndices; k++) {
-                // get the vertex's index
-                unsigned int l = face->mIndices[k];
-                unsigned int idx;
-                m3dv_t vertex;
-                m3dti_t ti;
-                // multiply the position vector by the transformation matrix
-                aiVector3D v = mesh->mVertices[l];
-                v *= nm;
-                vertex.x = v.x;
-                vertex.y = v.y;
-                vertex.z = v.z;
-                vertex.w = 1.0;
-                vertex.color = 0;
-                vertex.skinid = -1U;
-                // add color if defined
-                if(mesh->HasVertexColors(0))
-                    vertex.color = mkColor(&mesh->mColors[0][l]);
-                // save the vertex to the output
-                m3d->vertex = AddVrtx(m3d->vertex, &m3d->numvertex,
-                    &vertex, &idx);
-                m3d->face[n].vertex[k] = (M3D_INDEX)idx;
-                // do we have texture coordinates?
-                if(mesh->HasTextureCoords(0)) {
-                    ti.u = mesh->mTextureCoords[0][l].x;
-                    ti.v = mesh->mTextureCoords[0][l].y;
-                    m3d->tmap = AddTmap(m3d->tmap, &m3d->numtmap, &ti, &idx);
-                    m3d->face[n].texcoord[k] = (M3D_INDEX)idx;
-                }
-                // do we have normal vectors?
-                if(mesh->HasNormals()) {
-                    vertex.x = mesh->mNormals[l].x;
-                    vertex.y = mesh->mNormals[l].y;
-                    vertex.z = mesh->mNormals[l].z;
-                    vertex.color = 0;
-                    m3d->vertex = AddVrtx(m3d->vertex, &m3d->numvertex, &vertex, &idx);
-                    m3d->face[n].normal[k] = (M3D_INDEX)idx;
-                }
-            }
-        }
-    }
-    // repeat for the children nodes
-    for (unsigned int i = 0; i < pNode->mNumChildren; i++) {
-        NodeWalk(pNode->mChildren[i], nm);
-    }
+// convert aiColor4D into uint32_t
+uint32_t mkColor(aiColor4D *c) {
+	return ((uint8_t)(c->a * 255) << 24L) |
+		   ((uint8_t)(c->b * 255) << 16L) |
+		   ((uint8_t)(c->g * 255) << 8L) |
+		   ((uint8_t)(c->r * 255) << 0L);
 }
 
 // ------------------------------------------------------------------------------------------------
-// convert aiColor4D into uint32_t
-uint32_t M3DExporter::mkColor(aiColor4D* c)
-{
-    return  ((uint8_t)(c->a*255) << 24L) |
-            ((uint8_t)(c->b*255) << 16L) |
-            ((uint8_t)(c->g*255) <<  8L) |
-            ((uint8_t)(c->r*255) <<  0L);
+// add a material property to the output
+void addProp(m3dm_t *m, uint8_t type, uint32_t value) {
+	unsigned int i;
+	i = m->numprop++;
+	m->prop = (m3dp_t *)M3D_REALLOC(m->prop, m->numprop * sizeof(m3dp_t));
+	if (!m->prop) {
+		throw DeadlyExportError("memory allocation error");
+	}
+	m->prop[i].type = type;
+	m->prop[i].value.num = value;
 }
 
 // ------------------------------------------------------------------------------------------------
 // add a material to the output
-M3D_INDEX M3DExporter::addMaterial(const aiMaterial *mat)
-{
-    unsigned int mi = -1U;
-    aiColor4D c;
-    aiString name;
-    ai_real f;
-    char *fn;
-
-    if(mat && mat->Get(AI_MATKEY_NAME, name) == AI_SUCCESS && name.length &&
-        strcmp((char*)&name.data, AI_DEFAULT_MATERIAL_NAME)) {
-        // check if we have saved a material by this name. This has to be done
-        // because only the referenced materials should be added to the output
-        for(unsigned int i = 0; i < m3d->nummaterial; i++)
-            if(!strcmp((char*)&name.data, m3d->material[i].name)) {
-                mi = i;
-                break;
-            }
-        // if not found, add the material to the output
-        if(mi == -1U) {
-            unsigned int k;
-            mi = m3d->nummaterial++;
-            m3d->material = (m3dm_t*)M3D_REALLOC(m3d->material, m3d->nummaterial
-                * sizeof(m3dm_t));
-            if(!m3d->material) {
-                throw DeadlyExportError( "memory allocation error" );
-            }
-            m3d->material[mi].name = _m3d_safestr((char*)&name.data, 0);
-            m3d->material[mi].numprop = 0;
-            m3d->material[mi].prop = NULL;
-            // iterate through the material property table and see what we got
-            for(k = 0; k < 15; k++) {
-                unsigned int j;
-                if(m3d_propertytypes[k].format == m3dpf_map)
-                    continue;
-                if(aiProps[k].pKey) {
-                    switch(m3d_propertytypes[k].format) {
-                        case m3dpf_color:
-                            if(mat->Get(aiProps[k].pKey, aiProps[k].type,
-                                aiProps[k].index, c) == AI_SUCCESS)
-                                addProp(&m3d->material[mi],
-                                    m3d_propertytypes[k].id, mkColor(&c));
-                        break;
-                        case m3dpf_float:
-                            if(mat->Get(aiProps[k].pKey, aiProps[k].type,
-                                aiProps[k].index, f) == AI_SUCCESS)
-                                    addProp(&m3d->material[mi],
-                                        m3d_propertytypes[k].id,
-                                        /* not (uint32_t)f, because we don't want to convert
+M3D_INDEX addMaterial(const Assimp::M3DWrapper &m3d, const aiMaterial *mat) {
+	unsigned int mi = -1U;
+	aiColor4D c;
+	aiString name;
+	ai_real f;
+	char *fn;
+
+	if (mat && mat->Get(AI_MATKEY_NAME, name) == AI_SUCCESS && name.length &&
+			strcmp((char *)&name.data, AI_DEFAULT_MATERIAL_NAME)) {
+		// check if we have saved a material by this name. This has to be done
+		// because only the referenced materials should be added to the output
+		for (unsigned int i = 0; i < m3d->nummaterial; i++)
+			if (!strcmp((char *)&name.data, m3d->material[i].name)) {
+				mi = i;
+				break;
+			}
+		// if not found, add the material to the output
+		if (mi == -1U) {
+			unsigned int k;
+			mi = m3d->nummaterial++;
+			m3d->material = (m3dm_t *)M3D_REALLOC(m3d->material, m3d->nummaterial * sizeof(m3dm_t));
+			if (!m3d->material) {
+				throw DeadlyExportError("memory allocation error");
+			}
+			m3d->material[mi].name = _m3d_safestr((char *)&name.data, 0);
+			m3d->material[mi].numprop = 0;
+			m3d->material[mi].prop = NULL;
+			// iterate through the material property table and see what we got
+			for (k = 0; k < 15; k++) {
+				unsigned int j;
+				if (m3d_propertytypes[k].format == m3dpf_map)
+					continue;
+				if (aiProps[k].pKey) {
+					switch (m3d_propertytypes[k].format) {
+						case m3dpf_color:
+							if (mat->Get(aiProps[k].pKey, aiProps[k].type,
+										aiProps[k].index, c) == AI_SUCCESS)
+								addProp(&m3d->material[mi],
+										m3d_propertytypes[k].id, mkColor(&c));
+							break;
+						case m3dpf_float:
+							if (mat->Get(aiProps[k].pKey, aiProps[k].type,
+										aiProps[k].index, f) == AI_SUCCESS)
+								addProp(&m3d->material[mi],
+										m3d_propertytypes[k].id,
+										/* not (uint32_t)f, because we don't want to convert
                                          * it, we want to see it as 32 bits of memory */
-                                        *((uint32_t*)&f));
-                        break;
-                        case m3dpf_uint8:
-                            if(mat->Get(aiProps[k].pKey, aiProps[k].type,
-                                aiProps[k].index, j) == AI_SUCCESS) {
-                                // special conversion for illumination model property
-                                if(m3d_propertytypes[k].id == m3dp_il) {
-                                    switch(j) {
-                                        case aiShadingMode_NoShading: j = 0; break;
-                                        case aiShadingMode_Phong: j = 2; break;
-                                        default: j = 1; break;
-                                    }
-                                }
-                                addProp(&m3d->material[mi],
-                                    m3d_propertytypes[k].id, j);
-                            }
-                        break;
-                        default:
-                            if(mat->Get(aiProps[k].pKey, aiProps[k].type,
-                                aiProps[k].index, j) == AI_SUCCESS)
-                                addProp(&m3d->material[mi],
-                                    m3d_propertytypes[k].id, j);
-                        break;
-                    }
-                }
-                if(aiTxProps[k].pKey &&
-                    mat->GetTexture((aiTextureType)aiTxProps[k].type,
-                        aiTxProps[k].index, &name, NULL, NULL, NULL,
-                        NULL, NULL) == AI_SUCCESS) {
-                        unsigned int i;
-                        for(j = name.length-1; j > 0 && name.data[j]!='.'; j++);
-                        if(j && name.data[j]=='.' &&
-                            (name.data[j+1]=='p' || name.data[j+1]=='P') &&
-                            (name.data[j+1]=='n' || name.data[j+1]=='N') &&
-                            (name.data[j+1]=='g' || name.data[j+1]=='G'))
-                                name.data[j]=0;
-                        // do we have this texture saved already?
-                        fn = _m3d_safestr((char*)&name.data, 0);
-                        for(j = 0, i = -1U; j < m3d->numtexture; j++)
-                            if(!strcmp(fn, m3d->texture[j].name)) {
-                                i = j;
-                                free(fn);
-                                break;
-                        }
-                        if(i == -1U) {
-                            i = m3d->numtexture++;
-                            m3d->texture = (m3dtx_t*)M3D_REALLOC(
-                                m3d->texture,
-                                m3d->numtexture * sizeof(m3dtx_t));
-                            if(!m3d->texture) {
-                                throw DeadlyExportError( "memory allocation error" );
-                            }
-                            // we don't need the texture itself, only its name
-                            m3d->texture[i].name = fn;
-                            m3d->texture[i].w = 0;
-                            m3d->texture[i].h = 0;
-                            m3d->texture[i].d = NULL;
-                        }
-                        addProp(&m3d->material[mi],
-                            m3d_propertytypes[k].id + 128, i);
-                }
-            }
-        }
-    }
-    return mi;
+										*((uint32_t *)&f));
+							break;
+						case m3dpf_uint8:
+							if (mat->Get(aiProps[k].pKey, aiProps[k].type,
+										aiProps[k].index, j) == AI_SUCCESS) {
+								// special conversion for illumination model property
+								if (m3d_propertytypes[k].id == m3dp_il) {
+									switch (j) {
+										case aiShadingMode_NoShading: j = 0; break;
+										case aiShadingMode_Phong: j = 2; break;
+										default: j = 1; break;
+									}
+								}
+								addProp(&m3d->material[mi],
+										m3d_propertytypes[k].id, j);
+							}
+							break;
+						default:
+							if (mat->Get(aiProps[k].pKey, aiProps[k].type,
+										aiProps[k].index, j) == AI_SUCCESS)
+								addProp(&m3d->material[mi],
+										m3d_propertytypes[k].id, j);
+							break;
+					}
+				}
+				if (aiTxProps[k].pKey &&
+						mat->GetTexture((aiTextureType)aiTxProps[k].type,
+								aiTxProps[k].index, &name, NULL, NULL, NULL,
+								NULL, NULL) == AI_SUCCESS) {
+					unsigned int i;
+					for (j = name.length - 1; j > 0 && name.data[j] != '.'; j++)
+						;
+					if (j && name.data[j] == '.' &&
+							(name.data[j + 1] == 'p' || name.data[j + 1] == 'P') &&
+							(name.data[j + 1] == 'n' || name.data[j + 1] == 'N') &&
+							(name.data[j + 1] == 'g' || name.data[j + 1] == 'G'))
+						name.data[j] = 0;
+					// do we have this texture saved already?
+					fn = _m3d_safestr((char *)&name.data, 0);
+					for (j = 0, i = -1U; j < m3d->numtexture; j++)
+						if (!strcmp(fn, m3d->texture[j].name)) {
+							i = j;
+							free(fn);
+							break;
+						}
+					if (i == -1U) {
+						i = m3d->numtexture++;
+						m3d->texture = (m3dtx_t *)M3D_REALLOC(
+								m3d->texture,
+								m3d->numtexture * sizeof(m3dtx_t));
+						if (!m3d->texture) {
+							throw DeadlyExportError("memory allocation error");
+						}
+						// we don't need the texture itself, only its name
+						m3d->texture[i].name = fn;
+						m3d->texture[i].w = 0;
+						m3d->texture[i].h = 0;
+						m3d->texture[i].d = NULL;
+					}
+					addProp(&m3d->material[mi],
+							m3d_propertytypes[k].id + 128, i);
+				}
+			}
+		}
+	}
+	return mi;
+}
+
+namespace Assimp {
+
+// ---------------------------------------------------------------------
+// Worker function for exporting a scene to binary M3D.
+// Prototyped and registered in Exporter.cpp
+void ExportSceneM3D(
+		const char *pFile,
+		IOSystem *pIOSystem,
+		const aiScene *pScene,
+		const ExportProperties *pProperties) {
+	// initialize the exporter
+	M3DExporter exporter(pScene, pProperties);
+
+	// perform binary export
+	exporter.doExport(pFile, pIOSystem, false);
+}
+
+// ---------------------------------------------------------------------
+// Worker function for exporting a scene to ASCII A3D.
+// Prototyped and registered in Exporter.cpp
+void ExportSceneA3D(
+		const char *pFile,
+		IOSystem *pIOSystem,
+		const aiScene *pScene,
+		const ExportProperties *pProperties
+
+) {
+	// initialize the exporter
+	M3DExporter exporter(pScene, pProperties);
+
+	// perform ascii export
+	exporter.doExport(pFile, pIOSystem, true);
 }
 
 // ------------------------------------------------------------------------------------------------
-// add a material property to the output
-void M3DExporter::addProp(m3dm_t *m, uint8_t type, uint32_t value)
-{
-    unsigned int i;
-    i = m->numprop++;
-    m->prop = (m3dp_t*)M3D_REALLOC(m->prop, m->numprop * sizeof(m3dp_t));
-    if(!m->prop) { throw DeadlyExportError( "memory allocation error" ); }
-    m->prop[i].type = type;
-    m->prop[i].value.num = value;
+M3DExporter::M3DExporter(const aiScene *pScene, const ExportProperties *pProperties) :
+		mScene(pScene),
+		mProperties(pProperties),
+		outfile() {}
+
+// ------------------------------------------------------------------------------------------------
+void M3DExporter::doExport(
+		const char *pFile,
+		IOSystem *pIOSystem,
+		bool toAscii) {
+	// TODO: convert mProperties into M3D_EXP_* flags
+	(void)mProperties;
+
+	// open the indicated file for writing (in binary / ASCII mode)
+	outfile.reset(pIOSystem->Open(pFile, toAscii ? "wt" : "wb"));
+	if (!outfile) {
+		throw DeadlyExportError("could not open output .m3d file: " + std::string(pFile));
+	}
+
+	M3DWrapper m3d;
+	if (!m3d) {
+		throw DeadlyExportError("memory allocation error");
+	}
+	m3d->name = _m3d_safestr((char *)&mScene->mRootNode->mName.data, 2);
+
+	// Create a model from assimp structures
+	aiMatrix4x4 m;
+	NodeWalk(m3d, mScene->mRootNode, m);
+
+	// serialize the structures
+	unsigned int size;
+	unsigned char *output = m3d.Save(M3D_EXP_FLOAT, M3D_EXP_EXTRA | (toAscii ? M3D_EXP_ASCII : 0), size);
+
+	if (!output || size < 8) {
+		throw DeadlyExportError("unable to serialize into Model 3D");
+	}
+
+	// Write out serialized model
+	outfile->Write(output, size, 1);
+
+	// explicitly release file pointer,
+	// so we don't have to rely on class destruction.
+	outfile.reset();
+}
+
+// ------------------------------------------------------------------------------------------------
+// recursive node walker
+void M3DExporter::NodeWalk(const M3DWrapper &m3d, const aiNode *pNode, aiMatrix4x4 m) {
+	aiMatrix4x4 nm = m * pNode->mTransformation;
+
+	for (unsigned int i = 0; i < pNode->mNumMeshes; i++) {
+		const aiMesh *mesh = mScene->mMeshes[pNode->mMeshes[i]];
+		unsigned int mi = (M3D_INDEX)-1U;
+		if (mScene->mMaterials) {
+			// get the material for this mesh
+			mi = addMaterial(m3d, mScene->mMaterials[mesh->mMaterialIndex]);
+		}
+		// iterate through the mesh faces
+		for (unsigned int j = 0; j < mesh->mNumFaces; j++) {
+			unsigned int n;
+			const aiFace *face = &(mesh->mFaces[j]);
+			// only triangle meshes supported for now
+			if (face->mNumIndices != 3) {
+				throw DeadlyExportError("use aiProcess_Triangulate before export");
+			}
+			// add triangle to the output
+			n = m3d->numface++;
+			m3d->face = (m3df_t *)M3D_REALLOC(m3d->face,
+					m3d->numface * sizeof(m3df_t));
+			if (!m3d->face) {
+				throw DeadlyExportError("memory allocation error");
+			}
+			/* set all index to -1 by default */
+			m3d->face[n].vertex[0] = m3d->face[n].vertex[1] = m3d->face[n].vertex[2] =
+					m3d->face[n].normal[0] = m3d->face[n].normal[1] = m3d->face[n].normal[2] =
+							m3d->face[n].texcoord[0] = m3d->face[n].texcoord[1] = m3d->face[n].texcoord[2] = -1U;
+			m3d->face[n].materialid = mi;
+			for (unsigned int k = 0; k < face->mNumIndices; k++) {
+				// get the vertex's index
+				unsigned int l = face->mIndices[k];
+				unsigned int idx;
+				m3dv_t vertex;
+				m3dti_t ti;
+				// multiply the position vector by the transformation matrix
+				aiVector3D v = mesh->mVertices[l];
+				v *= nm;
+				vertex.x = v.x;
+				vertex.y = v.y;
+				vertex.z = v.z;
+				vertex.w = 1.0;
+				vertex.color = 0;
+				vertex.skinid = -1U;
+				// add color if defined
+				if (mesh->HasVertexColors(0))
+					vertex.color = mkColor(&mesh->mColors[0][l]);
+				// save the vertex to the output
+				m3d->vertex = AddVrtx(m3d->vertex, &m3d->numvertex,
+						&vertex, &idx);
+				m3d->face[n].vertex[k] = (M3D_INDEX)idx;
+				// do we have texture coordinates?
+				if (mesh->HasTextureCoords(0)) {
+					ti.u = mesh->mTextureCoords[0][l].x;
+					ti.v = mesh->mTextureCoords[0][l].y;
+					m3d->tmap = AddTmap(m3d->tmap, &m3d->numtmap, &ti, &idx);
+					m3d->face[n].texcoord[k] = (M3D_INDEX)idx;
+				}
+				// do we have normal vectors?
+				if (mesh->HasNormals()) {
+					vertex.x = mesh->mNormals[l].x;
+					vertex.y = mesh->mNormals[l].y;
+					vertex.z = mesh->mNormals[l].z;
+					vertex.color = 0;
+					m3d->vertex = AddVrtx(m3d->vertex, &m3d->numvertex, &vertex, &idx);
+					m3d->face[n].normal[k] = (M3D_INDEX)idx;
+				}
+			}
+		}
+	}
+	// repeat for the children nodes
+	for (unsigned int i = 0; i < pNode->mNumChildren; i++) {
+		NodeWalk(m3d, pNode->mChildren[i], nm);
+	}
 }
+} // namespace Assimp
 
 #endif // ASSIMP_BUILD_NO_M3D_EXPORTER
 #endif // ASSIMP_BUILD_NO_EXPORT

+ 3 - 9
code/M3D/M3DExporter.h

@@ -48,8 +48,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #ifndef ASSIMP_BUILD_NO_M3D_EXPORTER
 
-#include "m3d.h"
-
 #include <assimp/types.h>
 //#include <assimp/material.h>
 #include <assimp/StreamWriter.h> // StreamWriterLE
@@ -68,6 +66,8 @@ namespace Assimp
     class IOStream;
     class ExportProperties;
 
+    class M3DWrapper;
+
     // ---------------------------------------------------------------------
     /** Helper class to export a given scene to an M3D file. */
     // ---------------------------------------------------------------------
@@ -83,15 +83,9 @@ namespace Assimp
         const aiScene* mScene; // the scene to export
         const ExportProperties* mProperties; // currently unused
         std::shared_ptr<IOStream> outfile; // file to write to
-        m3d_t *m3d; // model for the C library to convert to
 
         // helper to do the recursive walking
-        void NodeWalk(const aiNode* pNode, aiMatrix4x4 m);
-        m3dv_t *AddVrtx(m3dv_t *vrtx, uint32_t *numvrtx, m3dv_t *v, uint32_t *idx);
-        m3dti_t *AddTmap(m3dti_t *tmap, uint32_t *numtmap, m3dti_t *ti, uint32_t *idx);
-        uint32_t mkColor(aiColor4D* c);
-        M3D_INDEX addMaterial(const aiMaterial *mat);
-        void addProp(m3dm_t *m, uint8_t type, uint32_t value);
+        void NodeWalk(const M3DWrapper &m3d, const aiNode* pNode, aiMatrix4x4 m);
     };
 }
 

+ 582 - 606
code/M3D/M3DImporter.cpp

@@ -44,20 +44,22 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #define M3D_IMPLEMENTATION
 #define M3D_ASCII
-#define M3D_NONORMALS       /* leave the post-processing to Assimp */
+#define M3D_NONORMALS /* leave the post-processing to Assimp */
 #define M3D_NOWEIGHTS
 #define M3D_NOANIMATION
 
-#include <assimp/IOStreamBuffer.h>
-#include <memory>
 #include <assimp/DefaultIOSystem.h>
-#include <assimp/Importer.hpp>
-#include <assimp/scene.h>
+#include <assimp/IOStreamBuffer.h>
 #include <assimp/ai_assert.h>
-#include <assimp/DefaultLogger.hpp>
 #include <assimp/importerdesc.h>
+#include <assimp/scene.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/Importer.hpp>
+#include <memory>
+
 #include "M3DImporter.h"
 #include "M3DMaterials.h"
+#include "M3DWrapper.h"
 
 // RESOURCES:
 // https://gitlab.com/bztsrc/model3d/blob/master/docs/m3d_format.md
@@ -85,682 +87,656 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
 
 static const aiImporterDesc desc = {
-    "Model 3D Importer",
-    "",
-    "",
-    "",
-    aiImporterFlags_SupportBinaryFlavour,
-    0,
-    0,
-    0,
-    0,
-    "m3d a3d"
+	"Model 3D Importer",
+	"",
+	"",
+	"",
+	aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour,
+	0,
+	0,
+	0,
+	0,
+	"m3d a3d"
 };
 
-// workaround: the SDK expects a C callback, but we want to use Assimp::IOSystem to implement that
-extern "C" {
-    void* m3dimporter_pIOHandler;
-
-    unsigned char *m3dimporter_readfile(char *fn, unsigned int *size) {
-        ai_assert( nullptr != fn );
-        ai_assert( nullptr != size );
-        std::string file(fn);
-        std::unique_ptr<Assimp::IOStream> pStream(
-            (reinterpret_cast<Assimp::IOSystem*>(m3dimporter_pIOHandler))->Open( file, "rb"));
-        size_t fileSize = 0;
-        unsigned char *data = NULL;
-        // sometimes pStream is nullptr for some reason (should be an empty object returning nothing I guess)
-        if(pStream) {
-            fileSize = pStream->FileSize();
-            // should be allocated with malloc(), because the library will call free() to deallocate
-            data = (unsigned char*)malloc(fileSize);
-            if( !data || !pStream.get() || !fileSize || fileSize != pStream->Read(data,1,fileSize)) {
-                pStream.reset();
-                *size = 0;
-                // don't throw a deadly exception, it's not fatal if we can't read an external asset
-                return nullptr;
-            }
-            pStream.reset();
-        }
-        *size = (int)fileSize;
-        return data;
-    }
-}
-
 namespace Assimp {
 
 using namespace std;
 
 // ------------------------------------------------------------------------------------------------
 //  Default constructor
-M3DImporter::M3DImporter()
-: mScene(nullptr)
-, m3d(nullptr) { }
-
-// ------------------------------------------------------------------------------------------------
-//  Destructor.
-M3DImporter::~M3DImporter() {}
+M3DImporter::M3DImporter() :
+		mScene(nullptr) {}
 
 // ------------------------------------------------------------------------------------------------
 //  Returns true, if file is a binary or ASCII Model 3D file.
-bool M3DImporter::CanRead(const std::string& pFile, IOSystem*  pIOHandler , bool checkSig) const {
-    const std::string extension = GetExtension(pFile);
-
-    if (extension == "m3d" || extension == "a3d")
-        return true;
-    else if (!extension.length() || checkSig)   {
-        if (!pIOHandler) {
-            return true;
-        }
-        /*
+bool M3DImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const {
+	const std::string extension = GetExtension(pFile);
+
+	if (extension == "m3d" || extension == "a3d")
+		return true;
+	else if (!extension.length() || checkSig) {
+		if (!pIOHandler) {
+			return true;
+		}
+		/*
          * don't use CheckMagicToken because that checks with swapped bytes too, leading to false
          * positives. This magic is not uint32_t, but char[4], so memcmp is the best way
 
         const char* tokens[] = {"3DMO", "3dmo"};
         return CheckMagicToken(pIOHandler,pFile,tokens,2,0,4);
         */
-        std::unique_ptr<IOStream> pStream (pIOHandler->Open(pFile, "rb"));
-        unsigned char data[4];
-        if(4 != pStream->Read(data,1,4)) {
-            return false;
-        }
-        return !memcmp(data, "3DMO", 4) /* bin */ || !memcmp(data, "3dmo", 4) /* ASCII */;
-    }
-    return false;
+		std::unique_ptr<IOStream> pStream(pIOHandler->Open(pFile, "rb"));
+		unsigned char data[4];
+		if (4 != pStream->Read(data, 1, 4)) {
+			return false;
+		}
+		return !memcmp(data, "3DMO", 4) /* bin */ || !memcmp(data, "3dmo", 4) /* ASCII */;
+	}
+	return false;
 }
 
 // ------------------------------------------------------------------------------------------------
-const aiImporterDesc* M3DImporter::GetInfo() const {
-    return &desc;
+const aiImporterDesc *M3DImporter::GetInfo() const {
+	return &desc;
 }
 
 // ------------------------------------------------------------------------------------------------
 //  Model 3D import implementation
-void M3DImporter::InternReadFile( const std::string &file, aiScene* pScene, IOSystem* pIOHandler) {
-    // Read file into memory
-    std::unique_ptr<IOStream> pStream( pIOHandler->Open( file, "rb"));
-    if( !pStream.get() ) {
-        throw DeadlyImportError( "Failed to open file " + file + "." );
-    }
-
-    // Get the file-size and validate it, throwing an exception when fails
-    size_t fileSize = pStream->FileSize();
-    if( fileSize < 8 ) {
-        throw DeadlyImportError( "M3D-file " + file + " is too small." );
-    }
-    std::unique_ptr<unsigned char[]> _buffer (new unsigned char[fileSize]);
-    unsigned char *data( _buffer.get() );
-    if(fileSize != pStream->Read(data,1,fileSize)) {
-        throw DeadlyImportError( "Failed to read the file " + file + "." );
-    }
-
-    // Get the path for external assets
-    std::string  folderName( "./" );
-    std::string::size_type pos = file.find_last_of( "\\/" );
-    if ( pos != std::string::npos ) {
-        folderName = file.substr( 0, pos );
-        if ( !folderName.empty() ) {
-            pIOHandler->PushDirectory( folderName );
-        }
-    }
-    // pass this IOHandler to the C callback
-    m3dimporter_pIOHandler = pIOHandler;
+void M3DImporter::InternReadFile(const std::string &file, aiScene *pScene, IOSystem *pIOHandler) {
+	// Read file into memory
+	std::unique_ptr<IOStream> pStream(pIOHandler->Open(file, "rb"));
+	if (!pStream.get()) {
+		throw DeadlyImportError("Failed to open file " + file + ".");
+	}
+
+	// Get the file-size and validate it, throwing an exception when fails
+	size_t fileSize = pStream->FileSize();
+	if (fileSize < 8) {
+		throw DeadlyImportError("M3D-file " + file + " is too small.");
+	}
+	std::vector<unsigned char> buffer(fileSize);
+	if (fileSize != pStream->Read(buffer.data(), 1, fileSize)) {
+		throw DeadlyImportError("Failed to read the file " + file + ".");
+	}
+
+	// Get the path for external assets
+	std::string folderName("./");
+	std::string::size_type pos = file.find_last_of("\\/");
+	if (pos != std::string::npos) {
+		folderName = file.substr(0, pos);
+		if (!folderName.empty()) {
+			pIOHandler->PushDirectory(folderName);
+		}
+	}
 
     //DefaultLogger::create("/dev/stderr", Logger::VERBOSE);
-    ASSIMP_LOG_DEBUG_F("M3D: loading ", file);
-
-    // let the C SDK do the hard work for us
-    m3d = m3d_load(&data[0], m3dimporter_readfile, free, nullptr);
-    m3dimporter_pIOHandler = nullptr;
-    if( !m3d ) {
-        throw DeadlyImportError( "Unable to parse " + file + " as M3D." );
-    }
-
-    // create the root node
-    pScene->mRootNode = new aiNode;
-    pScene->mRootNode->mName = aiString(std::string(std::string(m3d->name)));
-    pScene->mRootNode->mTransformation = aiMatrix4x4();
-    pScene->mRootNode->mNumChildren = 0;
-    mScene = pScene;
-
-    ASSIMP_LOG_DEBUG("M3D: root node " + std::string(m3d->name));
-
-    // now we just have to fill up the Assimp structures in pScene
-    importMaterials();
-    importTextures();
-    importBones(-1U, pScene->mRootNode);
-    importMeshes();
-    importAnimations();
-
-    // we don't need the SDK's version any more
-    m3d_free(m3d);
-
-    // Pop directory stack
-    if ( pIOHandler->StackSize() > 0 ) {
-        pIOHandler->PopDirectory();
-    }
+	ASSIMP_LOG_DEBUG_F("M3D: loading ", file);
+
+	// let the C SDK do the hard work for us
+	M3DWrapper m3d(pIOHandler, buffer);
+
+	
+	if (!m3d) {
+		throw DeadlyImportError("Unable to parse " + file + " as M3D.");
+	}
+
+	// create the root node
+	pScene->mRootNode = new aiNode;
+	pScene->mRootNode->mName = aiString(m3d.Name());
+	pScene->mRootNode->mTransformation = aiMatrix4x4();
+	pScene->mRootNode->mNumChildren = 0;
+	mScene = pScene;
+
+	ASSIMP_LOG_DEBUG("M3D: root node " + m3d.Name());
+
+	// now we just have to fill up the Assimp structures in pScene
+	importMaterials(m3d);
+    importTextures(m3d);
+	importBones(m3d, -1U, pScene->mRootNode);
+	importMeshes(m3d);
+	importAnimations(m3d);
+
+	// Pop directory stack
+	if (pIOHandler->StackSize() > 0) {
+		pIOHandler->PopDirectory();
+	}
 }
 
 // ------------------------------------------------------------------------------------------------
 // convert materials. properties are converted using a static table in M3DMaterials.h
-void M3DImporter::importMaterials()
-{
-    unsigned int i, j, k, l, n;
-    m3dm_t *m;
-    aiString name = aiString(AI_DEFAULT_MATERIAL_NAME);
-    aiColor4D c;
-    ai_real f;
-
-    ai_assert(mScene != nullptr);
-    ai_assert(m3d != nullptr);
-
-    mScene->mNumMaterials = m3d->nummaterial + 1;
-    mScene->mMaterials = new aiMaterial*[ m3d->nummaterial + 1 ];
-
-    ASSIMP_LOG_DEBUG_F("M3D: importMaterials ", mScene->mNumMaterials);
-
-    // add a default material as first
-    aiMaterial* mat = new aiMaterial;
-    mat->AddProperty( &name, AI_MATKEY_NAME );
-    c.a = 1.0; c.b = c.g = c.r = 0.6;
-    mat->AddProperty( &c, 1, AI_MATKEY_COLOR_DIFFUSE);
-    mScene->mMaterials[0] = mat;
-
-    for(i = 0; i < m3d->nummaterial; i++) {
-        m = &m3d->material[i];
-        aiMaterial* mat = new aiMaterial;
-        name.Set(std::string(m->name));
-        mat->AddProperty( &name, AI_MATKEY_NAME );
-        for(j = 0; j < m->numprop; j++) {
-            // look up property type
-            // 0 - 127 scalar values,
-            // 128 - 255 the same properties but for texture maps
-            k = 256;
-            for(l = 0; l < sizeof(m3d_propertytypes)/sizeof(m3d_propertytypes[0]); l++)
-                if(m->prop[j].type == m3d_propertytypes[l].id ||
-                    m->prop[j].type == m3d_propertytypes[l].id + 128) {
-                        k = l;
-                        break;
-                    }
-            // should never happen, but be safe than sorry
-            if(k == 256) continue;
-
-            // scalar properties
-            if(m->prop[j].type < 128 && aiProps[k].pKey) {
-                switch(m3d_propertytypes[k].format) {
-                    case m3dpf_color:
-                        c = mkColor(m->prop[j].value.color);
-                        mat->AddProperty(&c, 1, aiProps[k].pKey, aiProps[k].type, aiProps[k].index);
-                    break;
-                    case m3dpf_float:
-                        f = m->prop[j].value.fnum;
-                        mat->AddProperty(&f, 1, aiProps[k].pKey, aiProps[k].type, aiProps[k].index);
-                    break;
-                    default:
-                        n = m->prop[j].value.num;
-                        if(m->prop[j].type == m3dp_il) {
-                            switch(n) {
-                                case 0:  n = aiShadingMode_NoShading; break;
-                                case 2:  n = aiShadingMode_Phong; break;
-                                default: n = aiShadingMode_Gouraud; break;
-                            }
-                        }
-                        mat->AddProperty(&n, 1, aiProps[k].pKey, aiProps[k].type, aiProps[k].index);
-                    break;
-                }
-            }
-            // texture map properties
-            if(m->prop[j].type >= 128 && aiTxProps[k].pKey &&
-                // extra check, should never happen, do we have the refered texture?
-                m->prop[j].value.textureid < m3d->numtexture &&
-                m3d->texture[m->prop[j].value.textureid].name) {
-                    name.Set(std::string(std::string(m3d->texture[m->prop[j].value.textureid].name) + ".png"));
-                    mat->AddProperty(&name, aiTxProps[k].pKey, aiTxProps[k].type, aiTxProps[k].index);
-                    n = 0;
-                    mat->AddProperty(&n, 1, _AI_MATKEY_UVWSRC_BASE, aiProps[k].type, aiProps[k].index);
-            }
-        }
-        mScene->mMaterials[i + 1] = mat;
-    }
+void M3DImporter::importMaterials(const M3DWrapper &m3d_wrap) {
+	unsigned int i, j, k, l, n;
+	m3dm_t *m;
+	aiString name = aiString(AI_DEFAULT_MATERIAL_NAME);
+	aiColor4D c;
+	ai_real f;
+
+	ai_assert(mScene != nullptr);
+	ai_assert(m3d_wrap);
+
+	mScene->mNumMaterials = m3d_wrap->nummaterial + 1;
+	mScene->mMaterials = new aiMaterial *[mScene->mNumMaterials];
+
+	ASSIMP_LOG_DEBUG_F("M3D: importMaterials ", mScene->mNumMaterials);
+
+	// add a default material as first
+	aiMaterial *mat = new aiMaterial;
+	mat->AddProperty(&name, AI_MATKEY_NAME);
+	c.a = 1.0f;
+	c.b = c.g = c.r = 0.6f;
+	mat->AddProperty(&c, 1, AI_MATKEY_COLOR_DIFFUSE);
+	mScene->mMaterials[0] = mat;
+
+	for (i = 0; i < m3d_wrap->nummaterial; i++) {
+		m = &m3d_wrap->material[i];
+		aiMaterial *mat = new aiMaterial;
+		name.Set(std::string(m->name));
+		mat->AddProperty(&name, AI_MATKEY_NAME);
+		for (j = 0; j < m->numprop; j++) {
+			// look up property type
+			// 0 - 127 scalar values,
+			// 128 - 255 the same properties but for texture maps
+			k = 256;
+			for (l = 0; l < sizeof(m3d_propertytypes) / sizeof(m3d_propertytypes[0]); l++)
+				if (m->prop[j].type == m3d_propertytypes[l].id ||
+						m->prop[j].type == m3d_propertytypes[l].id + 128) {
+					k = l;
+					break;
+				}
+			// should never happen, but be safe than sorry
+			if (k == 256) continue;
+
+			// scalar properties
+			if (m->prop[j].type < 128 && aiProps[k].pKey) {
+				switch (m3d_propertytypes[k].format) {
+					case m3dpf_color:
+						c = mkColor(m->prop[j].value.color);
+						mat->AddProperty(&c, 1, aiProps[k].pKey, aiProps[k].type, aiProps[k].index);
+						break;
+					case m3dpf_float:
+						f = m->prop[j].value.fnum;
+						mat->AddProperty(&f, 1, aiProps[k].pKey, aiProps[k].type, aiProps[k].index);
+						break;
+					default:
+						n = m->prop[j].value.num;
+						if (m->prop[j].type == m3dp_il) {
+							switch (n) {
+								case 0: n = aiShadingMode_NoShading; break;
+								case 2: n = aiShadingMode_Phong; break;
+								default: n = aiShadingMode_Gouraud; break;
+							}
+						}
+						mat->AddProperty(&n, 1, aiProps[k].pKey, aiProps[k].type, aiProps[k].index);
+						break;
+				}
+			}
+			// texture map properties
+			if (m->prop[j].type >= 128 && aiTxProps[k].pKey &&
+					// extra check, should never happen, do we have the refered texture?
+					m->prop[j].value.textureid < m3d_wrap->numtexture &&
+					m3d_wrap->texture[m->prop[j].value.textureid].name) {
+				name.Set(std::string(std::string(m3d_wrap->texture[m->prop[j].value.textureid].name) + ".png"));
+				mat->AddProperty(&name, aiTxProps[k].pKey, aiTxProps[k].type, aiTxProps[k].index);
+				n = 0;
+				mat->AddProperty(&n, 1, _AI_MATKEY_UVWSRC_BASE, aiProps[k].type, aiProps[k].index);
+			}
+		}
+		mScene->mMaterials[i + 1] = mat;
+	}
 }
 
 // ------------------------------------------------------------------------------------------------
 // import textures, this is the simplest of all
-void M3DImporter::importTextures()
-{
-    unsigned int i;
-    const char *formatHint[] = { "rgba0800", "rgba0808", "rgba8880", "rgba8888" };
-    m3dtx_t *t;
-
-    ai_assert(mScene != nullptr);
-    ai_assert(m3d != nullptr);
-
-    mScene->mNumTextures = m3d->numtexture;
-    ASSIMP_LOG_DEBUG_F("M3D: importTextures ", mScene->mNumTextures);
-
-    if(!m3d->numtexture)
-        return;
-
-    mScene->mTextures = new aiTexture*[m3d->numtexture];
-    for(i = 0; i < m3d->numtexture; i++) {
-        unsigned int j, k;
-        t = &m3d->texture[i];
-        if(!t->w || !t->h || !t->f || !t->d) continue;
-        aiTexture *tx = new aiTexture;
-        strcpy(tx->achFormatHint, formatHint[t->f - 1]);
-        tx->mFilename = aiString(std::string(t->name) + ".png");
-        tx->mWidth = t->w;
-        tx->mHeight = t->h;
-        tx->pcData = new aiTexel[ tx->mWidth*tx->mHeight ];
-        for(j = k = 0; j < tx->mWidth*tx->mHeight; j++) {
-            switch(t->f) {
-                case 1: tx->pcData[j].g = t->d[k++]; break;
-                case 2: tx->pcData[j].g = t->d[k++]; tx->pcData[j].a = t->d[k++]; break;
-                case 3:
-                    tx->pcData[j].r = t->d[k++]; tx->pcData[j].g = t->d[k++];
-                    tx->pcData[j].b = t->d[k++]; tx->pcData[j].a = 255;
-                break;
-                case 4:
-                    tx->pcData[j].r = t->d[k++]; tx->pcData[j].g = t->d[k++];
-                    tx->pcData[j].b = t->d[k++]; tx->pcData[j].a = t->d[k++];
-                break;
-            }
-        }
-        mScene->mTextures[i] = tx;
-    }
+void M3DImporter::importTextures(const M3DWrapper &m3d) {
+	unsigned int i;
+	const char *formatHint[] = { "rgba0800", "rgba0808", "rgba8880", "rgba8888" };
+	m3dtx_t *t;
+
+	ai_assert(mScene != nullptr);
+	ai_assert(m3d);
+
+	mScene->mNumTextures = m3d->numtexture;
+	ASSIMP_LOG_DEBUG_F("M3D: importTextures ", mScene->mNumTextures);
+
+	if (!m3d->numtexture)
+		return;
+
+	mScene->mTextures = new aiTexture *[m3d->numtexture];
+	for (i = 0; i < m3d->numtexture; i++) {
+		unsigned int j, k;
+		t = &m3d->texture[i];
+		if (!t->w || !t->h || !t->f || !t->d) continue;
+		aiTexture *tx = new aiTexture;
+		strcpy(tx->achFormatHint, formatHint[t->f - 1]);
+		tx->mFilename = aiString(std::string(t->name) + ".png");
+		tx->mWidth = t->w;
+		tx->mHeight = t->h;
+		tx->pcData = new aiTexel[tx->mWidth * tx->mHeight];
+		for (j = k = 0; j < tx->mWidth * tx->mHeight; j++) {
+			switch (t->f) {
+				case 1: tx->pcData[j].g = t->d[k++]; break;
+				case 2:
+					tx->pcData[j].g = t->d[k++];
+					tx->pcData[j].a = t->d[k++];
+					break;
+				case 3:
+					tx->pcData[j].r = t->d[k++];
+					tx->pcData[j].g = t->d[k++];
+					tx->pcData[j].b = t->d[k++];
+					tx->pcData[j].a = 255;
+					break;
+				case 4:
+					tx->pcData[j].r = t->d[k++];
+					tx->pcData[j].g = t->d[k++];
+					tx->pcData[j].b = t->d[k++];
+					tx->pcData[j].a = t->d[k++];
+					break;
+			}
+		}
+		mScene->mTextures[i] = tx;
+	}
 }
 
 // ------------------------------------------------------------------------------------------------
 // this is tricky. M3D has a global vertex and UV list, and faces are indexing them
 // individually. In assimp there're per mesh vertex and UV lists, and they must be
 // indexed simultaneously.
-void M3DImporter::importMeshes()
-{
-    unsigned int i, j, k, l, numpoly = 3, lastMat = -2U;
-    std::vector<aiMesh*> *meshes = new std::vector<aiMesh*>();
-    std::vector<aiFace> *faces = nullptr;
-    std::vector<aiVector3D> *vertices = nullptr;
-    std::vector<aiVector3D> *normals = nullptr;
-    std::vector<aiVector3D> *texcoords = nullptr;
-    std::vector<aiColor4D> *colors = nullptr;
-    std::vector<unsigned int> *vertexids = nullptr;
-    aiMesh *pMesh = nullptr;
-
-    ai_assert(mScene != nullptr);
-    ai_assert(m3d != nullptr);
-    ai_assert(mScene->mRootNode != nullptr);
-
-    ASSIMP_LOG_DEBUG_F("M3D: importMeshes ", m3d->numface);
-
-    for(i = 0; i < m3d->numface; i++) {
-        // we must switch mesh if material changes
-        if(lastMat != m3d->face[i].materialid) {
-            lastMat = m3d->face[i].materialid;
-            if(pMesh && vertices && vertices->size() && faces && faces->size()) {
-                populateMesh(pMesh, faces, vertices, normals, texcoords, colors, vertexids);
-                meshes->push_back(pMesh);
-                delete faces;
-                delete vertices;
-                delete normals;
-                delete texcoords;
-                delete colors;
-                delete vertexids;   // this is not stored in pMesh, just to collect bone vertices
-            }
-            pMesh = new aiMesh;
-            pMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
-            pMesh->mMaterialIndex = lastMat + 1;
-            faces = new std::vector<aiFace>();
-            vertices = new std::vector<aiVector3D>();
-            normals = new std::vector<aiVector3D>();
-            texcoords = new std::vector<aiVector3D>();
-            colors = new std::vector<aiColor4D>();
-            vertexids = new std::vector<unsigned int>();
-        }
-        // add a face to temporary vector
-        aiFace *pFace = new aiFace;
-        pFace->mNumIndices = numpoly;
-        pFace->mIndices = new unsigned int[numpoly];
-        for(j = 0; j < numpoly; j++) {
-            aiVector3D pos, uv, norm;
-            k = vertices->size();
-            pFace->mIndices[j] = k;
-            l = m3d->face[i].vertex[j];
-            pos.x = m3d->vertex[l].x;
-            pos.y = m3d->vertex[l].y;
-            pos.z = m3d->vertex[l].z;
-            vertices->push_back(pos);
-            colors->push_back(mkColor(m3d->vertex[l].color));
-            // add a bone to temporary vector
-            if(m3d->vertex[l].skinid != -1U &&m3d->vertex[l].skinid != -2U && m3d->skin && m3d->bone) {
-                // this is complicated, because M3D stores a list of bone id / weight pairs per
-                // vertex but assimp uses lists of local vertex id/weight pairs per local bone list
-                vertexids->push_back(l);
-            }
-            l = m3d->face[i].texcoord[j];
-            if(l != -1U) {
-                uv.x = m3d->tmap[l].u;
-                uv.y = m3d->tmap[l].v;
-                uv.z = 0.0;
-                texcoords->push_back(uv);
-            }
-            l = m3d->face[i].normal[j];
-            if(l != -1U) {
-                norm.x = m3d->vertex[l].x;
-                norm.y = m3d->vertex[l].y;
-                norm.z = m3d->vertex[l].z;
-                normals->push_back(norm);
-            }
-        }
-        faces->push_back(*pFace);
-        delete pFace;
-    }
-    // if there's data left in the temporary vectors, flush them
-    if(pMesh && vertices->size() && faces->size()) {
-        populateMesh(pMesh, faces, vertices, normals, texcoords, colors, vertexids);
-        meshes->push_back(pMesh);
-    }
-
-    // create global mesh list in scene
-    mScene->mNumMeshes = meshes->size();
-    mScene->mMeshes = new aiMesh*[mScene->mNumMeshes];
-    std::copy(meshes->begin(), meshes->end(), mScene->mMeshes);
-
-    // create mesh indeces in root node
-    mScene->mRootNode->mNumMeshes = meshes->size();
-    mScene->mRootNode->mMeshes = new unsigned int[meshes->size()];
-    for(i = 0; i < meshes->size(); i++) {
-        mScene->mRootNode->mMeshes[i] = i;
-    }
-
-    delete meshes;
-    if(faces)       delete faces;
-    if(vertices)    delete vertices;
-    if(normals)     delete normals;
-    if(texcoords)   delete texcoords;
-    if(colors)      delete colors;
-    if(vertexids)   delete vertexids;
+void M3DImporter::importMeshes(const M3DWrapper &m3d) {
+	unsigned int i, j, k, l, numpoly = 3, lastMat = -2U;
+	std::vector<aiMesh *> *meshes = new std::vector<aiMesh *>();
+	std::vector<aiFace> *faces = nullptr;
+	std::vector<aiVector3D> *vertices = nullptr;
+	std::vector<aiVector3D> *normals = nullptr;
+	std::vector<aiVector3D> *texcoords = nullptr;
+	std::vector<aiColor4D> *colors = nullptr;
+	std::vector<unsigned int> *vertexids = nullptr;
+	aiMesh *pMesh = nullptr;
+
+	ai_assert(mScene != nullptr);
+	ai_assert(m3d);
+	ai_assert(mScene->mRootNode != nullptr);
+
+	ASSIMP_LOG_DEBUG_F("M3D: importMeshes ", m3d->numface);
+
+	for (i = 0; i < m3d->numface; i++) {
+		// we must switch mesh if material changes
+		if (lastMat != m3d->face[i].materialid) {
+			lastMat = m3d->face[i].materialid;
+			if (pMesh && vertices && vertices->size() && faces && faces->size()) {
+				populateMesh(m3d, pMesh, faces, vertices, normals, texcoords, colors, vertexids);
+				meshes->push_back(pMesh);
+				delete faces;
+				delete vertices;
+				delete normals;
+				delete texcoords;
+				delete colors;
+				delete vertexids; // this is not stored in pMesh, just to collect bone vertices
+			}
+			pMesh = new aiMesh;
+			pMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
+			pMesh->mMaterialIndex = lastMat + 1;
+			faces = new std::vector<aiFace>();
+			vertices = new std::vector<aiVector3D>();
+			normals = new std::vector<aiVector3D>();
+			texcoords = new std::vector<aiVector3D>();
+			colors = new std::vector<aiColor4D>();
+			vertexids = new std::vector<unsigned int>();
+		}
+		// add a face to temporary vector
+		aiFace *pFace = new aiFace;
+		pFace->mNumIndices = numpoly;
+		pFace->mIndices = new unsigned int[numpoly];
+		for (j = 0; j < numpoly; j++) {
+			aiVector3D pos, uv, norm;
+			k = static_cast<unsigned int>(vertices->size());
+			pFace->mIndices[j] = k;
+			l = m3d->face[i].vertex[j];
+			pos.x = m3d->vertex[l].x;
+			pos.y = m3d->vertex[l].y;
+			pos.z = m3d->vertex[l].z;
+			vertices->push_back(pos);
+			colors->push_back(mkColor(m3d->vertex[l].color));
+			// add a bone to temporary vector
+			if (m3d->vertex[l].skinid != -1U && m3d->vertex[l].skinid != -2U && m3d->skin && m3d->bone) {
+				// this is complicated, because M3D stores a list of bone id / weight pairs per
+				// vertex but assimp uses lists of local vertex id/weight pairs per local bone list
+				vertexids->push_back(l);
+			}
+			l = m3d->face[i].texcoord[j];
+			if (l != -1U) {
+				uv.x = m3d->tmap[l].u;
+				uv.y = m3d->tmap[l].v;
+				uv.z = 0.0;
+				texcoords->push_back(uv);
+			}
+			l = m3d->face[i].normal[j];
+			if (l != -1U) {
+				norm.x = m3d->vertex[l].x;
+				norm.y = m3d->vertex[l].y;
+				norm.z = m3d->vertex[l].z;
+				normals->push_back(norm);
+			}
+		}
+		faces->push_back(*pFace);
+		delete pFace;
+	}
+	// if there's data left in the temporary vectors, flush them
+	if (pMesh && vertices->size() && faces->size()) {
+		populateMesh(m3d, pMesh, faces, vertices, normals, texcoords, colors, vertexids);
+		meshes->push_back(pMesh);
+	}
+
+	// create global mesh list in scene
+	mScene->mNumMeshes = static_cast<unsigned int>(meshes->size());
+	mScene->mMeshes = new aiMesh *[mScene->mNumMeshes];
+	std::copy(meshes->begin(), meshes->end(), mScene->mMeshes);
+
+	// create mesh indeces in root node
+	mScene->mRootNode->mNumMeshes = static_cast<unsigned int>(meshes->size());
+	mScene->mRootNode->mMeshes = new unsigned int[meshes->size()];
+	for (i = 0; i < meshes->size(); i++) {
+		mScene->mRootNode->mMeshes[i] = i;
+	}
+
+	delete meshes;
+	if (faces) delete faces;
+	if (vertices) delete vertices;
+	if (normals) delete normals;
+	if (texcoords) delete texcoords;
+	if (colors) delete colors;
+	if (vertexids) delete vertexids;
 }
 
 // ------------------------------------------------------------------------------------------------
 // a reentrant node parser. Otherwise this is simple
-void M3DImporter::importBones(unsigned int parentid, aiNode *pParent)
-{
-    unsigned int i, n;
-
-    ai_assert(pParent != nullptr);
-    ai_assert(mScene != nullptr);
-    ai_assert(m3d != nullptr);
-
-    ASSIMP_LOG_DEBUG_F("M3D: importBones ", m3d->numbone, " parentid ", (int)parentid);
-
-    for(n = 0, i = parentid + 1; i < m3d->numbone; i++)
-        if(m3d->bone[i].parent == parentid) n++;
-    pParent->mChildren = new aiNode*[n];
-
-    for(i = parentid + 1; i < m3d->numbone; i++) {
-        if(m3d->bone[i].parent == parentid) {
-            aiNode *pChild = new aiNode;
-            pChild->mParent = pParent;
-            pChild->mName = aiString(std::string(m3d->bone[i].name));
-            convertPose(&pChild->mTransformation, m3d->bone[i].pos, m3d->bone[i].ori);
-            pChild->mNumChildren = 0;
-            pParent->mChildren[pParent->mNumChildren] = pChild;
-            pParent->mNumChildren++;
-            importBones(i, pChild);
-        }
-    }
+void M3DImporter::importBones(const M3DWrapper &m3d, unsigned int parentid, aiNode *pParent) {
+	unsigned int i, n;
+
+	ai_assert(pParent != nullptr);
+	ai_assert(mScene != nullptr);
+	ai_assert(m3d);
+
+	ASSIMP_LOG_DEBUG_F("M3D: importBones ", m3d->numbone, " parentid ", (int)parentid);
+
+	for (n = 0, i = parentid + 1; i < m3d->numbone; i++)
+		if (m3d->bone[i].parent == parentid) n++;
+	pParent->mChildren = new aiNode *[n];
+
+	for (i = parentid + 1; i < m3d->numbone; i++) {
+		if (m3d->bone[i].parent == parentid) {
+			aiNode *pChild = new aiNode;
+			pChild->mParent = pParent;
+			pChild->mName = aiString(std::string(m3d->bone[i].name));
+			convertPose(m3d, &pChild->mTransformation, m3d->bone[i].pos, m3d->bone[i].ori);
+			pChild->mNumChildren = 0;
+			pParent->mChildren[pParent->mNumChildren] = pChild;
+			pParent->mNumChildren++;
+			importBones(m3d, i, pChild);
+		}
+	}
 }
 
 // ------------------------------------------------------------------------------------------------
 // this is another headache. M3D stores list of changed bone id/position/orientation triplets and
 // a timestamp per frame, but assimp needs timestamp and lists of position, orientation lists per
 // bone, so we have to convert between the two conceptually different representation forms
-void M3DImporter::importAnimations()
-{
-    unsigned int i, j, k, l, pos, ori;
-    double t;
-    m3da_t *a;
-
-    ai_assert(mScene != nullptr);
-    ai_assert(m3d != nullptr);
-
-    mScene->mNumAnimations = m3d->numaction;
-
-    ASSIMP_LOG_DEBUG_F("M3D: importAnimations ", mScene->mNumAnimations);
-
-    if(!m3d->numaction || !m3d->numbone)
-        return;
-
-    mScene->mAnimations = new aiAnimation*[m3d->numaction];
-    for(i = 0; i < m3d->numaction; i++) {
-        a = &m3d->action[i];
-        aiAnimation *pAnim = new aiAnimation;
-        pAnim->mName = aiString(std::string(a->name));
-        pAnim->mDuration = ((double)a->durationmsec) / 10;
-        pAnim->mTicksPerSecond = 100;
-        // now we know how many bones are referenced in this animation
-        pAnim->mNumChannels = m3d->numbone;
-        pAnim->mChannels = new aiNodeAnim*[pAnim->mNumChannels];
-        for(l = 0; l < m3d->numbone; l++) {
-            unsigned int n;
-            pAnim->mChannels[l] = new aiNodeAnim;
-            pAnim->mChannels[l]->mNodeName = aiString(std::string(m3d->bone[l].name));
-            // now n is the size of positions / orientations arrays
-            pAnim->mChannels[l]->mNumPositionKeys = pAnim->mChannels[l]->mNumRotationKeys = a->numframe;
-            pAnim->mChannels[l]->mPositionKeys = new aiVectorKey[a->numframe];
-            pAnim->mChannels[l]->mRotationKeys = new aiQuatKey[a->numframe];
-            pos = m3d->bone[l].pos;
-            ori = m3d->bone[l].ori;
-            for(j = n = 0; j < a->numframe; j++) {
-                t = ((double)a->frame[j].msec) / 10;
-                for(k = 0; k < a->frame[j].numtransform; k++) {
-                    if(a->frame[j].transform[k].boneid == l) {
-                        pos = a->frame[j].transform[k].pos;
-                        ori = a->frame[j].transform[k].ori;
-                    }
-                }
-                m3dv_t *v = &m3d->vertex[pos];
-                m3dv_t *q = &m3d->vertex[ori];
-                pAnim->mChannels[l]->mPositionKeys[j].mTime = t;
-                pAnim->mChannels[l]->mPositionKeys[j].mValue.x = v->x;
-                pAnim->mChannels[l]->mPositionKeys[j].mValue.y = v->y;
-                pAnim->mChannels[l]->mPositionKeys[j].mValue.z = v->z;
-                pAnim->mChannels[l]->mRotationKeys[j].mTime = t;
-                pAnim->mChannels[l]->mRotationKeys[j].mValue.w = q->w;
-                pAnim->mChannels[l]->mRotationKeys[j].mValue.x = q->x;
-                pAnim->mChannels[l]->mRotationKeys[j].mValue.y = q->y;
-                pAnim->mChannels[l]->mRotationKeys[j].mValue.z = q->z;
-            }// foreach frame
-        }// foreach bones
-        mScene->mAnimations[i] = pAnim;
-    }
+void M3DImporter::importAnimations(const M3DWrapper &m3d) {
+	unsigned int i, j, k, l, pos, ori;
+	double t;
+	m3da_t *a;
+
+	ai_assert(mScene != nullptr);
+	ai_assert(m3d);
+
+	mScene->mNumAnimations = m3d->numaction;
+
+	ASSIMP_LOG_DEBUG_F("M3D: importAnimations ", mScene->mNumAnimations);
+
+	if (!m3d->numaction || !m3d->numbone)
+		return;
+
+	mScene->mAnimations = new aiAnimation *[m3d->numaction];
+	for (i = 0; i < m3d->numaction; i++) {
+		a = &m3d->action[i];
+		aiAnimation *pAnim = new aiAnimation;
+		pAnim->mName = aiString(std::string(a->name));
+		pAnim->mDuration = ((double)a->durationmsec) / 10;
+		pAnim->mTicksPerSecond = 100;
+		// now we know how many bones are referenced in this animation
+		pAnim->mNumChannels = m3d->numbone;
+		pAnim->mChannels = new aiNodeAnim *[pAnim->mNumChannels];
+		for (l = 0; l < m3d->numbone; l++) {
+			unsigned int n;
+			pAnim->mChannels[l] = new aiNodeAnim;
+			pAnim->mChannels[l]->mNodeName = aiString(std::string(m3d->bone[l].name));
+			// now n is the size of positions / orientations arrays
+			pAnim->mChannels[l]->mNumPositionKeys = pAnim->mChannels[l]->mNumRotationKeys = a->numframe;
+			pAnim->mChannels[l]->mPositionKeys = new aiVectorKey[a->numframe];
+			pAnim->mChannels[l]->mRotationKeys = new aiQuatKey[a->numframe];
+			pos = m3d->bone[l].pos;
+			ori = m3d->bone[l].ori;
+			for (j = n = 0; j < a->numframe; j++) {
+				t = ((double)a->frame[j].msec) / 10;
+				for (k = 0; k < a->frame[j].numtransform; k++) {
+					if (a->frame[j].transform[k].boneid == l) {
+						pos = a->frame[j].transform[k].pos;
+						ori = a->frame[j].transform[k].ori;
+					}
+				}
+				m3dv_t *v = &m3d->vertex[pos];
+				m3dv_t *q = &m3d->vertex[ori];
+				pAnim->mChannels[l]->mPositionKeys[j].mTime = t;
+				pAnim->mChannels[l]->mPositionKeys[j].mValue.x = v->x;
+				pAnim->mChannels[l]->mPositionKeys[j].mValue.y = v->y;
+				pAnim->mChannels[l]->mPositionKeys[j].mValue.z = v->z;
+				pAnim->mChannels[l]->mRotationKeys[j].mTime = t;
+				pAnim->mChannels[l]->mRotationKeys[j].mValue.w = q->w;
+				pAnim->mChannels[l]->mRotationKeys[j].mValue.x = q->x;
+				pAnim->mChannels[l]->mRotationKeys[j].mValue.y = q->y;
+				pAnim->mChannels[l]->mRotationKeys[j].mValue.z = q->z;
+			} // foreach frame
+		} // foreach bones
+		mScene->mAnimations[i] = pAnim;
+	}
 }
 
 // ------------------------------------------------------------------------------------------------
 // convert uint32_t into aiColor4D
 aiColor4D M3DImporter::mkColor(uint32_t c) {
-    aiColor4D color;
-    color.a = ((float)((c >> 24)&0xff)) / 255;
-    color.b = ((float)((c >> 16)&0xff)) / 255;
-    color.g = ((float)((c >>  8)&0xff)) / 255;
-    color.r = ((float)((c >>  0)&0xff)) / 255;
-    return color;
+	aiColor4D color;
+	color.a = ((float)((c >> 24) & 0xff)) / 255;
+	color.b = ((float)((c >> 16) & 0xff)) / 255;
+	color.g = ((float)((c >> 8) & 0xff)) / 255;
+	color.r = ((float)((c >> 0) & 0xff)) / 255;
+	return color;
 }
 
 // ------------------------------------------------------------------------------------------------
 // convert a position id and orientation id into a 4 x 4 transformation matrix
-void M3DImporter::convertPose(aiMatrix4x4 *m, unsigned int posid, unsigned int orientid)
-{
-    ai_assert(m != nullptr);
-    ai_assert(m3d != nullptr);
-    ai_assert(posid != -1U && posid < m3d->numvertex);
-    ai_assert(orientid != -1U && orientid < m3d->numvertex);
-    m3dv_t *p = &m3d->vertex[posid];
-    m3dv_t *q = &m3d->vertex[orientid];
-
-    /* quaternion to matrix. Do NOT use aiQuaternion to aiMatrix3x3, gives bad results */
-    if(q->x == 0.0 && q->y == 0.0 && q->z >= 0.7071065 && q->z <= 0.7071075 && q->w == 0.0) {
-        m->a2 = m->a3 = m->b1 = m->b3 = m->c1 = m->c2 = 0.0;
-        m->a1 = m->b2 = m->c3 = -1.0;
-    } else {
-        m->a1 = 1 - 2 * (q->y * q->y + q->z * q->z); if(m->a1 > -M3D_EPSILON && m->a1 < M3D_EPSILON) m->a1 = 0.0;
-        m->a2 = 2 * (q->x * q->y - q->z * q->w);     if(m->a2 > -M3D_EPSILON && m->a2 < M3D_EPSILON) m->a2 = 0.0;
-        m->a3 = 2 * (q->x * q->z + q->y * q->w);     if(m->a3 > -M3D_EPSILON && m->a3 < M3D_EPSILON) m->a3 = 0.0;
-        m->b1 = 2 * (q->x * q->y + q->z * q->w);     if(m->b1 > -M3D_EPSILON && m->b1 < M3D_EPSILON) m->b1 = 0.0;
-        m->b2 = 1 - 2 * (q->x * q->x + q->z * q->z); if(m->b2 > -M3D_EPSILON && m->b2 < M3D_EPSILON) m->b2 = 0.0;
-        m->b3 = 2 * (q->y * q->z - q->x * q->w);     if(m->b3 > -M3D_EPSILON && m->b3 < M3D_EPSILON) m->b3 = 0.0;
-        m->c1 = 2 * (q->x * q->z - q->y * q->w);     if(m->c1 > -M3D_EPSILON && m->c1 < M3D_EPSILON) m->c1 = 0.0;
-        m->c2 = 2 * (q->y * q->z + q->x * q->w);     if(m->c2 > -M3D_EPSILON && m->c2 < M3D_EPSILON) m->c2 = 0.0;
-        m->c3 = 1 - 2 * (q->x * q->x + q->y * q->y); if(m->c3 > -M3D_EPSILON && m->c3 < M3D_EPSILON) m->c3 = 0.0;
-    }
-
-    /* set translation */
-    m->a4 = p->x; m->b4 = p->y; m->c4 = p->z;
-
-    m->d1 = 0; m->d2 = 0; m->d3 = 0; m->d4 = 1;
+void M3DImporter::convertPose(const M3DWrapper &m3d, aiMatrix4x4 *m, unsigned int posid, unsigned int orientid) {
+	ai_assert(m != nullptr);
+	ai_assert(m3d);
+	ai_assert(posid != -1U && posid < m3d->numvertex);
+	ai_assert(orientid != -1U && orientid < m3d->numvertex);
+	m3dv_t *p = &m3d->vertex[posid];
+	m3dv_t *q = &m3d->vertex[orientid];
+
+	/* quaternion to matrix. Do NOT use aiQuaternion to aiMatrix3x3, gives bad results */
+	if (q->x == 0.0 && q->y == 0.0 && q->z >= 0.7071065 && q->z <= 0.7071075 && q->w == 0.0) {
+		m->a2 = m->a3 = m->b1 = m->b3 = m->c1 = m->c2 = 0.0;
+		m->a1 = m->b2 = m->c3 = -1.0;
+	} else {
+		m->a1 = 1 - 2 * (q->y * q->y + q->z * q->z);
+		if (m->a1 > -M3D_EPSILON && m->a1 < M3D_EPSILON) m->a1 = 0.0;
+		m->a2 = 2 * (q->x * q->y - q->z * q->w);
+		if (m->a2 > -M3D_EPSILON && m->a2 < M3D_EPSILON) m->a2 = 0.0;
+		m->a3 = 2 * (q->x * q->z + q->y * q->w);
+		if (m->a3 > -M3D_EPSILON && m->a3 < M3D_EPSILON) m->a3 = 0.0;
+		m->b1 = 2 * (q->x * q->y + q->z * q->w);
+		if (m->b1 > -M3D_EPSILON && m->b1 < M3D_EPSILON) m->b1 = 0.0;
+		m->b2 = 1 - 2 * (q->x * q->x + q->z * q->z);
+		if (m->b2 > -M3D_EPSILON && m->b2 < M3D_EPSILON) m->b2 = 0.0;
+		m->b3 = 2 * (q->y * q->z - q->x * q->w);
+		if (m->b3 > -M3D_EPSILON && m->b3 < M3D_EPSILON) m->b3 = 0.0;
+		m->c1 = 2 * (q->x * q->z - q->y * q->w);
+		if (m->c1 > -M3D_EPSILON && m->c1 < M3D_EPSILON) m->c1 = 0.0;
+		m->c2 = 2 * (q->y * q->z + q->x * q->w);
+		if (m->c2 > -M3D_EPSILON && m->c2 < M3D_EPSILON) m->c2 = 0.0;
+		m->c3 = 1 - 2 * (q->x * q->x + q->y * q->y);
+		if (m->c3 > -M3D_EPSILON && m->c3 < M3D_EPSILON) m->c3 = 0.0;
+	}
+
+	/* set translation */
+	m->a4 = p->x;
+	m->b4 = p->y;
+	m->c4 = p->z;
+
+	m->d1 = 0;
+	m->d2 = 0;
+	m->d3 = 0;
+	m->d4 = 1;
 }
 
 // ------------------------------------------------------------------------------------------------
 // find a node by name
-aiNode *M3DImporter::findNode(aiNode *pNode, aiString name)
-{
-    unsigned int i;
-
-    ai_assert(pNode != nullptr);
-    ai_assert(mScene != nullptr);
-
-    if(pNode->mName == name)
-        return pNode;
-    for(i = 0; i < pNode->mNumChildren; i++) {
-        aiNode *pChild = findNode(pNode->mChildren[i], name);
-        if(pChild) return pChild;
-    }
-    return nullptr;
+aiNode *M3DImporter::findNode(aiNode *pNode, aiString name) {
+	unsigned int i;
+
+	ai_assert(pNode != nullptr);
+	ai_assert(mScene != nullptr);
+
+	if (pNode->mName == name)
+		return pNode;
+	for (i = 0; i < pNode->mNumChildren; i++) {
+		aiNode *pChild = findNode(pNode->mChildren[i], name);
+		if (pChild) return pChild;
+	}
+	return nullptr;
 }
 
 // ------------------------------------------------------------------------------------------------
 // fills up offsetmatrix in mBones
-void M3DImporter::calculateOffsetMatrix(aiNode *pNode, aiMatrix4x4 *m)
-{
-    ai_assert(pNode != nullptr);
-    ai_assert(mScene != nullptr);
-
-    if(pNode->mParent) {
-        calculateOffsetMatrix(pNode->mParent, m);
-        *m *= pNode->mTransformation;
-    } else {
-        *m = pNode->mTransformation;
-    }
+void M3DImporter::calculateOffsetMatrix(aiNode *pNode, aiMatrix4x4 *m) {
+	ai_assert(pNode != nullptr);
+	ai_assert(mScene != nullptr);
+
+	if (pNode->mParent) {
+		calculateOffsetMatrix(pNode->mParent, m);
+		*m *= pNode->mTransformation;
+	} else {
+		*m = pNode->mTransformation;
+	}
 }
 
 // ------------------------------------------------------------------------------------------------
 // because M3D has a global mesh, global vertex ids and stores materialid on the face, we need
 // temporary lists to collect data for an aiMesh, which requires local arrays and local indeces
 // this function fills up an aiMesh with those temporary lists
-void M3DImporter::populateMesh(aiMesh *pMesh, std::vector<aiFace> *faces, std::vector<aiVector3D> *vertices,
-    std::vector<aiVector3D> *normals, std::vector<aiVector3D> *texcoords, std::vector<aiColor4D> *colors,
-    std::vector<unsigned int> *vertexids) {
-
-    ai_assert(pMesh != nullptr);
-    ai_assert(faces != nullptr);
-    ai_assert(vertices != nullptr);
-    ai_assert(normals != nullptr);
-    ai_assert(texcoords != nullptr);
-    ai_assert(colors != nullptr);
-    ai_assert(vertexids != nullptr);
-    ai_assert(m3d != nullptr);
-
-    ASSIMP_LOG_DEBUG_F("M3D: populateMesh numvertices ", vertices->size(), " numfaces ", faces->size(),
-        " numnormals ", normals->size(), " numtexcoord ", texcoords->size(), " numbones ", m3d->numbone);
-
-    if(vertices->size() && faces->size()) {
-        pMesh->mNumFaces = faces->size();
-        pMesh->mFaces = new aiFace[pMesh->mNumFaces];
-        std::copy(faces->begin(), faces->end(), pMesh->mFaces);
-        pMesh->mNumVertices = vertices->size();
-        pMesh->mVertices = new aiVector3D[pMesh->mNumVertices];
-        std::copy(vertices->begin(), vertices->end(), pMesh->mVertices);
-        if(normals->size() == vertices->size()) {
-            pMesh->mNormals = new aiVector3D[pMesh->mNumVertices];
-            std::copy(normals->begin(), normals->end(), pMesh->mNormals);
-        }
-        if(texcoords->size() == vertices->size()) {
-            pMesh->mTextureCoords[0] = new aiVector3D[pMesh->mNumVertices];
-            std::copy(texcoords->begin(), texcoords->end(), pMesh->mTextureCoords[0]);
-            pMesh->mNumUVComponents[0] = 2;
-        }
-        if(colors->size() == vertices->size()) {
-            pMesh->mColors[0] = new aiColor4D[pMesh->mNumVertices];
-            std::copy(colors->begin(), colors->end(), pMesh->mColors[0]);
-        }
-        // this is complicated, because M3D stores a list of bone id / weight pairs per
-        // vertex but assimp uses lists of local vertex id/weight pairs per local bone list
-        pMesh->mNumBones = m3d->numbone;
-        /* we need aiBone with mOffsetMatrix for bones without weights as well */
-        if(pMesh->mNumBones) {
-            pMesh->mBones = new aiBone*[pMesh->mNumBones];
-            for(unsigned int i = 0; i < m3d->numbone; i++) {
-                aiNode *pNode;
-                pMesh->mBones[i] = new aiBone;
-                pMesh->mBones[i]->mName = aiString(std::string(m3d->bone[i].name));
-                pMesh->mBones[i]->mNumWeights = 0;
-                pNode = findNode(mScene->mRootNode, pMesh->mBones[i]->mName);
-                if(pNode) {
-                    calculateOffsetMatrix(pNode, &pMesh->mBones[i]->mOffsetMatrix);
-                    pMesh->mBones[i]->mOffsetMatrix.Inverse();
-                } else
-                    pMesh->mBones[i]->mOffsetMatrix = aiMatrix4x4();
-            }
-            if(vertexids->size()) {
-                unsigned int i, j;
-                // first count how many vertices we have per bone
-                for(i = 0; i < vertexids->size(); i++) {
-                    unsigned int s = m3d->vertex[vertexids->at(i)].skinid;
-                    if(s != -1U && s!= -2U) {
-                        for(unsigned int k = 0; k < M3D_NUMBONE && m3d->skin[s].weight[k] > 0.0; k++) {
-                                aiString name = aiString(std::string(m3d->bone[m3d->skin[s].boneid[k]].name));
-                                for(j = 0; j < pMesh->mNumBones; j++) {
-                                    if(pMesh->mBones[j]->mName == name) {
-                                        pMesh->mBones[j]->mNumWeights++;
-                                        break;
-                                    }
-                                }
-                        }
-                    }
-                }
-                // allocate mWeights
-                for(j = 0; j < pMesh->mNumBones; j++) {
-                    aiBone *pBone = pMesh->mBones[j];
-                    if(pBone->mNumWeights) {
-                        pBone->mWeights = new aiVertexWeight[pBone->mNumWeights];
-                        pBone->mNumWeights = 0;
-                    }
-                }
-                // fill up with data
-                for(i = 0; i < vertexids->size(); i++) {
-                    unsigned int s = m3d->vertex[vertexids->at(i)].skinid;
-                    if(s != -1U && s!= -2U) {
-                        for(unsigned int k = 0; k < M3D_NUMBONE && m3d->skin[s].weight[k] > 0.0; k++) {
-                                aiString name = aiString(std::string(m3d->bone[m3d->skin[s].boneid[k]].name));
-                                for(j = 0; j < pMesh->mNumBones; j++) {
-                                    if(pMesh->mBones[j]->mName == name) {
-                                        aiBone *pBone = pMesh->mBones[j];
-                                        pBone->mWeights[pBone->mNumWeights].mVertexId = i;
-                                        pBone->mWeights[pBone->mNumWeights].mWeight = m3d->skin[s].weight[k];
-                                        pBone->mNumWeights++;
-                                        break;
-                                    }
-                                }
-                        } // foreach skin
-                    }
-                } // foreach vertexids
-            }
-        }
-    }
+void M3DImporter::populateMesh(const M3DWrapper &m3d, aiMesh *pMesh, std::vector<aiFace> *faces, std::vector<aiVector3D> *vertices,
+		std::vector<aiVector3D> *normals, std::vector<aiVector3D> *texcoords, std::vector<aiColor4D> *colors,
+		std::vector<unsigned int> *vertexids) {
+
+	ai_assert(pMesh != nullptr);
+	ai_assert(faces != nullptr);
+	ai_assert(vertices != nullptr);
+	ai_assert(normals != nullptr);
+	ai_assert(texcoords != nullptr);
+	ai_assert(colors != nullptr);
+	ai_assert(vertexids != nullptr);
+	ai_assert(m3d);
+
+	ASSIMP_LOG_DEBUG_F("M3D: populateMesh numvertices ", vertices->size(), " numfaces ", faces->size(),
+			" numnormals ", normals->size(), " numtexcoord ", texcoords->size(), " numbones ", m3d->numbone);
+
+	if (vertices->size() && faces->size()) {
+		pMesh->mNumFaces = static_cast<unsigned int>(faces->size());
+		pMesh->mFaces = new aiFace[pMesh->mNumFaces];
+		std::copy(faces->begin(), faces->end(), pMesh->mFaces);
+		pMesh->mNumVertices = static_cast<unsigned int>(vertices->size());
+		pMesh->mVertices = new aiVector3D[pMesh->mNumVertices];
+		std::copy(vertices->begin(), vertices->end(), pMesh->mVertices);
+		if (normals->size() == vertices->size()) {
+			pMesh->mNormals = new aiVector3D[pMesh->mNumVertices];
+			std::copy(normals->begin(), normals->end(), pMesh->mNormals);
+		}
+		if (texcoords->size() == vertices->size()) {
+			pMesh->mTextureCoords[0] = new aiVector3D[pMesh->mNumVertices];
+			std::copy(texcoords->begin(), texcoords->end(), pMesh->mTextureCoords[0]);
+			pMesh->mNumUVComponents[0] = 2;
+		}
+		if (colors->size() == vertices->size()) {
+			pMesh->mColors[0] = new aiColor4D[pMesh->mNumVertices];
+			std::copy(colors->begin(), colors->end(), pMesh->mColors[0]);
+		}
+		// this is complicated, because M3D stores a list of bone id / weight pairs per
+		// vertex but assimp uses lists of local vertex id/weight pairs per local bone list
+		pMesh->mNumBones = m3d->numbone;
+		/* we need aiBone with mOffsetMatrix for bones without weights as well */
+		if (pMesh->mNumBones) {
+			pMesh->mBones = new aiBone *[pMesh->mNumBones];
+			for (unsigned int i = 0; i < m3d->numbone; i++) {
+				aiNode *pNode;
+				pMesh->mBones[i] = new aiBone;
+				pMesh->mBones[i]->mName = aiString(std::string(m3d->bone[i].name));
+				pMesh->mBones[i]->mNumWeights = 0;
+				pNode = findNode(mScene->mRootNode, pMesh->mBones[i]->mName);
+				if (pNode) {
+					calculateOffsetMatrix(pNode, &pMesh->mBones[i]->mOffsetMatrix);
+					pMesh->mBones[i]->mOffsetMatrix.Inverse();
+				} else
+					pMesh->mBones[i]->mOffsetMatrix = aiMatrix4x4();
+			}
+			if (vertexids->size()) {
+				unsigned int i, j;
+				// first count how many vertices we have per bone
+				for (i = 0; i < vertexids->size(); i++) {
+					unsigned int s = m3d->vertex[vertexids->at(i)].skinid;
+					if (s != -1U && s != -2U) {
+						for (unsigned int k = 0; k < M3D_NUMBONE && m3d->skin[s].weight[k] > 0.0; k++) {
+							aiString name = aiString(std::string(m3d->bone[m3d->skin[s].boneid[k]].name));
+							for (j = 0; j < pMesh->mNumBones; j++) {
+								if (pMesh->mBones[j]->mName == name) {
+									pMesh->mBones[j]->mNumWeights++;
+									break;
+								}
+							}
+						}
+					}
+				}
+				// allocate mWeights
+				for (j = 0; j < pMesh->mNumBones; j++) {
+					aiBone *pBone = pMesh->mBones[j];
+					if (pBone->mNumWeights) {
+						pBone->mWeights = new aiVertexWeight[pBone->mNumWeights];
+						pBone->mNumWeights = 0;
+					}
+				}
+				// fill up with data
+				for (i = 0; i < vertexids->size(); i++) {
+					unsigned int s = m3d->vertex[vertexids->at(i)].skinid;
+					if (s != -1U && s != -2U) {
+						for (unsigned int k = 0; k < M3D_NUMBONE && m3d->skin[s].weight[k] > 0.0; k++) {
+							aiString name = aiString(std::string(m3d->bone[m3d->skin[s].boneid[k]].name));
+							for (j = 0; j < pMesh->mNumBones; j++) {
+								if (pMesh->mBones[j]->mName == name) {
+									aiBone *pBone = pMesh->mBones[j];
+									pBone->mWeights[pBone->mNumWeights].mVertexId = i;
+									pBone->mWeights[pBone->mNumWeights].mWeight = m3d->skin[s].weight[k];
+									pBone->mNumWeights++;
+									break;
+								}
+							}
+						} // foreach skin
+					}
+				} // foreach vertexids
+			}
+		}
+	}
 }
 
 // ------------------------------------------------------------------------------------------------
 
-}   // Namespace Assimp
+} // Namespace Assimp
 
 #endif // !! ASSIMP_BUILD_NO_M3D_IMPORTER

+ 29 - 32
code/M3D/M3DImporter.h

@@ -48,7 +48,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #ifndef ASSIMP_BUILD_NO_M3D_IMPORTER
 
-#include "m3d.h"
 #include <assimp/BaseImporter.h>
 #include <assimp/material.h>
 #include <vector>
@@ -60,43 +59,41 @@ struct aiFace;
 
 namespace Assimp {
 
+class M3DWrapper;
+
 class M3DImporter : public BaseImporter {
 public:
-    /// \brief  Default constructor
-    M3DImporter();
-
-    /// \brief  Destructor
-    ~M3DImporter();
+	/// \brief  Default constructor
+	M3DImporter();
 
 public:
-    /// \brief  Returns whether the class can handle the format of the given file.
-    /// \remark See BaseImporter::CanRead() for details.
-    bool CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const;
+	/// \brief  Returns whether the class can handle the format of the given file.
+	/// \remark See BaseImporter::CanRead() for details.
+	bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const;
 
 private:
-    aiScene* mScene; // the scene to import to
-    m3d_t *m3d; // model for the C library to convert from
-
-    //! \brief  Appends the supported extension.
-    const aiImporterDesc* GetInfo () const;
-
-    //! \brief  File import implementation.
-    void InternReadFile(const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler);
-
-    void importMaterials();
-    void importTextures();
-    void importMeshes();
-    void importBones(unsigned int parentid, aiNode *pParent);
-    void importAnimations();
-
-    // helper functions
-    aiColor4D mkColor(uint32_t c);
-    void convertPose(aiMatrix4x4 *m, unsigned int posid, unsigned int orientid);
-    aiNode *findNode(aiNode *pNode, aiString name);
-    void calculateOffsetMatrix(aiNode *pNode, aiMatrix4x4 *m);
-    void populateMesh(aiMesh *pMesh, std::vector<aiFace> *faces, std::vector<aiVector3D> *verteces,
-        std::vector<aiVector3D> *normals, std::vector<aiVector3D> *texcoords, std::vector<aiColor4D> *colors,
-        std::vector<unsigned int> *vertexids);
+	aiScene *mScene = nullptr; // the scene to import to
+
+	//! \brief  Appends the supported extension.
+	const aiImporterDesc *GetInfo() const;
+
+	//! \brief  File import implementation.
+	void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler);
+
+	void importMaterials(const M3DWrapper &m3d);
+	void importTextures(const M3DWrapper &m3d);
+	void importMeshes(const M3DWrapper &m3d);
+	void importBones(const M3DWrapper &m3d, unsigned int parentid, aiNode *pParent);
+	void importAnimations(const M3DWrapper &m3d);
+
+	// helper functions
+	aiColor4D mkColor(uint32_t c);
+	void convertPose(const M3DWrapper &m3d, aiMatrix4x4 *m, unsigned int posid, unsigned int orientid);
+	aiNode *findNode(aiNode *pNode, aiString name);
+	void calculateOffsetMatrix(aiNode *pNode, aiMatrix4x4 *m);
+	void populateMesh(const M3DWrapper &m3d, aiMesh *pMesh, std::vector<aiFace> *faces, std::vector<aiVector3D> *verteces,
+			std::vector<aiVector3D> *normals, std::vector<aiVector3D> *texcoords, std::vector<aiColor4D> *colors,
+			std::vector<unsigned int> *vertexids);
 };
 
 } // Namespace Assimp

+ 142 - 0
code/M3D/M3DWrapper.cpp

@@ -0,0 +1,142 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+Copyright (c) 2019 bzt
+
+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.
+
+----------------------------------------------------------------------
+*/
+
+#if !(ASSIMP_BUILD_NO_EXPORT || ASSIMP_BUILD_NO_M3D_EXPORTER) || !ASSIMP_BUILD_NO_M3D_IMPORTER
+
+#include "M3DWrapper.h"
+
+#include <assimp/DefaultIOSystem.h>
+#include <assimp/IOStreamBuffer.h>
+#include <assimp/ai_assert.h>
+
+#ifndef AI_M3D_USE_STDMUTEX
+#if (__cplusplus >= 201103L) || (_MSC_VER >= 1900) // C++11 and MSVC 2015 onwards
+#define AI_M3D_USE_STDMUTEX 1
+#else
+#define AI_M3D_USE_STDMUTEX 0
+#endif
+#endif
+
+#if AI_M3D_USE_STDMUTEX
+#include <mutex>
+std::mutex file_mutex;
+#endif
+
+// workaround: the M3D SDK expects a C callback, but we want to use Assimp::IOSystem to implement that
+// This makes it non-rentrant so lock a mutex (requires C++11)
+
+extern "C" {
+void *m3dimporter_pIOHandler;
+
+unsigned char *m3dimporter_readfile(char *fn, unsigned int *size) {
+	ai_assert(nullptr != fn);
+	ai_assert(nullptr != size);
+	std::string file(fn);
+	std::unique_ptr<Assimp::IOStream> pStream(
+			(reinterpret_cast<Assimp::IOSystem *>(m3dimporter_pIOHandler))->Open(file, "rb"));
+	size_t fileSize = 0;
+	unsigned char *data = NULL;
+	// sometimes pStream is nullptr for some reason (should be an empty object returning nothing I guess)
+	if (pStream) {
+		fileSize = pStream->FileSize();
+		// should be allocated with malloc(), because the library will call free() to deallocate
+		data = (unsigned char *)malloc(fileSize);
+		if (!data || !pStream.get() || !fileSize || fileSize != pStream->Read(data, 1, fileSize)) {
+			pStream.reset();
+			*size = 0;
+			// don't throw a deadly exception, it's not fatal if we can't read an external asset
+			return nullptr;
+		}
+		pStream.reset();
+	}
+	*size = (int)fileSize;
+	return data;
+}
+}
+
+namespace Assimp {
+M3DWrapper::M3DWrapper() {
+	// use malloc() here because m3d_free() will call free()
+	m3d_ = (m3d_t *)calloc(1, sizeof(m3d_t));
+}
+
+M3DWrapper::M3DWrapper(IOSystem *pIOHandler, const std::vector<unsigned char> &buffer) {
+#if AI_M3D_USE_STDMUTEX
+	// M3D is NOT thread-safe, so lock the global mutex
+	const std::lock_guard<std::mutex> lock(file_mutex);
+#endif
+	// pass this IOHandler to the C callback
+	m3dimporter_pIOHandler = pIOHandler;
+	m3d_ = m3d_load(const_cast<unsigned char *>(buffer.data()), m3dimporter_readfile, free, nullptr);
+	// Clear the C callback
+	m3dimporter_pIOHandler = nullptr;
+}
+
+M3DWrapper::~M3DWrapper() {
+	reset();
+}
+
+void M3DWrapper::reset() {
+	ClearSave();
+	if (m3d_)
+		m3d_free(m3d_);
+	m3d_ = nullptr;
+}
+
+unsigned char *M3DWrapper::Save(int quality, int flags, unsigned int &size) {
+#if (!(ASSIMP_BUILD_NO_EXPORT || ASSIMP_BUILD_NO_M3D_EXPORTER))
+	ClearSave();
+	saved_output_ = m3d_save(m3d_, quality, flags, &size);
+	return saved_output_;
+#else
+	return nullptr;
+#endif
+}
+
+void M3DWrapper::ClearSave() {
+	if (saved_output_)
+		M3D_FREE(saved_output_);
+	saved_output_ = nullptr;
+}
+} // namespace Assimp
+
+#endif

+ 97 - 0
code/M3D/M3DWrapper.h

@@ -0,0 +1,97 @@
+#pragma once
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+Copyright (c) 2019 bzt
+
+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 M3DWrapper.h
+*   @brief Declares a class to wrap the C m3d SDK
+*/
+#ifndef AI_M3DWRAPPER_H_INC
+#define AI_M3DWRAPPER_H_INC
+#if !(ASSIMP_BUILD_NO_EXPORT || ASSIMP_BUILD_NO_M3D_EXPORTER) || !ASSIMP_BUILD_NO_M3D_IMPORTER
+
+#include <memory>
+#include <vector>
+#include <string>
+
+#include "m3d.h"
+
+namespace Assimp {
+class IOSystem;
+
+class M3DWrapper {
+	m3d_t *m3d_ = nullptr;
+	unsigned char *saved_output_ = nullptr;
+
+public:
+	// Construct an empty M3D model
+	explicit M3DWrapper();
+
+	// Construct an M3D model from provided buffer
+	// NOTE: The m3d.h SDK function does not mark the data as const. Have assumed it does not write.
+	// BUG: SECURITY: The m3d.h SDK cannot be informed of the buffer size. BUFFER OVERFLOW IS CERTAIN
+	explicit M3DWrapper(IOSystem *pIOHandler, const std::vector<unsigned char> &buffer);
+
+	~M3DWrapper();
+
+	void reset();
+
+	// Name
+	inline std::string Name() const {
+		if (m3d_) return std::string(m3d_->name);
+		return std::string();
+	}
+
+	// Execute a save
+	unsigned char *Save(int quality, int flags, unsigned int &size);
+	void ClearSave();
+
+	inline explicit operator bool() const { return m3d_ != nullptr; }
+
+	// Allow direct access to M3D API
+	inline m3d_t *operator->() const { return m3d_; }
+	inline m3d_t *M3D() const { return m3d_; }
+};
+} // namespace Assimp
+
+#endif
+
+#endif // AI_M3DWRAPPER_H_INC

+ 3 - 2
code/PostProcessing/ConvertToLHProcess.h

@@ -137,8 +137,9 @@ public:
     // -------------------------------------------------------------------
     void Execute( aiScene* pScene);
 
-protected:
-    void ProcessMesh( aiMesh* pMesh);
+public:
+    /** Some other types of post-processing require winding order flips */
+    static void ProcessMesh( aiMesh* pMesh);
 };
 
 // ---------------------------------------------------------------------------

+ 260 - 253
code/PostProcessing/OptimizeGraph.cpp

@@ -43,13 +43,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *  @brief Implementation of the aiProcess_OptimizGraph step
  */
 
-
 #ifndef ASSIMP_BUILD_NO_OPTIMIZEGRAPH_PROCESS
 
 #include "OptimizeGraph.h"
 #include "ProcessHelper.h"
-#include <assimp/SceneCombiner.h>
+#include "ConvertToLHProcess.h"
 #include <assimp/Exceptional.h>
+#include <assimp/SceneCombiner.h>
 #include <stdio.h>
 
 using namespace Assimp;
@@ -60,292 +60,299 @@ using namespace Assimp;
  * The unhashed variant should be faster, except for *very* large data sets
  */
 #ifdef AI_OG_USE_HASHING
-    // Use our standard hashing function to compute the hash
-#   define AI_OG_GETKEY(str) SuperFastHash(str.data,str.length)
+// Use our standard hashing function to compute the hash
+#define AI_OG_GETKEY(str) SuperFastHash(str.data, str.length)
 #else
-    // Otherwise hope that std::string will utilize a static buffer
-    // for shorter node names. This would avoid endless heap copying.
-#   define AI_OG_GETKEY(str) std::string(str.data)
+// Otherwise hope that std::string will utilize a static buffer
+// for shorter node names. This would avoid endless heap copying.
+#define AI_OG_GETKEY(str) std::string(str.data)
 #endif
 
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
-OptimizeGraphProcess::OptimizeGraphProcess()
-: mScene()
-, nodes_in()
-, nodes_out()
-, count_merged() {
-    // empty
+OptimizeGraphProcess::OptimizeGraphProcess() :
+		mScene(),
+		nodes_in(),
+		nodes_out(),
+		count_merged() {
+	// empty
 }
 
 // ------------------------------------------------------------------------------------------------
 // Destructor, private as well
 OptimizeGraphProcess::~OptimizeGraphProcess() {
-    // empty
+	// empty
 }
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the processing step is present in the given flag field.
-bool OptimizeGraphProcess::IsActive( unsigned int pFlags) const {
-    return (0 != (pFlags & aiProcess_OptimizeGraph));
+bool OptimizeGraphProcess::IsActive(unsigned int pFlags) const {
+	return (0 != (pFlags & aiProcess_OptimizeGraph));
 }
 
 // ------------------------------------------------------------------------------------------------
 // Setup properties for the post-processing step
-void OptimizeGraphProcess::SetupProperties(const Importer* pImp) {
-    // Get value of AI_CONFIG_PP_OG_EXCLUDE_LIST
-    std::string tmp = pImp->GetPropertyString(AI_CONFIG_PP_OG_EXCLUDE_LIST,"");
-    AddLockedNodeList(tmp);
+void OptimizeGraphProcess::SetupProperties(const Importer *pImp) {
+	// Get value of AI_CONFIG_PP_OG_EXCLUDE_LIST
+	std::string tmp = pImp->GetPropertyString(AI_CONFIG_PP_OG_EXCLUDE_LIST, "");
+	AddLockedNodeList(tmp);
 }
 
 // ------------------------------------------------------------------------------------------------
 // Collect new children
-void OptimizeGraphProcess::CollectNewChildren(aiNode* nd, std::list<aiNode*>& nodes) {
-    nodes_in += nd->mNumChildren;
-
-    // Process children
-    std::list<aiNode*> child_nodes;
-    for (unsigned int i = 0; i < nd->mNumChildren; ++i) {
-        CollectNewChildren(nd->mChildren[i],child_nodes);
-        nd->mChildren[i] = nullptr;
-    }
-
-    // Check whether we need this node; if not we can replace it by our own children (warn, danger of incest).
-    if (locked.find(AI_OG_GETKEY(nd->mName)) == locked.end() ) {
-        for (std::list<aiNode*>::iterator it = child_nodes.begin(); it != child_nodes.end();) {
-
-            if (locked.find(AI_OG_GETKEY((*it)->mName)) == locked.end()) {
-                (*it)->mTransformation = nd->mTransformation * (*it)->mTransformation;
-                nodes.push_back(*it);
-
-                it = child_nodes.erase(it);
-                continue;
-            }
-            ++it;
-        }
-
-        if (nd->mNumMeshes || !child_nodes.empty()) {
-            nodes.push_back(nd);
-        } else {
-            delete nd; /* bye, node */
-            return;
-        }
-    } else {
-
-        // Retain our current position in the hierarchy
-        nodes.push_back(nd);
-
-        // Now check for possible optimizations in our list of child nodes. join as many as possible
-        aiNode* join_master = NULL;
-        aiMatrix4x4 inv;
-
-        const LockedSetType::const_iterator end = locked.end();
-
-        std::list<aiNode*> join;
-        for (std::list<aiNode*>::iterator it = child_nodes.begin(); it != child_nodes.end();)   {
-            aiNode* child = *it;
-            if (child->mNumChildren == 0 && locked.find(AI_OG_GETKEY(child->mName)) == end) {
-
-                // There may be no instanced meshes
-                unsigned int n = 0;
-                for (; n < child->mNumMeshes;++n) {
-                    if (meshes[child->mMeshes[n]] > 1) {
-                        break;
-                    }
-                }
-                if (n == child->mNumMeshes) {
-                    if (!join_master) {
-                        join_master = child;
-                        inv = join_master->mTransformation;
-                        inv.Inverse();
-                    } else {
-                        child->mTransformation = inv * child->mTransformation ;
-
-                        join.push_back(child);
-                        it = child_nodes.erase(it);
-                        continue;
-                    }
-                }
-            }
-            ++it;
-        }
-        if (join_master && !join.empty()) {
-            join_master->mName.length = ::ai_snprintf(join_master->mName.data, MAXLEN, "$MergedNode_%i",count_merged++);
-
-            unsigned int out_meshes = 0;
-            for (std::list<aiNode*>::iterator it = join.begin(); it != join.end(); ++it) {
-                out_meshes += (*it)->mNumMeshes;
-            }
-
-            // copy all mesh references in one array
-            if (out_meshes) {
-                unsigned int* meshes = new unsigned int[out_meshes+join_master->mNumMeshes], *tmp = meshes;
-                for (unsigned int n = 0; n < join_master->mNumMeshes;++n) {
-                    *tmp++ = join_master->mMeshes[n];
-                }
-
-                for (std::list<aiNode*>::iterator it = join.begin(); it != join.end(); ++it) {
-                    for (unsigned int n = 0; n < (*it)->mNumMeshes; ++n) {
-
-                        *tmp = (*it)->mMeshes[n];
-                        aiMesh* mesh = mScene->mMeshes[*tmp++];
-
-                        // manually move the mesh into the right coordinate system
-                        const aiMatrix3x3 IT = aiMatrix3x3( (*it)->mTransformation ).Inverse().Transpose();
-                        for (unsigned int a = 0; a < mesh->mNumVertices; ++a) {
-
-                            mesh->mVertices[a] *= (*it)->mTransformation;
-
-                            if (mesh->HasNormals())
-                                mesh->mNormals[a] *= IT;
-
-                            if (mesh->HasTangentsAndBitangents()) {
-                                mesh->mTangents[a] *= IT;
-                                mesh->mBitangents[a] *= IT;
-                            }
-                        }
-                    }
-                    delete *it; // bye, node
-                }
-                delete[] join_master->mMeshes;
-                join_master->mMeshes = meshes;
-                join_master->mNumMeshes += out_meshes;
-            }
-        }
-    }
-    // reassign children if something changed
-    if (child_nodes.empty() || child_nodes.size() > nd->mNumChildren) {
-
-        delete[] nd->mChildren;
-
-        if (!child_nodes.empty()) {
-            nd->mChildren = new aiNode*[child_nodes.size()];
-        }
-        else nd->mChildren = nullptr;
-    }
-
-    nd->mNumChildren = static_cast<unsigned int>(child_nodes.size());
-
-    if (nd->mChildren) {
-        aiNode** tmp = nd->mChildren;
-        for (std::list<aiNode*>::iterator it = child_nodes.begin(); it != child_nodes.end(); ++it) {
-            aiNode* node = *tmp++ = *it;
-            node->mParent = nd;
-        }
-    }
-
-    nodes_out += static_cast<unsigned int>(child_nodes.size());
+void OptimizeGraphProcess::CollectNewChildren(aiNode *nd, std::list<aiNode *> &nodes) {
+	nodes_in += nd->mNumChildren;
+
+	// Process children
+	std::list<aiNode *> child_nodes;
+	for (unsigned int i = 0; i < nd->mNumChildren; ++i) {
+		CollectNewChildren(nd->mChildren[i], child_nodes);
+		nd->mChildren[i] = nullptr;
+	}
+
+	// Check whether we need this node; if not we can replace it by our own children (warn, danger of incest).
+	if (locked.find(AI_OG_GETKEY(nd->mName)) == locked.end()) {
+		for (std::list<aiNode *>::iterator it = child_nodes.begin(); it != child_nodes.end();) {
+
+			if (locked.find(AI_OG_GETKEY((*it)->mName)) == locked.end()) {
+				(*it)->mTransformation = nd->mTransformation * (*it)->mTransformation;
+				nodes.push_back(*it);
+
+				it = child_nodes.erase(it);
+				continue;
+			}
+			++it;
+		}
+
+		if (nd->mNumMeshes || !child_nodes.empty()) {
+			nodes.push_back(nd);
+		} else {
+			delete nd; /* bye, node */
+			return;
+		}
+	} else {
+
+		// Retain our current position in the hierarchy
+		nodes.push_back(nd);
+
+		// Now check for possible optimizations in our list of child nodes. join as many as possible
+		aiNode *join_master = nullptr;
+		aiMatrix4x4 inv;
+
+		const LockedSetType::const_iterator end = locked.end();
+
+		std::list<aiNode *> join;
+		for (std::list<aiNode *>::iterator it = child_nodes.begin(); it != child_nodes.end();) {
+			aiNode *child = *it;
+			if (child->mNumChildren == 0 && locked.find(AI_OG_GETKEY(child->mName)) == end) {
+
+				// There may be no instanced meshes
+				unsigned int n = 0;
+				for (; n < child->mNumMeshes; ++n) {
+					if (meshes[child->mMeshes[n]] > 1) {
+						break;
+					}
+				}
+				if (n == child->mNumMeshes) {
+					if (!join_master) {
+						join_master = child;
+						inv = join_master->mTransformation;
+						inv.Inverse();
+					} else {
+						child->mTransformation = inv * child->mTransformation;
+
+						join.push_back(child);
+						it = child_nodes.erase(it);
+						continue;
+					}
+				}
+			}
+			++it;
+		}
+		if (join_master && !join.empty()) {
+			join_master->mName.length = ::ai_snprintf(join_master->mName.data, MAXLEN, "$MergedNode_%i", count_merged++);
+
+			unsigned int out_meshes = 0;
+			for (std::list<aiNode *>::const_iterator it = join.cbegin(); it != join.cend(); ++it) {
+				out_meshes += (*it)->mNumMeshes;
+			}
+
+			// copy all mesh references in one array
+			if (out_meshes) {
+				unsigned int *meshes = new unsigned int[out_meshes + join_master->mNumMeshes], *tmp = meshes;
+				for (unsigned int n = 0; n < join_master->mNumMeshes; ++n) {
+					*tmp++ = join_master->mMeshes[n];
+				}
+
+				for (const aiNode *join_node : join) {
+					for (unsigned int n = 0; n < join_node->mNumMeshes; ++n) {
+
+						*tmp = join_node->mMeshes[n];
+						aiMesh *mesh = mScene->mMeshes[*tmp++];
+
+						// Assume the transformation is affine
+						// manually move the mesh into the right coordinate system
+
+						// Check for odd negative scale (mirror)
+						if (join_node->mTransformation.Determinant() < 0) {
+							// Reverse the mesh face winding order
+                            FlipWindingOrderProcess::ProcessMesh(mesh);
+						}
+
+                        // Update positions, normals and tangents
+						const aiMatrix3x3 IT = aiMatrix3x3(join_node->mTransformation).Inverse().Transpose();
+						for (unsigned int a = 0; a < mesh->mNumVertices; ++a) {
+
+							mesh->mVertices[a] *= join_node->mTransformation;
+
+							if (mesh->HasNormals())
+								mesh->mNormals[a] *= IT;
+
+							if (mesh->HasTangentsAndBitangents()) {
+								mesh->mTangents[a] *= IT;
+								mesh->mBitangents[a] *= IT;
+							}
+						}
+					}
+					delete join_node; // bye, node
+				}
+				delete[] join_master->mMeshes;
+				join_master->mMeshes = meshes;
+				join_master->mNumMeshes += out_meshes;
+			}
+		}
+	}
+	// reassign children if something changed
+	if (child_nodes.empty() || child_nodes.size() > nd->mNumChildren) {
+
+		delete[] nd->mChildren;
+
+		if (!child_nodes.empty()) {
+			nd->mChildren = new aiNode *[child_nodes.size()];
+		} else
+			nd->mChildren = nullptr;
+	}
+
+	nd->mNumChildren = static_cast<unsigned int>(child_nodes.size());
+
+	if (nd->mChildren) {
+		aiNode **tmp = nd->mChildren;
+		for (std::list<aiNode *>::iterator it = child_nodes.begin(); it != child_nodes.end(); ++it) {
+			aiNode *node = *tmp++ = *it;
+			node->mParent = nd;
+		}
+	}
+
+	nodes_out += static_cast<unsigned int>(child_nodes.size());
 }
 
 // ------------------------------------------------------------------------------------------------
 // Execute the post-processing step on the given scene
-void OptimizeGraphProcess::Execute( aiScene* pScene) {
-    ASSIMP_LOG_DEBUG("OptimizeGraphProcess begin");
-    nodes_in = nodes_out = count_merged = 0;
-    mScene = pScene;
+void OptimizeGraphProcess::Execute(aiScene *pScene) {
+	ASSIMP_LOG_DEBUG("OptimizeGraphProcess begin");
+	nodes_in = nodes_out = count_merged = 0;
+	mScene = pScene;
 
-    meshes.resize(pScene->mNumMeshes,0);
-    FindInstancedMeshes(pScene->mRootNode);
+	meshes.resize(pScene->mNumMeshes, 0);
+	FindInstancedMeshes(pScene->mRootNode);
 
-    // build a blacklist of identifiers. If the name of a node matches one of these, we won't touch it
-    locked.clear();
-    for (std::list<std::string>::const_iterator it = locked_nodes.begin(); it != locked_nodes.end(); ++it) {
+	// build a blacklist of identifiers. If the name of a node matches one of these, we won't touch it
+	locked.clear();
+	for (std::list<std::string>::const_iterator it = locked_nodes.begin(); it != locked_nodes.end(); ++it) {
 #ifdef AI_OG_USE_HASHING
-        locked.insert(SuperFastHash((*it).c_str()));
+		locked.insert(SuperFastHash((*it).c_str()));
 #else
-        locked.insert(*it);
+		locked.insert(*it);
 #endif
-    }
-
-    for (unsigned int i = 0; i < pScene->mNumAnimations; ++i) {
-        for (unsigned int a = 0; a < pScene->mAnimations[i]->mNumChannels; ++a) {
-            aiNodeAnim* anim = pScene->mAnimations[i]->mChannels[a];
-            locked.insert(AI_OG_GETKEY(anim->mNodeName));
-        }
-    }
-
-    for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) {
-        for (unsigned int a = 0; a < pScene->mMeshes[i]->mNumBones; ++a) {
-
-            aiBone* bone = pScene->mMeshes[i]->mBones[a];
-            locked.insert(AI_OG_GETKEY(bone->mName));
-
-            // HACK: Meshes referencing bones may not be transformed; we need to look them.
-            // The easiest way to do this is to increase their reference counters ...
-            meshes[i] += 2;
-        }
-    }
-
-    for (unsigned int i = 0; i < pScene->mNumCameras; ++i) {
-        aiCamera* cam = pScene->mCameras[i];
-        locked.insert(AI_OG_GETKEY(cam->mName));
-    }
-
-    for (unsigned int i = 0; i < pScene->mNumLights; ++i) {
-        aiLight* lgh = pScene->mLights[i];
-        locked.insert(AI_OG_GETKEY(lgh->mName));
-    }
-
-    // Insert a dummy master node and make it read-only
-    aiNode* dummy_root = new aiNode(AI_RESERVED_NODE_NAME);
-    locked.insert(AI_OG_GETKEY(dummy_root->mName));
-
-    const aiString prev = pScene->mRootNode->mName;
-    pScene->mRootNode->mParent = dummy_root;
-
-    dummy_root->mChildren = new aiNode*[dummy_root->mNumChildren = 1];
-    dummy_root->mChildren[0] = pScene->mRootNode;
-
-    // Do our recursive processing of scenegraph nodes. For each node collect
-    // a fully new list of children and allow their children to place themselves
-    // on the same hierarchy layer as their parents.
-    std::list<aiNode*> nodes;
-    CollectNewChildren (dummy_root,nodes);
-
-    ai_assert(nodes.size() == 1);
-
-    if (dummy_root->mNumChildren == 0) {
-        pScene->mRootNode = NULL;
-        throw DeadlyImportError("After optimizing the scene graph, no data remains");
-    }
-
-    if (dummy_root->mNumChildren > 1) {
-        pScene->mRootNode = dummy_root;
-
-        // Keep the dummy node but assign the name of the old root node to it
-        pScene->mRootNode->mName = prev;
-    }
-    else {
-
-        // Remove the dummy root node again.
-        pScene->mRootNode = dummy_root->mChildren[0];
-
-        dummy_root->mChildren[0] = NULL;
-        delete dummy_root;
-    }
-
-    pScene->mRootNode->mParent = NULL;
-    if (!DefaultLogger::isNullLogger()) {
-        if ( nodes_in != nodes_out) {
-            ASSIMP_LOG_INFO_F("OptimizeGraphProcess finished; Input nodes: ", nodes_in, ", Output nodes: ", nodes_out);
-        } else {
-            ASSIMP_LOG_DEBUG("OptimizeGraphProcess finished");
-        }
-    }
-    meshes.clear();
-    locked.clear();
+	}
+
+	for (unsigned int i = 0; i < pScene->mNumAnimations; ++i) {
+		for (unsigned int a = 0; a < pScene->mAnimations[i]->mNumChannels; ++a) {
+			aiNodeAnim *anim = pScene->mAnimations[i]->mChannels[a];
+			locked.insert(AI_OG_GETKEY(anim->mNodeName));
+		}
+	}
+
+	for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) {
+		for (unsigned int a = 0; a < pScene->mMeshes[i]->mNumBones; ++a) {
+
+			aiBone *bone = pScene->mMeshes[i]->mBones[a];
+			locked.insert(AI_OG_GETKEY(bone->mName));
+
+			// HACK: Meshes referencing bones may not be transformed; we need to look them.
+			// The easiest way to do this is to increase their reference counters ...
+			meshes[i] += 2;
+		}
+	}
+
+	for (unsigned int i = 0; i < pScene->mNumCameras; ++i) {
+		aiCamera *cam = pScene->mCameras[i];
+		locked.insert(AI_OG_GETKEY(cam->mName));
+	}
+
+	for (unsigned int i = 0; i < pScene->mNumLights; ++i) {
+		aiLight *lgh = pScene->mLights[i];
+		locked.insert(AI_OG_GETKEY(lgh->mName));
+	}
+
+	// Insert a dummy master node and make it read-only
+	aiNode *dummy_root = new aiNode(AI_RESERVED_NODE_NAME);
+	locked.insert(AI_OG_GETKEY(dummy_root->mName));
+
+	const aiString prev = pScene->mRootNode->mName;
+	pScene->mRootNode->mParent = dummy_root;
+
+	dummy_root->mChildren = new aiNode *[dummy_root->mNumChildren = 1];
+	dummy_root->mChildren[0] = pScene->mRootNode;
+
+	// Do our recursive processing of scenegraph nodes. For each node collect
+	// a fully new list of children and allow their children to place themselves
+	// on the same hierarchy layer as their parents.
+	std::list<aiNode *> nodes;
+	CollectNewChildren(dummy_root, nodes);
+
+	ai_assert(nodes.size() == 1);
+
+	if (dummy_root->mNumChildren == 0) {
+		pScene->mRootNode = nullptr;
+		throw DeadlyImportError("After optimizing the scene graph, no data remains");
+	}
+
+	if (dummy_root->mNumChildren > 1) {
+		pScene->mRootNode = dummy_root;
+
+		// Keep the dummy node but assign the name of the old root node to it
+		pScene->mRootNode->mName = prev;
+	} else {
+
+		// Remove the dummy root node again.
+		pScene->mRootNode = dummy_root->mChildren[0];
+
+		dummy_root->mChildren[0] = nullptr;
+		delete dummy_root;
+	}
+
+	pScene->mRootNode->mParent = nullptr;
+	if (!DefaultLogger::isNullLogger()) {
+		if (nodes_in != nodes_out) {
+			ASSIMP_LOG_INFO_F("OptimizeGraphProcess finished; Input nodes: ", nodes_in, ", Output nodes: ", nodes_out);
+		} else {
+			ASSIMP_LOG_DEBUG("OptimizeGraphProcess finished");
+		}
+	}
+	meshes.clear();
+	locked.clear();
 }
 
 // ------------------------------------------------------------------------------------------------
 // Build a LUT of all instanced meshes
-void OptimizeGraphProcess::FindInstancedMeshes (aiNode* pNode)
-{
-    for (unsigned int i = 0; i < pNode->mNumMeshes;++i) {
-        ++meshes[pNode->mMeshes[i]];
-    }
-
-    for (unsigned int i = 0; i < pNode->mNumChildren; ++i)
-        FindInstancedMeshes(pNode->mChildren[i]);
+void OptimizeGraphProcess::FindInstancedMeshes(aiNode *pNode) {
+	for (unsigned int i = 0; i < pNode->mNumMeshes; ++i) {
+		++meshes[pNode->mMeshes[i]];
+	}
+
+	for (unsigned int i = 0; i < pNode->mNumChildren; ++i)
+		FindInstancedMeshes(pNode->mChildren[i]);
 }
 
 #endif // !! ASSIMP_BUILD_NO_OPTIMIZEGRAPH_PROCESS

+ 3 - 3
code/PostProcessing/OptimizeGraph.h

@@ -75,13 +75,13 @@ public:
     ~OptimizeGraphProcess();
 
     // -------------------------------------------------------------------
-    bool IsActive( unsigned int pFlags) const;
+    bool IsActive( unsigned int pFlags) const override;
 
     // -------------------------------------------------------------------
-    void Execute( aiScene* pScene);
+    void Execute( aiScene* pScene) override;
 
     // -------------------------------------------------------------------
-    void SetupProperties(const Importer* pImp);
+    void SetupProperties(const Importer* pImp) override;
 
     // -------------------------------------------------------------------
     /** @brief Add a list of node names to be locked and not modified.

+ 566 - 606
code/PostProcessing/PretransformVertices.cpp

@@ -45,11 +45,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *  @brief Implementation of the "PretransformVertices" post processing step
 */
 
-
 #include "PretransformVertices.h"
+#include "ConvertToLHProcess.h"
 #include "ProcessHelper.h"
-#include <assimp/SceneCombiner.h>
 #include <assimp/Exceptional.h>
+#include <assimp/SceneCombiner.h>
 
 using namespace Assimp;
 
@@ -59,670 +59,630 @@ using namespace Assimp;
 
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
-PretransformVertices::PretransformVertices()
-: configKeepHierarchy (false)
-, configNormalize(false)
-, configTransform(false)
-, configTransformation()
-, mConfigPointCloud( false ) {
-    // empty
+PretransformVertices::PretransformVertices() :
+		configKeepHierarchy(false),
+		configNormalize(false),
+		configTransform(false),
+		configTransformation(),
+		mConfigPointCloud(false) {
+	// empty
 }
 
 // ------------------------------------------------------------------------------------------------
 // Destructor, private as well
 PretransformVertices::~PretransformVertices() {
-    // nothing to do here
+	// nothing to do here
 }
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the processing step is present in the given flag field.
-bool PretransformVertices::IsActive( unsigned int pFlags) const
-{
-    return  (pFlags & aiProcess_PreTransformVertices) != 0;
+bool PretransformVertices::IsActive(unsigned int pFlags) const {
+	return (pFlags & aiProcess_PreTransformVertices) != 0;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Setup import configuration
-void PretransformVertices::SetupProperties(const Importer* pImp)
-{
-    // Get the current value of AI_CONFIG_PP_PTV_KEEP_HIERARCHY, AI_CONFIG_PP_PTV_NORMALIZE,
-    // AI_CONFIG_PP_PTV_ADD_ROOT_TRANSFORMATION and AI_CONFIG_PP_PTV_ROOT_TRANSFORMATION
-    configKeepHierarchy = (0 != pImp->GetPropertyInteger(AI_CONFIG_PP_PTV_KEEP_HIERARCHY,0));
-    configNormalize = (0 != pImp->GetPropertyInteger(AI_CONFIG_PP_PTV_NORMALIZE,0));
-    configTransform = (0 != pImp->GetPropertyInteger(AI_CONFIG_PP_PTV_ADD_ROOT_TRANSFORMATION,0));
+void PretransformVertices::SetupProperties(const Importer *pImp) {
+	// Get the current value of AI_CONFIG_PP_PTV_KEEP_HIERARCHY, AI_CONFIG_PP_PTV_NORMALIZE,
+	// AI_CONFIG_PP_PTV_ADD_ROOT_TRANSFORMATION and AI_CONFIG_PP_PTV_ROOT_TRANSFORMATION
+	configKeepHierarchy = (0 != pImp->GetPropertyInteger(AI_CONFIG_PP_PTV_KEEP_HIERARCHY, 0));
+	configNormalize = (0 != pImp->GetPropertyInteger(AI_CONFIG_PP_PTV_NORMALIZE, 0));
+	configTransform = (0 != pImp->GetPropertyInteger(AI_CONFIG_PP_PTV_ADD_ROOT_TRANSFORMATION, 0));
 
-    configTransformation = pImp->GetPropertyMatrix(AI_CONFIG_PP_PTV_ROOT_TRANSFORMATION, aiMatrix4x4());
+	configTransformation = pImp->GetPropertyMatrix(AI_CONFIG_PP_PTV_ROOT_TRANSFORMATION, aiMatrix4x4());
 
-    mConfigPointCloud = pImp->GetPropertyBool(AI_CONFIG_EXPORT_POINT_CLOUDS);
+	mConfigPointCloud = pImp->GetPropertyBool(AI_CONFIG_EXPORT_POINT_CLOUDS);
 }
 
 // ------------------------------------------------------------------------------------------------
 // Count the number of nodes
-unsigned int PretransformVertices::CountNodes( aiNode* pcNode )
-{
-    unsigned int iRet = 1;
-    for (unsigned int i = 0;i < pcNode->mNumChildren;++i)
-    {
-        iRet += CountNodes(pcNode->mChildren[i]);
-    }
-    return iRet;
+unsigned int PretransformVertices::CountNodes(const aiNode *pcNode) const {
+	unsigned int iRet = 1;
+	for (unsigned int i = 0; i < pcNode->mNumChildren; ++i) {
+		iRet += CountNodes(pcNode->mChildren[i]);
+	}
+	return iRet;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Get a bitwise combination identifying the vertex format of a mesh
-unsigned int PretransformVertices::GetMeshVFormat( aiMesh* pcMesh )
-{
-    // the vertex format is stored in aiMesh::mBones for later retrieval.
-    // there isn't a good reason to compute it a few hundred times
-    // from scratch. The pointer is unused as animations are lost
-    // during PretransformVertices.
-    if (pcMesh->mBones)
-        return (unsigned int)(uint64_t)pcMesh->mBones;
-
-
-    const unsigned int iRet = GetMeshVFormatUnique(pcMesh);
-
-    // store the value for later use
-    pcMesh->mBones = (aiBone**)(uint64_t)iRet;
-    return iRet;
+unsigned int PretransformVertices::GetMeshVFormat(aiMesh *pcMesh) const {
+	// the vertex format is stored in aiMesh::mBones for later retrieval.
+	// there isn't a good reason to compute it a few hundred times
+	// from scratch. The pointer is unused as animations are lost
+	// during PretransformVertices.
+	if (pcMesh->mBones)
+		return (unsigned int)(uint64_t)pcMesh->mBones;
+
+	const unsigned int iRet = GetMeshVFormatUnique(pcMesh);
+
+	// store the value for later use
+	pcMesh->mBones = (aiBone **)(uint64_t)iRet;
+	return iRet;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Count the number of vertices in the whole scene and a given
 // material index
-void PretransformVertices::CountVerticesAndFaces( aiScene* pcScene, aiNode* pcNode, unsigned int iMat,
-    unsigned int iVFormat, unsigned int* piFaces, unsigned int* piVertices)
-{
-    for (unsigned int i = 0; i < pcNode->mNumMeshes;++i)
-    {
-        aiMesh* pcMesh = pcScene->mMeshes[ pcNode->mMeshes[i] ];
-        if (iMat == pcMesh->mMaterialIndex && iVFormat == GetMeshVFormat(pcMesh))
-        {
-            *piVertices += pcMesh->mNumVertices;
-            *piFaces += pcMesh->mNumFaces;
-        }
-    }
-    for (unsigned int i = 0;i < pcNode->mNumChildren;++i)
-    {
-        CountVerticesAndFaces(pcScene,pcNode->mChildren[i],iMat,
-            iVFormat,piFaces,piVertices);
-    }
+void PretransformVertices::CountVerticesAndFaces(const aiScene *pcScene, const aiNode *pcNode, unsigned int iMat,
+		unsigned int iVFormat, unsigned int *piFaces, unsigned int *piVertices) const {
+	for (unsigned int i = 0; i < pcNode->mNumMeshes; ++i) {
+		aiMesh *pcMesh = pcScene->mMeshes[pcNode->mMeshes[i]];
+		if (iMat == pcMesh->mMaterialIndex && iVFormat == GetMeshVFormat(pcMesh)) {
+			*piVertices += pcMesh->mNumVertices;
+			*piFaces += pcMesh->mNumFaces;
+		}
+	}
+	for (unsigned int i = 0; i < pcNode->mNumChildren; ++i) {
+		CountVerticesAndFaces(pcScene, pcNode->mChildren[i], iMat,
+				iVFormat, piFaces, piVertices);
+	}
 }
 
 // ------------------------------------------------------------------------------------------------
 // Collect vertex/face data
-void PretransformVertices::CollectData( aiScene* pcScene, aiNode* pcNode, unsigned int iMat,
-    unsigned int iVFormat, aiMesh* pcMeshOut,
-    unsigned int aiCurrent[2], unsigned int* num_refs)
-{
-    // No need to multiply if there's no transformation
-    const bool identity = pcNode->mTransformation.IsIdentity();
-    for (unsigned int i = 0; i < pcNode->mNumMeshes;++i)
-    {
-        aiMesh* pcMesh = pcScene->mMeshes[ pcNode->mMeshes[i] ];
-        if (iMat == pcMesh->mMaterialIndex && iVFormat == GetMeshVFormat(pcMesh))
-        {
-            // Decrement mesh reference counter
-            unsigned int& num_ref = num_refs[pcNode->mMeshes[i]];
-            ai_assert(0 != num_ref);
-            --num_ref;
-            // Save the name of the last mesh
-            if (num_ref==0)
-            {
-                pcMeshOut->mName = pcMesh->mName;
-            }
-
-            if (identity)   {
-                // copy positions without modifying them
-                ::memcpy(pcMeshOut->mVertices + aiCurrent[AI_PTVS_VERTEX],
-                    pcMesh->mVertices,
-                    pcMesh->mNumVertices * sizeof(aiVector3D));
-
-                if (iVFormat & 0x2) {
-                    // copy normals without modifying them
-                    ::memcpy(pcMeshOut->mNormals + aiCurrent[AI_PTVS_VERTEX],
-                        pcMesh->mNormals,
-                        pcMesh->mNumVertices * sizeof(aiVector3D));
-                }
-                if (iVFormat & 0x4)
-                {
-                    // copy tangents without modifying them
-                    ::memcpy(pcMeshOut->mTangents + aiCurrent[AI_PTVS_VERTEX],
-                        pcMesh->mTangents,
-                        pcMesh->mNumVertices * sizeof(aiVector3D));
-                    // copy bitangents without modifying them
-                    ::memcpy(pcMeshOut->mBitangents + aiCurrent[AI_PTVS_VERTEX],
-                        pcMesh->mBitangents,
-                        pcMesh->mNumVertices * sizeof(aiVector3D));
-                }
-            }
-            else
-            {
-                // copy positions, transform them to worldspace
-                for (unsigned int n = 0; n < pcMesh->mNumVertices;++n)  {
-                    pcMeshOut->mVertices[aiCurrent[AI_PTVS_VERTEX]+n] = pcNode->mTransformation * pcMesh->mVertices[n];
-                }
-                aiMatrix4x4 mWorldIT = pcNode->mTransformation;
-                mWorldIT.Inverse().Transpose();
-
-                // TODO: implement Inverse() for aiMatrix3x3
-                aiMatrix3x3 m = aiMatrix3x3(mWorldIT);
-
-                if (iVFormat & 0x2)
-                {
-                    // copy normals, transform them to worldspace
-                    for (unsigned int n = 0; n < pcMesh->mNumVertices;++n)  {
-                        pcMeshOut->mNormals[aiCurrent[AI_PTVS_VERTEX]+n] =
-                            (m * pcMesh->mNormals[n]).Normalize();
-                    }
-                }
-                if (iVFormat & 0x4)
-                {
-                    // copy tangents and bitangents, transform them to worldspace
-                    for (unsigned int n = 0; n < pcMesh->mNumVertices;++n)  {
-                        pcMeshOut->mTangents  [aiCurrent[AI_PTVS_VERTEX]+n] = (m * pcMesh->mTangents[n]).Normalize();
-                        pcMeshOut->mBitangents[aiCurrent[AI_PTVS_VERTEX]+n] = (m * pcMesh->mBitangents[n]).Normalize();
-                    }
-                }
-            }
-            unsigned int p = 0;
-            while (iVFormat & (0x100 << p))
-            {
-                // copy texture coordinates
-                memcpy(pcMeshOut->mTextureCoords[p] + aiCurrent[AI_PTVS_VERTEX],
-                    pcMesh->mTextureCoords[p],
-                    pcMesh->mNumVertices * sizeof(aiVector3D));
-                ++p;
-            }
-            p = 0;
-            while (iVFormat & (0x1000000 << p))
-            {
-                // copy vertex colors
-                memcpy(pcMeshOut->mColors[p] + aiCurrent[AI_PTVS_VERTEX],
-                    pcMesh->mColors[p],
-                    pcMesh->mNumVertices * sizeof(aiColor4D));
-                ++p;
-            }
-            // now we need to copy all faces. since we will delete the source mesh afterwards,
-            // we don't need to reallocate the array of indices except if this mesh is
-            // referenced multiple times.
-            for (unsigned int planck = 0;planck < pcMesh->mNumFaces;++planck)
-            {
-                aiFace& f_src = pcMesh->mFaces[planck];
-                aiFace& f_dst = pcMeshOut->mFaces[aiCurrent[AI_PTVS_FACE]+planck];
-
-                const unsigned int num_idx = f_src.mNumIndices;
-
-                f_dst.mNumIndices = num_idx;
-
-                unsigned int* pi;
-                if (!num_ref) { /* if last time the mesh is referenced -> no reallocation */
-                    pi = f_dst.mIndices = f_src.mIndices;
-
-                    // offset all vertex indices
-                    for (unsigned int hahn = 0; hahn < num_idx;++hahn){
-                        pi[hahn] += aiCurrent[AI_PTVS_VERTEX];
-                    }
-                }
-                else {
-                    pi = f_dst.mIndices = new unsigned int[num_idx];
-
-                    // copy and offset all vertex indices
-                    for (unsigned int hahn = 0; hahn < num_idx;++hahn){
-                        pi[hahn] = f_src.mIndices[hahn] + aiCurrent[AI_PTVS_VERTEX];
-                    }
-                }
-
-                // Update the mPrimitiveTypes member of the mesh
-                switch (pcMesh->mFaces[planck].mNumIndices)
-                {
-                case 0x1:
-                    pcMeshOut->mPrimitiveTypes |= aiPrimitiveType_POINT;
-                    break;
-                case 0x2:
-                    pcMeshOut->mPrimitiveTypes |= aiPrimitiveType_LINE;
-                    break;
-                case 0x3:
-                    pcMeshOut->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE;
-                    break;
-                default:
-                    pcMeshOut->mPrimitiveTypes |= aiPrimitiveType_POLYGON;
-                    break;
-                };
-            }
-            aiCurrent[AI_PTVS_VERTEX] += pcMesh->mNumVertices;
-            aiCurrent[AI_PTVS_FACE]   += pcMesh->mNumFaces;
-        }
-    }
-
-    // append all children of us
-    for (unsigned int i = 0;i < pcNode->mNumChildren;++i) {
-        CollectData(pcScene,pcNode->mChildren[i],iMat,
-            iVFormat,pcMeshOut,aiCurrent,num_refs);
-    }
+void PretransformVertices::CollectData(const aiScene *pcScene, const aiNode *pcNode, unsigned int iMat,
+		unsigned int iVFormat, aiMesh *pcMeshOut,
+		unsigned int aiCurrent[2], unsigned int *num_refs) const {
+	// No need to multiply if there's no transformation
+	const bool identity = pcNode->mTransformation.IsIdentity();
+	for (unsigned int i = 0; i < pcNode->mNumMeshes; ++i) {
+		aiMesh *pcMesh = pcScene->mMeshes[pcNode->mMeshes[i]];
+		if (iMat == pcMesh->mMaterialIndex && iVFormat == GetMeshVFormat(pcMesh)) {
+			// Decrement mesh reference counter
+			unsigned int &num_ref = num_refs[pcNode->mMeshes[i]];
+			ai_assert(0 != num_ref);
+			--num_ref;
+			// Save the name of the last mesh
+			if (num_ref == 0) {
+				pcMeshOut->mName = pcMesh->mName;
+			}
+
+			if (identity) {
+				// copy positions without modifying them
+				::memcpy(pcMeshOut->mVertices + aiCurrent[AI_PTVS_VERTEX],
+						pcMesh->mVertices,
+						pcMesh->mNumVertices * sizeof(aiVector3D));
+
+				if (iVFormat & 0x2) {
+					// copy normals without modifying them
+					::memcpy(pcMeshOut->mNormals + aiCurrent[AI_PTVS_VERTEX],
+							pcMesh->mNormals,
+							pcMesh->mNumVertices * sizeof(aiVector3D));
+				}
+				if (iVFormat & 0x4) {
+					// copy tangents without modifying them
+					::memcpy(pcMeshOut->mTangents + aiCurrent[AI_PTVS_VERTEX],
+							pcMesh->mTangents,
+							pcMesh->mNumVertices * sizeof(aiVector3D));
+					// copy bitangents without modifying them
+					::memcpy(pcMeshOut->mBitangents + aiCurrent[AI_PTVS_VERTEX],
+							pcMesh->mBitangents,
+							pcMesh->mNumVertices * sizeof(aiVector3D));
+				}
+			} else {
+				// copy positions, transform them to worldspace
+				for (unsigned int n = 0; n < pcMesh->mNumVertices; ++n) {
+					pcMeshOut->mVertices[aiCurrent[AI_PTVS_VERTEX] + n] = pcNode->mTransformation * pcMesh->mVertices[n];
+				}
+				aiMatrix4x4 mWorldIT = pcNode->mTransformation;
+				mWorldIT.Inverse().Transpose();
+
+				// TODO: implement Inverse() for aiMatrix3x3
+				aiMatrix3x3 m = aiMatrix3x3(mWorldIT);
+
+				if (iVFormat & 0x2) {
+					// copy normals, transform them to worldspace
+					for (unsigned int n = 0; n < pcMesh->mNumVertices; ++n) {
+						pcMeshOut->mNormals[aiCurrent[AI_PTVS_VERTEX] + n] =
+								(m * pcMesh->mNormals[n]).Normalize();
+					}
+				}
+				if (iVFormat & 0x4) {
+					// copy tangents and bitangents, transform them to worldspace
+					for (unsigned int n = 0; n < pcMesh->mNumVertices; ++n) {
+						pcMeshOut->mTangents[aiCurrent[AI_PTVS_VERTEX] + n] = (m * pcMesh->mTangents[n]).Normalize();
+						pcMeshOut->mBitangents[aiCurrent[AI_PTVS_VERTEX] + n] = (m * pcMesh->mBitangents[n]).Normalize();
+					}
+				}
+			}
+			unsigned int p = 0;
+			while (iVFormat & (0x100 << p)) {
+				// copy texture coordinates
+				memcpy(pcMeshOut->mTextureCoords[p] + aiCurrent[AI_PTVS_VERTEX],
+						pcMesh->mTextureCoords[p],
+						pcMesh->mNumVertices * sizeof(aiVector3D));
+				++p;
+			}
+			p = 0;
+			while (iVFormat & (0x1000000 << p)) {
+				// copy vertex colors
+				memcpy(pcMeshOut->mColors[p] + aiCurrent[AI_PTVS_VERTEX],
+						pcMesh->mColors[p],
+						pcMesh->mNumVertices * sizeof(aiColor4D));
+				++p;
+			}
+			// now we need to copy all faces. since we will delete the source mesh afterwards,
+			// we don't need to reallocate the array of indices except if this mesh is
+			// referenced multiple times.
+			for (unsigned int planck = 0; planck < pcMesh->mNumFaces; ++planck) {
+				aiFace &f_src = pcMesh->mFaces[planck];
+				aiFace &f_dst = pcMeshOut->mFaces[aiCurrent[AI_PTVS_FACE] + planck];
+
+				const unsigned int num_idx = f_src.mNumIndices;
+
+				f_dst.mNumIndices = num_idx;
+
+				unsigned int *pi;
+				if (!num_ref) { /* if last time the mesh is referenced -> no reallocation */
+					pi = f_dst.mIndices = f_src.mIndices;
+
+					// offset all vertex indices
+					for (unsigned int hahn = 0; hahn < num_idx; ++hahn) {
+						pi[hahn] += aiCurrent[AI_PTVS_VERTEX];
+					}
+				} else {
+					pi = f_dst.mIndices = new unsigned int[num_idx];
+
+					// copy and offset all vertex indices
+					for (unsigned int hahn = 0; hahn < num_idx; ++hahn) {
+						pi[hahn] = f_src.mIndices[hahn] + aiCurrent[AI_PTVS_VERTEX];
+					}
+				}
+
+				// Update the mPrimitiveTypes member of the mesh
+				switch (pcMesh->mFaces[planck].mNumIndices) {
+					case 0x1:
+						pcMeshOut->mPrimitiveTypes |= aiPrimitiveType_POINT;
+						break;
+					case 0x2:
+						pcMeshOut->mPrimitiveTypes |= aiPrimitiveType_LINE;
+						break;
+					case 0x3:
+						pcMeshOut->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE;
+						break;
+					default:
+						pcMeshOut->mPrimitiveTypes |= aiPrimitiveType_POLYGON;
+						break;
+				};
+			}
+			aiCurrent[AI_PTVS_VERTEX] += pcMesh->mNumVertices;
+			aiCurrent[AI_PTVS_FACE] += pcMesh->mNumFaces;
+		}
+	}
+
+	// append all children of us
+	for (unsigned int i = 0; i < pcNode->mNumChildren; ++i) {
+		CollectData(pcScene, pcNode->mChildren[i], iMat,
+				iVFormat, pcMeshOut, aiCurrent, num_refs);
+	}
 }
 
 // ------------------------------------------------------------------------------------------------
 // Get a list of all vertex formats that occur for a given material index
 // The output list contains duplicate elements
-void PretransformVertices::GetVFormatList( aiScene* pcScene, unsigned int iMat,
-    std::list<unsigned int>& aiOut)
-{
-    for (unsigned int i = 0; i < pcScene->mNumMeshes;++i)
-    {
-        aiMesh* pcMesh = pcScene->mMeshes[ i ];
-        if (iMat == pcMesh->mMaterialIndex) {
-            aiOut.push_back(GetMeshVFormat(pcMesh));
-        }
-    }
+void PretransformVertices::GetVFormatList(const aiScene *pcScene, unsigned int iMat,
+		std::list<unsigned int> &aiOut) const {
+	for (unsigned int i = 0; i < pcScene->mNumMeshes; ++i) {
+		aiMesh *pcMesh = pcScene->mMeshes[i];
+		if (iMat == pcMesh->mMaterialIndex) {
+			aiOut.push_back(GetMeshVFormat(pcMesh));
+		}
+	}
 }
 
 // ------------------------------------------------------------------------------------------------
 // Compute the absolute transformation matrices of each node
-void PretransformVertices::ComputeAbsoluteTransform( aiNode* pcNode )
-{
-    if (pcNode->mParent)    {
-        pcNode->mTransformation = pcNode->mParent->mTransformation*pcNode->mTransformation;
-    }
-
-    for (unsigned int i = 0;i < pcNode->mNumChildren;++i)   {
-        ComputeAbsoluteTransform(pcNode->mChildren[i]);
-    }
+void PretransformVertices::ComputeAbsoluteTransform(aiNode *pcNode) {
+	if (pcNode->mParent) {
+		pcNode->mTransformation = pcNode->mParent->mTransformation * pcNode->mTransformation;
+	}
+
+	for (unsigned int i = 0; i < pcNode->mNumChildren; ++i) {
+		ComputeAbsoluteTransform(pcNode->mChildren[i]);
+	}
 }
 
 // ------------------------------------------------------------------------------------------------
 // Apply the node transformation to a mesh
-void PretransformVertices::ApplyTransform(aiMesh* mesh, const aiMatrix4x4& mat)
-{
-    // Check whether we need to transform the coordinates at all
-    if (!mat.IsIdentity()) {
-
-        if (mesh->HasPositions()) {
-            for (unsigned int i = 0; i < mesh->mNumVertices; ++i) {
-                mesh->mVertices[i] = mat * mesh->mVertices[i];
-            }
-        }
-        if (mesh->HasNormals() || mesh->HasTangentsAndBitangents()) {
-            aiMatrix4x4 mWorldIT = mat;
-            mWorldIT.Inverse().Transpose();
-
-            // TODO: implement Inverse() for aiMatrix3x3
-            aiMatrix3x3 m = aiMatrix3x3(mWorldIT);
-
-            if (mesh->HasNormals()) {
-                for (unsigned int i = 0; i < mesh->mNumVertices; ++i) {
-                    mesh->mNormals[i] = (m * mesh->mNormals[i]).Normalize();
-                }
-            }
-            if (mesh->HasTangentsAndBitangents()) {
-                for (unsigned int i = 0; i < mesh->mNumVertices; ++i) {
-                    mesh->mTangents[i]   = (m * mesh->mTangents[i]).Normalize();
-                    mesh->mBitangents[i] = (m * mesh->mBitangents[i]).Normalize();
-                }
-            }
-        }
-    }
+void PretransformVertices::ApplyTransform(aiMesh *mesh, const aiMatrix4x4 &mat) const {
+	// Check whether we need to transform the coordinates at all
+	if (!mat.IsIdentity()) {
+
+		// Check for odd negative scale (mirror)
+		if (mesh->HasFaces() && mat.Determinant() < 0) {
+			// Reverse the mesh face winding order
+			FlipWindingOrderProcess::ProcessMesh(mesh);
+		}
+
+		// Update positions
+		if (mesh->HasPositions()) {
+			for (unsigned int i = 0; i < mesh->mNumVertices; ++i) {
+				mesh->mVertices[i] = mat * mesh->mVertices[i];
+			}
+		}
+
+		// Update normals and tangents
+		if (mesh->HasNormals() || mesh->HasTangentsAndBitangents()) {
+			const aiMatrix3x3 m = aiMatrix3x3(mat).Inverse().Transpose();
+
+			if (mesh->HasNormals()) {
+				for (unsigned int i = 0; i < mesh->mNumVertices; ++i) {
+					mesh->mNormals[i] = (m * mesh->mNormals[i]).Normalize();
+				}
+			}
+			if (mesh->HasTangentsAndBitangents()) {
+				for (unsigned int i = 0; i < mesh->mNumVertices; ++i) {
+					mesh->mTangents[i] = (m * mesh->mTangents[i]).Normalize();
+					mesh->mBitangents[i] = (m * mesh->mBitangents[i]).Normalize();
+				}
+			}
+		}
+	}
 }
 
 // ------------------------------------------------------------------------------------------------
 // Simple routine to build meshes in worldspace, no further optimization
-void PretransformVertices::BuildWCSMeshes(std::vector<aiMesh*>& out, aiMesh** in,
-    unsigned int numIn, aiNode* node)
-{
-    // NOTE:
-    //  aiMesh::mNumBones store original source mesh, or UINT_MAX if not a copy
-    //  aiMesh::mBones store reference to abs. transform we multiplied with
-
-    // process meshes
-    for (unsigned int i = 0; i < node->mNumMeshes;++i) {
-        aiMesh* mesh = in[node->mMeshes[i]];
-
-        // check whether we can operate on this mesh
-        if (!mesh->mBones || *reinterpret_cast<aiMatrix4x4*>(mesh->mBones) == node->mTransformation) {
-            // yes, we can.
-            mesh->mBones = reinterpret_cast<aiBone**> (&node->mTransformation);
-            mesh->mNumBones = UINT_MAX;
-        }
-        else {
-
-            // try to find us in the list of newly created meshes
-            for (unsigned int n = 0; n < out.size(); ++n) {
-                aiMesh* ctz = out[n];
-                if (ctz->mNumBones == node->mMeshes[i] && *reinterpret_cast<aiMatrix4x4*>(ctz->mBones) ==  node->mTransformation) {
-
-                    // ok, use this one. Update node mesh index
-                    node->mMeshes[i] = numIn + n;
-                }
-            }
-            if (node->mMeshes[i] < numIn) {
-                // Worst case. Need to operate on a full copy of the mesh
-                ASSIMP_LOG_INFO("PretransformVertices: Copying mesh due to mismatching transforms");
-                aiMesh* ntz;
-
-                const unsigned int tmp = mesh->mNumBones; //
-                mesh->mNumBones = 0;
-                SceneCombiner::Copy(&ntz,mesh);
-                mesh->mNumBones = tmp;
-
-                ntz->mNumBones = node->mMeshes[i];
-                ntz->mBones = reinterpret_cast<aiBone**> (&node->mTransformation);
-
-                out.push_back(ntz);
-
-                node->mMeshes[i] = static_cast<unsigned int>(numIn + out.size() - 1);
-            }
-        }
-    }
-
-    // call children
-    for (unsigned int i = 0; i < node->mNumChildren;++i)
-        BuildWCSMeshes(out,in,numIn,node->mChildren[i]);
+void PretransformVertices::BuildWCSMeshes(std::vector<aiMesh *> &out, aiMesh **in,
+		unsigned int numIn, aiNode *node) const {
+	// NOTE:
+	//  aiMesh::mNumBones store original source mesh, or UINT_MAX if not a copy
+	//  aiMesh::mBones store reference to abs. transform we multiplied with
+
+	// process meshes
+	for (unsigned int i = 0; i < node->mNumMeshes; ++i) {
+		aiMesh *mesh = in[node->mMeshes[i]];
+
+		// check whether we can operate on this mesh
+		if (!mesh->mBones || *reinterpret_cast<aiMatrix4x4 *>(mesh->mBones) == node->mTransformation) {
+			// yes, we can.
+			mesh->mBones = reinterpret_cast<aiBone **>(&node->mTransformation);
+			mesh->mNumBones = UINT_MAX;
+		} else {
+
+			// try to find us in the list of newly created meshes
+			for (unsigned int n = 0; n < out.size(); ++n) {
+				aiMesh *ctz = out[n];
+				if (ctz->mNumBones == node->mMeshes[i] && *reinterpret_cast<aiMatrix4x4 *>(ctz->mBones) == node->mTransformation) {
+
+					// ok, use this one. Update node mesh index
+					node->mMeshes[i] = numIn + n;
+				}
+			}
+			if (node->mMeshes[i] < numIn) {
+				// Worst case. Need to operate on a full copy of the mesh
+				ASSIMP_LOG_INFO("PretransformVertices: Copying mesh due to mismatching transforms");
+				aiMesh *ntz;
+
+				const unsigned int tmp = mesh->mNumBones; //
+				mesh->mNumBones = 0;
+				SceneCombiner::Copy(&ntz, mesh);
+				mesh->mNumBones = tmp;
+
+				ntz->mNumBones = node->mMeshes[i];
+				ntz->mBones = reinterpret_cast<aiBone **>(&node->mTransformation);
+
+				out.push_back(ntz);
+
+				node->mMeshes[i] = static_cast<unsigned int>(numIn + out.size() - 1);
+			}
+		}
+	}
+
+	// call children
+	for (unsigned int i = 0; i < node->mNumChildren; ++i)
+		BuildWCSMeshes(out, in, numIn, node->mChildren[i]);
 }
 
 // ------------------------------------------------------------------------------------------------
 // Reset transformation matrices to identity
-void PretransformVertices::MakeIdentityTransform(aiNode* nd)
-{
-    nd->mTransformation = aiMatrix4x4();
+void PretransformVertices::MakeIdentityTransform(aiNode *nd) const {
+	nd->mTransformation = aiMatrix4x4();
 
-    // call children
-    for (unsigned int i = 0; i < nd->mNumChildren;++i)
-        MakeIdentityTransform(nd->mChildren[i]);
+	// call children
+	for (unsigned int i = 0; i < nd->mNumChildren; ++i)
+		MakeIdentityTransform(nd->mChildren[i]);
 }
 
 // ------------------------------------------------------------------------------------------------
 // Build reference counters for all meshes
-void PretransformVertices::BuildMeshRefCountArray(aiNode* nd, unsigned int * refs)
-{
-    for (unsigned int i = 0; i< nd->mNumMeshes;++i)
-        refs[nd->mMeshes[i]]++;
-
-    // call children
-    for (unsigned int i = 0; i < nd->mNumChildren;++i)
-        BuildMeshRefCountArray(nd->mChildren[i],refs);
+void PretransformVertices::BuildMeshRefCountArray(const aiNode *nd, unsigned int *refs) const {
+	for (unsigned int i = 0; i < nd->mNumMeshes; ++i)
+		refs[nd->mMeshes[i]]++;
+
+	// call children
+	for (unsigned int i = 0; i < nd->mNumChildren; ++i)
+		BuildMeshRefCountArray(nd->mChildren[i], refs);
 }
 
 // ------------------------------------------------------------------------------------------------
 // Executes the post processing step on the given imported data.
-void PretransformVertices::Execute( aiScene* pScene)
-{
-    ASSIMP_LOG_DEBUG("PretransformVerticesProcess begin");
-
-    // Return immediately if we have no meshes
-    if (!pScene->mNumMeshes)
-        return;
-
-    const unsigned int iOldMeshes = pScene->mNumMeshes;
-    const unsigned int iOldAnimationChannels = pScene->mNumAnimations;
-    const unsigned int iOldNodes = CountNodes(pScene->mRootNode);
-
-    if(configTransform) {
-        pScene->mRootNode->mTransformation = configTransformation;
-    }
-
-    // first compute absolute transformation matrices for all nodes
-    ComputeAbsoluteTransform(pScene->mRootNode);
-
-    // Delete aiMesh::mBones for all meshes. The bones are
-    // removed during this step and we need the pointer as
-    // temporary storage
-    for (unsigned int i = 0; i < pScene->mNumMeshes;++i)    {
-        aiMesh* mesh = pScene->mMeshes[i];
-
-        for (unsigned int a = 0; a < mesh->mNumBones;++a)
-            delete mesh->mBones[a];
-
-        delete[] mesh->mBones;
-        mesh->mBones = NULL;
-    }
-
-    // now build a list of output meshes
-    std::vector<aiMesh*> apcOutMeshes;
-
-    // Keep scene hierarchy? It's an easy job in this case ...
-    // we go on and transform all meshes, if one is referenced by nodes
-    // with different absolute transformations a depth copy of the mesh
-    // is required.
-    if( configKeepHierarchy ) {
-
-        // Hack: store the matrix we're transforming a mesh with in aiMesh::mBones
-        BuildWCSMeshes(apcOutMeshes,pScene->mMeshes,pScene->mNumMeshes, pScene->mRootNode);
-
-        // ... if new meshes have been generated, append them to the end of the scene
-        if (apcOutMeshes.size() > 0) {
-            aiMesh** npp = new aiMesh*[pScene->mNumMeshes + apcOutMeshes.size()];
-
-            memcpy(npp,pScene->mMeshes,sizeof(aiMesh*)*pScene->mNumMeshes);
-            memcpy(npp+pScene->mNumMeshes,&apcOutMeshes[0],sizeof(aiMesh*)*apcOutMeshes.size());
-
-            pScene->mNumMeshes  += static_cast<unsigned int>(apcOutMeshes.size());
-            delete[] pScene->mMeshes; pScene->mMeshes = npp;
-        }
-
-        // now iterate through all meshes and transform them to worldspace
-        for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) {
-            ApplyTransform(pScene->mMeshes[i],*reinterpret_cast<aiMatrix4x4*>( pScene->mMeshes[i]->mBones ));
-
-            // prevent improper destruction
-            pScene->mMeshes[i]->mBones    = NULL;
-            pScene->mMeshes[i]->mNumBones = 0;
-        }
-    } else {
-        apcOutMeshes.reserve(pScene->mNumMaterials<<1u);
-        std::list<unsigned int> aiVFormats;
-
-        std::vector<unsigned int> s(pScene->mNumMeshes,0);
-        BuildMeshRefCountArray(pScene->mRootNode,&s[0]);
-
-        for (unsigned int i = 0; i < pScene->mNumMaterials;++i)     {
-            // get the list of all vertex formats for this material
-            aiVFormats.clear();
-            GetVFormatList(pScene,i,aiVFormats);
-            aiVFormats.sort();
-            aiVFormats.unique();
-            for (std::list<unsigned int>::const_iterator j =  aiVFormats.begin();j != aiVFormats.end();++j) {
-                unsigned int iVertices = 0;
-                unsigned int iFaces = 0;
-                CountVerticesAndFaces(pScene,pScene->mRootNode,i,*j,&iFaces,&iVertices);
-                if (0 != iFaces && 0 != iVertices)
-                {
-                    apcOutMeshes.push_back(new aiMesh());
-                    aiMesh* pcMesh = apcOutMeshes.back();
-                    pcMesh->mNumFaces = iFaces;
-                    pcMesh->mNumVertices = iVertices;
-                    pcMesh->mFaces = new aiFace[iFaces];
-                    pcMesh->mVertices = new aiVector3D[iVertices];
-                    pcMesh->mMaterialIndex = i;
-                    if ((*j) & 0x2)pcMesh->mNormals = new aiVector3D[iVertices];
-                    if ((*j) & 0x4)
-                    {
-                        pcMesh->mTangents    = new aiVector3D[iVertices];
-                        pcMesh->mBitangents  = new aiVector3D[iVertices];
-                    }
-                    iFaces = 0;
-                    while ((*j) & (0x100 << iFaces))
-                    {
-                        pcMesh->mTextureCoords[iFaces] = new aiVector3D[iVertices];
-                        if ((*j) & (0x10000 << iFaces))pcMesh->mNumUVComponents[iFaces] = 3;
-                        else pcMesh->mNumUVComponents[iFaces] = 2;
-                        iFaces++;
-                    }
-                    iFaces = 0;
-                    while ((*j) & (0x1000000 << iFaces))
-                        pcMesh->mColors[iFaces++] = new aiColor4D[iVertices];
-
-                    // fill the mesh ...
-                    unsigned int aiTemp[2] = {0,0};
-                    CollectData(pScene,pScene->mRootNode,i,*j,pcMesh,aiTemp,&s[0]);
-                }
-            }
-        }
-
-        // If no meshes are referenced in the node graph it is possible that we get no output meshes.
-        if (apcOutMeshes.empty()) {
-            
-            throw DeadlyImportError("No output meshes: all meshes are orphaned and are not referenced by any nodes");
-        }
-        else
-        {
-            // now delete all meshes in the scene and build a new mesh list
-            for (unsigned int i = 0; i < pScene->mNumMeshes;++i)
-            {
-                aiMesh* mesh = pScene->mMeshes[i];
-                mesh->mNumBones = 0;
-                mesh->mBones    = NULL;
-
-                // we're reusing the face index arrays. avoid destruction
-                for (unsigned int a = 0; a < mesh->mNumFaces; ++a) {
-                    mesh->mFaces[a].mNumIndices = 0;
-                    mesh->mFaces[a].mIndices = NULL;
-                }
-
-                delete mesh;
-
-                // Invalidate the contents of the old mesh array. We will most
-                // likely have less output meshes now, so the last entries of
-                // the mesh array are not overridden. We set them to NULL to
-                // make sure the developer gets notified when his application
-                // attempts to access these fields ...
-                mesh = NULL;
-            }
-
-            // It is impossible that we have more output meshes than
-            // input meshes, so we can easily reuse the old mesh array
-            pScene->mNumMeshes = (unsigned int)apcOutMeshes.size();
-            for (unsigned int i = 0; i < pScene->mNumMeshes;++i) {
-                pScene->mMeshes[i] = apcOutMeshes[i];
-            }
-        }
-    }
-
-    // remove all animations from the scene
-    for (unsigned int i = 0; i < pScene->mNumAnimations;++i)
-        delete pScene->mAnimations[i];
-    delete[] pScene->mAnimations;
-
-    pScene->mAnimations    = NULL;
-    pScene->mNumAnimations = 0;
-
-    // --- we need to keep all cameras and lights
-    for (unsigned int i = 0; i < pScene->mNumCameras;++i)
-    {
-        aiCamera* cam = pScene->mCameras[i];
-        const aiNode* nd = pScene->mRootNode->FindNode(cam->mName);
-        ai_assert(NULL != nd);
-
-        // multiply all properties of the camera with the absolute
-        // transformation of the corresponding node
-        cam->mPosition = nd->mTransformation * cam->mPosition;
-        cam->mLookAt   = aiMatrix3x3( nd->mTransformation ) * cam->mLookAt;
-        cam->mUp       = aiMatrix3x3( nd->mTransformation ) * cam->mUp;
-    }
-
-    for (unsigned int i = 0; i < pScene->mNumLights;++i)
-    {
-        aiLight* l = pScene->mLights[i];
-        const aiNode* nd = pScene->mRootNode->FindNode(l->mName);
-        ai_assert(NULL != nd);
-
-        // multiply all properties of the camera with the absolute
-        // transformation of the corresponding node
-        l->mPosition   = nd->mTransformation * l->mPosition;
-        l->mDirection  = aiMatrix3x3( nd->mTransformation ) * l->mDirection;
-        l->mUp         = aiMatrix3x3( nd->mTransformation ) * l->mUp;
-    }
-
-    if( !configKeepHierarchy ) {
-
-        // now delete all nodes in the scene and build a new
-        // flat node graph with a root node and some level 1 children
-        aiNode* newRoot = new aiNode();
-        newRoot->mName = pScene->mRootNode->mName;
-        delete pScene->mRootNode;
-        pScene->mRootNode = newRoot;
-
-        if (1 == pScene->mNumMeshes && !pScene->mNumLights && !pScene->mNumCameras)
-        {
-            pScene->mRootNode->mNumMeshes = 1;
-            pScene->mRootNode->mMeshes = new unsigned int[1];
-            pScene->mRootNode->mMeshes[0] = 0;
-        }
-        else
-        {
-            pScene->mRootNode->mNumChildren = pScene->mNumMeshes+pScene->mNumLights+pScene->mNumCameras;
-            aiNode** nodes = pScene->mRootNode->mChildren = new aiNode*[pScene->mRootNode->mNumChildren];
-
-            // generate mesh nodes
-            for (unsigned int i = 0; i < pScene->mNumMeshes;++i,++nodes)
-            {
-                aiNode* pcNode = new aiNode();
-                *nodes = pcNode;
-                pcNode->mParent = pScene->mRootNode;
-                pcNode->mName = pScene->mMeshes[i]->mName;
-
-                // setup mesh indices
-                pcNode->mNumMeshes = 1;
-                pcNode->mMeshes = new unsigned int[1];
-                pcNode->mMeshes[0] = i;
-            }
-            // generate light nodes
-            for (unsigned int i = 0; i < pScene->mNumLights;++i,++nodes)
-            {
-                aiNode* pcNode = new aiNode();
-                *nodes = pcNode;
-                pcNode->mParent = pScene->mRootNode;
-                pcNode->mName.length = ai_snprintf(pcNode->mName.data, MAXLEN, "light_%u",i);
-                pScene->mLights[i]->mName = pcNode->mName;
-            }
-            // generate camera nodes
-            for (unsigned int i = 0; i < pScene->mNumCameras;++i,++nodes)
-            {
-                aiNode* pcNode = new aiNode();
-                *nodes = pcNode;
-                pcNode->mParent = pScene->mRootNode;
-                pcNode->mName.length = ::ai_snprintf(pcNode->mName.data,MAXLEN,"cam_%u",i);
-                pScene->mCameras[i]->mName = pcNode->mName;
-            }
-        }
-    }
-    else {
-        // ... and finally set the transformation matrix of all nodes to identity
-        MakeIdentityTransform(pScene->mRootNode);
-    }
-
-    if (configNormalize) {
-        // compute the boundary of all meshes
-        aiVector3D min,max;
-        MinMaxChooser<aiVector3D> ()(min,max);
-
-        for (unsigned int a = 0; a <  pScene->mNumMeshes; ++a) {
-            aiMesh* m = pScene->mMeshes[a];
-            for (unsigned int i = 0; i < m->mNumVertices;++i) {
-                min = std::min(m->mVertices[i],min);
-                max = std::max(m->mVertices[i],max);
-            }
-        }
-
-        // find the dominant axis
-        aiVector3D d = max-min;
-        const ai_real div = std::max(d.x,std::max(d.y,d.z))*ai_real( 0.5);
-
-        d = min + d * (ai_real)0.5;
-        for (unsigned int a = 0; a <  pScene->mNumMeshes; ++a) {
-            aiMesh* m = pScene->mMeshes[a];
-            for (unsigned int i = 0; i < m->mNumVertices;++i) {
-                m->mVertices[i] = (m->mVertices[i]-d)/div;
-            }
-        }
-    }
-
-    // print statistics
-    if (!DefaultLogger::isNullLogger()) {
-        ASSIMP_LOG_DEBUG("PretransformVerticesProcess finished");
-
-        ASSIMP_LOG_INFO_F("Removed ", iOldNodes, " nodes and ", iOldAnimationChannels, " animation channels (", 
-            CountNodes(pScene->mRootNode) ," output nodes)" );
-        ASSIMP_LOG_INFO_F("Kept ", pScene->mNumLights, " lights and ", pScene->mNumCameras, " cameras." );
-        ASSIMP_LOG_INFO_F("Moved ", iOldMeshes, " meshes to WCS (number of output meshes: ", pScene->mNumMeshes, ")");
-    }
+void PretransformVertices::Execute(aiScene *pScene) {
+	ASSIMP_LOG_DEBUG("PretransformVerticesProcess begin");
+
+	// Return immediately if we have no meshes
+	if (!pScene->mNumMeshes)
+		return;
+
+	const unsigned int iOldMeshes = pScene->mNumMeshes;
+	const unsigned int iOldAnimationChannels = pScene->mNumAnimations;
+	const unsigned int iOldNodes = CountNodes(pScene->mRootNode);
+
+	if (configTransform) {
+		pScene->mRootNode->mTransformation = configTransformation;
+	}
+
+	// first compute absolute transformation matrices for all nodes
+	ComputeAbsoluteTransform(pScene->mRootNode);
+
+	// Delete aiMesh::mBones for all meshes. The bones are
+	// removed during this step and we need the pointer as
+	// temporary storage
+	for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) {
+		aiMesh *mesh = pScene->mMeshes[i];
+
+		for (unsigned int a = 0; a < mesh->mNumBones; ++a)
+			delete mesh->mBones[a];
+
+		delete[] mesh->mBones;
+		mesh->mBones = NULL;
+	}
+
+	// now build a list of output meshes
+	std::vector<aiMesh *> apcOutMeshes;
+
+	// Keep scene hierarchy? It's an easy job in this case ...
+	// we go on and transform all meshes, if one is referenced by nodes
+	// with different absolute transformations a depth copy of the mesh
+	// is required.
+	if (configKeepHierarchy) {
+
+		// Hack: store the matrix we're transforming a mesh with in aiMesh::mBones
+		BuildWCSMeshes(apcOutMeshes, pScene->mMeshes, pScene->mNumMeshes, pScene->mRootNode);
+
+		// ... if new meshes have been generated, append them to the end of the scene
+		if (apcOutMeshes.size() > 0) {
+			aiMesh **npp = new aiMesh *[pScene->mNumMeshes + apcOutMeshes.size()];
+
+			memcpy(npp, pScene->mMeshes, sizeof(aiMesh *) * pScene->mNumMeshes);
+			memcpy(npp + pScene->mNumMeshes, &apcOutMeshes[0], sizeof(aiMesh *) * apcOutMeshes.size());
+
+			pScene->mNumMeshes += static_cast<unsigned int>(apcOutMeshes.size());
+			delete[] pScene->mMeshes;
+			pScene->mMeshes = npp;
+		}
+
+		// now iterate through all meshes and transform them to worldspace
+		for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) {
+			ApplyTransform(pScene->mMeshes[i], *reinterpret_cast<aiMatrix4x4 *>(pScene->mMeshes[i]->mBones));
+
+			// prevent improper destruction
+			pScene->mMeshes[i]->mBones = NULL;
+			pScene->mMeshes[i]->mNumBones = 0;
+		}
+	} else {
+		apcOutMeshes.reserve(pScene->mNumMaterials << 1u);
+		std::list<unsigned int> aiVFormats;
+
+		std::vector<unsigned int> s(pScene->mNumMeshes, 0);
+		BuildMeshRefCountArray(pScene->mRootNode, &s[0]);
+
+		for (unsigned int i = 0; i < pScene->mNumMaterials; ++i) {
+			// get the list of all vertex formats for this material
+			aiVFormats.clear();
+			GetVFormatList(pScene, i, aiVFormats);
+			aiVFormats.sort();
+			aiVFormats.unique();
+			for (std::list<unsigned int>::const_iterator j = aiVFormats.begin(); j != aiVFormats.end(); ++j) {
+				unsigned int iVertices = 0;
+				unsigned int iFaces = 0;
+				CountVerticesAndFaces(pScene, pScene->mRootNode, i, *j, &iFaces, &iVertices);
+				if (0 != iFaces && 0 != iVertices) {
+					apcOutMeshes.push_back(new aiMesh());
+					aiMesh *pcMesh = apcOutMeshes.back();
+					pcMesh->mNumFaces = iFaces;
+					pcMesh->mNumVertices = iVertices;
+					pcMesh->mFaces = new aiFace[iFaces];
+					pcMesh->mVertices = new aiVector3D[iVertices];
+					pcMesh->mMaterialIndex = i;
+					if ((*j) & 0x2) pcMesh->mNormals = new aiVector3D[iVertices];
+					if ((*j) & 0x4) {
+						pcMesh->mTangents = new aiVector3D[iVertices];
+						pcMesh->mBitangents = new aiVector3D[iVertices];
+					}
+					iFaces = 0;
+					while ((*j) & (0x100 << iFaces)) {
+						pcMesh->mTextureCoords[iFaces] = new aiVector3D[iVertices];
+						if ((*j) & (0x10000 << iFaces))
+							pcMesh->mNumUVComponents[iFaces] = 3;
+						else
+							pcMesh->mNumUVComponents[iFaces] = 2;
+						iFaces++;
+					}
+					iFaces = 0;
+					while ((*j) & (0x1000000 << iFaces))
+						pcMesh->mColors[iFaces++] = new aiColor4D[iVertices];
+
+					// fill the mesh ...
+					unsigned int aiTemp[2] = { 0, 0 };
+					CollectData(pScene, pScene->mRootNode, i, *j, pcMesh, aiTemp, &s[0]);
+				}
+			}
+		}
+
+		// If no meshes are referenced in the node graph it is possible that we get no output meshes.
+		if (apcOutMeshes.empty()) {
+
+			throw DeadlyImportError("No output meshes: all meshes are orphaned and are not referenced by any nodes");
+		} else {
+			// now delete all meshes in the scene and build a new mesh list
+			for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) {
+				aiMesh *mesh = pScene->mMeshes[i];
+				mesh->mNumBones = 0;
+				mesh->mBones = NULL;
+
+				// we're reusing the face index arrays. avoid destruction
+				for (unsigned int a = 0; a < mesh->mNumFaces; ++a) {
+					mesh->mFaces[a].mNumIndices = 0;
+					mesh->mFaces[a].mIndices = NULL;
+				}
+
+				delete mesh;
+
+				// Invalidate the contents of the old mesh array. We will most
+				// likely have less output meshes now, so the last entries of
+				// the mesh array are not overridden. We set them to NULL to
+				// make sure the developer gets notified when his application
+				// attempts to access these fields ...
+				mesh = NULL;
+			}
+
+			// It is impossible that we have more output meshes than
+			// input meshes, so we can easily reuse the old mesh array
+			pScene->mNumMeshes = (unsigned int)apcOutMeshes.size();
+			for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) {
+				pScene->mMeshes[i] = apcOutMeshes[i];
+			}
+		}
+	}
+
+	// remove all animations from the scene
+	for (unsigned int i = 0; i < pScene->mNumAnimations; ++i)
+		delete pScene->mAnimations[i];
+	delete[] pScene->mAnimations;
+
+	pScene->mAnimations = NULL;
+	pScene->mNumAnimations = 0;
+
+	// --- we need to keep all cameras and lights
+	for (unsigned int i = 0; i < pScene->mNumCameras; ++i) {
+		aiCamera *cam = pScene->mCameras[i];
+		const aiNode *nd = pScene->mRootNode->FindNode(cam->mName);
+		ai_assert(NULL != nd);
+
+		// multiply all properties of the camera with the absolute
+		// transformation of the corresponding node
+		cam->mPosition = nd->mTransformation * cam->mPosition;
+		cam->mLookAt = aiMatrix3x3(nd->mTransformation) * cam->mLookAt;
+		cam->mUp = aiMatrix3x3(nd->mTransformation) * cam->mUp;
+	}
+
+	for (unsigned int i = 0; i < pScene->mNumLights; ++i) {
+		aiLight *l = pScene->mLights[i];
+		const aiNode *nd = pScene->mRootNode->FindNode(l->mName);
+		ai_assert(NULL != nd);
+
+		// multiply all properties of the camera with the absolute
+		// transformation of the corresponding node
+		l->mPosition = nd->mTransformation * l->mPosition;
+		l->mDirection = aiMatrix3x3(nd->mTransformation) * l->mDirection;
+		l->mUp = aiMatrix3x3(nd->mTransformation) * l->mUp;
+	}
+
+	if (!configKeepHierarchy) {
+
+		// now delete all nodes in the scene and build a new
+		// flat node graph with a root node and some level 1 children
+		aiNode *newRoot = new aiNode();
+		newRoot->mName = pScene->mRootNode->mName;
+		delete pScene->mRootNode;
+		pScene->mRootNode = newRoot;
+
+		if (1 == pScene->mNumMeshes && !pScene->mNumLights && !pScene->mNumCameras) {
+			pScene->mRootNode->mNumMeshes = 1;
+			pScene->mRootNode->mMeshes = new unsigned int[1];
+			pScene->mRootNode->mMeshes[0] = 0;
+		} else {
+			pScene->mRootNode->mNumChildren = pScene->mNumMeshes + pScene->mNumLights + pScene->mNumCameras;
+			aiNode **nodes = pScene->mRootNode->mChildren = new aiNode *[pScene->mRootNode->mNumChildren];
+
+			// generate mesh nodes
+			for (unsigned int i = 0; i < pScene->mNumMeshes; ++i, ++nodes) {
+				aiNode *pcNode = new aiNode();
+				*nodes = pcNode;
+				pcNode->mParent = pScene->mRootNode;
+				pcNode->mName = pScene->mMeshes[i]->mName;
+
+				// setup mesh indices
+				pcNode->mNumMeshes = 1;
+				pcNode->mMeshes = new unsigned int[1];
+				pcNode->mMeshes[0] = i;
+			}
+			// generate light nodes
+			for (unsigned int i = 0; i < pScene->mNumLights; ++i, ++nodes) {
+				aiNode *pcNode = new aiNode();
+				*nodes = pcNode;
+				pcNode->mParent = pScene->mRootNode;
+				pcNode->mName.length = ai_snprintf(pcNode->mName.data, MAXLEN, "light_%u", i);
+				pScene->mLights[i]->mName = pcNode->mName;
+			}
+			// generate camera nodes
+			for (unsigned int i = 0; i < pScene->mNumCameras; ++i, ++nodes) {
+				aiNode *pcNode = new aiNode();
+				*nodes = pcNode;
+				pcNode->mParent = pScene->mRootNode;
+				pcNode->mName.length = ::ai_snprintf(pcNode->mName.data, MAXLEN, "cam_%u", i);
+				pScene->mCameras[i]->mName = pcNode->mName;
+			}
+		}
+	} else {
+		// ... and finally set the transformation matrix of all nodes to identity
+		MakeIdentityTransform(pScene->mRootNode);
+	}
+
+	if (configNormalize) {
+		// compute the boundary of all meshes
+		aiVector3D min, max;
+		MinMaxChooser<aiVector3D>()(min, max);
+
+		for (unsigned int a = 0; a < pScene->mNumMeshes; ++a) {
+			aiMesh *m = pScene->mMeshes[a];
+			for (unsigned int i = 0; i < m->mNumVertices; ++i) {
+				min = std::min(m->mVertices[i], min);
+				max = std::max(m->mVertices[i], max);
+			}
+		}
+
+		// find the dominant axis
+		aiVector3D d = max - min;
+		const ai_real div = std::max(d.x, std::max(d.y, d.z)) * ai_real(0.5);
+
+		d = min + d * (ai_real)0.5;
+		for (unsigned int a = 0; a < pScene->mNumMeshes; ++a) {
+			aiMesh *m = pScene->mMeshes[a];
+			for (unsigned int i = 0; i < m->mNumVertices; ++i) {
+				m->mVertices[i] = (m->mVertices[i] - d) / div;
+			}
+		}
+	}
+
+	// print statistics
+	if (!DefaultLogger::isNullLogger()) {
+		ASSIMP_LOG_DEBUG("PretransformVerticesProcess finished");
+
+		ASSIMP_LOG_INFO_F("Removed ", iOldNodes, " nodes and ", iOldAnimationChannels, " animation channels (",
+				CountNodes(pScene->mRootNode), " output nodes)");
+		ASSIMP_LOG_INFO_F("Kept ", pScene->mNumLights, " lights and ", pScene->mNumCameras, " cameras.");
+		ASSIMP_LOG_INFO_F("Moved ", iOldMeshes, " meshes to WCS (number of output meshes: ", pScene->mNumMeshes, ")");
+	}
 }

+ 81 - 81
code/PostProcessing/PretransformVertices.h

@@ -59,7 +59,7 @@ struct aiNode;
 
 class PretransformVerticesTest;
 
-namespace Assimp    {
+namespace Assimp {
 
 // ---------------------------------------------------------------------------
 /** The PretransformVertices pre-transforms all vertices in the node tree
@@ -68,97 +68,97 @@ namespace Assimp    {
 */
 class ASSIMP_API PretransformVertices : public BaseProcess {
 public:
-    PretransformVertices ();
-    ~PretransformVertices ();
+	PretransformVertices();
+	~PretransformVertices();
 
-    // -------------------------------------------------------------------
-    // Check whether step is active
-    bool IsActive( unsigned int pFlags) const;
+	// -------------------------------------------------------------------
+	// Check whether step is active
+	bool IsActive(unsigned int pFlags) const override;
 
-    // -------------------------------------------------------------------
-    // Execute step on a given scene
-    void Execute( aiScene* pScene);
+	// -------------------------------------------------------------------
+	// Execute step on a given scene
+	void Execute(aiScene *pScene) override;
 
-    // -------------------------------------------------------------------
-    // Setup import settings
-    void SetupProperties(const Importer* pImp);
+	// -------------------------------------------------------------------
+	// Setup import settings
+	void SetupProperties(const Importer *pImp) override;
 
-    // -------------------------------------------------------------------
-    /** @brief Toggle the 'keep hierarchy' option
+	// -------------------------------------------------------------------
+	/** @brief Toggle the 'keep hierarchy' option
      *  @param keep    true for keep configuration.
      */
-    void KeepHierarchy(bool keep) {
-        configKeepHierarchy = keep;
-    }
+	void KeepHierarchy(bool keep) {
+		configKeepHierarchy = keep;
+	}
 
-    // -------------------------------------------------------------------
-    /** @brief Check whether 'keep hierarchy' is currently enabled.
+	// -------------------------------------------------------------------
+	/** @brief Check whether 'keep hierarchy' is currently enabled.
      *  @return ...
      */
-    bool IsHierarchyKept() const {
-        return configKeepHierarchy;
-    }
+	bool IsHierarchyKept() const {
+		return configKeepHierarchy;
+	}
 
 private:
-    // -------------------------------------------------------------------
-    // Count the number of nodes
-    unsigned int CountNodes( aiNode* pcNode );
-
-    // -------------------------------------------------------------------
-    // Get a bitwise combination identifying the vertex format of a mesh
-    unsigned int GetMeshVFormat(aiMesh* pcMesh);
-
-    // -------------------------------------------------------------------
-    // Count the number of vertices in the whole scene and a given
-    // material index
-    void CountVerticesAndFaces( aiScene* pcScene, aiNode* pcNode,
-        unsigned int iMat,
-        unsigned int iVFormat,
-        unsigned int* piFaces,
-        unsigned int* piVertices);
-
-    // -------------------------------------------------------------------
-    // Collect vertex/face data
-    void CollectData( aiScene* pcScene, aiNode* pcNode,
-        unsigned int iMat,
-        unsigned int iVFormat,
-        aiMesh* pcMeshOut,
-        unsigned int aiCurrent[2],
-        unsigned int* num_refs);
-
-    // -------------------------------------------------------------------
-    // Get a list of all vertex formats that occur for a given material
-    // The output list contains duplicate elements
-    void GetVFormatList( aiScene* pcScene, unsigned int iMat,
-        std::list<unsigned int>& aiOut);
-
-    // -------------------------------------------------------------------
-    // Compute the absolute transformation matrices of each node
-    void ComputeAbsoluteTransform( aiNode* pcNode );
-
-    // -------------------------------------------------------------------
-    // Simple routine to build meshes in worldspace, no further optimization
-    void BuildWCSMeshes(std::vector<aiMesh*>& out, aiMesh** in,
-        unsigned int numIn, aiNode* node);
-
-    // -------------------------------------------------------------------
-    // Apply the node transformation to a mesh
-    void ApplyTransform(aiMesh* mesh, const aiMatrix4x4& mat);
-
-    // -------------------------------------------------------------------
-    // Reset transformation matrices to identity
-    void MakeIdentityTransform(aiNode* nd);
-
-    // -------------------------------------------------------------------
-    // Build reference counters for all meshes
-    void BuildMeshRefCountArray(aiNode* nd, unsigned int * refs);
-
-    //! Configuration option: keep scene hierarchy as long as possible
-    bool configKeepHierarchy;
-    bool configNormalize;
-    bool configTransform;
-    aiMatrix4x4 configTransformation;
-    bool mConfigPointCloud;
+	// -------------------------------------------------------------------
+	// Count the number of nodes
+	unsigned int CountNodes(const aiNode *pcNode) const;
+
+	// -------------------------------------------------------------------
+	// Get a bitwise combination identifying the vertex format of a mesh
+	unsigned int GetMeshVFormat(aiMesh *pcMesh) const;
+
+	// -------------------------------------------------------------------
+	// Count the number of vertices in the whole scene and a given
+	// material index
+	void CountVerticesAndFaces(const aiScene *pcScene, const aiNode *pcNode,
+			unsigned int iMat,
+			unsigned int iVFormat,
+			unsigned int *piFaces,
+			unsigned int *piVertices) const;
+
+	// -------------------------------------------------------------------
+	// Collect vertex/face data
+	void CollectData(const aiScene *pcScene, const aiNode *pcNode,
+			unsigned int iMat,
+			unsigned int iVFormat,
+			aiMesh *pcMeshOut,
+			unsigned int aiCurrent[2],
+			unsigned int *num_refs) const;
+
+	// -------------------------------------------------------------------
+	// Get a list of all vertex formats that occur for a given material
+	// The output list contains duplicate elements
+	void GetVFormatList(const aiScene *pcScene, unsigned int iMat,
+			std::list<unsigned int> &aiOut) const;
+
+	// -------------------------------------------------------------------
+	// Compute the absolute transformation matrices of each node
+	void ComputeAbsoluteTransform(aiNode *pcNode);
+
+	// -------------------------------------------------------------------
+	// Simple routine to build meshes in worldspace, no further optimization
+	void BuildWCSMeshes(std::vector<aiMesh *> &out, aiMesh **in,
+			unsigned int numIn, aiNode *node) const;
+
+	// -------------------------------------------------------------------
+	// Apply the node transformation to a mesh
+	void ApplyTransform(aiMesh *mesh, const aiMatrix4x4 &mat) const;
+
+	// -------------------------------------------------------------------
+	// Reset transformation matrices to identity
+	void MakeIdentityTransform(aiNode *nd) const;
+
+	// -------------------------------------------------------------------
+	// Build reference counters for all meshes
+	void BuildMeshRefCountArray(const aiNode *nd, unsigned int *refs) const;
+
+	//! Configuration option: keep scene hierarchy as long as possible
+	bool configKeepHierarchy;
+	bool configNormalize;
+	bool configTransform;
+	aiMatrix4x4 configTransformation;
+	bool mConfigPointCloud;
 };
 
 } // end of namespace Assimp

+ 21 - 1
code/glTF/glTFImporter.cpp

@@ -54,6 +54,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/ai_assert.h>
 #include <assimp/DefaultLogger.hpp>
 #include <assimp/importerdesc.h>
+#include <assimp/commonMetaData.h>
 
 #include <memory>
 
@@ -697,6 +698,25 @@ void glTFImporter::ImportEmbeddedTextures(glTF::Asset& r)
     }
 }
 
+void glTFImporter::ImportCommonMetadata(glTF::Asset& a)
+{
+    ai_assert(mScene->mMetaData == nullptr);
+    const bool hasVersion = !a.asset.version.empty();
+    const bool hasGenerator = !a.asset.generator.empty();
+    if (hasVersion || hasGenerator)
+    {
+        mScene->mMetaData = new aiMetadata;
+        if (hasVersion)
+        {
+            mScene->mMetaData->Add(AI_METADATA_SOURCE_FORMAT_VERSION, aiString(a.asset.version));
+        }
+        if (hasGenerator)
+        {
+            mScene->mMetaData->Add(AI_METADATA_SOURCE_GENERATOR, aiString(a.asset.generator));
+        }
+    }
+}
+
 void glTFImporter::InternReadFile(const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler)
 {
     // clean all member arrays
@@ -723,7 +743,7 @@ void glTFImporter::InternReadFile(const std::string& pFile, aiScene* pScene, IOS
     ImportLights(asset);
 
     ImportNodes(asset);
-
+    ImportCommonMetadata(asset);
 
     if (pScene->mNumMeshes == 0) {
         pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;

+ 1 - 1
code/glTF/glTFImporter.h

@@ -83,7 +83,7 @@ private:
     void ImportCameras(glTF::Asset& a);
     void ImportLights(glTF::Asset& a);
     void ImportNodes(glTF::Asset& a);
-
+    void ImportCommonMetadata(glTF::Asset& a);
 };
 
 } // Namespace assimp

+ 21 - 0
code/glTF2/glTF2Importer.cpp

@@ -55,6 +55,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/scene.h>
 #include <assimp/DefaultLogger.hpp>
 #include <assimp/Importer.hpp>
+#include <assimp/commonMetaData.h>
 
 #include <memory>
 #include <unordered_map>
@@ -1302,6 +1303,24 @@ void glTF2Importer::ImportEmbeddedTextures(glTF2::Asset &r) {
 	}
 }
 
+void glTF2Importer::ImportCommonMetadata(glTF2::Asset& a) {
+    ai_assert(mScene->mMetaData == nullptr);
+    const bool hasVersion = !a.asset.version.empty();
+    const bool hasGenerator = !a.asset.generator.empty();
+    if (hasVersion || hasGenerator)
+    {
+        mScene->mMetaData = new aiMetadata;
+        if (hasVersion)
+        {
+            mScene->mMetaData->Add(AI_METADATA_SOURCE_FORMAT_VERSION, aiString(a.asset.version));
+        }
+        if (hasGenerator)
+        {
+            mScene->mMetaData->Add(AI_METADATA_SOURCE_GENERATOR, aiString(a.asset.generator));
+        }
+    }
+}
+
 void glTF2Importer::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) {
 	// clean all member arrays
 	meshOffsets.clear();
@@ -1329,6 +1348,8 @@ void glTF2Importer::InternReadFile(const std::string &pFile, aiScene *pScene, IO
 
 	ImportAnimations(asset);
 
+    ImportCommonMetadata(asset);
+
 	if (pScene->mNumMeshes == 0) {
 		pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
 	}

+ 1 - 0
code/glTF2/glTF2Importer.h

@@ -84,6 +84,7 @@ private:
     void ImportLights(glTF2::Asset& a);
     void ImportNodes(glTF2::Asset& a);
     void ImportAnimations(glTF2::Asset& a);
+    void ImportCommonMetadata(glTF2::Asset& a);
 };
 
 } // Namespace assimp

+ 1 - 1
include/assimp/LineSplitter.h

@@ -72,7 +72,7 @@ for(LineSplitter splitter(stream);splitter;++splitter) {
        if (strtol(splitter[2]) > 5) { .. }
     }
 
-    std::cout << "Current line is: " << splitter.get_index() << std::endl;
+    ASSIMP_LOG_DEBUG_F("Current line is: ", splitter.get_index());
 }
 @endcode
 */

+ 63 - 0
include/assimp/commonMetaData.h

@@ -0,0 +1,63 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+
+/** @file commonMetaData.h
+ *  @brief Defines a set of common scene metadata keys.
+ */
+#pragma once
+#ifndef AI_COMMONMETADATA_H_INC
+#define AI_COMMONMETADATA_H_INC
+
+/// Scene metadata holding the name of the importer which loaded the source asset.
+/// This is always present if the scene was created from an imported asset.
+#define AI_METADATA_SOURCE_FORMAT "SourceAsset_Format"
+
+/// Scene metadata holding the version of the source asset as a string, if available.
+/// Not all formats add this metadata.
+#define AI_METADATA_SOURCE_FORMAT_VERSION "SourceAsset_FormatVersion"
+
+/// Scene metadata holding the name of the software which generated the source asset, if available.
+/// Not all formats add this metadata.
+#define AI_METADATA_SOURCE_GENERATOR "SourceAsset_Generator"
+
+#endif

+ 19 - 2
include/assimp/metadata.h

@@ -286,8 +286,8 @@ struct aiMetadata {
 			new_values[i] = mValues[i];
 		}
 
-		delete mKeys;
-		delete mValues;
+		delete[] mKeys;
+		delete[] mValues;
 
 		mKeys = new_keys;
 		mValues = new_values;
@@ -377,6 +377,23 @@ struct aiMetadata {
 		return true;
 	}
 
+    /// Check whether there is a metadata entry for the given key.
+    /// \param [in] Key - the key value value to check for.
+    inline
+    bool HasKey(const char* key) {
+        if ( nullptr == key ) {
+            return false;
+        }
+        
+        // Search for the given key
+        for (unsigned int i = 0; i < mNumProperties; ++i) {
+            if ( 0 == strncmp(mKeys[i].C_Str(), key, mKeys[i].length ) ) {
+                return true;
+            }
+        }
+        return false;
+    }
+
 #endif // __cplusplus
 
 };

+ 27 - 0
test/unit/utFBXImporterExporter.cpp

@@ -50,6 +50,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/material.h>
 #include <assimp/scene.h>
 #include <assimp/types.h>
+#include <assimp/commonMetaData.h>
 
 using namespace Assimp;
 
@@ -283,3 +284,29 @@ TEST_F(utFBXImporterExporter, importOrphantEmbeddedTextureTest) {
     ASSERT_TRUE(scene->mTextures[0]->pcData);
     ASSERT_EQ(9026u, scene->mTextures[0]->mWidth) << "FBX ASCII base64 compression used for a texture.";
 }
+
+TEST_F(utFBXImporterExporter, sceneMetadata) {
+    Assimp::Importer importer;
+    const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/FBX/global_settings.fbx",
+        aiProcess_ValidateDataStructure);
+    ASSERT_NE(scene, nullptr);
+    ASSERT_NE(scene->mMetaData, nullptr);
+    {
+        ASSERT_TRUE(scene->mMetaData->HasKey(AI_METADATA_SOURCE_FORMAT));
+        aiString format;
+        ASSERT_TRUE(scene->mMetaData->Get(AI_METADATA_SOURCE_FORMAT, format));
+        ASSERT_EQ(strcmp(format.C_Str(), "Autodesk FBX Importer"), 0);
+    }
+    {
+        ASSERT_TRUE(scene->mMetaData->HasKey(AI_METADATA_SOURCE_FORMAT_VERSION));
+        aiString version;
+        ASSERT_TRUE(scene->mMetaData->Get(AI_METADATA_SOURCE_FORMAT_VERSION, version));
+        ASSERT_EQ(strcmp(version.C_Str(), "7400"), 0);
+    }
+    {
+        ASSERT_TRUE(scene->mMetaData->HasKey(AI_METADATA_SOURCE_GENERATOR));
+        aiString generator;
+        ASSERT_TRUE(scene->mMetaData->Get(AI_METADATA_SOURCE_GENERATOR, generator));
+        ASSERT_EQ(strncmp(generator.C_Str(), "Blender", 7), 0);
+    }
+}

+ 28 - 0
test/unit/utglTF2ImportExport.cpp

@@ -45,6 +45,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/Exporter.hpp>
 #include <assimp/postprocess.h>
 #include <assimp/scene.h>
+#include <assimp/commonMetaData.h>
+
 #include <array>
 
 #include <assimp/pbrmaterial.h>
@@ -439,6 +441,32 @@ TEST_F(utglTF2ImportExport, error_string_preserved) {
 
 #endif // ASSIMP_BUILD_NO_EXPORT
 
+TEST_F(utglTF2ImportExport, sceneMetadata) {
+    Assimp::Importer importer;
+    const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF/BoxTextured.gltf",
+        aiProcess_ValidateDataStructure);
+    ASSERT_NE(scene, nullptr);
+    ASSERT_NE(scene->mMetaData, nullptr);
+    {
+        ASSERT_TRUE(scene->mMetaData->HasKey(AI_METADATA_SOURCE_FORMAT));
+        aiString format;
+        ASSERT_TRUE(scene->mMetaData->Get(AI_METADATA_SOURCE_FORMAT, format));
+        ASSERT_EQ(strcmp(format.C_Str(), "glTF2 Importer"), 0);
+    }
+    {
+        ASSERT_TRUE(scene->mMetaData->HasKey(AI_METADATA_SOURCE_FORMAT_VERSION));
+        aiString version;
+        ASSERT_TRUE(scene->mMetaData->Get(AI_METADATA_SOURCE_FORMAT_VERSION, version));
+        ASSERT_EQ(strcmp(version.C_Str(), "2.0"), 0);
+    }
+    {
+        ASSERT_TRUE(scene->mMetaData->HasKey(AI_METADATA_SOURCE_GENERATOR));
+        aiString generator;
+        ASSERT_TRUE(scene->mMetaData->Get(AI_METADATA_SOURCE_GENERATOR, generator));
+        ASSERT_EQ(strcmp(generator.C_Str(), "COLLADA2GLTF"), 0);
+    }
+}
+
 TEST_F(utglTF2ImportExport, texcoords) {
     Assimp::Importer importer;
     const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTexcoords-glTF/boxTexcoords.gltf",

+ 26 - 0
test/unit/utglTFImportExport.cpp

@@ -47,6 +47,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/postprocess.h>
 
 #include <assimp/scene.h>
+#include <assimp/commonMetaData.h>
 
 using namespace Assimp;
 
@@ -85,3 +86,28 @@ TEST_F(utglTFImportExport, incorrect_vertex_arrays) {
     EXPECT_EQ(scene->mMeshes[7]->mNumVertices, 35u);
     EXPECT_EQ(scene->mMeshes[7]->mNumFaces, 17u);
 }
+
+TEST_F(utglTFImportExport, sceneMetadata) {
+    Assimp::Importer importer;
+    const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF/TwoBoxes/TwoBoxes.gltf", aiProcess_ValidateDataStructure);
+    ASSERT_TRUE(scene);
+    ASSERT_TRUE(scene->mMetaData);
+    {
+        ASSERT_TRUE(scene->mMetaData->HasKey(AI_METADATA_SOURCE_FORMAT));
+        aiString format;
+        ASSERT_TRUE(scene->mMetaData->Get(AI_METADATA_SOURCE_FORMAT, format));
+        ASSERT_EQ(strcmp(format.C_Str(), "glTF Importer"), 0);
+    }
+    {
+        ASSERT_TRUE(scene->mMetaData->HasKey(AI_METADATA_SOURCE_FORMAT_VERSION));
+        aiString version;
+        ASSERT_TRUE(scene->mMetaData->Get(AI_METADATA_SOURCE_FORMAT_VERSION, version));
+        ASSERT_EQ(strcmp(version.C_Str(), "1.0"), 0);
+    }
+    {
+        ASSERT_TRUE(scene->mMetaData->HasKey(AI_METADATA_SOURCE_GENERATOR));
+        aiString generator;
+        ASSERT_TRUE(scene->mMetaData->Get(AI_METADATA_SOURCE_GENERATOR, generator));
+        ASSERT_EQ(strncmp(generator.C_Str(), "collada2gltf", 12), 0);
+    }
+}