Browse Source

Merge branch 'master' of https://github.com/assimp/assimp

Kim Kulling 8 năm trước cách đây
mục cha
commit
b5f770e456

+ 7 - 0
.gitattributes

@@ -13,3 +13,10 @@ CHANGES text eol=lf
 CREDITS text eol=lf
 LICENSE text eol=lf
 Readme.md text eol=lf
+# make sure that repo-specific settings (.gitignore, CI-setup,...)
+# are excluded from the source-package generated via 'git archive'
+.git*      	export-ignore
+/.travis*	export-ignore
+/.coveralls*	export-ignore
+appveyor.yml	export-ignore
+

+ 10 - 8
.travis.yml

@@ -28,14 +28,16 @@ env:
     - secure: "lZ7pHQvl5dpZWzBQAaIMf0wqrvtcZ4wiZKeIZjf83TEsflW8+z0uTpIuN30ZV6Glth/Sq1OhLnTP5+N57fZU/1ebA5twHdvP4bS5CIUUg71/CXQZNl36xeaqvxsG/xRrdpKOsPdjAOsQ9KPTQulsX43XDLS7CasMiLvYOpqKcPc="
     - PV=r8e PLATF=linux-x86_64 NDK_HOME=${TRAVIS_BUILD_DIR}/android-ndk-${PV} PATH=${PATH}:${NDK_HOME}
   matrix:
-    - LINUX=1 TRAVIS_NO_EXPORT=YES ENABLE_COVERALLS=ON
-    - LINUX=1 TRAVIS_NO_EXPORT=NO  ENABLE_COVERALLS=OFF
-    - LINUX=1 SHARED_BUILD=ON      ENABLE_COVERALLS=OFF
-    - LINUX=1 SHARED_BUILD=OFF     ENABLE_COVERALLS=OFF
-
-compiler:
-  - gcc
-  - clang
+    - os: linux LINUX=1 TRAVIS_NO_EXPORT=YES ENABLE_COVERALLS=ON
+      compiler: gcc
+    - os: linux LINUX=1 TRAVIS_NO_EXPORT=NO  ENABLE_COVERALLS=OFF
+      compiler: clang
+    - os: linux LINUX=1 SHARED_BUILD=ON TRAVIS_NO_EXPORT=NO  ENABLE_COVERALLS=OFF
+      compiler: gcc
+    - os: linux LINUX=1 SHARED_BUILD=ON TRAVIS_NO_EXPORT=NO  ENABLE_COVERALLS=OFF
+      compiler: clang
+    - os: osx
+      osx_image: xcode8.2
 
 install:
   - if [ $ANDROID ]; then wget -c http://dl.google.com/android/ndk/android-ndk-${PV}-${PLATF}.tar.bz2 && tar xf android-ndk-${PV}-${PLATF}.tar.bz2 ; fi

+ 6 - 0
CMakeLists.txt

@@ -148,6 +148,12 @@ find_package(OpenMP)
 if (OPENMP_FOUND)
     SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
     SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
+
+    IF(MSVC)
+        IF(MSVC_VERSION GREATER 1910)
+            SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zc:twoPhase-")
+        ENDIF()
+    ENDIF()
 endif()
 
 CONFIGURE_FILE(

+ 7 - 1
Readme.md

@@ -8,7 +8,6 @@ A library to import and export various 3d-model-formats including scene-post-pro
   <img alt="Coverity Scan Build Status"
        src="https://scan.coverity.com/projects/5607/badge.svg"/>
 </a>
-<span class="badge-patreon"><a href="https://www.patreon.com/assimp" title="Donate to this project using Patreon"><img src="https://img.shields.io/badge/patreon-donate-yellow.svg" alt="Patreon donate button" /></a></span>
 [![Coverage Status](https://coveralls.io/repos/github/assimp/assimp/badge.svg?branch=master)](https://coveralls.io/github/assimp/assimp?branch=master)
 [![Join the chat at https://gitter.im/assimp/assimp](https://badges.gitter.im/assimp/assimp.svg)](https://gitter.im/assimp/assimp?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
 <br>
@@ -19,6 +18,11 @@ Additionally, assimp features various __mesh post processing tools__: normals an
 
 This is the development repo containing the latest features and bugfixes. For productive use though, we recommend one of the stable releases available from [Github Assimp Releases](https://github.com/assimp/assimp/releases).
 
+Monthly donations via Patreon:
+<br>[![Patreon](https://cloud.githubusercontent.com/assets/8225057/5990484/70413560-a9ab-11e4-8942-1a63607c0b00.png)](http://www.patreon.com/assimp)
+
+<br>
+
 One-off donations via PayPal:
 <br>[![PayPal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=4JRJVPXC4QJM4)
 
@@ -114,6 +118,8 @@ Take a look into the `INSTALL` file. Our build system is CMake, if you used CMak
 * [.NET](port/AssimpNET/Readme.md)
 * [Pascal](port/AssimpPascal/Readme.md)
 * [Javascript (Alpha)](https://github.com/makc/assimp2json)
+* [Unity 3d Plugin] (https://www.assetstore.unity3d.com/en/#!/content/91777)
+* [JVM](https://github.com/kotlin-graphics/assimp) Full jvm port (currently supported obj, ply, stl, ~collada)
 
 ### Other tools ###
 [open3mod](https://github.com/acgessler/open3mod) is a powerful 3D model viewer based on Assimp's import and export abilities.

+ 6 - 3
code/3DSConverter.cpp

@@ -56,18 +56,19 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 using namespace Assimp;
 
+static const unsigned int NotSet = 0xcdcdcdcd;
+
 // ------------------------------------------------------------------------------------------------
 // Setup final material indices, generae a default material if necessary
 void Discreet3DSImporter::ReplaceDefaultMaterial()
 {
-
     // Try to find an existing material that matches the
     // typical default material setting:
     // - no textures
     // - diffuse color (in grey!)
     // NOTE: This is here to workaround the fact that some
     // exporters are writing a default material, too.
-    unsigned int idx = 0xcdcdcdcd;
+    unsigned int idx( NotSet );
     for (unsigned int i = 0; i < mScene->mMaterials.size();++i)
     {
         std::string s = mScene->mMaterials[i].mName;
@@ -93,7 +94,9 @@ void Discreet3DSImporter::ReplaceDefaultMaterial()
         }
         idx = i;
     }
-    if (0xcdcdcdcd == idx)idx = (unsigned int)mScene->mMaterials.size();
+    if ( NotSet == idx ) {
+        idx = ( unsigned int )mScene->mMaterials.size();
+    }
 
     // now iterate through all meshes and through all faces and
     // find all faces that are using the default material

+ 1 - 1
code/AssbinExporter.cpp

@@ -325,7 +325,7 @@ inline size_t WriteArray(IOStream * stream, const T* in, unsigned int size)
         {
             AssbinChunkWriter chunk( container, ASSBIN_CHUNK_AINODE );
 
-			size_t nb_metadata = (node->mMetaData != NULL ? node->mMetaData->mNumProperties : 0);
+			unsigned int nb_metadata = (node->mMetaData != NULL ? node->mMetaData->mNumProperties : 0);
 
             Write<aiString>(&chunk,node->mName);
             Write<aiMatrix4x4>(&chunk,node->mTransformation);

+ 5 - 5
code/ColladaExporter.cpp

@@ -866,8 +866,8 @@ void ColladaExporter::WriteController( size_t pIndex)
 
     std::vector<ai_real> bind_poses;
     bind_poses.reserve(mesh->mNumBones * 16);
-    for( size_t i = 0; i < mesh->mNumBones; ++i)
-        for( size_t j = 0; j < 4; ++j)
+    for(unsigned int i = 0; i < mesh->mNumBones; ++i)
+        for( unsigned int j = 0; j < 4; ++j)
             bind_poses.insert(bind_poses.end(), mesh->mBones[i]->mOffsetMatrix[j], mesh->mBones[i]->mOffsetMatrix[j] + 4);
 
     WriteFloatArray( idstr + "-skin-bind_poses", FloatType_Mat4x4, (const ai_real*) bind_poses.data(), bind_poses.size() / 16);
@@ -924,11 +924,11 @@ void ColladaExporter::WriteController( size_t pIndex)
 
     ai_uint weight_index = 0;
     std::vector<ai_int> joint_weight_indices(2 * joint_weight_indices_length, (ai_int)-1);
-    for( size_t i = 0; i < mesh->mNumBones; ++i)
-        for( size_t j = 0; j < mesh->mBones[i]->mNumWeights; ++j)
+    for( unsigned int i = 0; i < mesh->mNumBones; ++i)
+        for( unsigned j = 0; j < mesh->mBones[i]->mNumWeights; ++j)
         {
             unsigned int vId = mesh->mBones[i]->mWeights[j].mVertexId;
-            for( size_t k = 0; k < num_influences[vId]; ++k)
+            for( ai_uint k = 0; k < num_influences[vId]; ++k)
             {
                 if (joint_weight_indices[2 * (accum_influences[vId] + k)] == -1)
                 {

+ 8 - 8
code/ColladaLoader.cpp

@@ -728,7 +728,7 @@ aiMesh* ColladaLoader::CreateMesh( const ColladaParser& pParser, const Collada::
                                 ? aiMorphingMethod_MORPH_RELATIVE
                                 : aiMorphingMethod_MORPH_NORMALIZED;
         dstMesh->mAnimMeshes = new aiAnimMesh*[animMeshes.size()];
-        dstMesh->mNumAnimMeshes = animMeshes.size();
+        dstMesh->mNumAnimMeshes = static_cast<unsigned int>(animMeshes.size());
         for (unsigned int i = 0; i < animMeshes.size(); i++)
             dstMesh->mAnimMeshes[i] = animMeshes.at(i);
     }
@@ -1377,9 +1377,9 @@ void ColladaLoader::CreateAnimation( aiScene* pScene, const ColladaParser& pPars
         {
               aiNodeAnim* dstAnim = new aiNodeAnim;
               dstAnim->mNodeName = nodeName;
-              dstAnim->mNumPositionKeys = resultTrafos.size();
-              dstAnim->mNumRotationKeys= resultTrafos.size();
-              dstAnim->mNumScalingKeys = resultTrafos.size();
+              dstAnim->mNumPositionKeys = static_cast<unsigned int>(resultTrafos.size());
+              dstAnim->mNumRotationKeys = static_cast<unsigned int>(resultTrafos.size());
+              dstAnim->mNumScalingKeys = static_cast<unsigned int>(resultTrafos.size());
               dstAnim->mPositionKeys = new aiVectorKey[resultTrafos.size()];
               dstAnim->mRotationKeys = new aiQuatKey[resultTrafos.size()];
               dstAnim->mScalingKeys = new aiVectorKey[resultTrafos.size()];
@@ -1445,11 +1445,11 @@ void ColladaLoader::CreateAnimation( aiScene* pScene, const ColladaParser& pPars
                     ++morphAnimChannelIndex;
                 }
 
-                morphAnim->mNumKeys = morphTimeValues.size();
+                morphAnim->mNumKeys = static_cast<unsigned int>(morphTimeValues.size());
                 morphAnim->mKeys = new aiMeshMorphKey[morphAnim->mNumKeys];
                 for (unsigned int key = 0; key < morphAnim->mNumKeys; key++)
                 {
-                    morphAnim->mKeys[key].mNumValuesAndWeights = morphChannels.size();
+                    morphAnim->mKeys[key].mNumValuesAndWeights = static_cast<unsigned int>(morphChannels.size());
                     morphAnim->mKeys[key].mValues = new unsigned int [morphChannels.size()];
                     morphAnim->mKeys[key].mWeights = new double [morphChannels.size()];
 
@@ -1470,13 +1470,13 @@ void ColladaLoader::CreateAnimation( aiScene* pScene, const ColladaParser& pPars
     {
         aiAnimation* anim = new aiAnimation;
         anim->mName.Set( pName);
-        anim->mNumChannels = anims.size();
+        anim->mNumChannels = static_cast<unsigned int>(anims.size());
         if (anim->mNumChannels > 0)
         {
             anim->mChannels = new aiNodeAnim*[anims.size()];
             std::copy( anims.begin(), anims.end(), anim->mChannels);
         }
-        anim->mNumMorphMeshChannels = morphAnims.size();
+        anim->mNumMorphMeshChannels = static_cast<unsigned int>(morphAnims.size());
         if (anim->mNumMorphMeshChannels > 0)
         {
             anim->mMorphMeshChannels = new aiMeshMorphAnim*[anim->mNumMorphMeshChannels];

+ 10 - 1
code/ColladaParser.cpp

@@ -2231,7 +2231,7 @@ void ColladaParser::ReadIndexData( Mesh* pMesh)
     }
 
 #ifdef ASSIMP_BUILD_DEBUG
-	if (primType != Prim_TriFans && primType != Prim_TriStrips &&
+	if (primType != Prim_TriFans && primType != Prim_TriStrips && primType != Prim_LineStrip &&
         primType != Prim_Lines) { // this is ONLY to workaround a bug in SketchUp 15.3.331 where it writes the wrong 'count' when it writes out the 'lines'.
         ai_assert(actualPrimitives == numPrimitives);
     }
@@ -2400,6 +2400,10 @@ size_t ColladaParser::ReadPrimitives( Mesh* pMesh, std::vector<InputChannel>& pP
         size_t numberOfVertices = indices.size() / numOffsets;
         numPrimitives = numberOfVertices - 2;
     }
+    if (pPrimType == Prim_LineStrip) {
+        size_t numberOfVertices = indices.size() / numOffsets;
+        numPrimitives = numberOfVertices - 1;
+    }
 
     pMesh->mFaceSize.reserve( numPrimitives);
     pMesh->mFacePosIndices.reserve( indices.size() / numOffsets);
@@ -2416,6 +2420,11 @@ size_t ColladaParser::ReadPrimitives( Mesh* pMesh, std::vector<InputChannel>& pP
                 for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++)
                     CopyVertex(currentVertex, numOffsets, numPoints, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
                 break;
+            case Prim_LineStrip:
+                numPoints = 2;
+                for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++)
+                    CopyVertex(currentVertex, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
+                break;
             case Prim_Triangles:
                 numPoints = 3;
                 for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++)

+ 2 - 6
code/D3MFImporter.cpp

@@ -95,14 +95,10 @@ public:
     XmlSerializer(XmlReader* xmlReader)
         : xmlReader(xmlReader)
     {
-
+		// empty
     }
 
-    void ImportXml(aiScene* scene)
-    {
-
-        scene->mFlags |= AI_SCENE_FLAGS_NON_VERBOSE_FORMAT;
-
+    void ImportXml(aiScene* scene) {
         scene->mRootNode = new aiNode();
         std::vector<aiNode*> children;
 

+ 56 - 58
code/FBXBinaryTokenizer.cpp

@@ -56,47 +56,46 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 namespace Assimp {
 namespace FBX {
 
-enum Flag
-{
-   e_unknown_0 = 1 << 0,
-   e_unknown_1 = 1 << 1,
-   e_unknown_2 = 1 << 2,
-   e_unknown_3 = 1 << 3,
-   e_unknown_4 = 1 << 4,
-   e_unknown_5 = 1 << 5,
-   e_unknown_6 = 1 << 6,
-   e_unknown_7 = 1 << 7,
-   e_unknown_8 = 1 << 8,
-   e_unknown_9 = 1 << 9,
-   e_unknown_10 = 1 << 10,
-   e_unknown_11 = 1 << 11,
-   e_unknown_12 = 1 << 12,
-   e_unknown_13 = 1 << 13,
-   e_unknown_14 = 1 << 14,
-   e_unknown_15 = 1 << 15,
-   e_unknown_16 = 1 << 16,
-   e_unknown_17 = 1 << 17,
-   e_unknown_18 = 1 << 18,
-   e_unknown_19 = 1 << 19,
-   e_unknown_20 = 1 << 20,
-   e_unknown_21 = 1 << 21,
-   e_unknown_22 = 1 << 22,
-   e_unknown_23 = 1 << 23,
-   e_flag_field_size_64_bit = 1 << 24, // Not sure what is 
-   e_unknown_25 = 1 << 25,
-   e_unknown_26 = 1 << 26,
-   e_unknown_27 = 1 << 27,
-   e_unknown_28 = 1 << 28,
-   e_unknown_29 = 1 << 29,
-   e_unknown_30 = 1 << 30,
-   e_unknown_31 = 1 << 31
-};
-
-bool check_flag(uint32_t flags, Flag to_check)
-{
-   return (flags & to_check) != 0;
-}
-
+//enum Flag
+//{
+//   e_unknown_0 = 1 << 0,
+//   e_unknown_1 = 1 << 1,
+//   e_unknown_2 = 1 << 2,
+//   e_unknown_3 = 1 << 3,
+//   e_unknown_4 = 1 << 4,
+//   e_unknown_5 = 1 << 5,
+//   e_unknown_6 = 1 << 6,
+//   e_unknown_7 = 1 << 7,
+//   e_unknown_8 = 1 << 8,
+//   e_unknown_9 = 1 << 9,
+//   e_unknown_10 = 1 << 10,
+//   e_unknown_11 = 1 << 11,
+//   e_unknown_12 = 1 << 12,
+//   e_unknown_13 = 1 << 13,
+//   e_unknown_14 = 1 << 14,
+//   e_unknown_15 = 1 << 15,
+//   e_unknown_16 = 1 << 16,
+//   e_unknown_17 = 1 << 17,
+//   e_unknown_18 = 1 << 18,
+//   e_unknown_19 = 1 << 19,
+//   e_unknown_20 = 1 << 20,
+//   e_unknown_21 = 1 << 21,
+//   e_unknown_22 = 1 << 22,
+//   e_unknown_23 = 1 << 23,
+//   e_flag_field_size_64_bit = 1 << 24, // Not sure what is 
+//   e_unknown_25 = 1 << 25,
+//   e_unknown_26 = 1 << 26,
+//   e_unknown_27 = 1 << 27,
+//   e_unknown_28 = 1 << 28,
+//   e_unknown_29 = 1 << 29,
+//   e_unknown_30 = 1 << 30,
+//   e_unknown_31 = 1 << 31
+//};
+//
+//bool check_flag(uint32_t flags, Flag to_check)
+//{
+//	return (flags & to_check) != 0;
+//}
 // ------------------------------------------------------------------------------------------------
 Token::Token(const char* sbegin, const char* send, TokenType type, unsigned int offset)
     :
@@ -341,10 +340,10 @@ void ReadData(const char*& sbegin_out, const char*& send_out, const char* input,
 
 
 // ------------------------------------------------------------------------------------------------
-bool ReadScope(TokenList& output_tokens, const char* input, const char*& cursor, const char* end, uint32_t const flags)
+bool ReadScope(TokenList& output_tokens, const char* input, const char*& cursor, const char* end, bool const is64bits)
 {
     // the first word contains the offset at which this block ends
-    const uint64_t end_offset = /*check_flag(flags, e_flag_field_size_64_bit) ? ReadDoubleWord(input, cursor, end) : */ReadWord(input, cursor, end);
+	const uint64_t end_offset = is64bits ? ReadDoubleWord(input, cursor, end) : ReadWord(input, cursor, end);
 
     // we may get 0 if reading reached the end of the file -
     // fbx files have a mysterious extra footer which I don't know
@@ -362,10 +361,10 @@ bool ReadScope(TokenList& output_tokens, const char* input, const char*& cursor,
     }
 
     // the second data word contains the number of properties in the scope
-    const uint64_t prop_count = /*check_flag(flags, e_flag_field_size_64_bit) ? ReadDoubleWord(input, cursor, end) : */ReadWord(input, cursor, end);
+	const uint64_t prop_count = is64bits ? ReadDoubleWord(input, cursor, end) : ReadWord(input, cursor, end);
 
     // the third data word contains the length of the property list
-    const uint64_t prop_length = /*check_flag(flags, e_flag_field_size_64_bit) ? ReadDoubleWord(input, cursor, end) :*/ ReadWord(input, cursor, end);
+	const uint64_t prop_length = is64bits ? ReadDoubleWord(input, cursor, end) : ReadWord(input, cursor, end);
 
     // now comes the name of the scope/key
     const char* sbeg, *send;
@@ -392,7 +391,7 @@ bool ReadScope(TokenList& output_tokens, const char* input, const char*& cursor,
     // at the end of each nested block, there is a NUL record to indicate
     // that the sub-scope exists (i.e. to distinguish between P: and P : {})
     // this NUL record is 13 bytes long on 32 bit version and 25 bytes long on 64 bit.
-    const size_t sentinel_block_length = /*check_flag(flags, e_flag_field_size_64_bit) ? (sizeof(uint64_t) * 3 + 1) : */(sizeof(uint32_t) * 3 + 1);
+	const size_t sentinel_block_length = is64bits ? (sizeof(uint64_t)* 3 + 1) : (sizeof(uint32_t)* 3 + 1);
 
     if (Offset(input, cursor) < end_offset) {
         if (end_offset - Offset(input, cursor) < sentinel_block_length) {
@@ -403,7 +402,7 @@ bool ReadScope(TokenList& output_tokens, const char* input, const char*& cursor,
 
         // XXX this is vulnerable to stack overflowing ..
         while(Offset(input, cursor) < end_offset - sentinel_block_length) {
-            ReadScope(output_tokens, input, cursor, input + end_offset - sentinel_block_length, flags);
+			ReadScope(output_tokens, input, cursor, input + end_offset - sentinel_block_length, is64bits);
         }
         output_tokens.push_back(new_Token(cursor, cursor + 1, TokenType_CLOSE_BRACKET, Offset(input, cursor) ));
 
@@ -426,6 +425,7 @@ bool ReadScope(TokenList& output_tokens, const char* input, const char*& cursor,
 }
 
 // ------------------------------------------------------------------------------------------------
+// TODO: Test FBX Binary files newer than the 7500 version to check if the 64 bits address behaviour is consistent
 void TokenizeBinary(TokenList& output_tokens, const char* input, unsigned int length)
 {
     ai_assert(input);
@@ -438,19 +438,17 @@ void TokenizeBinary(TokenList& output_tokens, const char* input, unsigned int le
         TokenizeError("magic bytes not found",0);
     }
 
-
-    //uint32_t offset = 0x15;
-    const char* cursor = input + 0x15;
-
-    const uint32_t flags = ReadWord(input, cursor, input + length);
-
-    const uint8_t padding_0 = ReadByte(input, cursor, input + length); // unused
-    (void) padding_0;
-    const uint8_t padding_1 = ReadByte(input, cursor, input + length); // unused
-    (void) padding_1;
+    const char* cursor = input + 18;
+	const uint8_t unknown_1 = ReadByte(input, cursor, input + length);
+	const uint8_t unknown_2 = ReadByte(input, cursor, input + length);
+	const uint8_t unknown_3 = ReadByte(input, cursor, input + length);
+	const uint8_t unknown_4 = ReadByte(input, cursor, input + length);
+	const uint8_t unknown_5 = ReadByte(input, cursor, input + length);
+	const uint32_t version = ReadWord(input, cursor, input + length);
+	const bool is64bits = version >= 7500;
     while (cursor < input + length)
     {
-        if(!ReadScope(output_tokens, input, cursor, input + length, flags)) {
+		if (!ReadScope(output_tokens, input, cursor, input + length, is64bits)) {
             break;
         }
     }

+ 19 - 18
code/FBXConverter.cpp

@@ -2367,8 +2367,13 @@ void Converter::ConvertAnimationStack( const AnimationStack& st )
 
     int64_t start_time = st.LocalStart();
     int64_t stop_time = st.LocalStop();
-    double start_timeF = CONVERT_FBX_TIME( start_time );
-    double stop_timeF = CONVERT_FBX_TIME( stop_time );
+    bool has_local_startstop = start_time != 0 || stop_time != 0;
+    if ( !has_local_startstop ) {
+        // no time range given, so accept every keyframe and use the actual min/max time
+        // the numbers are INT64_MIN/MAX, the 20000 is for safety because GenerateNodeAnimations uses an epsilon of 10000
+        start_time = -9223372036854775807ll + 20000;
+        stop_time = 9223372036854775807ll - 20000;
+    }
 
     try {
         for( const NodeMap::value_type& kv : node_map ) {
@@ -2400,27 +2405,23 @@ void Converter::ConvertAnimationStack( const AnimationStack& st )
         return;
     }
 
-    //adjust relative timing for animation
-    {
-        double start_fps = start_timeF * anim_fps;
-
-        for ( unsigned int c = 0; c < anim->mNumChannels; c++ )
-        {
-            aiNodeAnim* channel = anim->mChannels[ c ];
-            for ( uint32_t i = 0; i < channel->mNumPositionKeys; i++ )
-                channel->mPositionKeys[ i ].mTime -= start_fps;
-            for ( uint32_t i = 0; i < channel->mNumRotationKeys; i++ )
-                channel->mRotationKeys[ i ].mTime -= start_fps;
-            for ( uint32_t i = 0; i < channel->mNumScalingKeys; i++ )
-                channel->mScalingKeys[ i ].mTime -= start_fps;
-        }
+    double start_time_fps = has_local_startstop ? (CONVERT_FBX_TIME(start_time) * anim_fps) : min_time;
+    double stop_time_fps = has_local_startstop ? (CONVERT_FBX_TIME(stop_time) * anim_fps) : max_time;
 
-        max_time -= min_time;
+    // adjust relative timing for animation
+    for ( unsigned int c = 0; c < anim->mNumChannels; c++ ) {
+        aiNodeAnim* channel = anim->mChannels[ c ];
+        for ( uint32_t i = 0; i < channel->mNumPositionKeys; i++ )
+            channel->mPositionKeys[ i ].mTime -= start_time_fps;
+        for ( uint32_t i = 0; i < channel->mNumRotationKeys; i++ )
+            channel->mRotationKeys[ i ].mTime -= start_time_fps;
+        for ( uint32_t i = 0; i < channel->mNumScalingKeys; i++ )
+            channel->mScalingKeys[ i ].mTime -= start_time_fps;
     }
 
     // for some mysterious reason, mDuration is simply the maximum key -- the
     // validator always assumes animations to start at zero.
-    anim->mDuration = ( stop_timeF - start_timeF ) * anim_fps;
+    anim->mDuration = stop_time_fps - start_time_fps;
     anim->mTicksPerSecond = anim_fps;
 }
 

+ 1 - 1
code/IFCProfile.cpp

@@ -128,7 +128,7 @@ void ProcessParametrizedProfile(const IfcParameterizedProfileDef& def, TempMesh&
             meshout.verts.push_back( IfcVector3( std::cos(angle)*radius, std::sin(angle)*radius, 0.f ));
         }
 
-        meshout.vertcnt.push_back(segments);
+        meshout.vertcnt.push_back(static_cast<unsigned int>(segments));
     }
     else if( const IfcIShapeProfileDef* const ishape = def.ToPtr<IfcIShapeProfileDef>()) {
         // construct simplified IBeam shape

+ 28 - 28
code/MDLFileData.h

@@ -164,7 +164,7 @@ struct Header {
 
     //! Could be the total size of the file (and not a float)
     float size;
-} /* PACK_STRUCT */;
+} PACK_STRUCT;
 
 
 // -------------------------------------------------------------------------------------
@@ -223,7 +223,7 @@ struct Header_MDL7 {
 
     //! Size of the Frame_MDL7 data structure used in the file
     uint16_t frame_stc_size;
-} /* PACK_STRUCT */;
+} PACK_STRUCT;
 
 
 // -------------------------------------------------------------------------------------
@@ -242,7 +242,7 @@ struct Bone_MDL7 {
 
     //! Optional name of the bone
     char name[1 /* DUMMY SIZE */];
-} /* PACK_STRUCT */;
+} PACK_STRUCT;
 
 #if (!defined AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_20_CHARS)
 #   define AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_20_CHARS (16 + 20)
@@ -290,7 +290,7 @@ struct Group_MDL7 {
 
     //! Number of frames
     int32_t numframes;
-} /* PACK_STRUCT */;
+} PACK_STRUCT;
 
 #define AI_MDL7_SKINTYPE_MIPFLAG                0x08
 #define AI_MDL7_SKINTYPE_MATERIAL               0x10
@@ -312,7 +312,7 @@ struct Deformer_MDL7 {
     int32_t group_index;
     int32_t elements;
     int32_t deformerdata_size;
-} /* PACK_STRUCT */;
+} PACK_STRUCT;
 
 
 // -------------------------------------------------------------------------------------
@@ -324,7 +324,7 @@ struct DeformerElement_MDL7 {
     int32_t element_index;
     char    element_name[AI_MDL7_MAX_BONENAMESIZE];
     int32_t weights;
-} /* PACK_STRUCT */;
+} PACK_STRUCT;
 
 // -------------------------------------------------------------------------------------
 /** \struct DeformerWeight_MDL7
@@ -334,7 +334,7 @@ struct DeformerWeight_MDL7 {
     //! for deformer_typ==0 (==bones) index == vertex index
     int32_t index;
     float   weight;
-} /* PACK_STRUCT */;
+} PACK_STRUCT;
 
 // don't know why this was in the original headers ...
 typedef int32_t MD7_MATERIAL_ASCDEFSIZE;
@@ -345,7 +345,7 @@ typedef int32_t MD7_MATERIAL_ASCDEFSIZE;
  */
 struct ColorValue_MDL7 {
     float r,g,b,a;
-} /* PACK_STRUCT */;
+} PACK_STRUCT;
 
 // -------------------------------------------------------------------------------------
 /** \struct Material_MDL7
@@ -366,7 +366,7 @@ struct Material_MDL7 {
 
     //! Phong power
     float           Power;
-} /* PACK_STRUCT */;
+} PACK_STRUCT;
 
 // -------------------------------------------------------------------------------------
 /** \struct Skin
@@ -388,7 +388,7 @@ struct Skin {
 
     //! Texture data
     uint8_t *data;
-} /* PACK_STRUCT */;
+} PACK_STRUCT;
 
 
 // -------------------------------------------------------------------------------------
@@ -399,7 +399,7 @@ struct Skin {
 struct Skin_MDL5 {
     int32_t size, width, height;
     uint8_t *data;
-} /* PACK_STRUCT */;
+} PACK_STRUCT;
 
 // maximum length of texture file name
 #if (!defined AI_MDL7_MAX_TEXNAMESIZE)
@@ -416,7 +416,7 @@ struct Skin_MDL7 {
     int32_t         width;
     int32_t         height;
     char            texture_name[AI_MDL7_MAX_TEXNAMESIZE];
-} /* PACK_STRUCT */;
+} PACK_STRUCT;
 
 // -------------------------------------------------------------------------------------
 /** \struct RGB565
@@ -426,7 +426,7 @@ struct RGB565 {
     uint16_t r : 5;
     uint16_t g : 6;
     uint16_t b : 5;
-} /* PACK_STRUCT */;
+} PACK_STRUCT;
 
 // -------------------------------------------------------------------------------------
 /** \struct ARGB4
@@ -455,7 +455,7 @@ struct GroupSkin {
 
     //! Data of each image
     uint8_t **data;
-} /* PACK_STRUCT */;
+} PACK_STRUCT;
 
 // -------------------------------------------------------------------------------------
 /** \struct TexCoord
@@ -470,7 +470,7 @@ struct TexCoord {
 
     //! Texture coordinate in the ty direction
     int32_t t;
-} /* PACK_STRUCT */;
+} PACK_STRUCT;
 
 // -------------------------------------------------------------------------------------
 /** \struct TexCoord_MDL3
@@ -482,7 +482,7 @@ struct TexCoord_MDL3 {
 
     //! position, vertically in range 0..skinheight-1
     int16_t v;
-} /* PACK_STRUCT */;
+} PACK_STRUCT;
 
 // -------------------------------------------------------------------------------------
 /** \struct TexCoord_MDL7
@@ -494,7 +494,7 @@ struct TexCoord_MDL7 {
 
     //! position, vertically in range 0..1
     float v;
-} /* PACK_STRUCT */;
+} PACK_STRUCT;
 
 // -------------------------------------------------------------------------------------
 /** \struct SkinSet_MDL7
@@ -510,7 +510,7 @@ struct SkinSet_MDL7
 
     //! Material index
     int32_t     material;    // size 4
-} /* PACK_STRUCT */;
+} PACK_STRUCT;
 
 // -------------------------------------------------------------------------------------
 /** \struct Triangle
@@ -523,7 +523,7 @@ struct Triangle
 
     //! Vertex indices
     int32_t vertex[3];
-} /* PACK_STRUCT */;
+} PACK_STRUCT;
 
 // -------------------------------------------------------------------------------------
 /** \struct Triangle_MDL3
@@ -536,7 +536,7 @@ struct Triangle_MDL3
 
     //! Index of 3 skin vertices in range 0..numskinverts
     uint16_t index_uv[3];
-} /* PACK_STRUCT */;
+} PACK_STRUCT;
 
 // -------------------------------------------------------------------------------------
 /** \struct Triangle_MDL7
@@ -549,7 +549,7 @@ struct Triangle_MDL7
 
     //! Two skinsets. The second will be used for multi-texturing
     SkinSet_MDL7  skinsets[2];
-} /* PACK_STRUCT */;
+} PACK_STRUCT;
 
 #if (!defined AI_MDL7_TRIANGLE_STD_SIZE_ONE_UV)
 #   define AI_MDL7_TRIANGLE_STD_SIZE_ONE_UV (6+sizeof(SkinSet_MDL7)-sizeof(uint32_t))
@@ -577,7 +577,7 @@ struct Vertex
 {
     uint8_t v[3];
     uint8_t normalIndex;
-} /* PACK_STRUCT */;
+} PACK_STRUCT;
 
 
 // -------------------------------------------------------------------------------------
@@ -603,7 +603,7 @@ struct Vertex_MDL7
         uint8_t norm162index;
         float norm[3];
     };
-} /* PACK_STRUCT */;
+} PACK_STRUCT;
 
 // -------------------------------------------------------------------------------------
 /** \struct BoneTransform_MDL7
@@ -620,7 +620,7 @@ struct BoneTransform_MDL7
     //! I HATE 3DGS AND THE SILLY DEVELOPER WHO DESIGNED
     //! THIS STUPID FILE FORMAT!
     int8_t _unused_[2];
-} /* PACK_STRUCT */;
+} PACK_STRUCT;
 
 
 #define AI_MDL7_MAX_FRAMENAMESIZE       16
@@ -654,7 +654,7 @@ struct SimpleFrame
 
     //! Vertex list of the frame
     Vertex *verts;
-} /* PACK_STRUCT */;
+} PACK_STRUCT;
 
 // -------------------------------------------------------------------------------------
 /** \struct Frame
@@ -667,7 +667,7 @@ struct Frame
 
     //! Frame data
     SimpleFrame frame;
-} /* PACK_STRUCT */;
+} PACK_STRUCT;
 
 
 // -------------------------------------------------------------------------------------
@@ -684,7 +684,7 @@ struct SimpleFrame_MDLn_SP
 
     //! Vertex list of the frame
     Vertex_MDL4 *verts;
-} /* PACK_STRUCT */;
+} PACK_STRUCT;
 
 // -------------------------------------------------------------------------------------
 /** \struct GroupFrame
@@ -706,7 +706,7 @@ struct GroupFrame
 
     //! List of single frames
     SimpleFrame *frames;
-} /* PACK_STRUCT */;
+} PACK_STRUCT;
 
 #include "./../include/assimp/Compiler/poppack1.h"
 

+ 3 - 3
code/MMDImporter.cpp

@@ -278,7 +278,7 @@ aiMesh *MMDImporter::CreateMesh(const pmx::PmxModel *pModel,
       bone_vertex_map[vsBDEF2_ptr->bone_index1].push_back(
           aiVertexWeight(index, vsBDEF2_ptr->bone_weight));
       bone_vertex_map[vsBDEF2_ptr->bone_index2].push_back(
-          aiVertexWeight(index, 1.0 - vsBDEF2_ptr->bone_weight));
+          aiVertexWeight(index, 1.0f - vsBDEF2_ptr->bone_weight));
       break;
     case pmx::PmxVertexSkinningType::BDEF4:
       bone_vertex_map[vsBDEF4_ptr->bone_index1].push_back(
@@ -295,7 +295,7 @@ aiMesh *MMDImporter::CreateMesh(const pmx::PmxModel *pModel,
       bone_vertex_map[vsSDEF_ptr->bone_index1].push_back(
           aiVertexWeight(index, vsSDEF_ptr->bone_weight));
       bone_vertex_map[vsSDEF_ptr->bone_index2].push_back(
-          aiVertexWeight(index, 1.0 - vsSDEF_ptr->bone_weight));
+          aiVertexWeight(index, 1.0f - vsSDEF_ptr->bone_weight));
       break;
     case pmx::PmxVertexSkinningType::QDEF:
       const auto vsQDEF_ptr =
@@ -325,7 +325,7 @@ aiMesh *MMDImporter::CreateMesh(const pmx::PmxModel *pModel,
     aiMatrix4x4::Translation(-pos, pBone->mOffsetMatrix);
     auto it = bone_vertex_map.find(ii);
     if (it != bone_vertex_map.end()) {
-      pBone->mNumWeights = it->second.size();
+      pBone->mNumWeights = static_cast<unsigned int>(it->second.size());
       pBone->mWeights = it->second.data();
       it->second.swap(*(new vector<aiVertexWeight>));
     }

+ 1 - 1
code/ObjFileMtlImporter.cpp

@@ -304,7 +304,7 @@ void ObjFileMtlImporter::createMaterial()
         m_pModel->m_pCurrentMaterial = new ObjFile::Material();
         m_pModel->m_pCurrentMaterial->MaterialName.Set( name );
         if (m_pModel->m_pCurrentMesh) {
-            m_pModel->m_pCurrentMesh->m_uiMaterialIndex = m_pModel->m_MaterialLib.size() - 1;
+            m_pModel->m_pCurrentMesh->m_uiMaterialIndex = static_cast<unsigned int>(m_pModel->m_MaterialLib.size() - 1);
         }
         m_pModel->m_MaterialLib.push_back( name );
         m_pModel->m_MaterialMap[ name ] = m_pModel->m_pCurrentMaterial;

+ 9 - 4
code/ObjFileParser.cpp

@@ -555,10 +555,15 @@ void ObjFileParser::getMaterialDesc() {
         // Search for material
         std::map<std::string, ObjFile::Material*>::iterator it = m_pModel->m_MaterialMap.find(strName);
         if (it == m_pModel->m_MaterialMap.end()) {
-            // Not found, use default material
-            m_pModel->m_pCurrentMaterial = m_pModel->m_pDefaultMaterial;
-            DefaultLogger::get()->error("OBJ: failed to locate material " + strName + ", skipping");
-            strName = m_pModel->m_pDefaultMaterial->MaterialName.C_Str();
+			// Not found, so we don't know anything about the material except for its name.
+			// This may be the case if the material library is missing. We don't want to lose all
+			// materials if that happens, so create a new named material instead of discarding it
+			// completely.
+			DefaultLogger::get()->error("OBJ: failed to locate material " + strName + ", creating new material");
+			m_pModel->m_pCurrentMaterial = new ObjFile::Material();
+			m_pModel->m_pCurrentMaterial->MaterialName.Set(strName);
+			m_pModel->m_MaterialLib.push_back(strName);
+			m_pModel->m_MaterialMap[strName] = m_pModel->m_pCurrentMaterial;
         } else {
             // Found, using detected material
             m_pModel->m_pCurrentMaterial = (*it).second;

+ 1 - 1
code/PlyLoader.cpp

@@ -538,7 +538,7 @@ void PLYImporter::LoadFace(const PLY::Element* pcElement, const PLY::ElementInst
   ai_assert(NULL != instElement);
 
   if (mGeneratedMesh == NULL)
-    throw DeadlyImportError("Invalid .ply file: Vertices shoud be declared before faces");
+    throw DeadlyImportError("Invalid .ply file: Vertices should be declared before faces");
 
   bool bOne = false;
 

+ 2 - 2
code/PlyParser.cpp

@@ -618,7 +618,7 @@ bool PLY::DOM::ParseInstanceBinary(IOStreamBuffer<char> &streamBuffer, DOM* p_pc
   }
 
   streamBuffer.getNextBlock(buffer);
-  unsigned int bufferSize = buffer.size();
+  unsigned int bufferSize = static_cast<unsigned int>(buffer.size());
   const char* pCur = (char*)&buffer[0];
   if (!p_pcOut->ParseElementInstanceListsBinary(streamBuffer, buffer, pCur, bufferSize, loader, p_bBE))
   {
@@ -1025,7 +1025,7 @@ bool PLY::PropertyInstance::ParseValueBinary(IOStreamBuffer<char> &streamBuffer,
       buffer = std::vector<char>(buffer.end() - bufferSize, buffer.end());
       buffer.insert(buffer.end(), nbuffer.begin(), nbuffer.end());
       nbuffer.clear();
-      bufferSize = buffer.size();
+      bufferSize = static_cast<unsigned int>(buffer.size());
       pCur = (char*)&buffer[0];
     }
     else

+ 8 - 2
code/PretransformVertices.cpp

@@ -160,6 +160,11 @@ void PretransformVertices::CollectData( aiScene* pcScene, aiNode* pcNode, unsign
             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
@@ -626,9 +631,10 @@ void PretransformVertices::Execute( aiScene* pScene)
 
         // 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 = new aiNode();
-        pScene->mRootNode->mName.Set("<dummy_root>");
 
         if (1 == pScene->mNumMeshes && !pScene->mNumLights && !pScene->mNumCameras)
         {
@@ -646,7 +652,7 @@ void PretransformVertices::Execute( aiScene* pScene)
             {
                 aiNode* pcNode = *nodes = new aiNode();
                 pcNode->mParent = pScene->mRootNode;
-                pcNode->mName.length = ::ai_snprintf(pcNode->mName.data,MAXLEN,"mesh_%u",i);
+                pcNode->mName = pScene->mMeshes[i]->mName;
 
                 // setup mesh indices
                 pcNode->mNumMeshes = 1;

+ 0 - 5
code/Q3BSPZipArchive.cpp

@@ -39,14 +39,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ----------------------------------------------------------------------
 */
 
-
-
 #ifndef ASSIMP_BUILD_NO_Q3BSP_IMPORTER
 
 #include "Q3BSPZipArchive.h"
-#include <algorithm>
 #include <cassert>
-#include <cstdlib>
 #include <assimp/ai_assert.h>
 
 namespace Assimp {
@@ -137,7 +133,6 @@ zlib_filefunc_def IOSystem2Unzip::get(IOSystem* pIOHandler) {
     return mapping;
 }
 
-// ------------------------------------------------------------------------------------------------
 ZipFile::ZipFile(size_t size) : m_Size(size) {
     ai_assert(m_Size != 0);
 

+ 0 - 1
code/Q3BSPZipArchive.h

@@ -44,7 +44,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <contrib/unzip/unzip.h>
 #include <assimp/IOStream.hpp>
 #include <assimp/IOSystem.hpp>
-#include <string>
 #include <vector>
 #include <map>
 #include <cassert>

+ 12 - 4
code/SceneCombiner.cpp

@@ -58,6 +58,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "time.h"
 #include <assimp/DefaultLogger.hpp>
 #include <assimp/scene.h>
+#include <assimp/mesh.h>
 #include <stdio.h>
 #include "ScenePrivate.h"
 
@@ -757,7 +758,7 @@ void SceneCombiner::MergeBones(aiMesh* out,std::vector<aiMesh*>::const_iterator
 
 // ------------------------------------------------------------------------------------------------
 // Merge a list of meshes
-void SceneCombiner::MergeMeshes(aiMesh** _out,unsigned int /*flags*/,
+void SceneCombiner::MergeMeshes(aiMesh** _out, unsigned int /*flags*/,
     std::vector<aiMesh*>::const_iterator begin,
     std::vector<aiMesh*>::const_iterator end)
 {
@@ -772,8 +773,14 @@ void SceneCombiner::MergeMeshes(aiMesh** _out,unsigned int /*flags*/,
     aiMesh* out = *_out = new aiMesh();
     out->mMaterialIndex = (*begin)->mMaterialIndex;
 
+    std::string name;
     // Find out how much output storage we'll need
-    for (std::vector<aiMesh*>::const_iterator it = begin; it != end;++it)   {
+    for (std::vector<aiMesh*>::const_iterator it = begin; it != end; ++it) {
+        const char *meshName( (*it)->mName.C_Str() );
+        name += std::string( meshName );
+        if ( it != end - 1 ) {
+            name += ".";
+        }
         out->mNumVertices   += (*it)->mNumVertices;
         out->mNumFaces      += (*it)->mNumFaces;
         out->mNumBones      += (*it)->mNumBones;
@@ -781,6 +788,7 @@ void SceneCombiner::MergeMeshes(aiMesh** _out,unsigned int /*flags*/,
         // combine primitive type flags
         out->mPrimitiveTypes |= (*it)->mPrimitiveTypes;
     }
+    out->mName.Set( name.c_str() );
 
     if (out->mNumVertices) {
         aiVector3D* pv2;
@@ -789,7 +797,7 @@ void SceneCombiner::MergeMeshes(aiMesh** _out,unsigned int /*flags*/,
         if ((**begin).HasPositions())   {
 
             pv2 = out->mVertices = new aiVector3D[out->mNumVertices];
-            for (std::vector<aiMesh*>::const_iterator it = begin; it != end;++it)   {
+            for (std::vector<aiMesh*>::const_iterator it = begin; it != end; ++it)  {
                 if ((*it)->mVertices)   {
                     ::memcpy(pv2,(*it)->mVertices,(*it)->mNumVertices*sizeof(aiVector3D));
                 }
@@ -809,7 +817,7 @@ void SceneCombiner::MergeMeshes(aiMesh** _out,unsigned int /*flags*/,
                 pv2 += (*it)->mNumVertices;
             }
         }
-        // copy tangents and bitangents
+        // copy tangents and bi-tangents
         if ((**begin).HasTangentsAndBitangents())   {
 
             pv2 = out->mTangents = new aiVector3D[out->mNumVertices];

+ 3 - 3
code/X3DImporter.cpp

@@ -95,8 +95,8 @@ struct WordIterator: public std::iterator<std::input_iterator_tag, const char*>
         end_ = other.end_;
         return *this;
     }
-    bool operator==(WordIterator &other) const { return start_ == other.start_; }
-    bool operator!=(WordIterator &other) const { return start_ != other.start_; }
+    bool operator==(const WordIterator &other) const { return start_ == other.start_; }
+    bool operator!=(const WordIterator &other) const { return start_ != other.start_; }
     WordIterator &operator++() {
         start_ += strcspn(start_, whitespace);
         start_ += strspn(start_, whitespace);
@@ -558,7 +558,7 @@ void X3DImporter::XML_ReadNode_GetAttrVal_AsArrF(const int pAttrIdx, std::vector
 
         WordIterator wordItBegin(val, val + strlen(val));
         WordIterator wordItEnd;
-        std::transform(wordItBegin, wordItEnd, std::back_inserter(pValue), [](const char *match) { return atof(match); });
+        std::transform(wordItBegin, wordItEnd, std::back_inserter(pValue), [](const char *match) { return static_cast<float>(atof(match)); });
     }
 }
 

+ 1 - 1
code/glTF2Asset.inl

@@ -242,7 +242,7 @@ Ref<T> LazyDict<T>::Create(const char* id)
     }
     T* inst = new T();
     inst->id = id;
-    inst->index = mObjs.size();
+    inst->index = static_cast<int>(mObjs.size());
     return Add(inst);
 }
 

+ 5 - 5
code/glTF2Exporter.cpp

@@ -886,7 +886,7 @@ inline void ExtractAnimationData(Asset& mAsset, std::string& animId, Ref<Animati
             timeData[i] = nodeChannel->mPositionKeys[frameIndex].mTime / ticksPerSecond;
         }
 
-        Ref<Accessor> timeAccessor = ExportData(mAsset, animId, buffer, numKeyframes, &timeData[0], AttribType::SCALAR, AttribType::SCALAR, ComponentType_FLOAT);
+        Ref<Accessor> timeAccessor = ExportData(mAsset, animId, buffer, static_cast<unsigned int>(numKeyframes), &timeData[0], AttribType::SCALAR, AttribType::SCALAR, ComponentType_FLOAT);
         if (timeAccessor) animRef->Parameters.TIME = timeAccessor;
     }
 
@@ -899,7 +899,7 @@ inline void ExtractAnimationData(Asset& mAsset, std::string& animId, Ref<Animati
             translationData[i] = nodeChannel->mPositionKeys[frameIndex].mValue;
         }
 
-        Ref<Accessor> tranAccessor = ExportData(mAsset, animId, buffer, numKeyframes, translationData, AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT);
+        Ref<Accessor> tranAccessor = ExportData(mAsset, animId, buffer, static_cast<unsigned int>(numKeyframes), translationData, AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT);
         if ( tranAccessor ) {
             animRef->Parameters.translation = tranAccessor;
         }
@@ -915,7 +915,7 @@ inline void ExtractAnimationData(Asset& mAsset, std::string& animId, Ref<Animati
             scaleData[i] = nodeChannel->mScalingKeys[frameIndex].mValue;
         }
 
-        Ref<Accessor> scaleAccessor = ExportData(mAsset, animId, buffer, numKeyframes, scaleData, AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT);
+        Ref<Accessor> scaleAccessor = ExportData(mAsset, animId, buffer, static_cast<unsigned int>(numKeyframes), scaleData, AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT);
         if ( scaleAccessor ) {
             animRef->Parameters.scale = scaleAccessor;
         }
@@ -934,7 +934,7 @@ inline void ExtractAnimationData(Asset& mAsset, std::string& animId, Ref<Animati
             rotationData[i][3] = nodeChannel->mRotationKeys[frameIndex].mValue.w;
         }
 
-        Ref<Accessor> rotAccessor = ExportData(mAsset, animId, buffer, numKeyframes, rotationData, AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT);
+        Ref<Accessor> rotAccessor = ExportData(mAsset, animId, buffer, static_cast<unsigned int>(numKeyframes), rotationData, AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT);
         if ( rotAccessor ) {
             animRef->Parameters.rotation = rotAccessor;
         }
@@ -989,7 +989,7 @@ void glTF2Exporter::ExportAnimations()
                 Animation::AnimChannel tmpAnimChannel;
                 Animation::AnimSampler tmpAnimSampler;
 
-                tmpAnimChannel.sampler = animRef->Samplers.size();
+                tmpAnimChannel.sampler = static_cast<int>(animRef->Samplers.size());
                 tmpAnimChannel.target.path = channelType;
                 tmpAnimSampler.output = channelType;
                 tmpAnimSampler.id = name + "_" + channelType;

+ 4 - 4
code/glTFExporter.cpp

@@ -875,7 +875,7 @@ inline void ExtractAnimationData(Asset& mAsset, std::string& animId, Ref<Animati
             timeData[i] = nodeChannel->mPositionKeys[frameIndex].mTime / ticksPerSecond;
         }
 
-        Ref<Accessor> timeAccessor = ExportData(mAsset, animId, buffer, numKeyframes, &timeData[0], AttribType::SCALAR, AttribType::SCALAR, ComponentType_FLOAT);
+        Ref<Accessor> timeAccessor = ExportData(mAsset, animId, buffer, static_cast<unsigned int>(numKeyframes), &timeData[0], AttribType::SCALAR, AttribType::SCALAR, ComponentType_FLOAT);
         if (timeAccessor) animRef->Parameters.TIME = timeAccessor;
     }
 
@@ -888,7 +888,7 @@ inline void ExtractAnimationData(Asset& mAsset, std::string& animId, Ref<Animati
             translationData[i] = nodeChannel->mPositionKeys[frameIndex].mValue;
         }
 
-        Ref<Accessor> tranAccessor = ExportData(mAsset, animId, buffer, numKeyframes, translationData, AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT);
+        Ref<Accessor> tranAccessor = ExportData(mAsset, animId, buffer, static_cast<unsigned int>(numKeyframes), translationData, AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT);
         if ( tranAccessor ) {
             animRef->Parameters.translation = tranAccessor;
         }
@@ -904,7 +904,7 @@ inline void ExtractAnimationData(Asset& mAsset, std::string& animId, Ref<Animati
             scaleData[i] = nodeChannel->mScalingKeys[frameIndex].mValue;
         }
 
-        Ref<Accessor> scaleAccessor = ExportData(mAsset, animId, buffer, numKeyframes, scaleData, AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT);
+        Ref<Accessor> scaleAccessor = ExportData(mAsset, animId, buffer, static_cast<unsigned int>(numKeyframes), scaleData, AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT);
         if ( scaleAccessor ) {
             animRef->Parameters.scale = scaleAccessor;
         }
@@ -923,7 +923,7 @@ inline void ExtractAnimationData(Asset& mAsset, std::string& animId, Ref<Animati
             rotationData[i][3] = nodeChannel->mRotationKeys[frameIndex].mValue.w;
         }
 
-        Ref<Accessor> rotAccessor = ExportData(mAsset, animId, buffer, numKeyframes, rotationData, AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT);
+        Ref<Accessor> rotAccessor = ExportData(mAsset, animId, buffer, static_cast<unsigned int>(numKeyframes), rotationData, AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT);
         if ( rotAccessor ) {
             animRef->Parameters.rotation = rotAccessor;
         }

+ 4 - 0
contrib/irrXML/CMakeLists.txt

@@ -11,3 +11,7 @@ set( IrrXML_SRCS
 add_library(IrrXML STATIC ${IrrXML_SRCS})
 set(IRRXML_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" CACHE INTERNAL "IrrXML_Include" )
 set(IRRXML_LIBRARY "IrrXML" CACHE INTERNAL "IrrXML" )
+
+install(TARGETS IrrXML
+  ARCHIVE DESTINATION ${ASSIMP_LIB_INSTALL_DIR}
+  COMPONENT ${LIBASSIMP_COMPONENT})

+ 2 - 5
include/assimp/SceneCombiner.h

@@ -217,10 +217,9 @@ public:
     static void MergeScenes(aiScene** dest,std::vector<aiScene*>& src,
         unsigned int flags = 0);
 
-
     // -------------------------------------------------------------------
-    /** Merges two or more scenes and attaches all sceenes to a specific
-     *  position in the node graph of the masteer scene.
+    /** Merges two or more scenes and attaches all scenes to a specific
+     *  position in the node graph of the master scene.
      *
      *  @param dest Receives a pointer to the destination scene. If the
      *    pointer doesn't point to NULL when the function is called, the
@@ -236,7 +235,6 @@ public:
         std::vector<AttachmentInfo>& src,
         unsigned int flags = 0);
 
-
     // -------------------------------------------------------------------
     /** Merges two or more meshes
      *
@@ -255,7 +253,6 @@ public:
         std::vector<aiMesh*>::const_iterator begin,
         std::vector<aiMesh*>::const_iterator end);
 
-
     // -------------------------------------------------------------------
     /** Merges two or more bones
      *

+ 33 - 21
port/PyAssimp/pyassimp/helper.py

@@ -9,6 +9,10 @@ import ctypes
 from ctypes import POINTER
 import operator
 
+from distutils.sysconfig import get_python_lib
+import re
+import sys
+
 try: import numpy
 except: numpy = None
 
@@ -26,7 +30,15 @@ if os.name=='posix':
     additional_dirs.append('/usr/lib/x86_64-linux-gnu')
     additional_dirs.append('/usr/local/lib/')
 
-    # note - this won't catch libassimp.so.N.n, but 
+    # check if running from anaconda.
+    if "conda" or "continuum" in sys.version.lower():
+      cur_path = get_python_lib()
+      pattern = re.compile('.*\/lib\/')
+      conda_lib = pattern.match(cur_path).group()
+      logger.info("Adding Anaconda lib path:"+ conda_lib)
+      additional_dirs.append(conda_lib)
+
+    # note - this won't catch libassimp.so.N.n, but
     # currently there's always a symlink called
     # libassimp.so in /usr/local/lib.
     ext_whitelist.append('.so')
@@ -39,7 +51,7 @@ elif os.name=='nt':
     for dir_candidate in path_dirs:
         if 'assimp' in dir_candidate.lower():
             additional_dirs.append(dir_candidate)
-            
+
 #print(additional_dirs)
 def vec2tuple(x):
     """ Converts a VECTOR3D to a Tuple """
@@ -61,10 +73,10 @@ def transform(vector3, matrix4x4):
             m2[0]*x + m2[1]*y + m2[2]*z + m2[3],
             m3[0]*x + m3[1]*y + m3[2]*z + m3[3]
             ]
-    
+
 def _inv(matrix4x4):
     m0,m1,m2,m3 = matrix4x4
-    
+
     det  =  m0[3]*m1[2]*m2[1]*m3[0] - m0[2]*m1[3]*m2[1]*m3[0] - \
             m0[3]*m1[1]*m2[2]*m3[0] + m0[1]*m1[3]*m2[2]*m3[0] + \
             m0[2]*m1[1]*m2[3]*m3[0] - m0[1]*m1[2]*m2[3]*m3[0] - \
@@ -77,7 +89,7 @@ def _inv(matrix4x4):
             m0[2]*m1[1]*m2[0]*m3[3] + m0[1]*m1[2]*m2[0]*m3[3] + \
             m0[2]*m1[0]*m2[1]*m3[3] - m0[0]*m1[2]*m2[1]*m3[3] - \
             m0[1]*m1[0]*m2[2]*m3[3] + m0[0]*m1[1]*m2[2]*m3[3]
-        
+
     return[[( m1[2]*m2[3]*m3[1] - m1[3]*m2[2]*m3[1] + m1[3]*m2[1]*m3[2] - m1[1]*m2[3]*m3[2] - m1[2]*m2[1]*m3[3] + m1[1]*m2[2]*m3[3]) /det,
             ( m0[3]*m2[2]*m3[1] - m0[2]*m2[3]*m3[1] - m0[3]*m2[1]*m3[2] + m0[1]*m2[3]*m3[2] + m0[2]*m2[1]*m3[3] - m0[1]*m2[2]*m3[3]) /det,
             ( m0[2]*m1[3]*m3[1] - m0[3]*m1[2]*m3[1] + m0[3]*m1[1]*m3[2] - m0[1]*m1[3]*m3[2] - m0[2]*m1[1]*m3[3] + m0[1]*m1[2]*m3[3]) /det,
@@ -94,7 +106,7 @@ def _inv(matrix4x4):
             ( m0[1]*m2[2]*m3[0] - m0[2]*m2[1]*m3[0] + m0[2]*m2[0]*m3[1] - m0[0]*m2[2]*m3[1] - m0[1]*m2[0]*m3[2] + m0[0]*m2[1]*m3[2]) /det,
             ( m0[2]*m1[1]*m3[0] - m0[1]*m1[2]*m3[0] - m0[2]*m1[0]*m3[1] + m0[0]*m1[2]*m3[1] + m0[1]*m1[0]*m3[2] - m0[0]*m1[1]*m3[2]) /det,
             ( m0[1]*m1[2]*m2[0] - m0[2]*m1[1]*m2[0] + m0[2]*m1[0]*m2[1] - m0[0]*m1[2]*m2[1] - m0[1]*m1[0]*m2[2] + m0[0]*m1[1]*m2[2]) /det]]
-   
+
 def get_bounding_box(scene):
     bb_min = [1e10, 1e10, 1e10] # x,y,z
     bb_max = [-1e10, -1e10, -1e10] # x,y,z
@@ -129,7 +141,7 @@ def get_bounding_box_for_node(node, bb_min, bb_max, transformation):
                 t3[0]*T0[2] + t3[1]*T1[2] + t3[2]*T2[2] + t3[3]*T3[2],
                 t3[0]*T0[3] + t3[1]*T1[3] + t3[2]*T2[3] + t3[3]*T3[3]
             ] ]
-    
+
     for mesh in node.meshes:
         for v in mesh.vertices:
             v = transform(v, transformation)
@@ -149,25 +161,25 @@ def get_bounding_box_for_node(node, bb_min, bb_max, transformation):
 def try_load_functions(library_path, dll):
     '''
     Try to bind to aiImportFile and aiReleaseImport
-    
+
     Arguments
     ---------
     library_path: path to current lib
     dll:          ctypes handle to library
-    
+
     Returns
     ---------
     If unsuccessful:
         None
     If successful:
-        Tuple containing (library_path, 
+        Tuple containing (library_path,
                           load from filename function,
                           load from memory function,
                           export to filename function,
-                          release function, 
+                          release function,
                           ctypes handle to assimp library)
     '''
-    
+
     try:
         load     = dll.aiImportFile
         release  = dll.aiReleaseImport
@@ -176,7 +188,7 @@ def try_load_functions(library_path, dll):
     except AttributeError:
         #OK, this is a library, but it doesn't have the functions we need
         return None
-    
+
     # library found!
     from .structs import Scene
     load.restype = POINTER(Scene)
@@ -185,13 +197,13 @@ def try_load_functions(library_path, dll):
 
 def search_library():
     '''
-    Loads the assimp library. 
+    Loads the assimp library.
     Throws exception AssimpError if no library_path is found
-    
-    Returns: tuple, (load from filename function, 
+
+    Returns: tuple, (load from filename function,
                      load from memory function,
                      export to filename function,
-                     release function, 
+                     release function,
                      dll)
     '''
     #this path
@@ -201,7 +213,7 @@ def search_library():
     try:
         ctypes.windll.kernel32.SetErrorMode(0x8007)
     except AttributeError:
-        pass    
+        pass
 
     candidates = []
     # test every file
@@ -209,7 +221,7 @@ def search_library():
         if os.path.isdir(curfolder):
             for filename in os.listdir(curfolder):
                 # our minimum requirement for candidates is that
-                # they should contain 'assimp' somewhere in 
+                # they should contain 'assimp' somewhere in
                 # their name
                 if filename.lower().find('assimp')==-1 or\
                     os.path.splitext(filename)[-1].lower() not in ext_whitelist:
@@ -248,10 +260,10 @@ def hasattr_silent(object, name):
     """
         Calls hasttr() with the given parameters and preserves the legacy (pre-Python 3.2)
         functionality of silently catching exceptions.
-        
+
         Returns the result of hasatter() or False if an exception was raised.
     """
-    
+
     try:
         return hasattr(object, name)
     except:

+ 1320 - 0
port/PyAssimp/scripts/3d_viewer_py3.py

@@ -0,0 +1,1320 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+
+""" This program loads a model with PyASSIMP, and display it.
+
+Based on:
+- pygame code from http://3dengine.org/Spectator_%28PyOpenGL%29
+- http://www.lighthouse3d.com/tutorials
+- http://www.songho.ca/opengl/gl_transform.html
+- http://code.activestate.com/recipes/325391/
+- ASSIMP's C++ SimpleOpenGL viewer
+
+Authors: Séverin Lemaignan, 2012-2016
+"""
+import sys
+import logging
+
+from functools import reduce
+
+logger = logging.getLogger("pyassimp")
+gllogger = logging.getLogger("OpenGL")
+gllogger.setLevel(logging.WARNING)
+logging.basicConfig(level=logging.INFO)
+
+import OpenGL
+
+OpenGL.ERROR_CHECKING = False
+OpenGL.ERROR_LOGGING = False
+# OpenGL.ERROR_ON_COPY = True
+# OpenGL.FULL_LOGGING = True
+from OpenGL.GL import *
+from OpenGL.arrays import vbo
+from OpenGL.GL import shaders
+
+import pygame
+import pygame.font
+import pygame.image
+
+import math, random
+from numpy import linalg
+
+import pyassimp
+from pyassimp.postprocess import *
+from pyassimp.helper import *
+import transformations
+
+ROTATION_180_X = numpy.array([[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]], dtype=numpy.float32)
+
+# rendering mode
+BASE = "BASE"
+COLORS = "COLORS"
+SILHOUETTE = "SILHOUETTE"
+HELPERS = "HELPERS"
+
+# Entities type
+ENTITY = "entity"
+CAMERA = "camera"
+MESH = "mesh"
+
+FLAT_VERTEX_SHADER_120 = """
+#version 120
+
+uniform mat4 u_viewProjectionMatrix;
+uniform mat4 u_modelMatrix;
+
+uniform vec4 u_materialDiffuse;
+
+attribute vec3 a_vertex;
+
+varying vec4 v_color;
+
+void main(void)
+{
+    v_color = u_materialDiffuse;
+    gl_Position = u_viewProjectionMatrix * u_modelMatrix * vec4(a_vertex, 1.0);
+}
+"""
+
+FLAT_VERTEX_SHADER_130 = """
+#version 130
+
+uniform mat4 u_viewProjectionMatrix;
+uniform mat4 u_modelMatrix;
+
+uniform vec4 u_materialDiffuse;
+
+in vec3 a_vertex;
+
+out vec4 v_color;
+
+void main(void)
+{
+    v_color = u_materialDiffuse;
+    gl_Position = u_viewProjectionMatrix * u_modelMatrix * vec4(a_vertex, 1.0);
+}
+"""
+
+BASIC_VERTEX_SHADER_120 = """
+#version 120
+
+uniform mat4 u_viewProjectionMatrix;
+uniform mat4 u_modelMatrix;
+uniform mat3 u_normalMatrix;
+uniform vec3 u_lightPos;
+
+uniform vec4 u_materialDiffuse;
+
+attribute vec3 a_vertex;
+attribute vec3 a_normal;
+
+varying vec4 v_color;
+
+void main(void)
+{
+    // Now the normal is in world space, as we pass the light in world space.
+    vec3 normal = u_normalMatrix * a_normal;
+
+    float dist = distance(a_vertex, u_lightPos);
+
+    // go to https://www.desmos.com/calculator/nmnaud1hrw to play with the parameters
+    // att is not used for now
+    float att=1.0/(1.0+0.8*dist*dist);
+
+    vec3 surf2light = normalize(u_lightPos - a_vertex);
+    vec3 norm = normalize(normal);
+    float dcont=max(0.0,dot(norm,surf2light));
+
+    float ambient = 0.3;
+    float intensity = dcont + 0.3 + ambient;
+
+    v_color = u_materialDiffuse  * intensity;
+
+    gl_Position = u_viewProjectionMatrix * u_modelMatrix * vec4(a_vertex, 1.0);
+}
+"""
+
+BASIC_VERTEX_SHADER_130 = """
+#version 130
+
+uniform mat4 u_viewProjectionMatrix;
+uniform mat4 u_modelMatrix;
+uniform mat3 u_normalMatrix;
+uniform vec3 u_lightPos;
+
+uniform vec4 u_materialDiffuse;
+
+in vec3 a_vertex;
+in vec3 a_normal;
+
+out vec4 v_color;
+
+void main(void)
+{
+    // Now the normal is in world space, as we pass the light in world space.
+    vec3 normal = u_normalMatrix * a_normal;
+
+    float dist = distance(a_vertex, u_lightPos);
+
+    // go to https://www.desmos.com/calculator/nmnaud1hrw to play with the parameters
+    // att is not used for now
+    float att=1.0/(1.0+0.8*dist*dist);
+
+    vec3 surf2light = normalize(u_lightPos - a_vertex);
+    vec3 norm = normalize(normal);
+    float dcont=max(0.0,dot(norm,surf2light));
+
+    float ambient = 0.3;
+    float intensity = dcont + 0.3 + ambient;
+
+    v_color = u_materialDiffuse  * intensity;
+
+    gl_Position = u_viewProjectionMatrix * u_modelMatrix * vec4(a_vertex, 1.0);
+}
+"""
+
+BASIC_FRAGMENT_SHADER_120 = """
+#version 120
+
+varying vec4 v_color;
+
+void main() {
+    gl_FragColor = v_color;
+}
+"""
+
+BASIC_FRAGMENT_SHADER_130 = """
+#version 130
+
+in vec4 v_color;
+
+void main() {
+    gl_FragColor = v_color;
+}
+"""
+
+GOOCH_VERTEX_SHADER_120 = """
+#version 120
+
+// attributes
+attribute vec3 a_vertex; // xyz - position
+attribute vec3 a_normal; // xyz - normal
+
+// uniforms
+uniform mat4 u_modelMatrix;
+uniform mat4 u_viewProjectionMatrix;
+uniform mat3 u_normalMatrix;
+uniform vec3 u_lightPos;
+uniform vec3 u_camPos;
+
+// output data from vertex to fragment shader
+varying vec3 o_normal;
+varying vec3 o_lightVector;
+
+///////////////////////////////////////////////////////////////////
+
+void main(void)
+{
+   // transform position and normal to world space
+   vec4 positionWorld = u_modelMatrix * vec4(a_vertex, 1.0);
+   vec3 normalWorld = u_normalMatrix * a_normal;
+
+   // calculate and pass vectors required for lighting
+   o_lightVector = u_lightPos - positionWorld.xyz;
+   o_normal = normalWorld;
+
+   // project world space position to the screen and output it
+   gl_Position = u_viewProjectionMatrix * positionWorld;
+}
+"""
+
+GOOCH_VERTEX_SHADER_130 = """
+#version 130
+
+// attributes
+in vec3 a_vertex; // xyz - position
+in vec3 a_normal; // xyz - normal
+
+// uniforms
+uniform mat4 u_modelMatrix;
+uniform mat4 u_viewProjectionMatrix;
+uniform mat3 u_normalMatrix;
+uniform vec3 u_lightPos;
+uniform vec3 u_camPos;
+
+// output data from vertex to fragment shader
+out vec3 o_normal;
+out vec3 o_lightVector;
+
+///////////////////////////////////////////////////////////////////
+
+void main(void)
+{
+   // transform position and normal to world space
+   vec4 positionWorld = u_modelMatrix * vec4(a_vertex, 1.0);
+   vec3 normalWorld = u_normalMatrix * a_normal;
+
+   // calculate and pass vectors required for lighting
+   o_lightVector = u_lightPos - positionWorld.xyz;
+   o_normal = normalWorld;
+
+   // project world space position to the screen and output it
+   gl_Position = u_viewProjectionMatrix * positionWorld;
+}
+"""
+
+GOOCH_FRAGMENT_SHADER_120 = """
+#version 120
+
+// data from vertex shader
+varying vec3 o_normal;
+varying vec3 o_lightVector;
+
+// diffuse color of the object
+uniform vec4 u_materialDiffuse;
+// cool color of gooch shading
+uniform vec3 u_coolColor;
+// warm color of gooch shading
+uniform vec3 u_warmColor;
+// how much to take from object color in final cool color
+uniform float u_alpha;
+// how much to take from object color in final warm color
+uniform float u_beta;
+
+///////////////////////////////////////////////////////////
+
+void main(void)
+{
+   // normlize vectors for lighting
+   vec3 normalVector = normalize(o_normal);
+   vec3 lightVector = normalize(o_lightVector);
+   // intensity of diffuse lighting [-1, 1]
+   float diffuseLighting = dot(lightVector, normalVector);
+   // map intensity of lighting from range [-1; 1] to [0, 1]
+   float interpolationValue = (1.0 + diffuseLighting)/2;
+
+   //////////////////////////////////////////////////////////////////
+
+   // cool color mixed with color of the object
+   vec3 coolColorMod = u_coolColor + vec3(u_materialDiffuse) * u_alpha;
+   // warm color mixed with color of the object
+   vec3 warmColorMod = u_warmColor + vec3(u_materialDiffuse) * u_beta;
+   // interpolation of cool and warm colors according
+   // to lighting intensity. The lower the light intensity,
+   // the larger part of the cool color is used
+   vec3 colorOut = mix(coolColorMod, warmColorMod, interpolationValue);
+
+   //////////////////////////////////////////////////////////////////
+
+   // save color
+   gl_FragColor.rgb = colorOut;
+   gl_FragColor.a = 1;
+}
+"""
+
+GOOCH_FRAGMENT_SHADER_130 = """
+#version 130
+
+// data from vertex shader
+in vec3 o_normal;
+in vec3 o_lightVector;
+
+// diffuse color of the object
+uniform vec4 u_materialDiffuse;
+// cool color of gooch shading
+uniform vec3 u_coolColor;
+// warm color of gooch shading
+uniform vec3 u_warmColor;
+// how much to take from object color in final cool color
+uniform float u_alpha;
+// how much to take from object color in final warm color
+uniform float u_beta;
+
+// output to framebuffer
+out vec4 resultingColor;
+
+///////////////////////////////////////////////////////////
+
+void main(void)
+{
+   // normlize vectors for lighting
+   vec3 normalVector = normalize(o_normal);
+   vec3 lightVector = normalize(o_lightVector);
+   // intensity of diffuse lighting [-1, 1]
+   float diffuseLighting = dot(lightVector, normalVector);
+   // map intensity of lighting from range [-1; 1] to [0, 1]
+   float interpolationValue = (1.0 + diffuseLighting)/2;
+
+   //////////////////////////////////////////////////////////////////
+
+   // cool color mixed with color of the object
+   vec3 coolColorMod = u_coolColor + vec3(u_materialDiffuse) * u_alpha;
+   // warm color mixed with color of the object
+   vec3 warmColorMod = u_warmColor + vec3(u_materialDiffuse) * u_beta;
+   // interpolation of cool and warm colors according
+   // to lighting intensity. The lower the light intensity,
+   // the larger part of the cool color is used
+   vec3 colorOut = mix(coolColorMod, warmColorMod, interpolationValue);
+
+   //////////////////////////////////////////////////////////////////
+
+   // save color
+   resultingColor.rgb = colorOut;
+   resultingColor.a = 1;
+}
+"""
+
+SILHOUETTE_VERTEX_SHADER_120 = """
+#version 120
+
+attribute vec3 a_vertex; // xyz - position
+attribute vec3 a_normal; // xyz - normal
+
+uniform mat4 u_modelMatrix;
+uniform mat4 u_viewProjectionMatrix;
+uniform mat4 u_modelViewMatrix;
+uniform vec4 u_materialDiffuse;
+uniform float u_bordersize; // width of the border
+
+varying vec4 v_color;
+
+void main(void){
+   v_color = u_materialDiffuse;
+   float distToCamera = -(u_modelViewMatrix * vec4(a_vertex, 1.0)).z;
+   vec4 tPos   = vec4(a_vertex + a_normal * u_bordersize * distToCamera, 1.0);
+   gl_Position = u_viewProjectionMatrix * u_modelMatrix * tPos;
+}
+"""
+
+SILHOUETTE_VERTEX_SHADER_130 = """
+#version 130
+
+in vec3 a_vertex; // xyz - position
+in vec3 a_normal; // xyz - normal
+
+uniform mat4 u_modelMatrix;
+uniform mat4 u_viewProjectionMatrix;
+uniform mat4 u_modelViewMatrix;
+uniform vec4 u_materialDiffuse;
+uniform float u_bordersize; // width of the border
+
+out vec4 v_color;
+
+void main(void){
+   v_color = u_materialDiffuse;
+   float distToCamera = -(u_modelViewMatrix * vec4(a_vertex, 1.0)).z;
+   vec4 tPos   = vec4(a_vertex + a_normal * u_bordersize * distToCamera, 1.0);
+   gl_Position = u_viewProjectionMatrix * u_modelMatrix * tPos;
+}
+"""
+DEFAULT_CLIP_PLANE_NEAR = 0.001
+DEFAULT_CLIP_PLANE_FAR = 1000.0
+
+
+def get_world_transform(scene, node):
+    if node == scene.rootnode:
+        return numpy.identity(4, dtype=numpy.float32)
+
+    parents = reversed(_get_parent_chain(scene, node, []))
+    parent_transform = reduce(numpy.dot, [p.transformation for p in parents])
+    return numpy.dot(parent_transform, node.transformation)
+
+
+def _get_parent_chain(scene, node, parents):
+    parent = node.parent
+
+    parents.append(parent)
+
+    if parent == scene.rootnode:
+        return parents
+
+    return _get_parent_chain(scene, parent, parents)
+
+
+class DefaultCamera:
+    def __init__(self, w, h, fov):
+        self.name = "default camera"
+        self.type = CAMERA
+        self.clipplanenear = DEFAULT_CLIP_PLANE_NEAR
+        self.clipplanefar = DEFAULT_CLIP_PLANE_FAR
+        self.aspect = w / h
+        self.horizontalfov = fov * math.pi / 180
+        self.transformation = numpy.array([[0.68, -0.32, 0.65, 7.48],
+                                           [0.73, 0.31, -0.61, -6.51],
+                                           [-0.01, 0.89, 0.44, 5.34],
+                                           [0., 0., 0., 1.]], dtype=numpy.float32)
+
+        self.transformation = numpy.dot(self.transformation, ROTATION_180_X)
+
+    def __str__(self):
+        return self.name
+
+
+class PyAssimp3DViewer:
+    base_name = "PyASSIMP 3D viewer"
+
+    def __init__(self, model, w=1024, h=768):
+
+        self.w = w
+        self.h = h
+
+        pygame.init()
+        pygame.display.set_caption(self.base_name)
+        pygame.display.set_mode((w, h), pygame.OPENGL | pygame.DOUBLEBUF)
+
+        glClearColor(0.18, 0.18, 0.18, 1.0)
+
+        shader_compilation_succeeded = False
+        try:
+            self.set_shaders_v130()
+            self.prepare_shaders()
+        except RuntimeError, message:
+            sys.stderr.write("%s\n" % message)
+            sys.stdout.write("Could not compile shaders in version 1.30, trying version 1.20\n")
+
+        if not shader_compilation_succeeded:
+            self.set_shaders_v120()
+            self.prepare_shaders()
+
+        self.scene = None
+        self.meshes = {}  # stores the OpenGL vertex/faces/normals buffers pointers
+
+        self.node2colorid = {}  # stores a color ID for each node. Useful for mouse picking and visibility checking
+        self.colorid2node = {}  # reverse dict of node2colorid
+
+        self.currently_selected = None
+        self.moving = False
+        self.moving_situation = None
+
+        self.default_camera = DefaultCamera(self.w, self.h, fov=70)
+        self.cameras = [self.default_camera]
+
+        self.current_cam_index = 0
+        self.current_cam = self.default_camera
+        self.set_camera_projection()
+
+        self.load_model(model)
+
+        # user interactions
+        self.focal_point = [0, 0, 0]
+        self.is_rotating = False
+        self.is_panning = False
+        self.is_zooming = False
+
+    def set_shaders_v120(self):
+      self.BASIC_VERTEX_SHADER = BASIC_VERTEX_SHADER_120
+      self.FLAT_VERTEX_SHADER = FLAT_VERTEX_SHADER_120
+      self.SILHOUETTE_VERTEX_SHADER = SILHOUETTE_VERTEX_SHADER_120
+      self.GOOCH_VERTEX_SHADER = GOOCH_VERTEX_SHADER_120
+
+      self.BASIC_FRAGMENT_SHADER = BASIC_FRAGMENT_SHADER_120
+      self.GOOCH_FRAGMENT_SHADER = GOOCH_FRAGMENT_SHADER_120
+
+    def set_shaders_v130(self):
+      self.BASIC_VERTEX_SHADER = BASIC_VERTEX_SHADER_130
+      self.FLAT_VERTEX_SHADER = FLAT_VERTEX_SHADER_130
+      self.SILHOUETTE_VERTEX_SHADER = SILHOUETTE_VERTEX_SHADER_130
+      self.GOOCH_VERTEX_SHADER = GOOCH_VERTEX_SHADER_130
+
+      self.BASIC_FRAGMENT_SHADER = BASIC_FRAGMENT_SHADER_130
+      self.GOOCH_FRAGMENT_SHADER = GOOCH_FRAGMENT_SHADER_130
+
+    def prepare_shaders(self):
+
+        ### Base shader
+        vertex = shaders.compileShader(self.BASIC_VERTEX_SHADER, GL_VERTEX_SHADER)
+        fragment = shaders.compileShader(self.BASIC_FRAGMENT_SHADER, GL_FRAGMENT_SHADER)
+
+        self.shader = shaders.compileProgram(vertex, fragment)
+
+        self.set_shader_accessors(('u_modelMatrix',
+                                   'u_viewProjectionMatrix',
+                                   'u_normalMatrix',
+                                   'u_lightPos',
+                                   'u_materialDiffuse'),
+                                  ('a_vertex',
+                                   'a_normal'), self.shader)
+
+        ### Flat shader
+        flatvertex = shaders.compileShader(self.FLAT_VERTEX_SHADER, GL_VERTEX_SHADER)
+        self.flatshader = shaders.compileProgram(flatvertex, fragment)
+
+        self.set_shader_accessors(('u_modelMatrix',
+                                   'u_viewProjectionMatrix',
+                                   'u_materialDiffuse',),
+                                  ('a_vertex',), self.flatshader)
+
+        ### Silhouette shader
+        silh_vertex = shaders.compileShader(self.SILHOUETTE_VERTEX_SHADER, GL_VERTEX_SHADER)
+        self.silhouette_shader = shaders.compileProgram(silh_vertex, fragment)
+
+        self.set_shader_accessors(('u_modelMatrix',
+                                   'u_viewProjectionMatrix',
+                                   'u_modelViewMatrix',
+                                   'u_materialDiffuse',
+                                   'u_bordersize'  # width of the silhouette
+                                   ),
+                                  ('a_vertex',
+                                   'a_normal'), self.silhouette_shader)
+
+        ### Gooch shader
+        gooch_vertex = shaders.compileShader(self.GOOCH_VERTEX_SHADER, GL_VERTEX_SHADER)
+        gooch_fragment = shaders.compileShader(self.GOOCH_FRAGMENT_SHADER, GL_FRAGMENT_SHADER)
+        self.gooch_shader = shaders.compileProgram(gooch_vertex, gooch_fragment)
+
+        self.set_shader_accessors(('u_modelMatrix',
+                                   'u_viewProjectionMatrix',
+                                   'u_normalMatrix',
+                                   'u_lightPos',
+                                   'u_materialDiffuse',
+                                   'u_coolColor',
+                                   'u_warmColor',
+                                   'u_alpha',
+                                   'u_beta'
+                                   ),
+                                  ('a_vertex',
+                                   'a_normal'), self.gooch_shader)
+
+    @staticmethod
+    def set_shader_accessors(uniforms, attributes, shader):
+        # add accessors to the shaders uniforms and attributes
+        for uniform in uniforms:
+            location = glGetUniformLocation(shader, uniform)
+            if location in (None, -1):
+                raise RuntimeError('No uniform: %s (maybe it is not used '
+                                   'anymore and has been optimized out by'
+                                   ' the shader compiler)' % uniform)
+            setattr(shader, uniform, location)
+
+        for attribute in attributes:
+            location = glGetAttribLocation(shader, attribute)
+            if location in (None, -1):
+                raise RuntimeError('No attribute: %s' % attribute)
+            setattr(shader, attribute, location)
+
+    @staticmethod
+    def prepare_gl_buffers(mesh):
+
+        mesh.gl = {}
+
+        # Fill the buffer for vertex and normals positions
+        v = numpy.array(mesh.vertices, 'f')
+        n = numpy.array(mesh.normals, 'f')
+
+        mesh.gl["vbo"] = vbo.VBO(numpy.hstack((v, n)))
+
+        # Fill the buffer for vertex positions
+        mesh.gl["faces"] = glGenBuffers(1)
+        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.gl["faces"])
+        glBufferData(GL_ELEMENT_ARRAY_BUFFER,
+                     numpy.array(mesh.faces, dtype=numpy.int32),
+                     GL_STATIC_DRAW)
+
+        mesh.gl["nbfaces"] = len(mesh.faces)
+
+        # Unbind buffers
+        glBindBuffer(GL_ARRAY_BUFFER, 0)
+        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)
+
+    @staticmethod
+    def get_rgb_from_colorid(colorid):
+        r = (colorid >> 0) & 0xff
+        g = (colorid >> 8) & 0xff
+        b = (colorid >> 16) & 0xff
+
+        return r, g, b
+
+    def get_color_id(self):
+        id = random.randint(0, 256 * 256 * 256)
+        if id not in self.colorid2node:
+            return id
+        else:
+            return self.get_color_id()
+
+    def glize(self, scene, node):
+
+        logger.info("Loading node <%s>" % node)
+        node.selected = True if self.currently_selected and self.currently_selected == node else False
+
+        node.transformation = node.transformation.astype(numpy.float32)
+
+        if node.meshes:
+            node.type = MESH
+            colorid = self.get_color_id()
+            self.colorid2node[colorid] = node
+            self.node2colorid[node.name] = colorid
+
+        elif node.name in [c.name for c in scene.cameras]:
+
+            # retrieve the ASSIMP camera object
+            [cam] = [c for c in scene.cameras if c.name == node.name]
+            node.type = CAMERA
+            logger.info("Added camera <%s>" % node.name)
+            logger.info("Camera position: %.3f, %.3f, %.3f" % tuple(node.transformation[:, 3][:3].tolist()))
+            self.cameras.append(node)
+            node.clipplanenear = cam.clipplanenear
+            node.clipplanefar = cam.clipplanefar
+
+            if numpy.allclose(cam.lookat, [0, 0, -1]) and numpy.allclose(cam.up, [0, 1, 0]):  # Cameras in .blend files
+
+                # Rotate by 180deg around X to have Z pointing forward
+                node.transformation = numpy.dot(node.transformation, ROTATION_180_X)
+            else:
+                raise RuntimeError(
+                    "I do not know how to normalize this camera orientation: lookat=%s, up=%s" % (cam.lookat, cam.up))
+
+            if cam.aspect == 0.0:
+                logger.warning("Camera aspect not set. Setting to default 4:3")
+                node.aspect = 1.333
+            else:
+                node.aspect = cam.aspect
+
+            node.horizontalfov = cam.horizontalfov
+
+        else:
+            node.type = ENTITY
+
+        for child in node.children:
+            self.glize(scene, child)
+
+    def load_model(self, path, postprocess=aiProcessPreset_TargetRealtime_MaxQuality):
+        logger.info("Loading model:" + path + "...")
+
+        if postprocess:
+            self.scene = pyassimp.load(path, processing=postprocess)
+        else:
+            self.scene = pyassimp.load(path)
+        logger.info("Done.")
+
+        scene = self.scene
+        # log some statistics
+        logger.info("  meshes: %d" % len(scene.meshes))
+        logger.info("  total faces: %d" % sum([len(mesh.faces) for mesh in scene.meshes]))
+        logger.info("  materials: %d" % len(scene.materials))
+        self.bb_min, self.bb_max = get_bounding_box(self.scene)
+        logger.info("  bounding box:" + str(self.bb_min) + " - " + str(self.bb_max))
+
+        self.scene_center = [(a + b) / 2. for a, b in zip(self.bb_min, self.bb_max)]
+
+        for index, mesh in enumerate(scene.meshes):
+            self.prepare_gl_buffers(mesh)
+
+        self.glize(scene, scene.rootnode)
+
+        # Finally release the model
+        pyassimp.release(scene)
+        logger.info("Ready for 3D rendering!")
+
+    def cycle_cameras(self):
+
+        self.current_cam_index = (self.current_cam_index + 1) % len(self.cameras)
+        self.current_cam = self.cameras[self.current_cam_index]
+        self.set_camera_projection(self.current_cam)
+        logger.info("Switched to camera <%s>" % self.current_cam)
+
+    def set_overlay_projection(self):
+        glViewport(0, 0, self.w, self.h)
+        glMatrixMode(GL_PROJECTION)
+        glLoadIdentity()
+        glOrtho(0.0, self.w - 1.0, 0.0, self.h - 1.0, -1.0, 1.0)
+        glMatrixMode(GL_MODELVIEW)
+        glLoadIdentity()
+
+    def set_camera_projection(self, camera=None):
+
+        if not camera:
+            camera = self.current_cam
+
+        znear = camera.clipplanenear or DEFAULT_CLIP_PLANE_NEAR
+        zfar = camera.clipplanefar or DEFAULT_CLIP_PLANE_FAR
+        aspect = camera.aspect
+        fov = camera.horizontalfov
+
+        glMatrixMode(GL_PROJECTION)
+        glLoadIdentity()
+
+        # Compute gl frustrum
+        tangent = math.tan(fov / 2.)
+        h = znear * tangent
+        w = h * aspect
+
+        # params: left, right, bottom, top, near, far
+        glFrustum(-w, w, -h, h, znear, zfar)
+        # equivalent to:
+        # gluPerspective(fov * 180/math.pi, aspect, znear, zfar)
+
+        self.projection_matrix = glGetFloatv(GL_PROJECTION_MATRIX).transpose()
+
+        glMatrixMode(GL_MODELVIEW)
+        glLoadIdentity()
+
+    def render_colors(self):
+
+        glEnable(GL_DEPTH_TEST)
+        glDepthFunc(GL_LEQUAL)
+
+        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
+        glEnable(GL_CULL_FACE)
+
+        glUseProgram(self.flatshader)
+
+        glUniformMatrix4fv(self.flatshader.u_viewProjectionMatrix, 1, GL_TRUE,
+                           numpy.dot(self.projection_matrix, self.view_matrix))
+
+        self.recursive_render(self.scene.rootnode, self.flatshader, mode=COLORS)
+
+        glUseProgram(0)
+
+    def get_hovered_node(self, mousex, mousey):
+        """
+        Attention: The performances of this method relies heavily on the size of the display!
+        """
+
+        # mouse out of the window?
+        if mousex < 0 or mousex >= self.w or mousey < 0 or mousey >= self.h:
+            return None
+
+        self.render_colors()
+        # Capture image from the OpenGL buffer
+        buf = (GLubyte * (3 * self.w * self.h))(0)
+        glReadPixels(0, 0, self.w, self.h, GL_RGB, GL_UNSIGNED_BYTE, buf)
+
+        # Reinterpret the RGB pixel buffer as a 1-D array of 24bits colors
+        a = numpy.ndarray(len(buf), numpy.dtype('>u1'), buf)
+        colors = numpy.zeros(len(buf) // 3, numpy.dtype('<u4'))
+        for i in range(3):
+            colors.view(dtype='>u1')[i::4] = a.view(dtype='>u1')[i::3]
+
+        colorid = colors[mousex + mousey * self.w]
+
+        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
+
+        if colorid in self.colorid2node:
+            return self.colorid2node[colorid]
+
+    def render(self, wireframe=False, twosided=False):
+
+        glEnable(GL_DEPTH_TEST)
+        glDepthFunc(GL_LEQUAL)
+
+        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE if wireframe else GL_FILL)
+        glDisable(GL_CULL_FACE) if twosided else glEnable(GL_CULL_FACE)
+
+        self.render_grid()
+
+        self.recursive_render(self.scene.rootnode, None, mode=HELPERS)
+
+        ### First, the silhouette
+
+        if False:
+            shader = self.silhouette_shader
+
+            # glDepthMask(GL_FALSE)
+            glCullFace(GL_FRONT)  # cull front faces
+
+            glUseProgram(shader)
+            glUniform1f(shader.u_bordersize, 0.01)
+
+            glUniformMatrix4fv(shader.u_viewProjectionMatrix, 1, GL_TRUE,
+                               numpy.dot(self.projection_matrix, self.view_matrix))
+
+            self.recursive_render(self.scene.rootnode, shader, mode=SILHOUETTE)
+
+            glUseProgram(0)
+
+        ### Then, inner shading
+        # glDepthMask(GL_TRUE)
+        glCullFace(GL_BACK)
+
+        use_gooch = False
+        if use_gooch:
+            shader = self.gooch_shader
+
+            glUseProgram(shader)
+            glUniform3f(shader.u_lightPos, -.5, -.5, .5)
+
+            ##### GOOCH specific
+            glUniform3f(shader.u_coolColor, 159.0 / 255, 148.0 / 255, 255.0 / 255)
+            glUniform3f(shader.u_warmColor, 255.0 / 255, 75.0 / 255, 75.0 / 255)
+            glUniform1f(shader.u_alpha, .25)
+            glUniform1f(shader.u_beta, .25)
+            #########
+        else:
+            shader = self.shader
+            glUseProgram(shader)
+            glUniform3f(shader.u_lightPos, -.5, -.5, .5)
+
+        glUniformMatrix4fv(shader.u_viewProjectionMatrix, 1, GL_TRUE,
+                           numpy.dot(self.projection_matrix, self.view_matrix))
+
+        self.recursive_render(self.scene.rootnode, shader)
+
+        glUseProgram(0)
+
+    def render_axis(self,
+                    transformation=numpy.identity(4, dtype=numpy.float32),
+                    label=None,
+                    size=0.2,
+                    selected=False):
+        m = transformation.transpose()  # OpenGL row major
+
+        glPushMatrix()
+        glMultMatrixf(m)
+
+        glLineWidth(3 if selected else 1)
+
+        size = 2 * size if selected else size
+
+        glBegin(GL_LINES)
+
+        # draw line for x axis
+        glColor3f(1.0, 0.0, 0.0)
+        glVertex3f(0.0, 0.0, 0.0)
+        glVertex3f(size, 0.0, 0.0)
+
+        # draw line for y axis
+        glColor3f(0.0, 1.0, 0.0)
+        glVertex3f(0.0, 0.0, 0.0)
+        glVertex3f(0.0, size, 0.0)
+
+        # draw line for Z axis
+        glColor3f(0.0, 0.0, 1.0)
+        glVertex3f(0.0, 0.0, 0.0)
+        glVertex3f(0.0, 0.0, size)
+
+        glEnd()
+
+        if label:
+            self.showtext(label)
+
+        glPopMatrix()
+
+    @staticmethod
+    def render_camera(camera, transformation):
+
+        m = transformation.transpose()  # OpenGL row major
+
+        aspect = camera.aspect
+
+        u = 0.1  # unit size (in m)
+        l = 3 * u  # lenght of the camera cone
+        f = 3 * u  # aperture of the camera cone
+
+        glPushMatrix()
+        glMultMatrixf(m)
+
+        glLineWidth(2)
+        glBegin(GL_LINE_STRIP)
+
+        glColor3f(.2, .2, .2)
+
+        glVertex3f(u, u, -u)
+        glVertex3f(u, -u, -u)
+        glVertex3f(-u, -u, -u)
+        glVertex3f(-u, u, -u)
+        glVertex3f(u, u, -u)
+
+        glVertex3f(u, u, 0.0)
+        glVertex3f(u, -u, 0.0)
+        glVertex3f(-u, -u, 0.0)
+        glVertex3f(-u, u, 0.0)
+        glVertex3f(u, u, 0.0)
+
+        glVertex3f(f * aspect, f, l)
+        glVertex3f(f * aspect, -f, l)
+        glVertex3f(-f * aspect, -f, l)
+        glVertex3f(-f * aspect, f, l)
+        glVertex3f(f * aspect, f, l)
+
+        glEnd()
+
+        glBegin(GL_LINE_STRIP)
+        glVertex3f(u, -u, -u)
+        glVertex3f(u, -u, 0.0)
+        glVertex3f(f * aspect, -f, l)
+        glEnd()
+
+        glBegin(GL_LINE_STRIP)
+        glVertex3f(-u, -u, -u)
+        glVertex3f(-u, -u, 0.0)
+        glVertex3f(-f * aspect, -f, l)
+        glEnd()
+
+        glBegin(GL_LINE_STRIP)
+        glVertex3f(-u, u, -u)
+        glVertex3f(-u, u, 0.0)
+        glVertex3f(-f * aspect, f, l)
+        glEnd()
+
+        glPopMatrix()
+
+    @staticmethod
+    def render_grid():
+
+        glLineWidth(1)
+        glColor3f(0.5, 0.5, 0.5)
+        glBegin(GL_LINES)
+        for i in range(-10, 11):
+            glVertex3f(i, -10.0, 0.0)
+            glVertex3f(i, 10.0, 0.0)
+
+        for i in range(-10, 11):
+            glVertex3f(-10.0, i, 0.0)
+            glVertex3f(10.0, i, 0.0)
+        glEnd()
+
+    def recursive_render(self, node, shader, mode=BASE, with_normals=True):
+        """ Main recursive rendering method.
+        """
+
+        normals = with_normals
+
+        if mode == COLORS:
+            normals = False
+
+
+        if not hasattr(node, "selected"):
+            node.selected = False
+
+        m = get_world_transform(self.scene, node)
+
+        # HELPERS mode
+        ###
+        if mode == HELPERS:
+            # if node.type == ENTITY:
+            self.render_axis(m,
+                             label=node.name if node != self.scene.rootnode else None,
+                             selected=node.selected if hasattr(node, "selected") else False)
+
+            if node.type == CAMERA:
+                self.render_camera(node, m)
+
+            for child in node.children:
+                    self.recursive_render(child, shader, mode)
+
+            return
+
+        # Mesh rendering modes
+        ###
+        if node.type == MESH:
+
+            for mesh in node.meshes:
+
+                stride = 24  # 6 * 4 bytes
+
+                if node.selected and mode == SILHOUETTE:
+                    glUniform4f(shader.u_materialDiffuse, 1.0, 0.0, 0.0, 1.0)
+                    glUniformMatrix4fv(shader.u_modelViewMatrix, 1, GL_TRUE,
+                                       numpy.dot(self.view_matrix, m))
+
+                else:
+                    if mode == COLORS:
+                        colorid = self.node2colorid[node.name]
+                        r, g, b = self.get_rgb_from_colorid(colorid)
+                        glUniform4f(shader.u_materialDiffuse, r / 255.0, g / 255.0, b / 255.0, 1.0)
+                    elif mode == SILHOUETTE:
+                        glUniform4f(shader.u_materialDiffuse, .0, .0, .0, 1.0)
+                    else:
+                        if node.selected:
+                            diffuse = (1.0, 0.0, 0.0, 1.0)  # selected nodes in red
+                        else:
+                            diffuse = mesh.material.properties["diffuse"]
+                        if len(diffuse) == 3:  # RGB instead of expected RGBA
+                            diffuse.append(1.0)
+                        glUniform4f(shader.u_materialDiffuse, *diffuse)
+                        # if ambient:
+                        #    glUniform4f( shader.Material_ambient, *mat["ambient"] )
+
+                if mode == BASE:  # not in COLORS or SILHOUETTE
+                    normal_matrix = linalg.inv(numpy.dot(self.view_matrix, m)[0:3, 0:3]).transpose()
+                    glUniformMatrix3fv(shader.u_normalMatrix, 1, GL_TRUE, normal_matrix)
+
+                glUniformMatrix4fv(shader.u_modelMatrix, 1, GL_TRUE, m)
+
+                vbo = mesh.gl["vbo"]
+                vbo.bind()
+
+                glEnableVertexAttribArray(shader.a_vertex)
+                if normals:
+                    glEnableVertexAttribArray(shader.a_normal)
+
+                glVertexAttribPointer(
+                    shader.a_vertex,
+                    3, GL_FLOAT, False, stride, vbo
+                )
+
+                if normals:
+                    glVertexAttribPointer(
+                        shader.a_normal,
+                        3, GL_FLOAT, False, stride, vbo + 12
+                    )
+
+                glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.gl["faces"])
+                glDrawElements(GL_TRIANGLES, mesh.gl["nbfaces"] * 3, GL_UNSIGNED_INT, None)
+
+                vbo.unbind()
+                glDisableVertexAttribArray(shader.a_vertex)
+
+                if normals:
+                    glDisableVertexAttribArray(shader.a_normal)
+
+                glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)
+
+        for child in node.children:
+            self.recursive_render(child, shader, mode)
+
+
+    def switch_to_overlay(self):
+        glPushMatrix()
+        self.set_overlay_projection()
+
+    def switch_from_overlay(self):
+        self.set_camera_projection()
+        glPopMatrix()
+
+    def select_node(self, node):
+        self.currently_selected = node
+        self.update_node_select(self.scene.rootnode)
+
+    def update_node_select(self, node):
+        if node is self.currently_selected:
+            node.selected = True
+        else:
+            node.selected = False
+
+        for child in node.children:
+            self.update_node_select(child)
+
+    def loop(self):
+
+        pygame.display.flip()
+
+        if not self.process_events():
+            return False  # ESC has been pressed
+
+        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
+
+        return True
+
+    def process_events(self):
+
+        LEFT_BUTTON = 1
+        MIDDLE_BUTTON = 2
+        RIGHT_BUTTON = 3
+        WHEEL_UP = 4
+        WHEEL_DOWN = 5
+
+        dx, dy = pygame.mouse.get_rel()
+        mousex, mousey = pygame.mouse.get_pos()
+
+        zooming_one_shot = False
+
+        ok = True
+
+        for evt in pygame.event.get():
+            if evt.type == pygame.MOUSEBUTTONDOWN and evt.button == LEFT_BUTTON:
+                hovered = self.get_hovered_node(mousex, self.h - mousey)
+                if hovered:
+                    if self.currently_selected and self.currently_selected == hovered:
+                        self.select_node(None)
+                    else:
+                        logger.info("Node %s selected" % hovered)
+                        self.select_node(hovered)
+                else:
+                    self.is_rotating = True
+            if evt.type == pygame.MOUSEBUTTONUP and evt.button == LEFT_BUTTON:
+                self.is_rotating = False
+
+            if evt.type == pygame.MOUSEBUTTONDOWN and evt.button == MIDDLE_BUTTON:
+                self.is_panning = True
+            if evt.type == pygame.MOUSEBUTTONUP and evt.button == MIDDLE_BUTTON:
+                self.is_panning = False
+
+            if evt.type == pygame.MOUSEBUTTONDOWN and evt.button == RIGHT_BUTTON:
+                self.is_zooming = True
+            if evt.type == pygame.MOUSEBUTTONUP and evt.button == RIGHT_BUTTON:
+                self.is_zooming = False
+
+            if evt.type == pygame.MOUSEBUTTONDOWN and evt.button in [WHEEL_UP, WHEEL_DOWN]:
+                zooming_one_shot = True
+                self.is_zooming = True
+                dy = -10 if evt.button == WHEEL_UP else 10
+
+            if evt.type == pygame.KEYDOWN:
+                ok = (ok and self.process_keystroke(evt.key, evt.mod))
+
+        self.controls_3d(dx, dy, zooming_one_shot)
+
+        return ok
+
+    def process_keystroke(self, key, mod):
+
+        # process arrow keys if an object is selected
+        if self.currently_selected:
+            up = 0
+            strafe = 0
+
+            if key == pygame.K_UP:
+                up = 1
+            if key == pygame.K_DOWN:
+                up = -1
+            if key == pygame.K_LEFT:
+                strafe = -1
+            if key == pygame.K_RIGHT:
+                strafe = 1
+
+            self.move_selected_node(up, strafe)
+
+        if key == pygame.K_f:
+            pygame.display.toggle_fullscreen()
+
+        if key == pygame.K_TAB:
+            self.cycle_cameras()
+
+        if key in [pygame.K_ESCAPE, pygame.K_q]:
+            return False
+
+        return True
+
+    def controls_3d(self, dx, dy, zooming_one_shot=False):
+
+        CAMERA_TRANSLATION_FACTOR = 0.01
+        CAMERA_ROTATION_FACTOR = 0.01
+
+        if not (self.is_rotating or self.is_panning or self.is_zooming):
+            return
+
+        current_pos = self.current_cam.transformation[:3, 3].copy()
+        distance = numpy.linalg.norm(self.focal_point - current_pos)
+
+        if self.is_rotating:
+            """ Orbiting the camera is implemented the following way:
+
+            - the rotation is split into a rotation around the *world* Z axis
+              (controlled by the horizontal mouse motion along X) and a
+              rotation around the *X* axis of the camera (pitch) *shifted to
+              the focal origin* (the world origin for now). This is controlled
+              by the vertical motion of the mouse (Y axis).
+
+            - as a result, the resulting transformation of the camera in the
+              world frame C' is:
+                C' = (T · Rx · T⁻¹ · (Rz · C)⁻¹)⁻¹
+
+              where:
+                - C is the original camera transformation in the world frame,
+                - Rz is the rotation along the Z axis (in the world frame)
+                - T is the translation camera -> world (ie, the inverse of the
+                  translation part of C
+                - Rx is the rotation around X in the (translated) camera frame
+            """
+
+            rotation_camera_x = dy * CAMERA_ROTATION_FACTOR
+            rotation_world_z = dx * CAMERA_ROTATION_FACTOR
+            world_z_rotation = transformations.euler_matrix(0, 0, rotation_world_z)
+            cam_x_rotation = transformations.euler_matrix(rotation_camera_x, 0, 0)
+
+            after_world_z_rotation = numpy.dot(world_z_rotation, self.current_cam.transformation)
+
+            inverse_transformation = transformations.inverse_matrix(after_world_z_rotation)
+
+            translation = transformations.translation_matrix(
+                transformations.decompose_matrix(inverse_transformation)[3])
+            inverse_translation = transformations.inverse_matrix(translation)
+
+            new_inverse = numpy.dot(inverse_translation, inverse_transformation)
+            new_inverse = numpy.dot(cam_x_rotation, new_inverse)
+            new_inverse = numpy.dot(translation, new_inverse)
+
+            self.current_cam.transformation = transformations.inverse_matrix(new_inverse).astype(numpy.float32)
+
+        if self.is_panning:
+            tx = -dx * CAMERA_TRANSLATION_FACTOR * distance
+            ty = dy * CAMERA_TRANSLATION_FACTOR * distance
+            cam_transform = transformations.translation_matrix((tx, ty, 0)).astype(numpy.float32)
+            self.current_cam.transformation = numpy.dot(self.current_cam.transformation, cam_transform)
+
+        if self.is_zooming:
+            tz = dy * CAMERA_TRANSLATION_FACTOR * distance
+            cam_transform = transformations.translation_matrix((0, 0, tz)).astype(numpy.float32)
+            self.current_cam.transformation = numpy.dot(self.current_cam.transformation, cam_transform)
+
+        if zooming_one_shot:
+            self.is_zooming = False
+
+        self.update_view_camera()
+
+    def update_view_camera(self):
+
+        self.view_matrix = linalg.inv(self.current_cam.transformation)
+
+        # Rotate by 180deg around X to have Z pointing backward (OpenGL convention)
+        self.view_matrix = numpy.dot(ROTATION_180_X, self.view_matrix)
+
+        glMatrixMode(GL_MODELVIEW)
+        glLoadIdentity()
+        glMultMatrixf(self.view_matrix.transpose())
+
+    def move_selected_node(self, up, strafe):
+        self.currently_selected.transformation[0][3] += strafe
+        self.currently_selected.transformation[2][3] += up
+
+    @staticmethod
+    def showtext(text, x=0, y=0, z=0, size=20):
+
+        # TODO: alpha blending does not work...
+        # glEnable(GL_BLEND)
+        # glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
+
+        font = pygame.font.Font(None, size)
+        text_surface = font.render(text, True, (10, 10, 10, 255),
+                                  (255 * 0.18, 255 * 0.18, 255 * 0.18, 0))
+        text_data = pygame.image.tostring(text_surface, "RGBA", True)
+        glRasterPos3d(x, y, z)
+        glDrawPixels(text_surface.get_width(),
+                     text_surface.get_height(),
+                     GL_RGBA, GL_UNSIGNED_BYTE,
+                     text_data)
+
+        # glDisable(GL_BLEND)
+
+
+def main(model, width, height):
+    app = PyAssimp3DViewer(model, w=width, h=height)
+
+    clock = pygame.time.Clock()
+
+    while app.loop():
+
+        app.update_view_camera()
+
+        ## Main rendering
+        app.render()
+
+        ## GUI text display
+        app.switch_to_overlay()
+        app.showtext("Active camera: %s" % str(app.current_cam), 10, app.h - 30)
+        if app.currently_selected:
+            app.showtext("Selected node: %s" % app.currently_selected, 10, app.h - 50)
+            pos = app.h - 70
+
+            app.showtext("(%sm, %sm, %sm)" % (app.currently_selected.transformation[0, 3],
+                                              app.currently_selected.transformation[1, 3],
+                                              app.currently_selected.transformation[2, 3]), 30, pos)
+
+        app.switch_from_overlay()
+
+        # Make sure we do not go over 30fps
+        clock.tick(30)
+
+    logger.info("Quitting! Bye bye!")
+
+
+#########################################################################
+#########################################################################
+
+if __name__ == '__main__':
+    if not len(sys.argv) > 1:
+        print("Usage: " + __file__ + " <model>")
+        sys.exit(2)
+
+    main(model=sys.argv[1], width=1024, height=768)

+ 5 - 2
port/PyAssimp/setup.py

@@ -8,6 +8,9 @@ setup(name='pyassimp',
       description='Python bindings for the Open Asset Import Library (ASSIMP)',
       url='https://github.com/assimp/assimp',
       packages=['pyassimp'],
-      data_files=[('share/pyassimp', ['README.md']),
-                  ('share/examples/pyassimp', ['scripts/' + f for f in os.listdir('scripts/')])], requires=['numpy']
+      data_files=[
+                  ('share/pyassimp', ['README.md']),
+                  ('share/examples/pyassimp', ['scripts/' + f for f in os.listdir('scripts/')])
+                 ],
+      requires=['numpy']
       )

+ 2 - 0
test/CMakeLists.txt

@@ -116,6 +116,7 @@ SET( TEST_SRCS
   unit/utRemoveRedundantMaterials.cpp
   unit/utRemoveVCProcess.cpp
   unit/utScenePreprocessor.cpp
+  unit/utSceneCombiner.cpp
   unit/utSharedPPData.cpp
   unit/utStringUtils.cpp
   unit/utSMDImportExport.cpp
@@ -130,6 +131,7 @@ SET( TEST_SRCS
   unit/utVector3.cpp
   unit/utXImporterExporter.cpp
   unit/utD3MFImportExport.cpp
+  unit/utQ3DImportExport.cpp
   unit/utProfiler.cpp
 )
 

+ 8 - 1
test/unit/utD3MFImportExport.cpp

@@ -43,13 +43,20 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "AbstractImportExportBase.h"
 
 #include <assimp/Importer.hpp>
+#include <assimp/scene.h>
 
 class utD3MFImporterExporter : public AbstractImportExportBase {
 public:
     virtual bool importerTest() {
         Assimp::Importer importer;
         const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/3MF/box.3mf", 0);
-        return nullptr != scene;
+        EXPECT_EQ( 1u, scene->mNumMeshes );
+        aiMesh *mesh = scene->mMeshes[ 0 ];
+        EXPECT_NE( nullptr, mesh );
+        EXPECT_EQ( 12u, mesh->mNumFaces );
+        EXPECT_EQ( 8u, mesh->mNumVertices );
+        
+        return ( nullptr != scene );
     }
 };
 

+ 0 - 2
test/unit/utOpenGEXImportExport.cpp

@@ -54,8 +54,6 @@ public:
         Assimp::Importer importer;
         const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/OpenGEX/Example.ogex", 0 );
         return nullptr != scene;
-
-        return true;
     }
 };
 

+ 61 - 0
test/unit/utQ3DImportExport.cpp

@@ -0,0 +1,61 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2017, assimp team
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+#include "UnitTestPCH.h"
+
+#include "AbstractImportExportBase.h"
+
+#include <assimp/Importer.hpp>
+
+using namespace Assimp;
+
+class utQ3DImportExport : public AbstractImportExportBase {
+public:
+    virtual bool importerTest() {
+        Assimp::Importer importer;
+        const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/Q3D/earth.q3o", 0 );
+        return nullptr != scene;
+    }
+};
+
+TEST_F(utQ3DImportExport, importTest) {
+    EXPECT_TRUE( importerTest() );
+}

+ 70 - 0
test/unit/utSceneCombiner.cpp

@@ -0,0 +1,70 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2017, assimp team
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+#include "UnitTestPCH.h"
+#include <assimp/SceneCombiner.h>
+#include <assimp/mesh.h>
+
+using namespace ::Assimp;
+
+class utSceneCombiner : public ::testing::Test {
+    // empty
+};
+
+TEST_F( utSceneCombiner, MergeMeshes_ValidNames_Test ) {
+    std::vector<aiMesh*> merge_list;
+    aiMesh *mesh1 = new aiMesh;
+    mesh1->mName.Set( "mesh_1" );
+    merge_list.push_back( mesh1 );
+
+    aiMesh *mesh2 = new aiMesh;
+    mesh2->mName.Set( "mesh_2" );
+    merge_list.push_back( mesh2 );
+
+    aiMesh *mesh3 = new aiMesh;
+    mesh3->mName.Set( "mesh_3" );
+    merge_list.push_back( mesh3 );
+
+    aiMesh *out( nullptr );
+    SceneCombiner::MergeMeshes( &out, 0, merge_list.begin(), merge_list.end() );
+    std::string outName = out->mName.C_Str();
+    EXPECT_EQ( "mesh_1.mesh_2.mesh_3", outName );
+}

+ 5 - 1
tools/assimp_qt_viewer/glview.cpp

@@ -6,7 +6,11 @@
 #include "glview.hpp"
 
 // Header files, OpenGL.
-#include <GL/glu.h>
+#if defined(__APPLE__)
+# include <OpenGL/glu.h>
+#else
+# include <GL/glu.h>
+#endif
 
 // Header files, DevIL.
 #include <il.h>