Browse Source

Merge branch 'master' into preserve_error_string

Mike Samsonov 5 years ago
parent
commit
631da3a3d1
46 changed files with 3168 additions and 1996 deletions
  1. 1 0
      .gitignore
  2. 1 1
      CMakeLists.txt
  3. 7 2
      Readme.md
  4. 41 30
      code/Collada/ColladaParser.cpp
  5. 2 2
      code/Common/DefaultLogger.cpp
  6. 1 2
      code/Common/Exporter.cpp
  7. 6 0
      code/Common/Version.cpp
  8. 31 6
      code/M3D/M3DExporter.cpp
  9. 2 0
      code/M3D/M3DExporter.h
  10. 50 21
      code/M3D/M3DImporter.cpp
  11. 2 2
      code/M3D/M3DMaterials.h
  12. 391 196
      code/M3D/m3d.h
  13. 5 5
      code/Material/MaterialSystem.cpp
  14. 8 5
      code/PostProcessing/ValidateDataStructure.cpp
  15. 0 3
      code/glTF/glTFAsset.inl
  16. 7 10
      code/glTF/glTFCommon.h
  17. 16 1
      code/glTF2/glTF2Asset.h
  18. 61 5
      code/glTF2/glTF2Asset.inl
  19. 1 0
      code/glTF2/glTF2Exporter.h
  20. 1111 1136
      code/glTF2/glTF2Importer.cpp
  21. 2 0
      contrib/zip/.gitignore
  22. 74 9
      contrib/zip/CMakeLists.txt
  23. 6 6
      contrib/zip/README.md
  24. 1 1
      contrib/zip/appveyor.yml
  25. 400 57
      contrib/zip/src/miniz.h
  26. 40 22
      contrib/zip/src/zip.c
  27. 226 231
      contrib/zip/src/zip.h
  28. 12 15
      contrib/zip/test/CMakeLists.txt
  29. 36 2
      contrib/zip/test/test.c
  30. 24 1
      contrib/zip/test/test_miniz.c
  31. 7 0
      include/assimp/version.h
  32. 46 0
      samples/SimpleTexturedDirectx11/CMakeLists.txt
  33. 0 28
      samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11.sln
  34. 2 0
      samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/ModelLoader.cpp
  35. 0 146
      samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/SimpleTexturedDirectx11.vcxproj
  36. 0 50
      samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/SimpleTexturedDirectx11.vcxproj.filters
  37. BIN
      test/models/glTF2/textureTransform/Arrow.png
  38. BIN
      test/models/glTF2/textureTransform/Correct.png
  39. BIN
      test/models/glTF2/textureTransform/Error.png
  40. 0 0
      test/models/glTF2/textureTransform/License.txt
  41. BIN
      test/models/glTF2/textureTransform/NotSupported.png
  42. BIN
      test/models/glTF2/textureTransform/TextureTransformTest.bin
  43. 540 0
      test/models/glTF2/textureTransform/TextureTransformTest.gltf
  44. BIN
      test/models/glTF2/textureTransform/UV.png
  45. 1 1
      test/unit/utM3DImportExport.cpp
  46. 7 0
      test/unit/utglTF2ImportExport.cpp

+ 1 - 0
.gitignore

@@ -2,6 +2,7 @@
 build
 .project
 *.kdev4*
+.DS_Store
 
 # build artefacts
 *.o

+ 1 - 1
CMakeLists.txt

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

+ 7 - 2
Readme.md

@@ -60,14 +60,19 @@ __Importers__:
 - ENFF
 - [FBX](https://en.wikipedia.org/wiki/FBX)
 - [glTF 1.0](https://en.wikipedia.org/wiki/GlTF#glTF_1.0) + GLB
-- [glTF 2.0](https://en.wikipedia.org/wiki/GlTF#glTF_2.0)
+- [glTF 2.0](https://en.wikipedia.org/wiki/GlTF#glTF_2.0):
+  At the moment for glTF2.0 the following extensions are supported:
+  + KHR_lights_punctual ( 5.0 )
+  + KHR_materials_pbrSpecularGlossiness ( 5.0 )
+  + KHR_materials_unlit ( 5.0 )
+  + KHR_texture_transform ( 5.1 under test )
 - HMB
 - IFC-STEP
 - IRR / IRRMESH
 - [LWO](https://en.wikipedia.org/wiki/LightWave_3D)
 - LWS
 - LXO
-- [M3D](https://gitlab.com/bztsrc/model3d)
+- [M3D](https://bztsrc.gitlab.io/model3d)
 - MD2
 - MD3
 - MD5

+ 41 - 30
code/Collada/ColladaParser.cpp

@@ -3234,13 +3234,12 @@ void ColladaParser::ReadScene()
 
 // ------------------------------------------------------------------------------------------------
 // Aborts the file reading with an exception
-AI_WONT_RETURN void ColladaParser::ThrowException(const std::string& pError) const
-{
+AI_WONT_RETURN void ColladaParser::ThrowException(const std::string& pError) const {
     throw DeadlyImportError(format() << "Collada: " << mFileName << " - " << pError);
 }
-void ColladaParser::ReportWarning(const char* msg, ...)
-{
-    ai_assert(NULL != msg);
+
+void ColladaParser::ReportWarning(const char* msg, ...) {
+    ai_assert(nullptr != msg);
 
     va_list args;
     va_start(args, msg);
@@ -3255,11 +3254,11 @@ void ColladaParser::ReportWarning(const char* msg, ...)
 
 // ------------------------------------------------------------------------------------------------
 // Skips all data until the end node of the current element
-void ColladaParser::SkipElement()
-{
+void ColladaParser::SkipElement() {
     // nothing to skip if it's an <element />
-    if (mReader->isEmptyElement())
+    if (mReader->isEmptyElement()) {
         return;
+    }
 
     // reroute
     SkipElement(mReader->getNodeName());
@@ -3267,63 +3266,75 @@ void ColladaParser::SkipElement()
 
 // ------------------------------------------------------------------------------------------------
 // Skips all data until the end node of the given element
-void ColladaParser::SkipElement(const char* pElement)
-{
+void ColladaParser::SkipElement(const char* pElement) {
     // copy the current node's name because it'a pointer to the reader's internal buffer,
     // which is going to change with the upcoming parsing
     std::string element = pElement;
-    while (mReader->read())
-    {
-        if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
-            if (mReader->getNodeName() == element)
+    while (mReader->read()) {
+        if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) {
+            if (mReader->getNodeName() == element) {
                 break;
+            }
+        }
     }
 }
 
 // ------------------------------------------------------------------------------------------------
 // Tests for an opening element of the given name, throws an exception if not found
-void ColladaParser::TestOpening(const char* pName)
-{
+void ColladaParser::TestOpening(const char* pName) {
     // read element start
-    if (!mReader->read())
+    if (!mReader->read()) {
         ThrowException(format() << "Unexpected end of file while beginning of <" << pName << "> element.");
+    }
     // whitespace in front is ok, just read again if found
-    if (mReader->getNodeType() == irr::io::EXN_TEXT)
-        if (!mReader->read())
+    if (mReader->getNodeType() == irr::io::EXN_TEXT) {
+        if (!mReader->read()) {
             ThrowException(format() << "Unexpected end of file while reading beginning of <" << pName << "> element.");
+        }
+    }
 
-    if (mReader->getNodeType() != irr::io::EXN_ELEMENT || strcmp(mReader->getNodeName(), pName) != 0)
+    if (mReader->getNodeType() != irr::io::EXN_ELEMENT || strcmp(mReader->getNodeName(), pName) != 0) {
         ThrowException(format() << "Expected start of <" << pName << "> element.");
+    }
 }
 
 // ------------------------------------------------------------------------------------------------
 // Tests for the closing tag of the given element, throws an exception if not found
-void ColladaParser::TestClosing(const char* pName)
-{
+void ColladaParser::TestClosing(const char* pName) {
+    // check if we have an empty (self-closing) element
+    if (mReader->isEmptyElement()) {
+        return;
+    }
+
     // check if we're already on the closing tag and return right away
-    if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END && strcmp(mReader->getNodeName(), pName) == 0)
+    if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END && strcmp(mReader->getNodeName(), pName) == 0) {
         return;
+    }
 
     // if not, read some more
-    if (!mReader->read())
+    if (!mReader->read()) {
         ThrowException(format() << "Unexpected end of file while reading end of <" << pName << "> element.");
+    }
     // whitespace in front is ok, just read again if found
-    if (mReader->getNodeType() == irr::io::EXN_TEXT)
-        if (!mReader->read())
+    if (mReader->getNodeType() == irr::io::EXN_TEXT) {
+        if (!mReader->read()) {
             ThrowException(format() << "Unexpected end of file while reading end of <" << pName << "> element.");
+        }
+    }
 
     // but this has the be the closing tag, or we're lost
-    if (mReader->getNodeType() != irr::io::EXN_ELEMENT_END || strcmp(mReader->getNodeName(), pName) != 0)
+    if (mReader->getNodeType() != irr::io::EXN_ELEMENT_END || strcmp(mReader->getNodeName(), pName) != 0) {
         ThrowException(format() << "Expected end of <" << pName << "> element.");
+    }
 }
 
 // ------------------------------------------------------------------------------------------------
 // Returns the index of the named attribute or -1 if not found. Does not throw, therefore useful for optional attributes
-int ColladaParser::GetAttribute(const char* pAttr) const
-{
+int ColladaParser::GetAttribute(const char* pAttr) const {
     int index = TestAttribute(pAttr);
-    if (index != -1)
+    if (index != -1) {
         return index;
+    }
 
     // attribute not found -> throw an exception
     ThrowException(format() << "Expected attribute \"" << pAttr << "\" for element <" << mReader->getNodeName() << ">.");

+ 2 - 2
code/Common/DefaultLogger.cpp

@@ -107,7 +107,7 @@ LogStream* LogStream::createDefaultStream(aiDefaultLogStream    streams,
         return nullptr;
 #endif
 
-        // Platform-independent default streams
+    // Platform-independent default streams
     case aiDefaultLogStream_STDERR:
         return new StdOStreamLogStream(std::cerr);
     case aiDefaultLogStream_STDOUT:
@@ -121,7 +121,7 @@ LogStream* LogStream::createDefaultStream(aiDefaultLogStream    streams,
     };
 
     // For compilers without dead code path detection
-    return NULL;
+    return nullptr;
 }
 
 // ----------------------------------------------------------------------------------

+ 1 - 2
code/Common/Exporter.cpp

@@ -445,8 +445,7 @@ aiReturn Exporter::Export( const aiScene* pScene, const char* pFormatId, const c
 
                 ExportProperties emptyProperties;  // Never pass NULL ExportProperties so Exporters don't have to worry.
                 ExportProperties* pProp = pProperties ? (ExportProperties*)pProperties : &emptyProperties;
-                                pProp->SetPropertyBool("bJoinIdenticalVertices", must_join_again);
-                                exp.mExportFunction(pPath,pimpl->mIOSystem.get(),scenecopy.get(), pProp);
+                pProp->SetPropertyBool("bJoinIdenticalVertices", must_join_again);
                 exp.mExportFunction(pPath,pimpl->mIOSystem.get(),scenecopy.get(), pProp);
 
                 pimpl->mProgressHandler->UpdateFileWrite(4, 4);

+ 6 - 0
code/Common/Version.cpp

@@ -66,6 +66,12 @@ ASSIMP_API const char*  aiGetLegalString  ()    {
     return LEGAL_INFORMATION;
 }
 
+// ------------------------------------------------------------------------------------------------
+// Get Assimp patch version
+ASSIMP_API unsigned int aiGetVersionPatch() {
+	return VER_PATCH;
+}
+
 // ------------------------------------------------------------------------------------------------
 // Get Assimp minor version
 ASSIMP_API unsigned int aiGetVersionMinor ()    {

+ 31 - 6
code/M3D/M3DExporter.cpp

@@ -169,6 +169,33 @@ void M3DExporter::doExport (
     outfile.reset();
 }
 
+
+// ------------------------------------------------------------------------------------------------
+// helper to add a vertex (private to NodeWalk)
+m3dv_t *M3DExporter::AddVrtx(m3dv_t *vrtx, uint32_t *numvrtx, m3dv_t *v, uint32_t *idx)
+{
+    if(v->x == (M3D_FLOAT)-0.0) v->x = (M3D_FLOAT)0.0;
+    if(v->y == (M3D_FLOAT)-0.0) v->y = (M3D_FLOAT)0.0;
+    if(v->z == (M3D_FLOAT)-0.0) v->z = (M3D_FLOAT)0.0;
+    if(v->w == (M3D_FLOAT)-0.0) v->w = (M3D_FLOAT)0.0;
+    vrtx = (m3dv_t*)M3D_REALLOC(vrtx, ((*numvrtx) + 1) * sizeof(m3dv_t));
+    memcpy(&vrtx[*numvrtx], v, sizeof(m3dv_t));
+    *idx = *numvrtx;
+    (*numvrtx)++;
+    return vrtx;
+}
+
+// ------------------------------------------------------------------------------------------------
+// helper to add a tmap (private to NodeWalk)
+m3dti_t *M3DExporter::AddTmap(m3dti_t *tmap, uint32_t *numtmap, m3dti_t *ti, uint32_t *idx)
+{
+    tmap = (m3dti_t*)M3D_REALLOC(tmap, ((*numtmap) + 1) * sizeof(m3dti_t));
+    memcpy(&tmap[*numtmap], ti, sizeof(m3dti_t));
+    *idx = *numtmap;
+    (*numtmap)++;
+    return tmap;
+}
+
 // ------------------------------------------------------------------------------------------------
 // recursive node walker
 void M3DExporter::NodeWalk(const aiNode* pNode, aiMatrix4x4 m)
@@ -221,25 +248,23 @@ void M3DExporter::NodeWalk(const aiNode* pNode, aiMatrix4x4 m)
                 if(mesh->HasVertexColors(0))
                     vertex.color = mkColor(&mesh->mColors[0][l]);
                 // save the vertex to the output
-                m3d->vertex = _m3d_addvrtx(m3d->vertex, &m3d->numvertex,
+                m3d->vertex = AddVrtx(m3d->vertex, &m3d->numvertex,
                     &vertex, &idx);
                 m3d->face[n].vertex[k] = (M3D_INDEX)idx;
                 // do we have texture coordinates?
                 if(mesh->HasTextureCoords(0)) {
                     ti.u = mesh->mTextureCoords[0][l].x;
                     ti.v = mesh->mTextureCoords[0][l].y;
-                    m3d->tmap = _m3d_addtmap(m3d->tmap, &m3d->numtmap, &ti,
-                        &idx);
+                    m3d->tmap = AddTmap(m3d->tmap, &m3d->numtmap, &ti, &idx);
                     m3d->face[n].texcoord[k] = (M3D_INDEX)idx;
                 }
                 // do we have normal vectors?
                 if(mesh->HasNormals()) {
-                    vertex.color = 0;
                     vertex.x = mesh->mNormals[l].x;
                     vertex.y = mesh->mNormals[l].y;
                     vertex.z = mesh->mNormals[l].z;
-                    m3d->vertex = _m3d_addnorm(m3d->vertex, &m3d->numvertex,
-                        &vertex, &idx);
+                    vertex.color = 0;
+                    m3d->vertex = AddVrtx(m3d->vertex, &m3d->numvertex, &vertex, &idx);
                     m3d->face[n].normal[k] = (M3D_INDEX)idx;
                 }
             }

+ 2 - 0
code/M3D/M3DExporter.h

@@ -87,6 +87,8 @@ namespace Assimp
 
         // helper to do the recursive walking
         void NodeWalk(const aiNode* pNode, aiMatrix4x4 m);
+        m3dv_t *AddVrtx(m3dv_t *vrtx, uint32_t *numvrtx, m3dv_t *v, uint32_t *idx);
+        m3dti_t *AddTmap(m3dti_t *tmap, uint32_t *numtmap, m3dti_t *ti, uint32_t *idx);
         uint32_t mkColor(aiColor4D* c);
         M3D_INDEX addMaterial(const aiMaterial *mat);
         void addProp(m3dm_t *m, uint8_t type, uint32_t value);

+ 50 - 21
code/M3D/M3DImporter.cpp

@@ -44,6 +44,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #define M3D_IMPLEMENTATION
 #define M3D_ASCII
+#define M3D_NONORMALS       /* leave the post-processing to Assimp */
+#define M3D_NOWEIGHTS
+#define M3D_NOANIMATION
 
 #include <assimp/IOStreamBuffer.h>
 #include <memory>
@@ -104,16 +107,21 @@ extern "C" {
         std::string file(fn);
         std::unique_ptr<Assimp::IOStream> pStream(
             (reinterpret_cast<Assimp::IOSystem*>(m3dimporter_pIOHandler))->Open( file, "rb"));
-        size_t fileSize = pStream->FileSize();
-        // should be allocated with malloc(), because the library will call free() to deallocate
-        unsigned char *data = (unsigned char*)malloc(fileSize);
-        if( !data || !pStream.get() || !fileSize || fileSize != pStream->Read(data,1,fileSize)) {
+        size_t fileSize = 0;
+        unsigned char *data = NULL;
+        // sometimes pStream is nullptr for some reason (should be an empty object returning nothing I guess)
+        if(pStream) {
+            fileSize = pStream->FileSize();
+            // should be allocated with malloc(), because the library will call free() to deallocate
+            data = (unsigned char*)malloc(fileSize);
+            if( !data || !pStream.get() || !fileSize || fileSize != pStream->Read(data,1,fileSize)) {
+                pStream.reset();
+                *size = 0;
+                // don't throw a deadly exception, it's not fatal if we can't read an external asset
+                return nullptr;
+            }
             pStream.reset();
-            *size = 0;
-            // don't throw a deadly exception, it's not fatal if we can't read an external asset
-            return nullptr;
         }
-        pStream.reset();
         *size = (int)fileSize;
         return data;
     }
@@ -307,7 +315,7 @@ void M3DImporter::importMaterials()
                 m->prop[j].value.textureid < m3d->numtexture &&
                 m3d->texture[m->prop[j].value.textureid].name) {
                     name.Set(std::string(std::string(m3d->texture[m->prop[j].value.textureid].name) + ".png"));
-                    mat->AddProperty(&name, aiProps[k].pKey, aiProps[k].type, aiProps[k].index);
+                    mat->AddProperty(&name, aiTxProps[k].pKey, aiTxProps[k].type, aiTxProps[k].index);
                     n = 0;
                     mat->AddProperty(&n, 1, _AI_MATKEY_UVWSRC_BASE, aiProps[k].type, aiProps[k].index);
             }
@@ -321,6 +329,7 @@ void M3DImporter::importMaterials()
 void M3DImporter::importTextures()
 {
     unsigned int i;
+    const char *formatHint[] = { "rgba0800", "rgba0808", "rgba8880", "rgba8888" };
     m3dtx_t *t;
 
     ai_assert(mScene != nullptr);
@@ -334,14 +343,29 @@ void M3DImporter::importTextures()
 
     mScene->mTextures = new aiTexture*[m3d->numtexture];
     for(i = 0; i < m3d->numtexture; i++) {
+        unsigned int j, k;
         t = &m3d->texture[i];
+        if(!t->w || !t->h || !t->f || !t->d) continue;
         aiTexture *tx = new aiTexture;
-        strcpy(tx->achFormatHint, "rgba8888");
+        strcpy(tx->achFormatHint, formatHint[t->f - 1]);
         tx->mFilename = aiString(std::string(t->name) + ".png");
         tx->mWidth = t->w;
         tx->mHeight = t->h;
         tx->pcData = new aiTexel[ tx->mWidth*tx->mHeight ];
-        memcpy(tx->pcData, t->d, tx->mWidth*tx->mHeight*4);
+        for(j = k = 0; j < tx->mWidth*tx->mHeight; j++) {
+            switch(t->f) {
+                case 1: tx->pcData[j].g = t->d[k++]; break;
+                case 2: tx->pcData[j].g = t->d[k++]; tx->pcData[j].a = t->d[k++]; break;
+                case 3:
+                    tx->pcData[j].r = t->d[k++]; tx->pcData[j].g = t->d[k++];
+                    tx->pcData[j].b = t->d[k++]; tx->pcData[j].a = 255;
+                break;
+                case 4:
+                    tx->pcData[j].r = t->d[k++]; tx->pcData[j].g = t->d[k++];
+                    tx->pcData[j].b = t->d[k++]; tx->pcData[j].a = t->d[k++];
+                break;
+            }
+        }
         mScene->mTextures[i] = tx;
     }
 }
@@ -372,9 +396,14 @@ void M3DImporter::importMeshes()
         // we must switch mesh if material changes
         if(lastMat != m3d->face[i].materialid) {
             lastMat = m3d->face[i].materialid;
-            if(pMesh && vertices->size() && faces->size()) {
+            if(pMesh && vertices && vertices->size() && faces && faces->size()) {
                 populateMesh(pMesh, faces, vertices, normals, texcoords, colors, vertexids);
                 meshes->push_back(pMesh);
+                delete faces;
+                delete vertices;
+                delete normals;
+                delete texcoords;
+                delete colors;
                 delete vertexids;   // this is not stored in pMesh, just to collect bone vertices
             }
             pMesh = new aiMesh;
@@ -574,15 +603,15 @@ void M3DImporter::convertPose(aiMatrix4x4 *m, unsigned int posid, unsigned int o
         m->a2 = m->a3 = m->b1 = m->b3 = m->c1 = m->c2 = 0.0;
         m->a1 = m->b2 = m->c3 = -1.0;
     } else {
-        m->a1 = 1 - 2 * (q->y * q->y + q->z * q->z); if(m->a1 > -1e-7 && m->a1 < 1e-7) m->a1 = 0.0;
-        m->a2 = 2 * (q->x * q->y - q->z * q->w);     if(m->a2 > -1e-7 && m->a2 < 1e-7) m->a2 = 0.0;
-        m->a3 = 2 * (q->x * q->z + q->y * q->w);     if(m->a3 > -1e-7 && m->a3 < 1e-7) m->a3 = 0.0;
-        m->b1 = 2 * (q->x * q->y + q->z * q->w);     if(m->b1 > -1e-7 && m->b1 < 1e-7) m->b1 = 0.0;
-        m->b2 = 1 - 2 * (q->x * q->x + q->z * q->z); if(m->b2 > -1e-7 && m->b2 < 1e-7) m->b2 = 0.0;
-        m->b3 = 2 * (q->y * q->z - q->x * q->w);     if(m->b3 > -1e-7 && m->b3 < 1e-7) m->b3 = 0.0;
-        m->c1 = 2 * (q->x * q->z - q->y * q->w);     if(m->c1 > -1e-7 && m->c1 < 1e-7) m->c1 = 0.0;
-        m->c2 = 2 * (q->y * q->z + q->x * q->w);     if(m->c2 > -1e-7 && m->c2 < 1e-7) m->c2 = 0.0;
-        m->c3 = 1 - 2 * (q->x * q->x + q->y * q->y); if(m->c3 > -1e-7 && m->c3 < 1e-7) m->c3 = 0.0;
+        m->a1 = 1 - 2 * (q->y * q->y + q->z * q->z); if(m->a1 > -M3D_EPSILON && m->a1 < M3D_EPSILON) m->a1 = 0.0;
+        m->a2 = 2 * (q->x * q->y - q->z * q->w);     if(m->a2 > -M3D_EPSILON && m->a2 < M3D_EPSILON) m->a2 = 0.0;
+        m->a3 = 2 * (q->x * q->z + q->y * q->w);     if(m->a3 > -M3D_EPSILON && m->a3 < M3D_EPSILON) m->a3 = 0.0;
+        m->b1 = 2 * (q->x * q->y + q->z * q->w);     if(m->b1 > -M3D_EPSILON && m->b1 < M3D_EPSILON) m->b1 = 0.0;
+        m->b2 = 1 - 2 * (q->x * q->x + q->z * q->z); if(m->b2 > -M3D_EPSILON && m->b2 < M3D_EPSILON) m->b2 = 0.0;
+        m->b3 = 2 * (q->y * q->z - q->x * q->w);     if(m->b3 > -M3D_EPSILON && m->b3 < M3D_EPSILON) m->b3 = 0.0;
+        m->c1 = 2 * (q->x * q->z - q->y * q->w);     if(m->c1 > -M3D_EPSILON && m->c1 < M3D_EPSILON) m->c1 = 0.0;
+        m->c2 = 2 * (q->y * q->z + q->x * q->w);     if(m->c2 > -M3D_EPSILON && m->c2 < M3D_EPSILON) m->c2 = 0.0;
+        m->c3 = 1 - 2 * (q->x * q->x + q->y * q->y); if(m->c3 > -M3D_EPSILON && m->c3 < M3D_EPSILON) m->c3 = 0.0;
     }
 
     /* set translation */

+ 2 - 2
code/M3D/M3DMaterials.h

@@ -75,7 +75,7 @@ static const aiMatProp aiProps[] = {
     { AI_MATKEY_REFLECTIVITY },                                 /* m3dp_Pm */
     { NULL, 0, 0 },                                             /* m3dp_Ps */
     { AI_MATKEY_REFRACTI },                                     /* m3dp_Ni */
-    { NULL, 0, 0 },
+    { NULL, 0, 0 },                                             /* m3dp_Nt */
     { NULL, 0, 0 },
     { NULL, 0, 0 },
     { NULL, 0, 0 }
@@ -97,7 +97,7 @@ static const aiMatProp aiTxProps[] = {
     { AI_MATKEY_TEXTURE(aiTextureType_METALNESS,0) },        /* m3dp_map_Pm */
     { NULL, 0, 0 },                                          /* m3dp_map_Ps */
     { AI_MATKEY_TEXTURE(aiTextureType_AMBIENT_OCCLUSION,0) },/* m3dp_map_Ni */
-    { NULL, 0, 0 },
+    { NULL, 0, 0 },                                          /* m3dp_map_Nt */
     { NULL, 0, 0 },
     { NULL, 0, 0 },
     { NULL, 0, 0 }

File diff suppressed because it is too large
+ 391 - 196
code/M3D/m3d.h


+ 5 - 5
code/Material/MaterialSystem.cpp

@@ -273,14 +273,14 @@ aiReturn aiGetMaterialColor(const aiMaterial* pMat,
 }
 
 // ------------------------------------------------------------------------------------------------
-// Get a aiUVTransform (4 floats) from the material
+// Get a aiUVTransform (5 floats) from the material
 aiReturn aiGetMaterialUVTransform(const aiMaterial* pMat,
     const char* pKey,
     unsigned int type,
     unsigned int index,
     aiUVTransform* pOut)
 {
-    unsigned int iMax = 4;
+    unsigned int iMax = 5;
     return aiGetMaterialFloatArray(pMat,pKey,type,index,(ai_real*)pOut,&iMax);
 }
 
@@ -471,12 +471,12 @@ aiReturn aiMaterial::AddBinaryProperty (const void* pInput,
     aiPropertyTypeInfo pType
     )
 {
-    ai_assert( pInput != NULL );
-    ai_assert( pKey != NULL );
+    ai_assert( pInput != nullptr );
+	ai_assert(pKey != nullptr );
     ai_assert( 0 != pSizeInBytes );
 
     if ( 0 == pSizeInBytes ) {
-
+		return AI_FAILURE;
     }
 
     // first search the list whether there is already an entry with this key

+ 8 - 5
code/PostProcessing/ValidateDataStructure.cpp

@@ -603,15 +603,18 @@ void ValidateDSProcess::SearchForInvalidTextures(const aiMaterial* pMaterial,
         ReportError("%s #%i is set, but there are only %i %s textures",
             szType,iIndex,iNumIndices,szType);
     }
-    if (!iNumIndices)return;
+	if (!iNumIndices) {
+		return;
+	}
     std::vector<aiTextureMapping> mappings(iNumIndices);
 
     // Now check whether all UV indices are valid ...
     bool bNoSpecified = true;
-    for (unsigned int i = 0; i < pMaterial->mNumProperties;++i)
-    {
+    for (unsigned int i = 0; i < pMaterial->mNumProperties;++i) {
         aiMaterialProperty* prop = pMaterial->mProperties[i];
-        if (prop->mSemantic != type)continue;
+		if (prop->mSemantic != type) {
+			continue;
+		}
 
         if ((int)prop->mIndex >= iNumIndices)
         {
@@ -634,7 +637,7 @@ void ValidateDSProcess::SearchForInvalidTextures(const aiMaterial* pMaterial,
                 ReportError("Material property %s%i is expected to be 5 floats large (size is %i)",
                     prop->mKey.data,prop->mIndex, prop->mDataLength);
             }
-            mappings[prop->mIndex] = *((aiTextureMapping*)prop->mData);
+			//mappings[prop->mIndex] = ((aiUVTransform*)prop->mData);
         }
         else if (!::strcmp(prop->mKey.data,"$tex.uvwsrc")) {
             if (aiPTI_Integer != prop->mType || sizeof(int) > prop->mDataLength)

+ 0 - 3
code/glTF/glTFAsset.inl

@@ -1427,9 +1427,6 @@ inline void Asset::ReadExtensionsUsed(Document& doc)
         }
     }
 
-    #define CHECK_EXT(EXT) \
-        if (exts.find(#EXT) != exts.end()) extensionsUsed.EXT = true;
-
     CHECK_EXT(KHR_binary_glTF);
     CHECK_EXT(KHR_materials_common);
 

+ 7 - 10
code/glTF/glTFCommon.h

@@ -188,7 +188,7 @@ namespace glTFCommon {
         size_t DecodeBase64(const char* in, size_t inLength, uint8_t*& out);
 
         inline
-            size_t DecodeBase64(const char* in, uint8_t*& out) {
+        size_t DecodeBase64(const char* in, uint8_t*& out) {
             return DecodeBase64(in, strlen(in), out);
         }
 
@@ -221,25 +221,22 @@ namespace glTFCommon {
         };
 
         inline
-            char EncodeCharBase64(uint8_t b) {
+        char EncodeCharBase64(uint8_t b) {
             return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="[size_t(b)];
         }
 
         inline
-            uint8_t DecodeCharBase64(char c) {
+        uint8_t DecodeCharBase64(char c) {
             return DATA<true>::tableDecodeBase64[size_t(c)]; // TODO faster with lookup table or ifs?
-            /*if (c >= 'A' && c <= 'Z') return c - 'A';
-            if (c >= 'a' && c <= 'z') return c - 'a' + 26;
-            if (c >= '0' && c <= '9') return c - '0' + 52;
-            if (c == '+') return 62;
-            if (c == '/') return 63;
-            return 64; // '-' */
         }
 
         size_t DecodeBase64(const char* in, size_t inLength, uint8_t*& out);
 
         void EncodeBase64(const uint8_t* in, size_t inLength, std::string& out);
-    }
+    } // namespace Util
+
+#define CHECK_EXT(EXT) \
+	if (exts.find(#EXT) != exts.end()) extensionsUsed.EXT = true;
 
 }
 

+ 16 - 1
code/glTF2/glTF2Asset.h

@@ -685,6 +685,13 @@ namespace glTF2
         Ref<Texture> texture;
         unsigned int index;
         unsigned int texCoord = 0;
+
+        bool textureTransformSupported = false;
+        struct TextureTransformExt {
+			float offset[2];
+			float rotation;
+			float scale[2];
+		} TextureTransformExt_t;
     };
 
     struct NormalTextureInfo : TextureInfo
@@ -1024,9 +1031,15 @@ namespace glTF2
             bool KHR_materials_pbrSpecularGlossiness;
             bool KHR_materials_unlit;
             bool KHR_lights_punctual;
-
+			bool KHR_texture_transform;
         } extensionsUsed;
 
+        //! Keeps info about the required extensions
+        struct RequiredExtensions
+        {
+            bool KHR_draco_mesh_compression;
+        } extensionsRequired;
+
         AssetMetadata asset;
 
 
@@ -1069,6 +1082,7 @@ namespace glTF2
             , textures      (*this, "textures")
         {
             memset(&extensionsUsed, 0, sizeof(extensionsUsed));
+            memset(&extensionsRequired, 0, sizeof(extensionsRequired));
         }
 
         //! Main function
@@ -1087,6 +1101,7 @@ namespace glTF2
         void ReadBinaryHeader(IOStream& stream, std::vector<char>& sceneData);
 
         void ReadExtensionsUsed(Document& doc);
+        void ReadExtensionsRequired(Document& doc);
 
         IOStream* OpenFile(std::string path, const char* mode, bool absolute = false);
     };

+ 61 - 5
code/glTF2/glTF2Asset.inl

@@ -800,8 +800,34 @@ inline void Texture::Read(Value& obj, Asset& r)
 }
 
 namespace {
-    inline void SetTextureProperties(Asset& r, Value* prop, TextureInfo& out)
-    {
+    inline void SetTextureProperties(Asset& r, Value* prop, TextureInfo& out) {
+	    if (r.extensionsUsed.KHR_texture_transform) {
+            if (Value *extensions = FindObject(*prop, "extensions")) {
+				out.textureTransformSupported = true;
+                if (Value *pKHR_texture_transform = FindObject(*extensions, "KHR_texture_transform")) {
+					if (Value *array = FindArray(*pKHR_texture_transform, "offset")) {
+						out.TextureTransformExt_t.offset[0] = (*array)[0].GetFloat();
+						out.TextureTransformExt_t.offset[1] = (*array)[1].GetFloat();
+					} else {
+						out.TextureTransformExt_t.offset[0] = 0;
+						out.TextureTransformExt_t.offset[1] = 0;
+					}      
+					
+                    if (!ReadMember(*pKHR_texture_transform, "rotation", out.TextureTransformExt_t.rotation)) {
+						out.TextureTransformExt_t.rotation = 0;					
+                    }
+					
+                    if (Value *array = FindArray(*pKHR_texture_transform, "scale")) {
+						out.TextureTransformExt_t.scale[0] = (*array)[0].GetFloat();
+						out.TextureTransformExt_t.scale[1] = (*array)[1].GetFloat();
+					} else {
+						out.TextureTransformExt_t.scale[0] = 1;
+						out.TextureTransformExt_t.scale[1] = 1;
+                    }
+				}
+			}
+        }
+
         if (Value* index = FindUInt(*prop, "index")) {
             out.texture = r.textures.Retrieve(index->GetUint());
         }
@@ -877,6 +903,9 @@ inline void Material::Read(Value& material, Asset& r)
             }
         }
 
+        if (r.extensionsUsed.KHR_texture_transform) {
+		}
+
         unlit = nullptr != FindObject(*extensions, "KHR_materials_unlit");
     }
 }
@@ -1403,6 +1432,12 @@ inline void Asset::Load(const std::string& pFile, bool isBinary)
     // Load the metadata
     asset.Read(doc);
     ReadExtensionsUsed(doc);
+    ReadExtensionsRequired(doc);
+
+    // Currently Draco is not supported
+    if (extensionsRequired.KHR_draco_mesh_compression) {
+        throw DeadlyImportError("GLTF: Draco mesh compression not currently supported.");
+    }
 
     // Prepare the dictionaries
     for (size_t i = 0; i < mDicts.size(); ++i) {
@@ -1449,6 +1484,29 @@ inline void Asset::SetAsBinary()
     }
 }
 
+// As required extensions are only a concept in glTF 2.0, this is here
+// instead of glTFCommon.h
+#define CHECK_REQUIRED_EXT(EXT) \
+	if (exts.find(#EXT) != exts.end()) extensionsRequired.EXT = true;
+
+inline void Asset::ReadExtensionsRequired(Document& doc)
+{
+    Value* extsRequired = FindArray(doc, "extensionsRequired");
+    if (nullptr == extsRequired) {
+	return;
+    }
+
+    std::gltf_unordered_map<std::string, bool> exts;
+    for (unsigned int i = 0; i < extsRequired->Size(); ++i) {
+        if ((*extsRequired)[i].IsString()) {
+            exts[(*extsRequired)[i].GetString()] = true;
+        }
+    }
+
+    CHECK_REQUIRED_EXT(KHR_draco_mesh_compression);
+
+    #undef CHECK_REQUIRED_EXT
+}
 
 inline void Asset::ReadExtensionsUsed(Document& doc)
 {
@@ -1463,12 +1521,10 @@ inline void Asset::ReadExtensionsUsed(Document& doc)
         }
     }
 
-    #define CHECK_EXT(EXT) \
-        if (exts.find(#EXT) != exts.end()) extensionsUsed.EXT = true;
-
     CHECK_EXT(KHR_materials_pbrSpecularGlossiness);
     CHECK_EXT(KHR_materials_unlit);
     CHECK_EXT(KHR_lights_punctual);
+	CHECK_EXT(KHR_texture_transform);
 
     #undef CHECK_EXT
 }

+ 1 - 0
code/glTF2/glTF2Exporter.h

@@ -74,6 +74,7 @@ namespace glTF2
     struct Texture;
 
     // Vec/matrix types, as raw float arrays
+	typedef float (vec2)[2];
     typedef float (vec3)[3];
     typedef float (vec4)[4];
 }

+ 1111 - 1136
code/glTF2/glTF2Importer.cpp

@@ -43,18 +43,18 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #ifndef ASSIMP_BUILD_NO_GLTF_IMPORTER
 
 #include "glTF2/glTF2Importer.h"
+#include "PostProcessing/MakeVerboseFormat.h"
 #include "glTF2/glTF2Asset.h"
 #include "glTF2/glTF2AssetWriter.h"
-#include "PostProcessing/MakeVerboseFormat.h"
 
+#include <assimp/CreateAnimMesh.h>
 #include <assimp/StringComparison.h>
 #include <assimp/StringUtils.h>
-#include <assimp/Importer.hpp>
-#include <assimp/scene.h>
 #include <assimp/ai_assert.h>
-#include <assimp/DefaultLogger.hpp>
 #include <assimp/importerdesc.h>
-#include <assimp/CreateAnimMesh.h>
+#include <assimp/scene.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/Importer.hpp>
 
 #include <memory>
 #include <unordered_map>
@@ -67,11 +67,11 @@ using namespace glTF2;
 using namespace glTFCommon;
 
 namespace {
-    // generate bi-tangents from normals and tangents according to spec
-    struct Tangent {
-        aiVector3D xyz;
-        ai_real w;
-    };
+// generate bi-tangents from normals and tangents according to spec
+struct Tangent {
+	aiVector3D xyz;
+	ai_real w;
+};
 } // namespace
 
 //
@@ -79,66 +79,63 @@ namespace {
 //
 
 static const aiImporterDesc desc = {
-    "glTF2 Importer",
-    "",
-    "",
-    "",
-    aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour | aiImporterFlags_LimitedSupport | aiImporterFlags_Experimental,
-    0,
-    0,
-    0,
-    0,
-    "gltf glb"
+	"glTF2 Importer",
+	"",
+	"",
+	"",
+	aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour | aiImporterFlags_LimitedSupport | aiImporterFlags_Experimental,
+	0,
+	0,
+	0,
+	0,
+	"gltf glb"
 };
 
-glTF2Importer::glTF2Importer()
-: BaseImporter()
-, meshOffsets()
-, embeddedTexIdxs()
-, mScene( NULL ) {
-    // empty
+glTF2Importer::glTF2Importer() :
+		BaseImporter(),
+		meshOffsets(),
+		embeddedTexIdxs(),
+		mScene(NULL) {
+	// empty
 }
 
 glTF2Importer::~glTF2Importer() {
-    // empty
+	// empty
 }
 
-const aiImporterDesc* glTF2Importer::GetInfo() const
-{
-    return &desc;
+const aiImporterDesc *glTF2Importer::GetInfo() const {
+	return &desc;
 }
 
-bool glTF2Importer::CanRead(const std::string& pFile, IOSystem* pIOHandler, bool /* checkSig */) const
-{
-    const std::string &extension = GetExtension(pFile);
+bool glTF2Importer::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /* checkSig */) const {
+	const std::string &extension = GetExtension(pFile);
 
-    if (extension != "gltf" && extension != "glb")
-        return false;
+	if (extension != "gltf" && extension != "glb")
+		return false;
 
-    if (pIOHandler) {
-        glTF2::Asset asset(pIOHandler);
-        asset.Load(pFile, extension == "glb");
-        std::string version = asset.asset.version;
-        return !version.empty() && version[0] == '2';
-    }
+	if (pIOHandler) {
+		glTF2::Asset asset(pIOHandler);
+		asset.Load(pFile, extension == "glb");
+		std::string version = asset.asset.version;
+		return !version.empty() && version[0] == '2';
+	}
 
-    return false;
+	return false;
 }
 
-static aiTextureMapMode ConvertWrappingMode(SamplerWrap gltfWrapMode)
-{
-    switch (gltfWrapMode) {
-        case SamplerWrap::Mirrored_Repeat:
-            return aiTextureMapMode_Mirror;
-
-        case SamplerWrap::Clamp_To_Edge:
-            return aiTextureMapMode_Clamp;
-
-        case SamplerWrap::UNSET:
-        case SamplerWrap::Repeat:
-        default:
-            return aiTextureMapMode_Wrap;
-    }
+static aiTextureMapMode ConvertWrappingMode(SamplerWrap gltfWrapMode) {
+	switch (gltfWrapMode) {
+		case SamplerWrap::Mirrored_Repeat:
+			return aiTextureMapMode_Mirror;
+
+		case SamplerWrap::Clamp_To_Edge:
+			return aiTextureMapMode_Clamp;
+
+		case SamplerWrap::UNSET:
+		case SamplerWrap::Repeat:
+		default:
+			return aiTextureMapMode_Wrap;
+	}
 }
 
 /*static void CopyValue(const glTF2::vec3& v, aiColor3D& out)
@@ -180,1182 +177,1160 @@ static void CopyValue(const glTF2::vec4& v, aiQuaternion& out)
     o.a4 = v[12]; o.b4 = v[13]; o.c4 = v[14]; o.d4 = v[15];
 }*/
 
-inline void SetMaterialColorProperty(Asset& /*r*/, vec4& prop, aiMaterial* mat, const char* pKey, unsigned int type, unsigned int idx)
-{
-    aiColor4D col;
-    CopyValue(prop, col);
-    mat->AddProperty(&col, 1, pKey, type, idx);
+inline void SetMaterialColorProperty(Asset & /*r*/, vec4 &prop, aiMaterial *mat, const char *pKey, unsigned int type, unsigned int idx) {
+	aiColor4D col;
+	CopyValue(prop, col);
+	mat->AddProperty(&col, 1, pKey, type, idx);
 }
 
-inline void SetMaterialColorProperty(Asset& /*r*/, vec3& prop, aiMaterial* mat, const char* pKey, unsigned int type, unsigned int idx)
-{
-    aiColor4D col;
-    glTFCommon::CopyValue(prop, col);
-    mat->AddProperty(&col, 1, pKey, type, idx);
+inline void SetMaterialColorProperty(Asset & /*r*/, vec3 &prop, aiMaterial *mat, const char *pKey, unsigned int type, unsigned int idx) {
+	aiColor4D col;
+	glTFCommon::CopyValue(prop, col);
+	mat->AddProperty(&col, 1, pKey, type, idx);
 }
 
-inline void SetMaterialTextureProperty(std::vector<int>& embeddedTexIdxs, Asset& /*r*/, glTF2::TextureInfo prop, aiMaterial* mat, aiTextureType texType, unsigned int texSlot = 0)
-{
-    if (prop.texture && prop.texture->source) {
-        aiString uri(prop.texture->source->uri);
+inline void SetMaterialTextureProperty(std::vector<int> &embeddedTexIdxs, Asset & /*r*/, glTF2::TextureInfo prop, aiMaterial *mat, aiTextureType texType, unsigned int texSlot = 0) {
+	if (prop.texture && prop.texture->source) {
+		aiString uri(prop.texture->source->uri);
 
-        int texIdx = embeddedTexIdxs[prop.texture->source.GetIndex()];
-        if (texIdx != -1) { // embedded
-            // setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture)
-            uri.data[0] = '*';
-            uri.length = 1 + ASSIMP_itoa10(uri.data + 1, MAXLEN - 1, texIdx);
-        }
+		int texIdx = embeddedTexIdxs[prop.texture->source.GetIndex()];
+		if (texIdx != -1) { // embedded
+			// setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture)
+			uri.data[0] = '*';
+			uri.length = 1 + ASSIMP_itoa10(uri.data + 1, MAXLEN - 1, texIdx);
+		}
 
         mat->AddProperty(&uri, AI_MATKEY_TEXTURE(texType, texSlot));
-        mat->AddProperty(&prop.texCoord, 1, _AI_MATKEY_GLTF_TEXTURE_TEXCOORD_BASE, texType, texSlot);
-
-        if (prop.texture->sampler) {
-            Ref<Sampler> sampler = prop.texture->sampler;
-
-            aiString name(sampler->name);
-            aiString id(sampler->id);
-
-            mat->AddProperty(&name, AI_MATKEY_GLTF_MAPPINGNAME(texType, texSlot));
-            mat->AddProperty(&id, AI_MATKEY_GLTF_MAPPINGID(texType, texSlot));
-
-            aiTextureMapMode wrapS = ConvertWrappingMode(sampler->wrapS);
-            aiTextureMapMode wrapT = ConvertWrappingMode(sampler->wrapT);
-            mat->AddProperty(&wrapS, 1, AI_MATKEY_MAPPINGMODE_U(texType, texSlot));
-            mat->AddProperty(&wrapT, 1, AI_MATKEY_MAPPINGMODE_V(texType, texSlot));
 
-            if (sampler->magFilter != SamplerMagFilter::UNSET) {
-                mat->AddProperty(&sampler->magFilter, 1, AI_MATKEY_GLTF_MAPPINGFILTER_MAG(texType, texSlot));
-            }
-
-            if (sampler->minFilter != SamplerMinFilter::UNSET) {
-                mat->AddProperty(&sampler->minFilter, 1, AI_MATKEY_GLTF_MAPPINGFILTER_MIN(texType, texSlot));
-            }
-        }
-    }
+		if (prop.textureTransformSupported) {
+			aiUVTransform transform;
+			transform.mScaling.x = prop.TextureTransformExt_t.scale[0];
+			transform.mScaling.y = prop.TextureTransformExt_t.scale[1];
+			transform.mRotation = -prop.TextureTransformExt_t.rotation; // must be negated
+
+			// A change of coordinates is required to map glTF UV transformations into the space used by
+			// Assimp. In glTF all UV origins are at 0,1 (top left of texture) in Assimp space. In Assimp
+			// rotation occurs around the image center (0.5,0.5) where as in glTF rotation is around the
+			// texture origin. All three can be corrected for solely by a change of the translation since
+			// the transformations available are shape preserving. Note the importer already flips the V
+			// coordinate of the actual meshes during import.
+			const ai_real rcos(cos(-transform.mRotation));
+			const ai_real rsin(sin(-transform.mRotation));
+			transform.mTranslation.x = (0.5 * transform.mScaling.x) * (-rcos + rsin + 1) + prop.TextureTransformExt_t.offset[0];
+			transform.mTranslation.y = ((0.5 * transform.mScaling.y) * (rsin + rcos - 1)) + 1 - transform.mScaling.y - prop.TextureTransformExt_t.offset[1];;
+
+			mat->AddProperty(&transform, 1, _AI_MATKEY_UVTRANSFORM_BASE, texType, texSlot);
+		}
+
+		if (prop.texture->sampler) {
+			Ref<Sampler> sampler = prop.texture->sampler;
+
+			aiString name(sampler->name);
+			aiString id(sampler->id);
+
+			mat->AddProperty(&name, AI_MATKEY_GLTF_MAPPINGNAME(texType, texSlot));
+			mat->AddProperty(&id, AI_MATKEY_GLTF_MAPPINGID(texType, texSlot));
+
+			aiTextureMapMode wrapS = ConvertWrappingMode(sampler->wrapS);
+			aiTextureMapMode wrapT = ConvertWrappingMode(sampler->wrapT);
+			mat->AddProperty(&wrapS, 1, AI_MATKEY_MAPPINGMODE_U(texType, texSlot));
+			mat->AddProperty(&wrapT, 1, AI_MATKEY_MAPPINGMODE_V(texType, texSlot));
+
+			if (sampler->magFilter != SamplerMagFilter::UNSET) {
+				mat->AddProperty(&sampler->magFilter, 1, AI_MATKEY_GLTF_MAPPINGFILTER_MAG(texType, texSlot));
+			}
+
+			if (sampler->minFilter != SamplerMinFilter::UNSET) {
+				mat->AddProperty(&sampler->minFilter, 1, AI_MATKEY_GLTF_MAPPINGFILTER_MIN(texType, texSlot));
+			}
+		}
+	}
 }
 
-inline void SetMaterialTextureProperty(std::vector<int>& embeddedTexIdxs, Asset& r, glTF2::NormalTextureInfo& prop, aiMaterial* mat, aiTextureType texType, unsigned int texSlot = 0)
-{
-    SetMaterialTextureProperty( embeddedTexIdxs, r, (glTF2::TextureInfo) prop, mat, texType, texSlot );
+inline void SetMaterialTextureProperty(std::vector<int> &embeddedTexIdxs, Asset &r, glTF2::NormalTextureInfo &prop, aiMaterial *mat, aiTextureType texType, unsigned int texSlot = 0) {
+	SetMaterialTextureProperty(embeddedTexIdxs, r, (glTF2::TextureInfo)prop, mat, texType, texSlot);
 
-    if (prop.texture && prop.texture->source) {
-         mat->AddProperty(&prop.scale, 1, AI_MATKEY_GLTF_TEXTURE_SCALE(texType, texSlot));
-    }
+	if (prop.texture && prop.texture->source) {
+		mat->AddProperty(&prop.scale, 1, AI_MATKEY_GLTF_TEXTURE_SCALE(texType, texSlot));
+	}
 }
 
-inline void SetMaterialTextureProperty(std::vector<int>& embeddedTexIdxs, Asset& r, glTF2::OcclusionTextureInfo& prop, aiMaterial* mat, aiTextureType texType, unsigned int texSlot = 0)
-{
-    SetMaterialTextureProperty( embeddedTexIdxs, r, (glTF2::TextureInfo) prop, mat, texType, texSlot );
+inline void SetMaterialTextureProperty(std::vector<int> &embeddedTexIdxs, Asset &r, glTF2::OcclusionTextureInfo &prop, aiMaterial *mat, aiTextureType texType, unsigned int texSlot = 0) {
+	SetMaterialTextureProperty(embeddedTexIdxs, r, (glTF2::TextureInfo)prop, mat, texType, texSlot);
 
-    if (prop.texture && prop.texture->source) {
-        mat->AddProperty(&prop.strength, 1, AI_MATKEY_GLTF_TEXTURE_STRENGTH(texType, texSlot));
-    }
+	if (prop.texture && prop.texture->source) {
+		mat->AddProperty(&prop.strength, 1, AI_MATKEY_GLTF_TEXTURE_STRENGTH(texType, texSlot));
+	}
 }
 
-static aiMaterial* ImportMaterial(std::vector<int>& embeddedTexIdxs, Asset& r, Material& mat)
-{
-    aiMaterial* aimat = new aiMaterial();
+static aiMaterial *ImportMaterial(std::vector<int> &embeddedTexIdxs, Asset &r, Material &mat) {
+	aiMaterial *aimat = new aiMaterial();
 
-   if (!mat.name.empty()) {
-        aiString str(mat.name);
+	if (!mat.name.empty()) {
+		aiString str(mat.name);
 
-        aimat->AddProperty(&str, AI_MATKEY_NAME);
-    }
+		aimat->AddProperty(&str, AI_MATKEY_NAME);
+	}
 
-    SetMaterialColorProperty(r, mat.pbrMetallicRoughness.baseColorFactor, aimat, AI_MATKEY_COLOR_DIFFUSE);
-    SetMaterialColorProperty(r, mat.pbrMetallicRoughness.baseColorFactor, aimat, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR);
+	SetMaterialColorProperty(r, mat.pbrMetallicRoughness.baseColorFactor, aimat, AI_MATKEY_COLOR_DIFFUSE);
+	SetMaterialColorProperty(r, mat.pbrMetallicRoughness.baseColorFactor, aimat, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR);
 
-    SetMaterialTextureProperty(embeddedTexIdxs, r, mat.pbrMetallicRoughness.baseColorTexture, aimat, aiTextureType_DIFFUSE);
-    SetMaterialTextureProperty(embeddedTexIdxs, r, mat.pbrMetallicRoughness.baseColorTexture, aimat, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_TEXTURE);
+	SetMaterialTextureProperty(embeddedTexIdxs, r, mat.pbrMetallicRoughness.baseColorTexture, aimat, aiTextureType_DIFFUSE);
+	SetMaterialTextureProperty(embeddedTexIdxs, r, mat.pbrMetallicRoughness.baseColorTexture, aimat, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_TEXTURE);
 
-    SetMaterialTextureProperty(embeddedTexIdxs, r, mat.pbrMetallicRoughness.metallicRoughnessTexture, aimat, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE);
+	SetMaterialTextureProperty(embeddedTexIdxs, r, mat.pbrMetallicRoughness.metallicRoughnessTexture, aimat, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE);
 
-    aimat->AddProperty(&mat.pbrMetallicRoughness.metallicFactor, 1, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR);
-    aimat->AddProperty(&mat.pbrMetallicRoughness.roughnessFactor, 1, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR);
+	aimat->AddProperty(&mat.pbrMetallicRoughness.metallicFactor, 1, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR);
+	aimat->AddProperty(&mat.pbrMetallicRoughness.roughnessFactor, 1, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR);
 
-    float roughnessAsShininess = 1 - mat.pbrMetallicRoughness.roughnessFactor;
-    roughnessAsShininess *= roughnessAsShininess * 1000;
-    aimat->AddProperty(&roughnessAsShininess, 1, AI_MATKEY_SHININESS);
+	float roughnessAsShininess = 1 - mat.pbrMetallicRoughness.roughnessFactor;
+	roughnessAsShininess *= roughnessAsShininess * 1000;
+	aimat->AddProperty(&roughnessAsShininess, 1, AI_MATKEY_SHININESS);
 
-    SetMaterialTextureProperty(embeddedTexIdxs, r, mat.normalTexture, aimat, aiTextureType_NORMALS);
-    SetMaterialTextureProperty(embeddedTexIdxs, r, mat.occlusionTexture, aimat, aiTextureType_LIGHTMAP);
-    SetMaterialTextureProperty(embeddedTexIdxs, r, mat.emissiveTexture, aimat, aiTextureType_EMISSIVE);
-    SetMaterialColorProperty(r, mat.emissiveFactor, aimat, AI_MATKEY_COLOR_EMISSIVE);
+	SetMaterialTextureProperty(embeddedTexIdxs, r, mat.normalTexture, aimat, aiTextureType_NORMALS);
+	SetMaterialTextureProperty(embeddedTexIdxs, r, mat.occlusionTexture, aimat, aiTextureType_LIGHTMAP);
+	SetMaterialTextureProperty(embeddedTexIdxs, r, mat.emissiveTexture, aimat, aiTextureType_EMISSIVE);
+	SetMaterialColorProperty(r, mat.emissiveFactor, aimat, AI_MATKEY_COLOR_EMISSIVE);
 
-    aimat->AddProperty(&mat.doubleSided, 1, AI_MATKEY_TWOSIDED);
+	aimat->AddProperty(&mat.doubleSided, 1, AI_MATKEY_TWOSIDED);
 
-    aiString alphaMode(mat.alphaMode);
-    aimat->AddProperty(&alphaMode, AI_MATKEY_GLTF_ALPHAMODE);
-    aimat->AddProperty(&mat.alphaCutoff, 1, AI_MATKEY_GLTF_ALPHACUTOFF);
+	aiString alphaMode(mat.alphaMode);
+	aimat->AddProperty(&alphaMode, AI_MATKEY_GLTF_ALPHAMODE);
+	aimat->AddProperty(&mat.alphaCutoff, 1, AI_MATKEY_GLTF_ALPHACUTOFF);
 
-    //pbrSpecularGlossiness
-    if (mat.pbrSpecularGlossiness.isPresent) {
-        PbrSpecularGlossiness &pbrSG = mat.pbrSpecularGlossiness.value;
+	//pbrSpecularGlossiness
+	if (mat.pbrSpecularGlossiness.isPresent) {
+		PbrSpecularGlossiness &pbrSG = mat.pbrSpecularGlossiness.value;
 
-        aimat->AddProperty(&mat.pbrSpecularGlossiness.isPresent, 1, AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS);
-        SetMaterialColorProperty(r, pbrSG.diffuseFactor, aimat, AI_MATKEY_COLOR_DIFFUSE);
-        SetMaterialColorProperty(r, pbrSG.specularFactor, aimat, AI_MATKEY_COLOR_SPECULAR);
+		aimat->AddProperty(&mat.pbrSpecularGlossiness.isPresent, 1, AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS);
+		SetMaterialColorProperty(r, pbrSG.diffuseFactor, aimat, AI_MATKEY_COLOR_DIFFUSE);
+		SetMaterialColorProperty(r, pbrSG.specularFactor, aimat, AI_MATKEY_COLOR_SPECULAR);
 
-        float glossinessAsShininess = pbrSG.glossinessFactor * 1000.0f;
-        aimat->AddProperty(&glossinessAsShininess, 1, AI_MATKEY_SHININESS);
-        aimat->AddProperty(&pbrSG.glossinessFactor, 1, AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS_GLOSSINESS_FACTOR);
+		float glossinessAsShininess = pbrSG.glossinessFactor * 1000.0f;
+		aimat->AddProperty(&glossinessAsShininess, 1, AI_MATKEY_SHININESS);
+		aimat->AddProperty(&pbrSG.glossinessFactor, 1, AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS_GLOSSINESS_FACTOR);
 
-        SetMaterialTextureProperty(embeddedTexIdxs, r, pbrSG.diffuseTexture, aimat, aiTextureType_DIFFUSE);
+		SetMaterialTextureProperty(embeddedTexIdxs, r, pbrSG.diffuseTexture, aimat, aiTextureType_DIFFUSE);
 
-        SetMaterialTextureProperty(embeddedTexIdxs, r, pbrSG.specularGlossinessTexture, aimat, aiTextureType_SPECULAR);
-    }
-    if (mat.unlit) {
-        aimat->AddProperty(&mat.unlit, 1, AI_MATKEY_GLTF_UNLIT);
-    }
+		SetMaterialTextureProperty(embeddedTexIdxs, r, pbrSG.specularGlossinessTexture, aimat, aiTextureType_SPECULAR);
+	}
+	if (mat.unlit) {
+		aimat->AddProperty(&mat.unlit, 1, AI_MATKEY_GLTF_UNLIT);
+	}
 
-    return aimat;
+	return aimat;
 }
 
-void glTF2Importer::ImportMaterials(glTF2::Asset& r)
-{
-    const unsigned int numImportedMaterials = unsigned(r.materials.Size());
-    Material defaultMaterial;
+void glTF2Importer::ImportMaterials(glTF2::Asset &r) {
+	const unsigned int numImportedMaterials = unsigned(r.materials.Size());
+	Material defaultMaterial;
 
-    mScene->mNumMaterials = numImportedMaterials + 1;
-    mScene->mMaterials = new aiMaterial*[mScene->mNumMaterials];
-    mScene->mMaterials[numImportedMaterials] = ImportMaterial(embeddedTexIdxs, r, defaultMaterial);
+	mScene->mNumMaterials = numImportedMaterials + 1;
+	mScene->mMaterials = new aiMaterial *[mScene->mNumMaterials];
+	mScene->mMaterials[numImportedMaterials] = ImportMaterial(embeddedTexIdxs, r, defaultMaterial);
 
-    for (unsigned int i = 0; i < numImportedMaterials; ++i) {
-       mScene->mMaterials[i] = ImportMaterial(embeddedTexIdxs, r, r.materials[i]);
-    }
+	for (unsigned int i = 0; i < numImportedMaterials; ++i) {
+		mScene->mMaterials[i] = ImportMaterial(embeddedTexIdxs, r, r.materials[i]);
+	}
 }
 
-
-static inline void SetFace(aiFace& face, int a)
-{
-    face.mNumIndices = 1;
-    face.mIndices = new unsigned int[1];
-    face.mIndices[0] = a;
+static inline void SetFace(aiFace &face, int a) {
+	face.mNumIndices = 1;
+	face.mIndices = new unsigned int[1];
+	face.mIndices[0] = a;
 }
 
-static inline void SetFace(aiFace& face, int a, int b)
-{
-    face.mNumIndices = 2;
-    face.mIndices = new unsigned int[2];
-    face.mIndices[0] = a;
-    face.mIndices[1] = b;
+static inline void SetFace(aiFace &face, int a, int b) {
+	face.mNumIndices = 2;
+	face.mIndices = new unsigned int[2];
+	face.mIndices[0] = a;
+	face.mIndices[1] = b;
 }
 
-static inline void SetFace(aiFace& face, int a, int b, int c)
-{
-    face.mNumIndices = 3;
-    face.mIndices = new unsigned int[3];
-    face.mIndices[0] = a;
-    face.mIndices[1] = b;
-    face.mIndices[2] = c;
+static inline void SetFace(aiFace &face, int a, int b, int c) {
+	face.mNumIndices = 3;
+	face.mIndices = new unsigned int[3];
+	face.mIndices[0] = a;
+	face.mIndices[1] = b;
+	face.mIndices[2] = c;
 }
 
 #ifdef ASSIMP_BUILD_DEBUG
-static inline bool CheckValidFacesIndices(aiFace* faces, unsigned nFaces, unsigned nVerts)
-{
-    for (unsigned i = 0; i < nFaces; ++i) {
-        for (unsigned j = 0; j < faces[i].mNumIndices; ++j) {
-            unsigned idx = faces[i].mIndices[j];
-            if (idx >= nVerts)
-                return false;
-        }
-    }
-    return true;
+static inline bool CheckValidFacesIndices(aiFace *faces, unsigned nFaces, unsigned nVerts) {
+	for (unsigned i = 0; i < nFaces; ++i) {
+		for (unsigned j = 0; j < faces[i].mNumIndices; ++j) {
+			unsigned idx = faces[i].mIndices[j];
+			if (idx >= nVerts)
+				return false;
+		}
+	}
+	return true;
 }
 #endif // ASSIMP_BUILD_DEBUG
 
-void glTF2Importer::ImportMeshes(glTF2::Asset& r)
-{
-    std::vector<aiMesh*> meshes;
-
-    unsigned int k = 0;
-
-    for (unsigned int m = 0; m < r.meshes.Size(); ++m) {
-        Mesh& mesh = r.meshes[m];
-
-        meshOffsets.push_back(k);
-        k += unsigned(mesh.primitives.size());
-
-        for (unsigned int p = 0; p < mesh.primitives.size(); ++p) {
-            Mesh::Primitive& prim = mesh.primitives[p];
-
-            aiMesh* aim = new aiMesh();
-            meshes.push_back(aim);
-
-            aim->mName = mesh.name.empty() ? mesh.id : mesh.name;
-
-            if (mesh.primitives.size() > 1) {
-                ai_uint32& len = aim->mName.length;
-                aim->mName.data[len] = '-';
-                len += 1 + ASSIMP_itoa10(aim->mName.data + len + 1, unsigned(MAXLEN - len - 1), p);
-            }
-
-            switch (prim.mode) {
-                case PrimitiveMode_POINTS:
-                    aim->mPrimitiveTypes |= aiPrimitiveType_POINT;
-                    break;
-
-                case PrimitiveMode_LINES:
-                case PrimitiveMode_LINE_LOOP:
-                case PrimitiveMode_LINE_STRIP:
-                    aim->mPrimitiveTypes |= aiPrimitiveType_LINE;
-                    break;
-
-                case PrimitiveMode_TRIANGLES:
-                case PrimitiveMode_TRIANGLE_STRIP:
-                case PrimitiveMode_TRIANGLE_FAN:
-                    aim->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE;
-                    break;
-
-            }
-
-            Mesh::Primitive::Attributes& attr = prim.attributes;
-
-            if (attr.position.size() > 0 && attr.position[0]) {
-                aim->mNumVertices = static_cast<unsigned int>(attr.position[0]->count);
-                attr.position[0]->ExtractData(aim->mVertices);
-            }
-
-            if (attr.normal.size() > 0 && attr.normal[0]) {
-                attr.normal[0]->ExtractData(aim->mNormals);
-
-                // only extract tangents if normals are present
-                if (attr.tangent.size() > 0 && attr.tangent[0]) {
-                    // generate bitangents from normals and tangents according to spec
-                    Tangent *tangents = nullptr;
-
-                    attr.tangent[0]->ExtractData(tangents);
-
-                    aim->mTangents = new aiVector3D[aim->mNumVertices];
-                    aim->mBitangents = new aiVector3D[aim->mNumVertices];
-
-                    for (unsigned int i = 0; i < aim->mNumVertices; ++i) {
-                        aim->mTangents[i] = tangents[i].xyz;
-                        aim->mBitangents[i] = (aim->mNormals[i] ^ tangents[i].xyz) * tangents[i].w;
-                    }
-
-                    delete [] tangents;
-                }
-            }
-
-            for (size_t c = 0; c < attr.color.size() && c < AI_MAX_NUMBER_OF_COLOR_SETS; ++c) {
-                if (attr.color[c]->count != aim->mNumVertices) {
-                    DefaultLogger::get()->warn("Color stream size in mesh \"" + mesh.name +
-                        "\" does not match the vertex count");
-                    continue;
-                }
-                attr.color[c]->ExtractData(aim->mColors[c]);
-            }
-            for (size_t tc = 0; tc < attr.texcoord.size() && tc < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++tc) {
-                if (attr.texcoord[tc]->count != aim->mNumVertices) {
-                    DefaultLogger::get()->warn("Texcoord stream size in mesh \"" + mesh.name +
-                                               "\" does not match the vertex count");
-                    continue;
-                }
-
-                attr.texcoord[tc]->ExtractData(aim->mTextureCoords[tc]);
-                aim->mNumUVComponents[tc] = attr.texcoord[tc]->GetNumComponents();
-
-                aiVector3D* values = aim->mTextureCoords[tc];
-                for (unsigned int i = 0; i < aim->mNumVertices; ++i) {
-                    values[i].y = 1 - values[i].y; // Flip Y coords
-                }
-            }
-
-            std::vector<Mesh::Primitive::Target>& targets = prim.targets;
-            if (targets.size() > 0) {
-                aim->mNumAnimMeshes = (unsigned int)targets.size();
-                aim->mAnimMeshes = new aiAnimMesh*[aim->mNumAnimMeshes];
-                for (size_t i = 0; i < targets.size(); i++) {
-                    aim->mAnimMeshes[i] = aiCreateAnimMesh(aim);
-                    aiAnimMesh& aiAnimMesh = *(aim->mAnimMeshes[i]);
-                    Mesh::Primitive::Target& target = targets[i];
-
-                    if (target.position.size() > 0) {
-                        aiVector3D *positionDiff = nullptr;
-                        target.position[0]->ExtractData(positionDiff);
-                        for(unsigned int vertexId = 0; vertexId < aim->mNumVertices; vertexId++) {
-                            aiAnimMesh.mVertices[vertexId] += positionDiff[vertexId];
-                        }
-                        delete [] positionDiff;
-                    }
-                    if (target.normal.size() > 0) {
-                        aiVector3D *normalDiff = nullptr;
-                        target.normal[0]->ExtractData(normalDiff);
-                        for(unsigned int vertexId = 0; vertexId < aim->mNumVertices; vertexId++) {
-                            aiAnimMesh.mNormals[vertexId] += normalDiff[vertexId];
-                        }
-                        delete [] normalDiff;
-                    }
-                    if (target.tangent.size() > 0) {
-                        Tangent *tangent = nullptr;
-                        attr.tangent[0]->ExtractData(tangent);
-
-                        aiVector3D *tangentDiff = nullptr;
-                        target.tangent[0]->ExtractData(tangentDiff);
-
-                        for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; ++vertexId) {
-                            tangent[vertexId].xyz += tangentDiff[vertexId];
-                            aiAnimMesh.mTangents[vertexId] = tangent[vertexId].xyz;
-                            aiAnimMesh.mBitangents[vertexId] = (aiAnimMesh.mNormals[vertexId] ^ tangent[vertexId].xyz) * tangent[vertexId].w;
-                        }
-                        delete [] tangent;
-                        delete [] tangentDiff;
-                    }
-                    if (mesh.weights.size() > i) {
-                        aiAnimMesh.mWeight = mesh.weights[i];
-                    }
-                }
-            }
-
-
-            aiFace* faces = 0;
-            size_t nFaces = 0;
-
-            if (prim.indices) {
-                size_t count = prim.indices->count;
-
-                Accessor::Indexer data = prim.indices->GetIndexer();
-                ai_assert(data.IsValid());
-
-                switch (prim.mode) {
-                    case PrimitiveMode_POINTS: {
-                        nFaces = count;
-                        faces = new aiFace[nFaces];
-                        for (unsigned int i = 0; i < count; ++i) {
-                            SetFace(faces[i], data.GetUInt(i));
-                        }
-                        break;
-                    }
-
-                    case PrimitiveMode_LINES: {
-                        nFaces = count / 2;
-                        if (nFaces * 2 != count) {
-                            ASSIMP_LOG_WARN("The number of vertices was not compatible with the LINES mode. Some vertices were dropped.");
-                            count = nFaces * 2;
-                        }
-                        faces = new aiFace[nFaces];
-                        for (unsigned int i = 0; i < count; i += 2) {
-                            SetFace(faces[i / 2], data.GetUInt(i), data.GetUInt(i + 1));
-                        }
-                        break;
-                    }
-
-                    case PrimitiveMode_LINE_LOOP:
-                    case PrimitiveMode_LINE_STRIP: {
-                        nFaces = count - ((prim.mode == PrimitiveMode_LINE_STRIP) ? 1 : 0);
-                        faces = new aiFace[nFaces];
-                        SetFace(faces[0], data.GetUInt(0), data.GetUInt(1));
-                        for (unsigned int i = 2; i < count; ++i) {
-                            SetFace(faces[i - 1], faces[i - 2].mIndices[1], data.GetUInt(i));
-                        }
-                        if (prim.mode == PrimitiveMode_LINE_LOOP) { // close the loop
-                            SetFace(faces[count - 1], faces[count - 2].mIndices[1], faces[0].mIndices[0]);
-                        }
-                        break;
-                    }
-
-                    case PrimitiveMode_TRIANGLES: {
-                        nFaces = count / 3;
-                        if (nFaces * 3 != count) {
-                            ASSIMP_LOG_WARN("The number of vertices was not compatible with the TRIANGLES mode. Some vertices were dropped.");
-                            count = nFaces * 3;
-                        }
-                        faces = new aiFace[nFaces];
-                        for (unsigned int i = 0; i < count; i += 3) {
-                            SetFace(faces[i / 3], data.GetUInt(i), data.GetUInt(i + 1), data.GetUInt(i + 2));
-                        }
-                        break;
-                    }
-                    case PrimitiveMode_TRIANGLE_STRIP: {
-                        nFaces = count - 2;
-                        faces = new aiFace[nFaces];
-                        for (unsigned int i = 0; i < nFaces; ++i) {
-                            //The ordering is to ensure that the triangles are all drawn with the same orientation
-                            if ((i + 1) % 2 == 0)
-                            {
-                                //For even n, vertices n + 1, n, and n + 2 define triangle n
-                                SetFace(faces[i], data.GetUInt(i + 1), data.GetUInt(i), data.GetUInt(i + 2));
-                            }
-                            else
-                            {
-                                //For odd n, vertices n, n+1, and n+2 define triangle n
-                                SetFace(faces[i], data.GetUInt(i), data.GetUInt(i + 1), data.GetUInt(i + 2));
-                            }
-                        }
-                        break;
-                    }
-                    case PrimitiveMode_TRIANGLE_FAN:
-                        nFaces = count - 2;
-                        faces = new aiFace[nFaces];
-                        SetFace(faces[0], data.GetUInt(0), data.GetUInt(1), data.GetUInt(2));
-                        for (unsigned int i = 1; i < nFaces; ++i) {
-                            SetFace(faces[i], faces[0].mIndices[0], faces[i - 1].mIndices[2], data.GetUInt(i + 2));
-                        }
-                        break;
-                }
-            }
-            else { // no indices provided so directly generate from counts
-
-                // use the already determined count as it includes checks
-                unsigned int count = aim->mNumVertices;
-
-                switch (prim.mode) {
-                case PrimitiveMode_POINTS: {
-                    nFaces = count;
-                    faces = new aiFace[nFaces];
-                    for (unsigned int i = 0; i < count; ++i) {
-                        SetFace(faces[i], i);
-                    }
-                    break;
-                }
-
-                case PrimitiveMode_LINES: {
-                    nFaces = count / 2;
-                    if (nFaces * 2 != count) {
-                        ASSIMP_LOG_WARN("The number of vertices was not compatible with the LINES mode. Some vertices were dropped.");
-                        count = nFaces * 2;
-                    }
-                    faces = new aiFace[nFaces];
-                    for (unsigned int i = 0; i < count; i += 2) {
-                        SetFace(faces[i / 2], i, i + 1);
-                    }
-                    break;
-                }
-
-                case PrimitiveMode_LINE_LOOP:
-                case PrimitiveMode_LINE_STRIP: {
-                    nFaces = count - ((prim.mode == PrimitiveMode_LINE_STRIP) ? 1 : 0);
-                    faces = new aiFace[nFaces];
-                    SetFace(faces[0], 0, 1);
-                    for (unsigned int i = 2; i < count; ++i) {
-                        SetFace(faces[i - 1], faces[i - 2].mIndices[1], i);
-                    }
-                    if (prim.mode == PrimitiveMode_LINE_LOOP) { // close the loop
-                        SetFace(faces[count - 1], faces[count - 2].mIndices[1], faces[0].mIndices[0]);
-                    }
-                    break;
-                }
-
-                case PrimitiveMode_TRIANGLES: {
-                    nFaces = count / 3;
-                    if (nFaces * 3 != count) {
-                        ASSIMP_LOG_WARN("The number of vertices was not compatible with the TRIANGLES mode. Some vertices were dropped.");
-                        count = nFaces * 3;
-                    }
-                    faces = new aiFace[nFaces];
-                    for (unsigned int i = 0; i < count; i += 3) {
-                        SetFace(faces[i / 3], i, i + 1, i + 2);
-                    }
-                    break;
-                }
-                case PrimitiveMode_TRIANGLE_STRIP: {
-                    nFaces = count - 2;
-                    faces = new aiFace[nFaces];
-                    for (unsigned int i = 0; i < nFaces; ++i) {
-                        //The ordering is to ensure that the triangles are all drawn with the same orientation
-                        if ((i+1) % 2 == 0)
-                        {
-                            //For even n, vertices n + 1, n, and n + 2 define triangle n
-                            SetFace(faces[i], i+1, i, i+2);
-                        }
-                        else
-                        {
-                            //For odd n, vertices n, n+1, and n+2 define triangle n
-                            SetFace(faces[i], i, i+1, i+2);
-                        }
-                    }
-                    break;
-                }
-                case PrimitiveMode_TRIANGLE_FAN:
-                    nFaces = count - 2;
-                    faces = new aiFace[nFaces];
-                    SetFace(faces[0], 0, 1, 2);
-                    for (unsigned int i = 1; i < nFaces; ++i) {
-                        SetFace(faces[i], faces[0].mIndices[0], faces[i - 1].mIndices[2], i + 2);
-                    }
-                    break;
-                }
-            }
-
-            if (faces) {
-                aim->mFaces = faces;
-                aim->mNumFaces = static_cast<unsigned int>(nFaces);
-                ai_assert(CheckValidFacesIndices(faces, static_cast<unsigned>(nFaces), aim->mNumVertices));
-            }
-
-            if (prim.material) {
-                aim->mMaterialIndex = prim.material.GetIndex();
-            }
-            else {
-                aim->mMaterialIndex = mScene->mNumMaterials - 1;
-            }
-
-        }
-    }
-
-    meshOffsets.push_back(k);
-
-    CopyVector(meshes, mScene->mMeshes, mScene->mNumMeshes);
+void glTF2Importer::ImportMeshes(glTF2::Asset &r) {
+	std::vector<aiMesh *> meshes;
+
+	unsigned int k = 0;
+
+	for (unsigned int m = 0; m < r.meshes.Size(); ++m) {
+		Mesh &mesh = r.meshes[m];
+
+		meshOffsets.push_back(k);
+		k += unsigned(mesh.primitives.size());
+
+		for (unsigned int p = 0; p < mesh.primitives.size(); ++p) {
+			Mesh::Primitive &prim = mesh.primitives[p];
+
+			aiMesh *aim = new aiMesh();
+			meshes.push_back(aim);
+
+			aim->mName = mesh.name.empty() ? mesh.id : mesh.name;
+
+			if (mesh.primitives.size() > 1) {
+				ai_uint32 &len = aim->mName.length;
+				aim->mName.data[len] = '-';
+				len += 1 + ASSIMP_itoa10(aim->mName.data + len + 1, unsigned(MAXLEN - len - 1), p);
+			}
+
+			switch (prim.mode) {
+				case PrimitiveMode_POINTS:
+					aim->mPrimitiveTypes |= aiPrimitiveType_POINT;
+					break;
+
+				case PrimitiveMode_LINES:
+				case PrimitiveMode_LINE_LOOP:
+				case PrimitiveMode_LINE_STRIP:
+					aim->mPrimitiveTypes |= aiPrimitiveType_LINE;
+					break;
+
+				case PrimitiveMode_TRIANGLES:
+				case PrimitiveMode_TRIANGLE_STRIP:
+				case PrimitiveMode_TRIANGLE_FAN:
+					aim->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE;
+					break;
+			}
+
+			Mesh::Primitive::Attributes &attr = prim.attributes;
+
+			if (attr.position.size() > 0 && attr.position[0]) {
+				aim->mNumVertices = static_cast<unsigned int>(attr.position[0]->count);
+				attr.position[0]->ExtractData(aim->mVertices);
+			}
+
+			if (attr.normal.size() > 0 && attr.normal[0]) {
+				attr.normal[0]->ExtractData(aim->mNormals);
+
+				// only extract tangents if normals are present
+				if (attr.tangent.size() > 0 && attr.tangent[0]) {
+					// generate bitangents from normals and tangents according to spec
+					Tangent *tangents = nullptr;
+
+					attr.tangent[0]->ExtractData(tangents);
+
+					aim->mTangents = new aiVector3D[aim->mNumVertices];
+					aim->mBitangents = new aiVector3D[aim->mNumVertices];
+
+					for (unsigned int i = 0; i < aim->mNumVertices; ++i) {
+						aim->mTangents[i] = tangents[i].xyz;
+						aim->mBitangents[i] = (aim->mNormals[i] ^ tangents[i].xyz) * tangents[i].w;
+					}
+
+					delete[] tangents;
+				}
+			}
+
+			for (size_t c = 0; c < attr.color.size() && c < AI_MAX_NUMBER_OF_COLOR_SETS; ++c) {
+				if (attr.color[c]->count != aim->mNumVertices) {
+					DefaultLogger::get()->warn("Color stream size in mesh \"" + mesh.name +
+											   "\" does not match the vertex count");
+					continue;
+				}
+				attr.color[c]->ExtractData(aim->mColors[c]);
+			}
+			for (size_t tc = 0; tc < attr.texcoord.size() && tc < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++tc) {
+				if (attr.texcoord[tc]->count != aim->mNumVertices) {
+					DefaultLogger::get()->warn("Texcoord stream size in mesh \"" + mesh.name +
+											   "\" does not match the vertex count");
+					continue;
+				}
+
+				attr.texcoord[tc]->ExtractData(aim->mTextureCoords[tc]);
+				aim->mNumUVComponents[tc] = attr.texcoord[tc]->GetNumComponents();
+
+				aiVector3D *values = aim->mTextureCoords[tc];
+				for (unsigned int i = 0; i < aim->mNumVertices; ++i) {
+					values[i].y = 1 - values[i].y; // Flip Y coords
+				}
+			}
+
+			std::vector<Mesh::Primitive::Target> &targets = prim.targets;
+			if (targets.size() > 0) {
+				aim->mNumAnimMeshes = (unsigned int)targets.size();
+				aim->mAnimMeshes = new aiAnimMesh *[aim->mNumAnimMeshes];
+				for (size_t i = 0; i < targets.size(); i++) {
+					aim->mAnimMeshes[i] = aiCreateAnimMesh(aim);
+					aiAnimMesh &aiAnimMesh = *(aim->mAnimMeshes[i]);
+					Mesh::Primitive::Target &target = targets[i];
+
+					if (target.position.size() > 0) {
+						aiVector3D *positionDiff = nullptr;
+						target.position[0]->ExtractData(positionDiff);
+						for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; vertexId++) {
+							aiAnimMesh.mVertices[vertexId] += positionDiff[vertexId];
+						}
+						delete[] positionDiff;
+					}
+					if (target.normal.size() > 0) {
+						aiVector3D *normalDiff = nullptr;
+						target.normal[0]->ExtractData(normalDiff);
+						for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; vertexId++) {
+							aiAnimMesh.mNormals[vertexId] += normalDiff[vertexId];
+						}
+						delete[] normalDiff;
+					}
+					if (target.tangent.size() > 0) {
+						Tangent *tangent = nullptr;
+						attr.tangent[0]->ExtractData(tangent);
+
+						aiVector3D *tangentDiff = nullptr;
+						target.tangent[0]->ExtractData(tangentDiff);
+
+						for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; ++vertexId) {
+							tangent[vertexId].xyz += tangentDiff[vertexId];
+							aiAnimMesh.mTangents[vertexId] = tangent[vertexId].xyz;
+							aiAnimMesh.mBitangents[vertexId] = (aiAnimMesh.mNormals[vertexId] ^ tangent[vertexId].xyz) * tangent[vertexId].w;
+						}
+						delete[] tangent;
+						delete[] tangentDiff;
+					}
+					if (mesh.weights.size() > i) {
+						aiAnimMesh.mWeight = mesh.weights[i];
+					}
+				}
+			}
+
+			aiFace *faces = 0;
+			size_t nFaces = 0;
+
+			if (prim.indices) {
+				size_t count = prim.indices->count;
+
+				Accessor::Indexer data = prim.indices->GetIndexer();
+				ai_assert(data.IsValid());
+
+				switch (prim.mode) {
+					case PrimitiveMode_POINTS: {
+						nFaces = count;
+						faces = new aiFace[nFaces];
+						for (unsigned int i = 0; i < count; ++i) {
+							SetFace(faces[i], data.GetUInt(i));
+						}
+						break;
+					}
+
+					case PrimitiveMode_LINES: {
+						nFaces = count / 2;
+						if (nFaces * 2 != count) {
+							ASSIMP_LOG_WARN("The number of vertices was not compatible with the LINES mode. Some vertices were dropped.");
+							count = nFaces * 2;
+						}
+						faces = new aiFace[nFaces];
+						for (unsigned int i = 0; i < count; i += 2) {
+							SetFace(faces[i / 2], data.GetUInt(i), data.GetUInt(i + 1));
+						}
+						break;
+					}
+
+					case PrimitiveMode_LINE_LOOP:
+					case PrimitiveMode_LINE_STRIP: {
+						nFaces = count - ((prim.mode == PrimitiveMode_LINE_STRIP) ? 1 : 0);
+						faces = new aiFace[nFaces];
+						SetFace(faces[0], data.GetUInt(0), data.GetUInt(1));
+						for (unsigned int i = 2; i < count; ++i) {
+							SetFace(faces[i - 1], faces[i - 2].mIndices[1], data.GetUInt(i));
+						}
+						if (prim.mode == PrimitiveMode_LINE_LOOP) { // close the loop
+							SetFace(faces[count - 1], faces[count - 2].mIndices[1], faces[0].mIndices[0]);
+						}
+						break;
+					}
+
+					case PrimitiveMode_TRIANGLES: {
+						nFaces = count / 3;
+						if (nFaces * 3 != count) {
+							ASSIMP_LOG_WARN("The number of vertices was not compatible with the TRIANGLES mode. Some vertices were dropped.");
+							count = nFaces * 3;
+						}
+						faces = new aiFace[nFaces];
+						for (unsigned int i = 0; i < count; i += 3) {
+							SetFace(faces[i / 3], data.GetUInt(i), data.GetUInt(i + 1), data.GetUInt(i + 2));
+						}
+						break;
+					}
+					case PrimitiveMode_TRIANGLE_STRIP: {
+						nFaces = count - 2;
+						faces = new aiFace[nFaces];
+						for (unsigned int i = 0; i < nFaces; ++i) {
+							//The ordering is to ensure that the triangles are all drawn with the same orientation
+							if ((i + 1) % 2 == 0) {
+								//For even n, vertices n + 1, n, and n + 2 define triangle n
+								SetFace(faces[i], data.GetUInt(i + 1), data.GetUInt(i), data.GetUInt(i + 2));
+							} else {
+								//For odd n, vertices n, n+1, and n+2 define triangle n
+								SetFace(faces[i], data.GetUInt(i), data.GetUInt(i + 1), data.GetUInt(i + 2));
+							}
+						}
+						break;
+					}
+					case PrimitiveMode_TRIANGLE_FAN:
+						nFaces = count - 2;
+						faces = new aiFace[nFaces];
+						SetFace(faces[0], data.GetUInt(0), data.GetUInt(1), data.GetUInt(2));
+						for (unsigned int i = 1; i < nFaces; ++i) {
+							SetFace(faces[i], faces[0].mIndices[0], faces[i - 1].mIndices[2], data.GetUInt(i + 2));
+						}
+						break;
+				}
+			} else { // no indices provided so directly generate from counts
+
+				// use the already determined count as it includes checks
+				unsigned int count = aim->mNumVertices;
+
+				switch (prim.mode) {
+					case PrimitiveMode_POINTS: {
+						nFaces = count;
+						faces = new aiFace[nFaces];
+						for (unsigned int i = 0; i < count; ++i) {
+							SetFace(faces[i], i);
+						}
+						break;
+					}
+
+					case PrimitiveMode_LINES: {
+						nFaces = count / 2;
+						if (nFaces * 2 != count) {
+							ASSIMP_LOG_WARN("The number of vertices was not compatible with the LINES mode. Some vertices were dropped.");
+							count = (unsigned int)nFaces * 2;
+						}
+						faces = new aiFace[nFaces];
+						for (unsigned int i = 0; i < count; i += 2) {
+							SetFace(faces[i / 2], i, i + 1);
+						}
+						break;
+					}
+
+					case PrimitiveMode_LINE_LOOP:
+					case PrimitiveMode_LINE_STRIP: {
+						nFaces = count - ((prim.mode == PrimitiveMode_LINE_STRIP) ? 1 : 0);
+						faces = new aiFace[nFaces];
+						SetFace(faces[0], 0, 1);
+						for (unsigned int i = 2; i < count; ++i) {
+							SetFace(faces[i - 1], faces[i - 2].mIndices[1], i);
+						}
+						if (prim.mode == PrimitiveMode_LINE_LOOP) { // close the loop
+							SetFace(faces[count - 1], faces[count - 2].mIndices[1], faces[0].mIndices[0]);
+						}
+						break;
+					}
+
+					case PrimitiveMode_TRIANGLES: {
+						nFaces = count / 3;
+						if (nFaces * 3 != count) {
+							ASSIMP_LOG_WARN("The number of vertices was not compatible with the TRIANGLES mode. Some vertices were dropped.");
+							count = (unsigned int)nFaces * 3;
+						}
+						faces = new aiFace[nFaces];
+						for (unsigned int i = 0; i < count; i += 3) {
+							SetFace(faces[i / 3], i, i + 1, i + 2);
+						}
+						break;
+					}
+					case PrimitiveMode_TRIANGLE_STRIP: {
+						nFaces = count - 2;
+						faces = new aiFace[nFaces];
+						for (unsigned int i = 0; i < nFaces; ++i) {
+							//The ordering is to ensure that the triangles are all drawn with the same orientation
+							if ((i + 1) % 2 == 0) {
+								//For even n, vertices n + 1, n, and n + 2 define triangle n
+								SetFace(faces[i], i + 1, i, i + 2);
+							} else {
+								//For odd n, vertices n, n+1, and n+2 define triangle n
+								SetFace(faces[i], i, i + 1, i + 2);
+							}
+						}
+						break;
+					}
+					case PrimitiveMode_TRIANGLE_FAN:
+						nFaces = count - 2;
+						faces = new aiFace[nFaces];
+						SetFace(faces[0], 0, 1, 2);
+						for (unsigned int i = 1; i < nFaces; ++i) {
+							SetFace(faces[i], faces[0].mIndices[0], faces[i - 1].mIndices[2], i + 2);
+						}
+						break;
+				}
+			}
+
+			if (faces) {
+				aim->mFaces = faces;
+				aim->mNumFaces = static_cast<unsigned int>(nFaces);
+				ai_assert(CheckValidFacesIndices(faces, static_cast<unsigned>(nFaces), aim->mNumVertices));
+			}
+
+			if (prim.material) {
+				aim->mMaterialIndex = prim.material.GetIndex();
+			} else {
+				aim->mMaterialIndex = mScene->mNumMaterials - 1;
+			}
+		}
+	}
+
+	meshOffsets.push_back(k);
+
+	CopyVector(meshes, mScene->mMeshes, mScene->mNumMeshes);
 }
 
-void glTF2Importer::ImportCameras(glTF2::Asset& r)
-{
-    if (!r.cameras.Size()) return;
-
-    mScene->mNumCameras = r.cameras.Size();
-    mScene->mCameras = new aiCamera*[r.cameras.Size()];
-
-    for (size_t i = 0; i < r.cameras.Size(); ++i) {
-        Camera& cam = r.cameras[i];
-
-        aiCamera* aicam = mScene->mCameras[i] = new aiCamera();
-
-        // cameras point in -Z by default, rest is specified in node transform
-        aicam->mLookAt = aiVector3D(0.f,0.f,-1.f);
-
-        if (cam.type == Camera::Perspective) {
-
-            aicam->mAspect        = cam.cameraProperties.perspective.aspectRatio;
-            aicam->mHorizontalFOV = cam.cameraProperties.perspective.yfov * ((aicam->mAspect == 0.f) ? 1.f : aicam->mAspect);
-            aicam->mClipPlaneFar  = cam.cameraProperties.perspective.zfar;
-            aicam->mClipPlaneNear = cam.cameraProperties.perspective.znear;
-        } else {
-            aicam->mClipPlaneFar = cam.cameraProperties.ortographic.zfar;
-            aicam->mClipPlaneNear = cam.cameraProperties.ortographic.znear;
-            aicam->mHorizontalFOV = 0.0;
-            aicam->mAspect = 1.0f;
-            if (0.f != cam.cameraProperties.ortographic.ymag ) {
-                aicam->mAspect = cam.cameraProperties.ortographic.xmag / cam.cameraProperties.ortographic.ymag;
-            }
-        }
-    }
+void glTF2Importer::ImportCameras(glTF2::Asset &r) {
+	if (!r.cameras.Size()) return;
+
+	mScene->mNumCameras = r.cameras.Size();
+	mScene->mCameras = new aiCamera *[r.cameras.Size()];
+
+	for (size_t i = 0; i < r.cameras.Size(); ++i) {
+		Camera &cam = r.cameras[i];
+
+		aiCamera *aicam = mScene->mCameras[i] = new aiCamera();
+
+		// cameras point in -Z by default, rest is specified in node transform
+		aicam->mLookAt = aiVector3D(0.f, 0.f, -1.f);
+
+		if (cam.type == Camera::Perspective) {
+
+			aicam->mAspect = cam.cameraProperties.perspective.aspectRatio;
+			aicam->mHorizontalFOV = cam.cameraProperties.perspective.yfov * ((aicam->mAspect == 0.f) ? 1.f : aicam->mAspect);
+			aicam->mClipPlaneFar = cam.cameraProperties.perspective.zfar;
+			aicam->mClipPlaneNear = cam.cameraProperties.perspective.znear;
+		} else {
+			aicam->mClipPlaneFar = cam.cameraProperties.ortographic.zfar;
+			aicam->mClipPlaneNear = cam.cameraProperties.ortographic.znear;
+			aicam->mHorizontalFOV = 0.0;
+			aicam->mAspect = 1.0f;
+			if (0.f != cam.cameraProperties.ortographic.ymag) {
+				aicam->mAspect = cam.cameraProperties.ortographic.xmag / cam.cameraProperties.ortographic.ymag;
+			}
+		}
+	}
 }
 
-void glTF2Importer::ImportLights(glTF2::Asset& r)
-{
-    if (!r.lights.Size())
-        return;
-
-    mScene->mNumLights = r.lights.Size();
-    mScene->mLights = new aiLight*[r.lights.Size()];
-
-    for (size_t i = 0; i < r.lights.Size(); ++i) {
-        Light& light = r.lights[i];
-
-        aiLight* ail = mScene->mLights[i] = new aiLight();
-
-        switch (light.type)
-        {
-        case Light::Directional:
-            ail->mType = aiLightSource_DIRECTIONAL; break;
-        case Light::Point:
-            ail->mType = aiLightSource_POINT; break;
-        case Light::Spot:
-            ail->mType = aiLightSource_SPOT; break;
-        }
-
-        if (ail->mType != aiLightSource_POINT)
-        {
-            ail->mDirection = aiVector3D(0.0f, 0.0f, -1.0f);
-            ail->mUp = aiVector3D(0.0f, 1.0f, 0.0f);
-        }
-
-        vec3 colorWithIntensity = { light.color[0] * light.intensity, light.color[1] * light.intensity, light.color[2] * light.intensity };
-        CopyValue(colorWithIntensity, ail->mColorAmbient);
-        CopyValue(colorWithIntensity, ail->mColorDiffuse);
-        CopyValue(colorWithIntensity, ail->mColorSpecular);
-
-        if (ail->mType == aiLightSource_DIRECTIONAL)
-        {
-            ail->mAttenuationConstant = 1.0;
-            ail->mAttenuationLinear = 0.0;
-            ail->mAttenuationQuadratic = 0.0;
-        }
-        else
-        {
-            //in PBR attenuation is calculated using inverse square law which can be expressed
-            //using assimps equation: 1/(att0 + att1 * d + att2 * d*d) with the following parameters
-            //this is correct equation for the case when range (see
-            //https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual)
-            //is not present. When range is not present it is assumed that it is infinite and so numerator is 1.
-            //When range is present then numerator might be any value in range [0,1] and then assimps equation
-            //will not suffice. In this case range is added into metadata in ImportNode function
-            //and its up to implementation to read it when it wants to
-            ail->mAttenuationConstant = 0.0;
-            ail->mAttenuationLinear = 0.0;
-            ail->mAttenuationQuadratic = 1.0;
-        }
-
-        if (ail->mType == aiLightSource_SPOT)
-        {
-            ail->mAngleInnerCone = light.innerConeAngle;
-            ail->mAngleOuterCone = light.outerConeAngle;
-        }
-    }
+void glTF2Importer::ImportLights(glTF2::Asset &r) {
+	if (!r.lights.Size())
+		return;
+
+	mScene->mNumLights = r.lights.Size();
+	mScene->mLights = new aiLight *[r.lights.Size()];
+
+	for (size_t i = 0; i < r.lights.Size(); ++i) {
+		Light &light = r.lights[i];
+
+		aiLight *ail = mScene->mLights[i] = new aiLight();
+
+		switch (light.type) {
+			case Light::Directional:
+				ail->mType = aiLightSource_DIRECTIONAL;
+				break;
+			case Light::Point:
+				ail->mType = aiLightSource_POINT;
+				break;
+			case Light::Spot:
+				ail->mType = aiLightSource_SPOT;
+				break;
+		}
+
+		if (ail->mType != aiLightSource_POINT) {
+			ail->mDirection = aiVector3D(0.0f, 0.0f, -1.0f);
+			ail->mUp = aiVector3D(0.0f, 1.0f, 0.0f);
+		}
+
+		vec3 colorWithIntensity = { light.color[0] * light.intensity, light.color[1] * light.intensity, light.color[2] * light.intensity };
+		CopyValue(colorWithIntensity, ail->mColorAmbient);
+		CopyValue(colorWithIntensity, ail->mColorDiffuse);
+		CopyValue(colorWithIntensity, ail->mColorSpecular);
+
+		if (ail->mType == aiLightSource_DIRECTIONAL) {
+			ail->mAttenuationConstant = 1.0;
+			ail->mAttenuationLinear = 0.0;
+			ail->mAttenuationQuadratic = 0.0;
+		} else {
+			//in PBR attenuation is calculated using inverse square law which can be expressed
+			//using assimps equation: 1/(att0 + att1 * d + att2 * d*d) with the following parameters
+			//this is correct equation for the case when range (see
+			//https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual)
+			//is not present. When range is not present it is assumed that it is infinite and so numerator is 1.
+			//When range is present then numerator might be any value in range [0,1] and then assimps equation
+			//will not suffice. In this case range is added into metadata in ImportNode function
+			//and its up to implementation to read it when it wants to
+			ail->mAttenuationConstant = 0.0;
+			ail->mAttenuationLinear = 0.0;
+			ail->mAttenuationQuadratic = 1.0;
+		}
+
+		if (ail->mType == aiLightSource_SPOT) {
+			ail->mAngleInnerCone = light.innerConeAngle;
+			ail->mAngleOuterCone = light.outerConeAngle;
+		}
+	}
 }
 
-static void GetNodeTransform(aiMatrix4x4& matrix, const glTF2::Node& node) {
-    if (node.matrix.isPresent) {
-        CopyValue(node.matrix.value, matrix);
-    }
-    else {
-        if (node.translation.isPresent) {
-            aiVector3D trans;
-            CopyValue(node.translation.value, trans);
-            aiMatrix4x4 t;
-            aiMatrix4x4::Translation(trans, t);
-            matrix = matrix * t;
-        }
-
-        if (node.rotation.isPresent) {
-            aiQuaternion rot;
-            CopyValue(node.rotation.value, rot);
-            matrix = matrix * aiMatrix4x4(rot.GetMatrix());
-        }
-
-        if (node.scale.isPresent) {
-            aiVector3D scal(1.f);
-            CopyValue(node.scale.value, scal);
-            aiMatrix4x4 s;
-            aiMatrix4x4::Scaling(scal, s);
-            matrix = matrix * s;
-        }
-    }
+static void GetNodeTransform(aiMatrix4x4 &matrix, const glTF2::Node &node) {
+	if (node.matrix.isPresent) {
+		CopyValue(node.matrix.value, matrix);
+	} else {
+		if (node.translation.isPresent) {
+			aiVector3D trans;
+			CopyValue(node.translation.value, trans);
+			aiMatrix4x4 t;
+			aiMatrix4x4::Translation(trans, t);
+			matrix = matrix * t;
+		}
+
+		if (node.rotation.isPresent) {
+			aiQuaternion rot;
+			CopyValue(node.rotation.value, rot);
+			matrix = matrix * aiMatrix4x4(rot.GetMatrix());
+		}
+
+		if (node.scale.isPresent) {
+			aiVector3D scal(1.f);
+			CopyValue(node.scale.value, scal);
+			aiMatrix4x4 s;
+			aiMatrix4x4::Scaling(scal, s);
+			matrix = matrix * s;
+		}
+	}
 }
 
-static void BuildVertexWeightMapping(Mesh::Primitive& primitive, std::vector<std::vector<aiVertexWeight>>& map)
-{
-    Mesh::Primitive::Attributes& attr = primitive.attributes;
-    if (attr.weight.empty() || attr.joint.empty()) {
-        return;
-    }
-    if (attr.weight[0]->count != attr.joint[0]->count) {
-        return;
-    }
-
-    size_t num_vertices = attr.weight[0]->count;
-
-    struct Weights { float values[4]; };
-    Weights* weights = nullptr;
-    attr.weight[0]->ExtractData(weights);
-
-    struct Indices8 { uint8_t values[4]; };
-    struct Indices16 { uint16_t values[4]; };
-    Indices8* indices8 = nullptr;
-    Indices16* indices16 = nullptr;
-    if (attr.joint[0]->GetElementSize() == 4) {
-        attr.joint[0]->ExtractData(indices8);
-    }else {
-        attr.joint[0]->ExtractData(indices16);
-    }
-    // 
-    if (nullptr == indices8 && nullptr == indices16) {
-        // Something went completely wrong!
-        ai_assert(false);
-        return;
-    }
-
-    for (size_t i = 0; i < num_vertices; ++i) {
-        for (int j = 0; j < 4; ++j) {
-            const unsigned int bone = (indices8!=nullptr) ? indices8[i].values[j] : indices16[i].values[j];
-            const float weight = weights[i].values[j];
-            if (weight > 0 && bone < map.size()) {
-                map[bone].reserve(8);
-                map[bone].emplace_back(static_cast<unsigned int>(i), weight);
-            }
-        }
-    }
-
-    delete[] weights;
-    delete[] indices8;
-    delete[] indices16;
+static void BuildVertexWeightMapping(Mesh::Primitive &primitive, std::vector<std::vector<aiVertexWeight>> &map) {
+	Mesh::Primitive::Attributes &attr = primitive.attributes;
+	if (attr.weight.empty() || attr.joint.empty()) {
+		return;
+	}
+	if (attr.weight[0]->count != attr.joint[0]->count) {
+		return;
+	}
+
+	size_t num_vertices = attr.weight[0]->count;
+
+	struct Weights {
+		float values[4];
+	};
+	Weights *weights = nullptr;
+	attr.weight[0]->ExtractData(weights);
+
+	struct Indices8 {
+		uint8_t values[4];
+	};
+	struct Indices16 {
+		uint16_t values[4];
+	};
+	Indices8 *indices8 = nullptr;
+	Indices16 *indices16 = nullptr;
+	if (attr.joint[0]->GetElementSize() == 4) {
+		attr.joint[0]->ExtractData(indices8);
+	} else {
+		attr.joint[0]->ExtractData(indices16);
+	}
+	//
+	if (nullptr == indices8 && nullptr == indices16) {
+		// Something went completely wrong!
+		ai_assert(false);
+		return;
+	}
+
+	for (size_t i = 0; i < num_vertices; ++i) {
+		for (int j = 0; j < 4; ++j) {
+			const unsigned int bone = (indices8 != nullptr) ? indices8[i].values[j] : indices16[i].values[j];
+			const float weight = weights[i].values[j];
+			if (weight > 0 && bone < map.size()) {
+				map[bone].reserve(8);
+				map[bone].emplace_back(static_cast<unsigned int>(i), weight);
+			}
+		}
+	}
+
+	delete[] weights;
+	delete[] indices8;
+	delete[] indices16;
 }
 
-static std::string GetNodeName(const Node& node)
-{
-    return node.name.empty() ? node.id : node.name;
+static std::string GetNodeName(const Node &node) {
+	return node.name.empty() ? node.id : node.name;
 }
 
-aiNode* ImportNode(aiScene* pScene, glTF2::Asset& r, std::vector<unsigned int>& meshOffsets, glTF2::Ref<glTF2::Node>& ptr)
-{
-    Node& node = *ptr;
-
-    aiNode* ainode = new aiNode(GetNodeName(node));
-
-    if (!node.children.empty()) {
-        ainode->mNumChildren = unsigned(node.children.size());
-        ainode->mChildren = new aiNode*[ainode->mNumChildren];
-
-        for (unsigned int i = 0; i < ainode->mNumChildren; ++i) {
-            aiNode* child = ImportNode(pScene, r, meshOffsets, node.children[i]);
-            child->mParent = ainode;
-            ainode->mChildren[i] = child;
-        }
-    }
-
-    GetNodeTransform(ainode->mTransformation, node);
-
-    if (!node.meshes.empty()) {
-        // GLTF files contain at most 1 mesh per node.
-        assert(node.meshes.size() == 1);
-        int mesh_idx = node.meshes[0].GetIndex();
-        int count = meshOffsets[mesh_idx + 1] - meshOffsets[mesh_idx];
-
-        ainode->mNumMeshes = count;
-        ainode->mMeshes = new unsigned int[count];
-
-        if (node.skin) {
-            for (int primitiveNo = 0; primitiveNo < count; ++primitiveNo) {
-                aiMesh* mesh = pScene->mMeshes[meshOffsets[mesh_idx]+primitiveNo];
-                mesh->mNumBones = static_cast<unsigned int>(node.skin->jointNames.size());
-                mesh->mBones = new aiBone*[mesh->mNumBones];
-
-                // GLTF and Assimp choose to store bone weights differently.
-                // GLTF has each vertex specify which bones influence the vertex.
-                // Assimp has each bone specify which vertices it has influence over.
-                // To convert this data, we first read over the vertex data and pull
-                // out the bone-to-vertex mapping.  Then, when creating the aiBones,
-                // we copy the bone-to-vertex mapping into the bone.  This is unfortunate
-                // both because it's somewhat slow and because, for many applications,
-                // we then need to reconvert the data back into the vertex-to-bone
-                // mapping which makes things doubly-slow.
-                std::vector<std::vector<aiVertexWeight>> weighting(mesh->mNumBones);
-                BuildVertexWeightMapping(node.meshes[0]->primitives[primitiveNo], weighting);
-
-                mat4* pbindMatrices = nullptr;
-                node.skin->inverseBindMatrices->ExtractData(pbindMatrices);
-
-                for (uint32_t i = 0; i < mesh->mNumBones; ++i) {
-                    aiBone* bone = new aiBone();
-
-                    Ref<Node> joint = node.skin->jointNames[i];
-                    if (!joint->name.empty()) {
-                      bone->mName = joint->name;
-                    } else {
-                      // Assimp expects each bone to have a unique name.
-                      static const std::string kDefaultName = "bone_";
-                      char postfix[10] = {0};
-                      ASSIMP_itoa10(postfix, i);
-                      bone->mName = (kDefaultName + postfix);
-                    }
-                    GetNodeTransform(bone->mOffsetMatrix, *joint);
-
-                    CopyValue(pbindMatrices[i], bone->mOffsetMatrix);
-
-                    std::vector<aiVertexWeight>& weights = weighting[i];
-
-                    bone->mNumWeights = static_cast<uint32_t>(weights.size());
-                    if (bone->mNumWeights > 0) {
-                      bone->mWeights = new aiVertexWeight[bone->mNumWeights];
-                      memcpy(bone->mWeights, weights.data(), bone->mNumWeights * sizeof(aiVertexWeight));
-                    } else {
-                      // Assimp expects all bones to have at least 1 weight.
-                      bone->mWeights = new aiVertexWeight[1];
-                      bone->mNumWeights = 1;
-                      bone->mWeights->mVertexId = 0;
-                      bone->mWeights->mWeight = 0.f;
-                    }
-                    mesh->mBones[i] = bone;
-                }
-
-                if (pbindMatrices) {
-                    delete[] pbindMatrices;
-                }
-            }
-        }
-
-        int k = 0;
-        for (unsigned int j = meshOffsets[mesh_idx]; j < meshOffsets[mesh_idx + 1]; ++j, ++k) {
-            ainode->mMeshes[k] = j;
-        }
-    }
-
-    if (node.camera) {
-        pScene->mCameras[node.camera.GetIndex()]->mName = ainode->mName;
-    }
-
-    if (node.light) {
-        pScene->mLights[node.light.GetIndex()]->mName = ainode->mName;
-
-        //range is optional - see https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual
-        //it is added to meta data of parent node, because there is no other place to put it
-        if (node.light->range.isPresent)
-        {
-            ainode->mMetaData = aiMetadata::Alloc(1);
-            ainode->mMetaData->Set(0, "PBR_LightRange", node.light->range.value);
-        }
-    }
-
-    return ainode;
+aiNode *ImportNode(aiScene *pScene, glTF2::Asset &r, std::vector<unsigned int> &meshOffsets, glTF2::Ref<glTF2::Node> &ptr) {
+	Node &node = *ptr;
+
+	aiNode *ainode = new aiNode(GetNodeName(node));
+
+	if (!node.children.empty()) {
+		ainode->mNumChildren = unsigned(node.children.size());
+		ainode->mChildren = new aiNode *[ainode->mNumChildren];
+
+		for (unsigned int i = 0; i < ainode->mNumChildren; ++i) {
+			aiNode *child = ImportNode(pScene, r, meshOffsets, node.children[i]);
+			child->mParent = ainode;
+			ainode->mChildren[i] = child;
+		}
+	}
+
+	GetNodeTransform(ainode->mTransformation, node);
+
+	if (!node.meshes.empty()) {
+		// GLTF files contain at most 1 mesh per node.
+		assert(node.meshes.size() == 1);
+		int mesh_idx = node.meshes[0].GetIndex();
+		int count = meshOffsets[mesh_idx + 1] - meshOffsets[mesh_idx];
+
+		ainode->mNumMeshes = count;
+		ainode->mMeshes = new unsigned int[count];
+
+		if (node.skin) {
+			for (int primitiveNo = 0; primitiveNo < count; ++primitiveNo) {
+				aiMesh *mesh = pScene->mMeshes[meshOffsets[mesh_idx] + primitiveNo];
+				mesh->mNumBones = static_cast<unsigned int>(node.skin->jointNames.size());
+				mesh->mBones = new aiBone *[mesh->mNumBones];
+
+				// GLTF and Assimp choose to store bone weights differently.
+				// GLTF has each vertex specify which bones influence the vertex.
+				// Assimp has each bone specify which vertices it has influence over.
+				// To convert this data, we first read over the vertex data and pull
+				// out the bone-to-vertex mapping.  Then, when creating the aiBones,
+				// we copy the bone-to-vertex mapping into the bone.  This is unfortunate
+				// both because it's somewhat slow and because, for many applications,
+				// we then need to reconvert the data back into the vertex-to-bone
+				// mapping which makes things doubly-slow.
+				std::vector<std::vector<aiVertexWeight>> weighting(mesh->mNumBones);
+				BuildVertexWeightMapping(node.meshes[0]->primitives[primitiveNo], weighting);
+
+				mat4 *pbindMatrices = nullptr;
+				node.skin->inverseBindMatrices->ExtractData(pbindMatrices);
+
+				for (uint32_t i = 0; i < mesh->mNumBones; ++i) {
+					aiBone *bone = new aiBone();
+
+					Ref<Node> joint = node.skin->jointNames[i];
+					if (!joint->name.empty()) {
+						bone->mName = joint->name;
+					} else {
+						// Assimp expects each bone to have a unique name.
+						static const std::string kDefaultName = "bone_";
+						char postfix[10] = { 0 };
+						ASSIMP_itoa10(postfix, i);
+						bone->mName = (kDefaultName + postfix);
+					}
+					GetNodeTransform(bone->mOffsetMatrix, *joint);
+
+					CopyValue(pbindMatrices[i], bone->mOffsetMatrix);
+
+					std::vector<aiVertexWeight> &weights = weighting[i];
+
+					bone->mNumWeights = static_cast<uint32_t>(weights.size());
+					if (bone->mNumWeights > 0) {
+						bone->mWeights = new aiVertexWeight[bone->mNumWeights];
+						memcpy(bone->mWeights, weights.data(), bone->mNumWeights * sizeof(aiVertexWeight));
+					} else {
+						// Assimp expects all bones to have at least 1 weight.
+						bone->mWeights = new aiVertexWeight[1];
+						bone->mNumWeights = 1;
+						bone->mWeights->mVertexId = 0;
+						bone->mWeights->mWeight = 0.f;
+					}
+					mesh->mBones[i] = bone;
+				}
+
+				if (pbindMatrices) {
+					delete[] pbindMatrices;
+				}
+			}
+		}
+
+		int k = 0;
+		for (unsigned int j = meshOffsets[mesh_idx]; j < meshOffsets[mesh_idx + 1]; ++j, ++k) {
+			ainode->mMeshes[k] = j;
+		}
+	}
+
+	if (node.camera) {
+		pScene->mCameras[node.camera.GetIndex()]->mName = ainode->mName;
+	}
+
+	if (node.light) {
+		pScene->mLights[node.light.GetIndex()]->mName = ainode->mName;
+
+		//range is optional - see https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual
+		//it is added to meta data of parent node, because there is no other place to put it
+		if (node.light->range.isPresent) {
+			ainode->mMetaData = aiMetadata::Alloc(1);
+			ainode->mMetaData->Set(0, "PBR_LightRange", node.light->range.value);
+		}
+	}
+
+	return ainode;
 }
 
-void glTF2Importer::ImportNodes(glTF2::Asset& r)
-{
-    if (!r.scene) return;
-
-    std::vector< Ref<Node> > rootNodes = r.scene->nodes;
-
-    // The root nodes
-    unsigned int numRootNodes = unsigned(rootNodes.size());
-    if (numRootNodes == 1) { // a single root node: use it
-        mScene->mRootNode = ImportNode(mScene, r, meshOffsets, rootNodes[0]);
-    }
-    else if (numRootNodes > 1) { // more than one root node: create a fake root
-        aiNode* root = new aiNode("ROOT");
-        root->mChildren = new aiNode*[numRootNodes];
-        for (unsigned int i = 0; i < numRootNodes; ++i) {
-            aiNode* node = ImportNode(mScene, r, meshOffsets, rootNodes[i]);
-            node->mParent = root;
-            root->mChildren[root->mNumChildren++] = node;
-        }
-        mScene->mRootNode = root;
-    }
-
-    //if (!mScene->mRootNode) {
-    //  mScene->mRootNode = new aiNode("EMPTY");
-    //}
+void glTF2Importer::ImportNodes(glTF2::Asset &r) {
+	if (!r.scene) return;
+
+	std::vector<Ref<Node>> rootNodes = r.scene->nodes;
+
+	// The root nodes
+	unsigned int numRootNodes = unsigned(rootNodes.size());
+	if (numRootNodes == 1) { // a single root node: use it
+		mScene->mRootNode = ImportNode(mScene, r, meshOffsets, rootNodes[0]);
+	} else if (numRootNodes > 1) { // more than one root node: create a fake root
+		aiNode *root = new aiNode("ROOT");
+		root->mChildren = new aiNode *[numRootNodes];
+		for (unsigned int i = 0; i < numRootNodes; ++i) {
+			aiNode *node = ImportNode(mScene, r, meshOffsets, rootNodes[i]);
+			node->mParent = root;
+			root->mChildren[root->mNumChildren++] = node;
+		}
+		mScene->mRootNode = root;
+	}
 }
 
 struct AnimationSamplers {
-    AnimationSamplers()
-    : translation(nullptr)
-    , rotation(nullptr)
-    , scale(nullptr)
-    , weight(nullptr) {
-        // empty
-    }
-
-    Animation::Sampler* translation;
-    Animation::Sampler* rotation;
-    Animation::Sampler* scale;
-    Animation::Sampler* weight;
+	AnimationSamplers() :
+			translation(nullptr),
+			rotation(nullptr),
+			scale(nullptr),
+			weight(nullptr) {
+		// empty
+	}
+
+	Animation::Sampler *translation;
+	Animation::Sampler *rotation;
+	Animation::Sampler *scale;
+	Animation::Sampler *weight;
 };
 
-aiNodeAnim* CreateNodeAnim(glTF2::Asset& r, Node& node, AnimationSamplers& samplers)
-{
-    aiNodeAnim* anim = new aiNodeAnim();
-    anim->mNodeName = GetNodeName(node);
-
-    static const float kMillisecondsFromSeconds = 1000.f;
-
-    if (samplers.translation) {
-        float* times = nullptr;
-        samplers.translation->input->ExtractData(times);
-        aiVector3D* values = nullptr;
-        samplers.translation->output->ExtractData(values);
-        anim->mNumPositionKeys = static_cast<uint32_t>(samplers.translation->input->count);
-        anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys];
-        for (unsigned int i = 0; i < anim->mNumPositionKeys; ++i) {
-            anim->mPositionKeys[i].mTime = times[i] * kMillisecondsFromSeconds;
-            anim->mPositionKeys[i].mValue = values[i];
-        }
-        delete[] times;
-        delete[] values;
-    } else if (node.translation.isPresent) {
-        anim->mNumPositionKeys = 1;
-        anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys];
-        anim->mPositionKeys->mTime = 0.f;
-        anim->mPositionKeys->mValue.x = node.translation.value[0];
-        anim->mPositionKeys->mValue.y = node.translation.value[1];
-        anim->mPositionKeys->mValue.z = node.translation.value[2];
-    }
-
-    if (samplers.rotation) {
-        float* times = nullptr;
-        samplers.rotation->input->ExtractData(times);
-        aiQuaternion* values = nullptr;
-        samplers.rotation->output->ExtractData(values);
-        anim->mNumRotationKeys = static_cast<uint32_t>(samplers.rotation->input->count);
-        anim->mRotationKeys = new aiQuatKey[anim->mNumRotationKeys];
-        for (unsigned int i = 0; i < anim->mNumRotationKeys; ++i) {
-            anim->mRotationKeys[i].mTime = times[i] * kMillisecondsFromSeconds;
-            anim->mRotationKeys[i].mValue.x = values[i].w;
-            anim->mRotationKeys[i].mValue.y = values[i].x;
-            anim->mRotationKeys[i].mValue.z = values[i].y;
-            anim->mRotationKeys[i].mValue.w = values[i].z;
-        }
-        delete[] times;
-        delete[] values;
-    } else if (node.rotation.isPresent) {
-        anim->mNumRotationKeys = 1;
-        anim->mRotationKeys = new aiQuatKey[anim->mNumRotationKeys];
-        anim->mRotationKeys->mTime = 0.f;
-        anim->mRotationKeys->mValue.x = node.rotation.value[0];
-        anim->mRotationKeys->mValue.y = node.rotation.value[1];
-        anim->mRotationKeys->mValue.z = node.rotation.value[2];
-        anim->mRotationKeys->mValue.w = node.rotation.value[3];
-    }
-
-    if (samplers.scale) {
-        float* times = nullptr;
-        samplers.scale->input->ExtractData(times);
-        aiVector3D* values = nullptr;
-        samplers.scale->output->ExtractData(values);
-        anim->mNumScalingKeys = static_cast<uint32_t>(samplers.scale->input->count);
-        anim->mScalingKeys = new aiVectorKey[anim->mNumScalingKeys];
-        for (unsigned int i = 0; i < anim->mNumScalingKeys; ++i) {
-            anim->mScalingKeys[i].mTime = times[i] * kMillisecondsFromSeconds;
-            anim->mScalingKeys[i].mValue = values[i];
-        }
-        delete[] times;
-        delete[] values;
-    } else if (node.scale.isPresent) {
-        anim->mNumScalingKeys = 1;
-        anim->mScalingKeys = new aiVectorKey[anim->mNumScalingKeys];
-        anim->mScalingKeys->mTime = 0.f;
-        anim->mScalingKeys->mValue.x = node.scale.value[0];
-        anim->mScalingKeys->mValue.y = node.scale.value[1];
-        anim->mScalingKeys->mValue.z = node.scale.value[2];
-    }
-
-    return anim;
+aiNodeAnim *CreateNodeAnim(glTF2::Asset &r, Node &node, AnimationSamplers &samplers) {
+	aiNodeAnim *anim = new aiNodeAnim();
+	anim->mNodeName = GetNodeName(node);
+
+	static const float kMillisecondsFromSeconds = 1000.f;
+
+	if (samplers.translation) {
+		float *times = nullptr;
+		samplers.translation->input->ExtractData(times);
+		aiVector3D *values = nullptr;
+		samplers.translation->output->ExtractData(values);
+		anim->mNumPositionKeys = static_cast<uint32_t>(samplers.translation->input->count);
+		anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys];
+		for (unsigned int i = 0; i < anim->mNumPositionKeys; ++i) {
+			anim->mPositionKeys[i].mTime = times[i] * kMillisecondsFromSeconds;
+			anim->mPositionKeys[i].mValue = values[i];
+		}
+		delete[] times;
+		delete[] values;
+	} else if (node.translation.isPresent) {
+		anim->mNumPositionKeys = 1;
+		anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys];
+		anim->mPositionKeys->mTime = 0.f;
+		anim->mPositionKeys->mValue.x = node.translation.value[0];
+		anim->mPositionKeys->mValue.y = node.translation.value[1];
+		anim->mPositionKeys->mValue.z = node.translation.value[2];
+	}
+
+	if (samplers.rotation) {
+		float *times = nullptr;
+		samplers.rotation->input->ExtractData(times);
+		aiQuaternion *values = nullptr;
+		samplers.rotation->output->ExtractData(values);
+		anim->mNumRotationKeys = static_cast<uint32_t>(samplers.rotation->input->count);
+		anim->mRotationKeys = new aiQuatKey[anim->mNumRotationKeys];
+		for (unsigned int i = 0; i < anim->mNumRotationKeys; ++i) {
+			anim->mRotationKeys[i].mTime = times[i] * kMillisecondsFromSeconds;
+			anim->mRotationKeys[i].mValue.x = values[i].w;
+			anim->mRotationKeys[i].mValue.y = values[i].x;
+			anim->mRotationKeys[i].mValue.z = values[i].y;
+			anim->mRotationKeys[i].mValue.w = values[i].z;
+		}
+		delete[] times;
+		delete[] values;
+	} else if (node.rotation.isPresent) {
+		anim->mNumRotationKeys = 1;
+		anim->mRotationKeys = new aiQuatKey[anim->mNumRotationKeys];
+		anim->mRotationKeys->mTime = 0.f;
+		anim->mRotationKeys->mValue.x = node.rotation.value[0];
+		anim->mRotationKeys->mValue.y = node.rotation.value[1];
+		anim->mRotationKeys->mValue.z = node.rotation.value[2];
+		anim->mRotationKeys->mValue.w = node.rotation.value[3];
+	}
+
+	if (samplers.scale) {
+		float *times = nullptr;
+		samplers.scale->input->ExtractData(times);
+		aiVector3D *values = nullptr;
+		samplers.scale->output->ExtractData(values);
+		anim->mNumScalingKeys = static_cast<uint32_t>(samplers.scale->input->count);
+		anim->mScalingKeys = new aiVectorKey[anim->mNumScalingKeys];
+		for (unsigned int i = 0; i < anim->mNumScalingKeys; ++i) {
+			anim->mScalingKeys[i].mTime = times[i] * kMillisecondsFromSeconds;
+			anim->mScalingKeys[i].mValue = values[i];
+		}
+		delete[] times;
+		delete[] values;
+	} else if (node.scale.isPresent) {
+		anim->mNumScalingKeys = 1;
+		anim->mScalingKeys = new aiVectorKey[anim->mNumScalingKeys];
+		anim->mScalingKeys->mTime = 0.f;
+		anim->mScalingKeys->mValue.x = node.scale.value[0];
+		anim->mScalingKeys->mValue.y = node.scale.value[1];
+		anim->mScalingKeys->mValue.z = node.scale.value[2];
+	}
+
+	return anim;
 }
 
-aiMeshMorphAnim* CreateMeshMorphAnim(glTF2::Asset& r, Node& node, AnimationSamplers& samplers)
-{
-    aiMeshMorphAnim* anim = new aiMeshMorphAnim();
-    anim->mName = GetNodeName(node);
-
-    static const float kMillisecondsFromSeconds = 1000.f;
-
-    if (nullptr != samplers.weight) {
-        float* times = nullptr;
-        samplers.weight->input->ExtractData(times);
-        float* values = nullptr;
-        samplers.weight->output->ExtractData(values);
-        anim->mNumKeys = static_cast<uint32_t>(samplers.weight->input->count);
-
-        const unsigned int numMorphs = samplers.weight->output->count / anim->mNumKeys;
-
-        anim->mKeys = new aiMeshMorphKey[anim->mNumKeys];
-        unsigned int k = 0u;
-        for (unsigned int i = 0u; i < anim->mNumKeys; ++i) {
-            anim->mKeys[i].mTime = times[i] * kMillisecondsFromSeconds;
-            anim->mKeys[i].mNumValuesAndWeights = numMorphs;
-            anim->mKeys[i].mValues = new unsigned int[numMorphs];
-            anim->mKeys[i].mWeights = new double[numMorphs];
-
-            for (unsigned int j = 0u; j < numMorphs; ++j, ++k) {
-                anim->mKeys[i].mValues[j] = j;
-                anim->mKeys[i].mWeights[j] = ( 0.f > values[k] ) ? 0.f : values[k];
-            }
-        }
-
-        delete[] times;
-        delete[] values;
-    }
-
-    return anim;
+aiMeshMorphAnim *CreateMeshMorphAnim(glTF2::Asset &r, Node &node, AnimationSamplers &samplers) {
+	aiMeshMorphAnim *anim = new aiMeshMorphAnim();
+	anim->mName = GetNodeName(node);
+
+	static const float kMillisecondsFromSeconds = 1000.f;
+
+	if (nullptr != samplers.weight) {
+		float *times = nullptr;
+		samplers.weight->input->ExtractData(times);
+		float *values = nullptr;
+		samplers.weight->output->ExtractData(values);
+		anim->mNumKeys = static_cast<uint32_t>(samplers.weight->input->count);
+
+		const unsigned int numMorphs = (unsigned int)samplers.weight->output->count / anim->mNumKeys;
+
+		anim->mKeys = new aiMeshMorphKey[anim->mNumKeys];
+		unsigned int k = 0u;
+		for (unsigned int i = 0u; i < anim->mNumKeys; ++i) {
+			anim->mKeys[i].mTime = times[i] * kMillisecondsFromSeconds;
+			anim->mKeys[i].mNumValuesAndWeights = numMorphs;
+			anim->mKeys[i].mValues = new unsigned int[numMorphs];
+			anim->mKeys[i].mWeights = new double[numMorphs];
+
+			for (unsigned int j = 0u; j < numMorphs; ++j, ++k) {
+				anim->mKeys[i].mValues[j] = j;
+				anim->mKeys[i].mWeights[j] = (0.f > values[k]) ? 0.f : values[k];
+			}
+		}
+
+		delete[] times;
+		delete[] values;
+	}
+
+	return anim;
 }
 
-std::unordered_map<unsigned int, AnimationSamplers> GatherSamplers(Animation& anim)
-{
-    std::unordered_map<unsigned int, AnimationSamplers> samplers;
-    for (unsigned int c = 0; c < anim.channels.size(); ++c) {
-        Animation::Channel& channel = anim.channels[c];
-        if (channel.sampler >= static_cast<int>(anim.samplers.size())) {
-            continue;
-        }
-
-        const unsigned int node_index = channel.target.node.GetIndex();
-
-        AnimationSamplers& sampler = samplers[node_index];
-        if (channel.target.path == AnimationPath_TRANSLATION) {
-            sampler.translation = &anim.samplers[channel.sampler];
-        } else if (channel.target.path == AnimationPath_ROTATION) {
-            sampler.rotation = &anim.samplers[channel.sampler];
-        } else if (channel.target.path == AnimationPath_SCALE) {
-            sampler.scale = &anim.samplers[channel.sampler];
-        } else if (channel.target.path == AnimationPath_WEIGHTS) {
-            sampler.weight = &anim.samplers[channel.sampler];
-        }
-    }
-
-    return samplers;
+std::unordered_map<unsigned int, AnimationSamplers> GatherSamplers(Animation &anim) {
+	std::unordered_map<unsigned int, AnimationSamplers> samplers;
+	for (unsigned int c = 0; c < anim.channels.size(); ++c) {
+		Animation::Channel &channel = anim.channels[c];
+		if (channel.sampler >= static_cast<int>(anim.samplers.size())) {
+			continue;
+		}
+
+		const unsigned int node_index = channel.target.node.GetIndex();
+
+		AnimationSamplers &sampler = samplers[node_index];
+		if (channel.target.path == AnimationPath_TRANSLATION) {
+			sampler.translation = &anim.samplers[channel.sampler];
+		} else if (channel.target.path == AnimationPath_ROTATION) {
+			sampler.rotation = &anim.samplers[channel.sampler];
+		} else if (channel.target.path == AnimationPath_SCALE) {
+			sampler.scale = &anim.samplers[channel.sampler];
+		} else if (channel.target.path == AnimationPath_WEIGHTS) {
+			sampler.weight = &anim.samplers[channel.sampler];
+		}
+	}
+
+	return samplers;
 }
 
-void glTF2Importer::ImportAnimations(glTF2::Asset& r)
-{
-    if (!r.scene) return;
-
-    mScene->mNumAnimations = r.animations.Size();
-    if (mScene->mNumAnimations == 0) {
-        return;
-    }
-
-    mScene->mAnimations = new aiAnimation*[mScene->mNumAnimations];
-    for (unsigned int i = 0; i < r.animations.Size(); ++i) {
-        Animation& anim = r.animations[i];
-
-        aiAnimation* ai_anim = new aiAnimation();
-        ai_anim->mName = anim.name;
-        ai_anim->mDuration = 0;
-        ai_anim->mTicksPerSecond = 0;
-
-        std::unordered_map<unsigned int, AnimationSamplers> samplers = GatherSamplers(anim);
-
-        uint32_t numChannels = 0u;
-        uint32_t numMorphMeshChannels = 0u;
-
-        for (auto& iter : samplers) {
-            if ((nullptr != iter.second.rotation) || (nullptr != iter.second.scale) || (nullptr != iter.second.translation)) {
-                ++numChannels;
-            }
-            if (nullptr != iter.second.weight) {
-                ++numMorphMeshChannels;
-            }
-        }
-
-        ai_anim->mNumChannels = numChannels;
-        if (ai_anim->mNumChannels > 0) {
-            ai_anim->mChannels = new aiNodeAnim*[ai_anim->mNumChannels];
-            int j = 0;
-            for (auto& iter : samplers) {
-                if ((nullptr != iter.second.rotation) || (nullptr != iter.second.scale) || (nullptr != iter.second.translation)) {
-                    ai_anim->mChannels[j] = CreateNodeAnim(r, r.nodes[iter.first], iter.second);
-                    ++j;
-                }
-            }
-        }
-
-        ai_anim->mNumMorphMeshChannels = numMorphMeshChannels;
-        if (ai_anim->mNumMorphMeshChannels > 0) {
-            ai_anim->mMorphMeshChannels = new aiMeshMorphAnim*[ai_anim->mNumMorphMeshChannels];
-            int j = 0;
-            for (auto& iter : samplers) {
-                if (nullptr != iter.second.weight) {
-                  ai_anim->mMorphMeshChannels[j] = CreateMeshMorphAnim(r, r.nodes[iter.first], iter.second);
-                  ++j;
-                }
-            }
-        }
-
-        // Use the latest keyframe for the duration of the animation
-        double maxDuration = 0;
-        unsigned int maxNumberOfKeys = 0;
-        for (unsigned int j = 0; j < ai_anim->mNumChannels; ++j) {
-            auto chan = ai_anim->mChannels[j];
-            if (chan->mNumPositionKeys) {
-                auto lastPosKey = chan->mPositionKeys[chan->mNumPositionKeys - 1];
-                if (lastPosKey.mTime > maxDuration) {
-                    maxDuration = lastPosKey.mTime;
-                }
-                maxNumberOfKeys = std::max(maxNumberOfKeys, chan->mNumPositionKeys);
-            }
-            if (chan->mNumRotationKeys) {
-                auto lastRotKey = chan->mRotationKeys[chan->mNumRotationKeys - 1];
-                if (lastRotKey.mTime > maxDuration) {
-                    maxDuration = lastRotKey.mTime;
-                }
-                maxNumberOfKeys = std::max(maxNumberOfKeys, chan->mNumRotationKeys);
-            }
-            if (chan->mNumScalingKeys) {
-                auto lastScaleKey = chan->mScalingKeys[chan->mNumScalingKeys - 1];
-                if (lastScaleKey.mTime > maxDuration) {
-                    maxDuration = lastScaleKey.mTime;
-                }
-                maxNumberOfKeys = std::max(maxNumberOfKeys, chan->mNumScalingKeys);
-            }
-        }
-
-        for (unsigned int j = 0; j < ai_anim->mNumMorphMeshChannels; ++j) {
-            const auto* const chan = ai_anim->mMorphMeshChannels[j];
-
-            if (0u != chan->mNumKeys) {
-                const auto& lastKey = chan->mKeys[chan->mNumKeys - 1u];
-                if (lastKey.mTime > maxDuration) {
-                    maxDuration = lastKey.mTime;
-                }
-                maxNumberOfKeys = std::max(maxNumberOfKeys, chan->mNumKeys);
-            }
-        }
-
-        ai_anim->mDuration = maxDuration;
-        ai_anim->mTicksPerSecond = 1000.0;
-
-        mScene->mAnimations[i] = ai_anim;
-    }
+void glTF2Importer::ImportAnimations(glTF2::Asset &r) {
+	if (!r.scene) return;
+
+	mScene->mNumAnimations = r.animations.Size();
+	if (mScene->mNumAnimations == 0) {
+		return;
+	}
+
+	mScene->mAnimations = new aiAnimation *[mScene->mNumAnimations];
+	for (unsigned int i = 0; i < r.animations.Size(); ++i) {
+		Animation &anim = r.animations[i];
+
+		aiAnimation *ai_anim = new aiAnimation();
+		ai_anim->mName = anim.name;
+		ai_anim->mDuration = 0;
+		ai_anim->mTicksPerSecond = 0;
+
+		std::unordered_map<unsigned int, AnimationSamplers> samplers = GatherSamplers(anim);
+
+		uint32_t numChannels = 0u;
+		uint32_t numMorphMeshChannels = 0u;
+
+		for (auto &iter : samplers) {
+			if ((nullptr != iter.second.rotation) || (nullptr != iter.second.scale) || (nullptr != iter.second.translation)) {
+				++numChannels;
+			}
+			if (nullptr != iter.second.weight) {
+				++numMorphMeshChannels;
+			}
+		}
+
+		ai_anim->mNumChannels = numChannels;
+		if (ai_anim->mNumChannels > 0) {
+			ai_anim->mChannels = new aiNodeAnim *[ai_anim->mNumChannels];
+			int j = 0;
+			for (auto &iter : samplers) {
+				if ((nullptr != iter.second.rotation) || (nullptr != iter.second.scale) || (nullptr != iter.second.translation)) {
+					ai_anim->mChannels[j] = CreateNodeAnim(r, r.nodes[iter.first], iter.second);
+					++j;
+				}
+			}
+		}
+
+		ai_anim->mNumMorphMeshChannels = numMorphMeshChannels;
+		if (ai_anim->mNumMorphMeshChannels > 0) {
+			ai_anim->mMorphMeshChannels = new aiMeshMorphAnim *[ai_anim->mNumMorphMeshChannels];
+			int j = 0;
+			for (auto &iter : samplers) {
+				if (nullptr != iter.second.weight) {
+					ai_anim->mMorphMeshChannels[j] = CreateMeshMorphAnim(r, r.nodes[iter.first], iter.second);
+					++j;
+				}
+			}
+		}
+
+		// Use the latest keyframe for the duration of the animation
+		double maxDuration = 0;
+		unsigned int maxNumberOfKeys = 0;
+		for (unsigned int j = 0; j < ai_anim->mNumChannels; ++j) {
+			auto chan = ai_anim->mChannels[j];
+			if (chan->mNumPositionKeys) {
+				auto lastPosKey = chan->mPositionKeys[chan->mNumPositionKeys - 1];
+				if (lastPosKey.mTime > maxDuration) {
+					maxDuration = lastPosKey.mTime;
+				}
+				maxNumberOfKeys = std::max(maxNumberOfKeys, chan->mNumPositionKeys);
+			}
+			if (chan->mNumRotationKeys) {
+				auto lastRotKey = chan->mRotationKeys[chan->mNumRotationKeys - 1];
+				if (lastRotKey.mTime > maxDuration) {
+					maxDuration = lastRotKey.mTime;
+				}
+				maxNumberOfKeys = std::max(maxNumberOfKeys, chan->mNumRotationKeys);
+			}
+			if (chan->mNumScalingKeys) {
+				auto lastScaleKey = chan->mScalingKeys[chan->mNumScalingKeys - 1];
+				if (lastScaleKey.mTime > maxDuration) {
+					maxDuration = lastScaleKey.mTime;
+				}
+				maxNumberOfKeys = std::max(maxNumberOfKeys, chan->mNumScalingKeys);
+			}
+		}
+
+		for (unsigned int j = 0; j < ai_anim->mNumMorphMeshChannels; ++j) {
+			const auto *const chan = ai_anim->mMorphMeshChannels[j];
+
+			if (0u != chan->mNumKeys) {
+				const auto &lastKey = chan->mKeys[chan->mNumKeys - 1u];
+				if (lastKey.mTime > maxDuration) {
+					maxDuration = lastKey.mTime;
+				}
+				maxNumberOfKeys = std::max(maxNumberOfKeys, chan->mNumKeys);
+			}
+		}
+
+		ai_anim->mDuration = maxDuration;
+		ai_anim->mTicksPerSecond = 1000.0;
+
+		mScene->mAnimations[i] = ai_anim;
+	}
 }
 
-void glTF2Importer::ImportEmbeddedTextures(glTF2::Asset& r)
-{
-    embeddedTexIdxs.resize(r.images.Size(), -1);
+void glTF2Importer::ImportEmbeddedTextures(glTF2::Asset &r) {
+	embeddedTexIdxs.resize(r.images.Size(), -1);
 
-    int numEmbeddedTexs = 0;
-    for (size_t i = 0; i < r.images.Size(); ++i) {
-        if (r.images[i].HasData())
-            numEmbeddedTexs += 1;
-    }
+	int numEmbeddedTexs = 0;
+	for (size_t i = 0; i < r.images.Size(); ++i) {
+		if (r.images[i].HasData())
+			numEmbeddedTexs += 1;
+	}
 
-    if (numEmbeddedTexs == 0)
-        return;
+	if (numEmbeddedTexs == 0)
+		return;
 
-    mScene->mTextures = new aiTexture*[numEmbeddedTexs];
+	mScene->mTextures = new aiTexture *[numEmbeddedTexs];
 
-    // Add the embedded textures
-    for (size_t i = 0; i < r.images.Size(); ++i) {
-        Image &img = r.images[i];
-        if (!img.HasData()) continue;
+	// Add the embedded textures
+	for (size_t i = 0; i < r.images.Size(); ++i) {
+		Image &img = r.images[i];
+		if (!img.HasData()) continue;
 
-        int idx = mScene->mNumTextures++;
-        embeddedTexIdxs[i] = idx;
+		int idx = mScene->mNumTextures++;
+		embeddedTexIdxs[i] = idx;
 
-        aiTexture* tex = mScene->mTextures[idx] = new aiTexture();
+		aiTexture *tex = mScene->mTextures[idx] = new aiTexture();
 
-        size_t length = img.GetDataLength();
-        void* data = img.StealData();
+		size_t length = img.GetDataLength();
+		void *data = img.StealData();
 
-        tex->mWidth = static_cast<unsigned int>(length);
-        tex->mHeight = 0;
-        tex->pcData = reinterpret_cast<aiTexel*>(data);
+		tex->mWidth = static_cast<unsigned int>(length);
+		tex->mHeight = 0;
+		tex->pcData = reinterpret_cast<aiTexel *>(data);
 
-        if (!img.mimeType.empty()) {
-            const char* ext = strchr(img.mimeType.c_str(), '/') + 1;
-            if (ext) {
-                if (strcmp(ext, "jpeg") == 0) ext = "jpg";
+		if (!img.mimeType.empty()) {
+			const char *ext = strchr(img.mimeType.c_str(), '/') + 1;
+			if (ext) {
+				if (strcmp(ext, "jpeg") == 0) ext = "jpg";
 
-                size_t len = strlen(ext);
-                if (len <= 3) {
-                    strcpy(tex->achFormatHint, ext);
-                }
-            }
-        }
-    }
+				size_t len = strlen(ext);
+				if (len <= 3) {
+					strcpy(tex->achFormatHint, ext);
+				}
+			}
+		}
+	}
 }
 
-void glTF2Importer::InternReadFile(const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler)
-{
-    // clean all member arrays
-    meshOffsets.clear();
-    embeddedTexIdxs.clear();
+void glTF2Importer::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) {
+	// clean all member arrays
+	meshOffsets.clear();
+	embeddedTexIdxs.clear();
 
-    this->mScene = pScene;
+	this->mScene = pScene;
 
-    // read the asset file
-    glTF2::Asset asset(pIOHandler);
-    asset.Load(pFile, GetExtension(pFile) == "glb");
+	// read the asset file
+	glTF2::Asset asset(pIOHandler);
+	asset.Load(pFile, GetExtension(pFile) == "glb");
 
-    //
-    // Copy the data out
-    //
+	//
+	// Copy the data out
+	//
 
-    ImportEmbeddedTextures(asset);
-    ImportMaterials(asset);
+	ImportEmbeddedTextures(asset);
+	ImportMaterials(asset);
 
-    ImportMeshes(asset);
+	ImportMeshes(asset);
 
-    ImportCameras(asset);
-    ImportLights(asset);
+	ImportCameras(asset);
+	ImportLights(asset);
 
-    ImportNodes(asset);
+	ImportNodes(asset);
 
-    ImportAnimations(asset);
+	ImportAnimations(asset);
 
-    if (pScene->mNumMeshes == 0) {
-        pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
-    }
+	if (pScene->mNumMeshes == 0) {
+		pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
+	}
 }
 
 #endif // ASSIMP_BUILD_NO_GLTF_IMPORTER
-

+ 2 - 0
contrib/zip/.gitignore

@@ -1,6 +1,7 @@
 /build/
 /test/build/
 /xcodeproj/
+.vscode/
 
 # Object files
 *.o
@@ -54,3 +55,4 @@ zip.dir/
 test/test.exe.vcxproj.filters
 test/test.exe.vcxproj
 test/test.exe.dir/
+

+ 74 - 9
contrib/zip/CMakeLists.txt

@@ -1,10 +1,14 @@
-cmake_minimum_required(VERSION 2.8)
-project(zip)
-enable_language(C)
+cmake_minimum_required(VERSION 3.0)
+
+project(zip
+  LANGUAGES C
+  VERSION "0.1.15")
 set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})
 
+option(CMAKE_DISABLE_TESTING "Disable test creation" OFF)
+
 if (MSVC)
-  # Use secure functions by defaualt and suppress warnings about "deprecated" functions
+  # Use secure functions by default and suppress warnings about "deprecated" functions
   set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /D _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1")
   set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /D _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES_COUNT=1")
   set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /D _CRT_NONSTDC_NO_WARNINGS=1 /D _CRT_SECURE_NO_WARNINGS=1")
@@ -12,28 +16,80 @@ elseif ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU" OR
         "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR
         "${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang")
   set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -Wall -Wextra -Werror -pedantic")
+  if(ENABLE_COVERAGE)
+    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage")
+    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage")
+  endif()
 endif (MSVC)
 
 # zip
 set(SRC src/miniz.h src/zip.h src/zip.c)
 add_library(${PROJECT_NAME} ${SRC})
-target_include_directories(${PROJECT_NAME} INTERFACE src)
+target_include_directories(${PROJECT_NAME} PUBLIC
+  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>
+  $<INSTALL_INTERFACE:include>
+)
 
 # test
 if (NOT CMAKE_DISABLE_TESTING)
   enable_testing()
   add_subdirectory(test)
   find_package(Sanitizers)
-  add_sanitizers(${PROJECT_NAME} test.exe)
-  add_sanitizers(${PROJECT_NAME} test_miniz.exe)
+  add_sanitizers(${PROJECT_NAME} ${test_out} ${test_miniz_out})
 endif()
 
+####
+# Installation (https://github.com/forexample/package-example) {
+
+set(CONFIG_INSTALL_DIR "lib/cmake/${PROJECT_NAME}")
+set(INCLUDE_INSTALL_DIR "include")
+
+set(GENERATED_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated")
+
+# Configuration
+set(VERSION_CONFIG "${GENERATED_DIR}/${PROJECT_NAME}ConfigVersion.cmake")
+set(PROJECT_CONFIG "${GENERATED_DIR}/${PROJECT_NAME}Config.cmake")
+set(TARGETS_EXPORT_NAME "${PROJECT_NAME}Targets")
+set(NAMESPACE "${PROJECT_NAME}::")
+
+# Include module with fuction 'write_basic_package_version_file'
+include(CMakePackageConfigHelpers)
+
+# Note: PROJECT_VERSION is used as a VERSION
+write_basic_package_version_file(
+    "${VERSION_CONFIG}" COMPATIBILITY SameMajorVersion
+)
+
+# Use variables:
+#   * TARGETS_EXPORT_NAME
+#   * PROJECT_NAME
+configure_package_config_file(
+    "cmake/Config.cmake.in"
+    "${PROJECT_CONFIG}"
+    INSTALL_DESTINATION "${CONFIG_INSTALL_DIR}"
+)
+
+install(
+    FILES "${PROJECT_CONFIG}" "${VERSION_CONFIG}"
+    DESTINATION "${CONFIG_INSTALL_DIR}"
+)
+
+install(
+    EXPORT "${TARGETS_EXPORT_NAME}"
+    NAMESPACE "${NAMESPACE}"
+    DESTINATION "${CONFIG_INSTALL_DIR}"
+)
+
+# }
+
 install(TARGETS ${PROJECT_NAME}
+        EXPORT ${TARGETS_EXPORT_NAME}
         RUNTIME DESTINATION bin
         ARCHIVE DESTINATION lib
         LIBRARY DESTINATION lib
-        COMPONENT library)
-install(FILES ${PROJECT_SOURCE_DIR}/src/zip.h DESTINATION include)
+        INCLUDES DESTINATION ${INCLUDE_INSTALL_DIR}
+)
+install(FILES ${PROJECT_SOURCE_DIR}/src/zip.h DESTINATION ${INCLUDE_INSTALL_DIR}/zip)
 
 # uninstall target (https://gitlab.kitware.com/cmake/community/wikis/FAQ#can-i-do-make-uninstall-with-cmake)
 if(NOT TARGET uninstall)
@@ -45,3 +101,12 @@ if(NOT TARGET uninstall)
     add_custom_target(uninstall
         COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake/cmake_uninstall.cmake)
 endif()
+
+find_package(Doxygen)
+if(DOXYGEN_FOUND)
+    configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile @ONLY)
+    add_custom_target(doc
+        ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile
+        WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+        COMMENT "Generating API documentation with Doxygen" VERBATIM)
+endif()

+ 6 - 6
contrib/zip/README.md

@@ -71,7 +71,7 @@ int arg = 2;
 zip_extract("foo.zip", "/tmp", on_extract_entry, &arg);
 ```
 
-*   Extract a zip entry into memory.
+* Extract a zip entry into memory.
 ```c
 void *buf = NULL;
 size_t bufsize;
@@ -89,7 +89,7 @@ zip_close(zip);
 free(buf);
 ```
 
-*   Extract a zip entry into memory (no internal allocation).
+* Extract a zip entry into memory (no internal allocation).
 ```c
 unsigned char *buf;
 size_t bufsize;
@@ -110,7 +110,7 @@ zip_close(zip);
 free(buf);
 ```
 
-*   Extract a zip entry into memory using callback.
+* Extract a zip entry into memory using callback.
 ```c
 struct buffer_t {
     char *data;
@@ -144,7 +144,7 @@ free(buf.data);
 ```
 
 
-*   Extract a zip entry into a file.
+* Extract a zip entry into a file.
 ```c
 struct zip_t *zip = zip_open("foo.zip", 0, 'r');
 {
@@ -157,7 +157,7 @@ struct zip_t *zip = zip_open("foo.zip", 0, 'r');
 zip_close(zip);
 ```
 
-*   List of all zip entries
+* List of all zip entries
 ```c
 struct zip_t *zip = zip_open("foo.zip", 0, 'r');
 int i, n = zip_total_entries(zip);
@@ -174,7 +174,7 @@ for (i = 0; i < n; ++i) {
 zip_close(zip);
 ```
 
-## Bindings
+# Bindings
 Compile zip library as a dynamic library.
 ```shell
 $ mkdir build

+ 1 - 1
contrib/zip/appveyor.yml

@@ -1,4 +1,4 @@
-version: zip-0.1.9.{build}
+version: zip-0.1.15.{build}
 build_script:
 - cmd: >-
     cd c:\projects\zip

+ 400 - 57
contrib/zip/src/miniz.h

@@ -221,6 +221,7 @@
 #ifndef MINIZ_HEADER_INCLUDED
 #define MINIZ_HEADER_INCLUDED
 
+#include <stdint.h>
 #include <stdlib.h>
 
 // Defines to completely disable specific portions of miniz.c:
@@ -284,7 +285,8 @@
 /* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES only if not set */
 #if !defined(MINIZ_USE_UNALIGNED_LOADS_AND_STORES)
 #if MINIZ_X86_OR_X64_CPU
-/* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient integer loads and stores from unaligned addresses. */
+/* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient
+ * integer loads and stores from unaligned addresses. */
 #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1
 #define MINIZ_UNALIGNED_USE_MEMCPY
 #else
@@ -354,6 +356,44 @@ enum {
   MZ_FIXED = 4
 };
 
+/* miniz error codes. Be sure to update mz_zip_get_error_string() if you add or
+ * modify this enum. */
+typedef enum {
+  MZ_ZIP_NO_ERROR = 0,
+  MZ_ZIP_UNDEFINED_ERROR,
+  MZ_ZIP_TOO_MANY_FILES,
+  MZ_ZIP_FILE_TOO_LARGE,
+  MZ_ZIP_UNSUPPORTED_METHOD,
+  MZ_ZIP_UNSUPPORTED_ENCRYPTION,
+  MZ_ZIP_UNSUPPORTED_FEATURE,
+  MZ_ZIP_FAILED_FINDING_CENTRAL_DIR,
+  MZ_ZIP_NOT_AN_ARCHIVE,
+  MZ_ZIP_INVALID_HEADER_OR_CORRUPTED,
+  MZ_ZIP_UNSUPPORTED_MULTIDISK,
+  MZ_ZIP_DECOMPRESSION_FAILED,
+  MZ_ZIP_COMPRESSION_FAILED,
+  MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE,
+  MZ_ZIP_CRC_CHECK_FAILED,
+  MZ_ZIP_UNSUPPORTED_CDIR_SIZE,
+  MZ_ZIP_ALLOC_FAILED,
+  MZ_ZIP_FILE_OPEN_FAILED,
+  MZ_ZIP_FILE_CREATE_FAILED,
+  MZ_ZIP_FILE_WRITE_FAILED,
+  MZ_ZIP_FILE_READ_FAILED,
+  MZ_ZIP_FILE_CLOSE_FAILED,
+  MZ_ZIP_FILE_SEEK_FAILED,
+  MZ_ZIP_FILE_STAT_FAILED,
+  MZ_ZIP_INVALID_PARAMETER,
+  MZ_ZIP_INVALID_FILENAME,
+  MZ_ZIP_BUF_TOO_SMALL,
+  MZ_ZIP_INTERNAL_ERROR,
+  MZ_ZIP_FILE_NOT_FOUND,
+  MZ_ZIP_ARCHIVE_TOO_LARGE,
+  MZ_ZIP_VALIDATION_FAILED,
+  MZ_ZIP_WRITE_CALLBACK_FAILED,
+  MZ_ZIP_TOTAL_ERRORS
+} mz_zip_error;
+
 // Method
 #define MZ_DEFLATED 8
 
@@ -696,6 +736,7 @@ typedef size_t (*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs,
                                     void *pBuf, size_t n);
 typedef size_t (*mz_file_write_func)(void *pOpaque, mz_uint64 file_ofs,
                                      const void *pBuf, size_t n);
+typedef mz_bool (*mz_file_needs_keepalive)(void *pOpaque);
 
 struct mz_zip_internal_state_tag;
 typedef struct mz_zip_internal_state_tag mz_zip_internal_state;
@@ -707,13 +748,27 @@ typedef enum {
   MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3
 } mz_zip_mode;
 
-typedef struct mz_zip_archive_tag {
+typedef enum {
+  MZ_ZIP_TYPE_INVALID = 0,
+  MZ_ZIP_TYPE_USER,
+  MZ_ZIP_TYPE_MEMORY,
+  MZ_ZIP_TYPE_HEAP,
+  MZ_ZIP_TYPE_FILE,
+  MZ_ZIP_TYPE_CFILE,
+  MZ_ZIP_TOTAL_TYPES
+} mz_zip_type;
+
+typedef struct {
   mz_uint64 m_archive_size;
   mz_uint64 m_central_directory_file_ofs;
-  mz_uint m_total_files;
+
+  /* We only support up to UINT32_MAX files in zip64 mode. */
+  mz_uint32 m_total_files;
   mz_zip_mode m_zip_mode;
+  mz_zip_type m_zip_type;
+  mz_zip_error m_last_error;
 
-  mz_uint m_file_offset_alignment;
+  mz_uint64 m_file_offset_alignment;
 
   mz_alloc_func m_pAlloc;
   mz_free_func m_pFree;
@@ -722,6 +777,7 @@ typedef struct mz_zip_archive_tag {
 
   mz_file_read_func m_pRead;
   mz_file_write_func m_pWrite;
+  mz_file_needs_keepalive m_pNeeds_keepalive;
   void *m_pIO_opaque;
 
   mz_zip_internal_state *m_pState;
@@ -1263,6 +1319,9 @@ mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits,
                                                 int strategy);
 #endif // #ifndef MINIZ_NO_ZLIB_APIS
 
+#define MZ_UINT16_MAX (0xFFFFU)
+#define MZ_UINT32_MAX (0xFFFFFFFFU)
+
 #ifdef __cplusplus
 }
 #endif
@@ -1311,6 +1370,11 @@ typedef unsigned char mz_validate_uint64[sizeof(mz_uint64) == 8 ? 1 : -1];
    ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U))
 #endif
 
+#define MZ_READ_LE64(p)                                                        \
+  (((mz_uint64)MZ_READ_LE32(p)) |                                              \
+   (((mz_uint64)MZ_READ_LE32((const mz_uint8 *)(p) + sizeof(mz_uint32)))       \
+    << 32U))
+
 #ifdef _MSC_VER
 #define MZ_FORCEINLINE __forceinline
 #elif defined(__GNUC__)
@@ -4160,6 +4224,17 @@ enum {
   MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30,
   MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46,
   MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22,
+
+  /* ZIP64 archive identifier and record sizes */
+  MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06064b50,
+  MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG = 0x07064b50,
+  MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE = 56,
+  MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE = 20,
+  MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID = 0x0001,
+  MZ_ZIP_DATA_DESCRIPTOR_ID = 0x08074b50,
+  MZ_ZIP_DATA_DESCRIPTER_SIZE64 = 24,
+  MZ_ZIP_DATA_DESCRIPTER_SIZE32 = 16,
+
   // Central directory header record offsets
   MZ_ZIP_CDH_SIG_OFS = 0,
   MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4,
@@ -4199,6 +4274,31 @@ enum {
   MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12,
   MZ_ZIP_ECDH_CDIR_OFS_OFS = 16,
   MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20,
+
+  /* ZIP64 End of central directory locator offsets */
+  MZ_ZIP64_ECDL_SIG_OFS = 0,                    /* 4 bytes */
+  MZ_ZIP64_ECDL_NUM_DISK_CDIR_OFS = 4,          /* 4 bytes */
+  MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS = 8,  /* 8 bytes */
+  MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS = 16, /* 4 bytes */
+
+  /* ZIP64 End of central directory header offsets */
+  MZ_ZIP64_ECDH_SIG_OFS = 0,                       /* 4 bytes */
+  MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS = 4,            /* 8 bytes */
+  MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS = 12,          /* 2 bytes */
+  MZ_ZIP64_ECDH_VERSION_NEEDED_OFS = 14,           /* 2 bytes */
+  MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS = 16,            /* 4 bytes */
+  MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS = 20,            /* 4 bytes */
+  MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 24, /* 8 bytes */
+  MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS = 32,       /* 8 bytes */
+  MZ_ZIP64_ECDH_CDIR_SIZE_OFS = 40,                /* 8 bytes */
+  MZ_ZIP64_ECDH_CDIR_OFS_OFS = 48,                 /* 8 bytes */
+  MZ_ZIP_VERSION_MADE_BY_DOS_FILESYSTEM_ID = 0,
+  MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG = 0x10,
+  MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED = 1,
+  MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG = 32,
+  MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION = 64,
+  MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED = 8192,
+  MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8 = 1 << 11
 };
 
 typedef struct {
@@ -4211,7 +4311,24 @@ struct mz_zip_internal_state_tag {
   mz_zip_array m_central_dir;
   mz_zip_array m_central_dir_offsets;
   mz_zip_array m_sorted_central_dir_offsets;
+
+  /* The flags passed in when the archive is initially opened. */
+  uint32_t m_init_flags;
+
+  /* MZ_TRUE if the archive has a zip64 end of central directory headers, etc.
+   */
+  mz_bool m_zip64;
+
+  /* MZ_TRUE if we found zip64 extended info in the central directory (m_zip64
+   * will also be slammed to true too, even if we didn't find a zip64 end of
+   * central dir header, etc.) */
+  mz_bool m_zip64_has_extended_info_fields;
+
+  /* These fields are used by the file, FILE, memory, and memory/heap read/write
+   * helpers. */
   MZ_FILE *m_pFile;
+  mz_uint64 m_file_archive_start_ofs;
+
   void *m_pMem;
   size_t m_mem_size;
   size_t m_mem_capacity;
@@ -4363,6 +4480,13 @@ static mz_bool mz_zip_set_file_times(const char *pFilename, time_t access_time,
 #endif /* #ifndef MINIZ_NO_STDIO */
 #endif /* #ifndef MINIZ_NO_TIME */
 
+static MZ_FORCEINLINE mz_bool mz_zip_set_error(mz_zip_archive *pZip,
+                                               mz_zip_error err_num) {
+  if (pZip)
+    pZip->m_last_error = err_num;
+  return MZ_FALSE;
+}
+
 static mz_bool mz_zip_reader_init_internal(mz_zip_archive *pZip,
                                            mz_uint32 flags) {
   (void)flags;
@@ -4480,127 +4604,346 @@ mz_zip_reader_sort_central_dir_offsets_by_filename(mz_zip_archive *pZip) {
   }
 }
 
-static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip,
-                                              mz_uint32 flags) {
-  mz_uint cdir_size, num_this_disk, cdir_disk_index;
-  mz_uint64 cdir_ofs;
+static mz_bool mz_zip_reader_locate_header_sig(mz_zip_archive *pZip,
+                                               mz_uint32 record_sig,
+                                               mz_uint32 record_size,
+                                               mz_int64 *pOfs) {
   mz_int64 cur_file_ofs;
-  const mz_uint8 *p;
   mz_uint32 buf_u32[4096 / sizeof(mz_uint32)];
   mz_uint8 *pBuf = (mz_uint8 *)buf_u32;
-  mz_bool sort_central_dir =
-      ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0);
-  // Basic sanity checks - reject files which are too small, and check the first
-  // 4 bytes of the file to make sure a local header is there.
-  if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)
+
+  /* Basic sanity checks - reject files which are too small */
+  if (pZip->m_archive_size < record_size)
     return MZ_FALSE;
-  // Find the end of central directory record by scanning the file from the end
-  // towards the beginning.
+
+  /* Find the record by scanning the file from the end towards the beginning. */
   cur_file_ofs =
       MZ_MAX((mz_int64)pZip->m_archive_size - (mz_int64)sizeof(buf_u32), 0);
   for (;;) {
     int i,
         n = (int)MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs);
+
     if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (mz_uint)n)
       return MZ_FALSE;
-    for (i = n - 4; i >= 0; --i)
-      if (MZ_READ_LE32(pBuf + i) == MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG)
-        break;
+
+    for (i = n - 4; i >= 0; --i) {
+      mz_uint s = MZ_READ_LE32(pBuf + i);
+      if (s == record_sig) {
+        if ((pZip->m_archive_size - (cur_file_ofs + i)) >= record_size)
+          break;
+      }
+    }
+
     if (i >= 0) {
       cur_file_ofs += i;
       break;
     }
+
+    /* Give up if we've searched the entire file, or we've gone back "too far"
+     * (~64kb) */
     if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >=
-                            (0xFFFF + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)))
+                            (MZ_UINT16_MAX + record_size)))
       return MZ_FALSE;
+
     cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0);
   }
-  // Read and verify the end of central directory record.
+
+  *pOfs = cur_file_ofs;
+  return MZ_TRUE;
+}
+
+static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip,
+                                              mz_uint flags) {
+  mz_uint cdir_size = 0, cdir_entries_on_this_disk = 0, num_this_disk = 0,
+          cdir_disk_index = 0;
+  mz_uint64 cdir_ofs = 0;
+  mz_int64 cur_file_ofs = 0;
+  const mz_uint8 *p;
+
+  mz_uint32 buf_u32[4096 / sizeof(mz_uint32)];
+  mz_uint8 *pBuf = (mz_uint8 *)buf_u32;
+  mz_bool sort_central_dir =
+      ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0);
+  mz_uint32 zip64_end_of_central_dir_locator_u32
+      [(MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + sizeof(mz_uint32) - 1) /
+       sizeof(mz_uint32)];
+  mz_uint8 *pZip64_locator = (mz_uint8 *)zip64_end_of_central_dir_locator_u32;
+
+  mz_uint32 zip64_end_of_central_dir_header_u32
+      [(MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) /
+       sizeof(mz_uint32)];
+  mz_uint8 *pZip64_end_of_central_dir =
+      (mz_uint8 *)zip64_end_of_central_dir_header_u32;
+
+  mz_uint64 zip64_end_of_central_dir_ofs = 0;
+
+  /* Basic sanity checks - reject files which are too small, and check the first
+   * 4 bytes of the file to make sure a local header is there. */
+  if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)
+    return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);
+
+  if (!mz_zip_reader_locate_header_sig(
+          pZip, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG,
+          MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE, &cur_file_ofs))
+    return mz_zip_set_error(pZip, MZ_ZIP_FAILED_FINDING_CENTRAL_DIR);
+
+  /* Read and verify the end of central directory record. */
   if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf,
                     MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) !=
       MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)
-    return MZ_FALSE;
-  if ((MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) !=
-       MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) ||
-      ((pZip->m_total_files =
-            MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS)) !=
-       MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS)))
-    return MZ_FALSE;
+    return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
+
+  if (MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) !=
+      MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG)
+    return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);
+
+  if (cur_file_ofs >= (MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE +
+                       MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) {
+    if (pZip->m_pRead(pZip->m_pIO_opaque,
+                      cur_file_ofs - MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE,
+                      pZip64_locator,
+                      MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) ==
+        MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) {
+      if (MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_SIG_OFS) ==
+          MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG) {
+        zip64_end_of_central_dir_ofs = MZ_READ_LE64(
+            pZip64_locator + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS);
+        if (zip64_end_of_central_dir_ofs >
+            (pZip->m_archive_size - MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE))
+          return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);
+
+        if (pZip->m_pRead(pZip->m_pIO_opaque, zip64_end_of_central_dir_ofs,
+                          pZip64_end_of_central_dir,
+                          MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) ==
+            MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) {
+          if (MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIG_OFS) ==
+              MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG) {
+            pZip->m_pState->m_zip64 = MZ_TRUE;
+          }
+        }
+      }
+    }
+  }
 
+  pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS);
+  cdir_entries_on_this_disk =
+      MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS);
   num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS);
   cdir_disk_index = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS);
+  cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS);
+  cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS);
+
+  if (pZip->m_pState->m_zip64) {
+    mz_uint32 zip64_total_num_of_disks =
+        MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS);
+    mz_uint64 zip64_cdir_total_entries = MZ_READ_LE64(
+        pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS);
+    mz_uint64 zip64_cdir_total_entries_on_this_disk = MZ_READ_LE64(
+        pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS);
+    mz_uint64 zip64_size_of_end_of_central_dir_record = MZ_READ_LE64(
+        pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS);
+    mz_uint64 zip64_size_of_central_directory =
+        MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_SIZE_OFS);
+
+    if (zip64_size_of_end_of_central_dir_record <
+        (MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - 12))
+      return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+
+    if (zip64_total_num_of_disks != 1U)
+      return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK);
+
+    /* Check for miniz's practical limits */
+    if (zip64_cdir_total_entries > MZ_UINT32_MAX)
+      return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);
+
+    pZip->m_total_files = (mz_uint32)zip64_cdir_total_entries;
+
+    if (zip64_cdir_total_entries_on_this_disk > MZ_UINT32_MAX)
+      return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);
+
+    cdir_entries_on_this_disk =
+        (mz_uint32)zip64_cdir_total_entries_on_this_disk;
+
+    /* Check for miniz's current practical limits (sorry, this should be enough
+     * for millions of files) */
+    if (zip64_size_of_central_directory > MZ_UINT32_MAX)
+      return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE);
+
+    cdir_size = (mz_uint32)zip64_size_of_central_directory;
+
+    num_this_disk = MZ_READ_LE32(pZip64_end_of_central_dir +
+                                 MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS);
+
+    cdir_disk_index = MZ_READ_LE32(pZip64_end_of_central_dir +
+                                   MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS);
+
+    cdir_ofs =
+        MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_OFS_OFS);
+  }
+
+  if (pZip->m_total_files != cdir_entries_on_this_disk)
+    return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK);
+
   if (((num_this_disk | cdir_disk_index) != 0) &&
       ((num_this_disk != 1) || (cdir_disk_index != 1)))
-    return MZ_FALSE;
+    return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK);
 
-  if ((cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS)) <
-      pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)
-    return MZ_FALSE;
+  if (cdir_size < pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)
+    return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
 
-  cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS);
   if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size)
-    return MZ_FALSE;
+    return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
 
   pZip->m_central_directory_file_ofs = cdir_ofs;
 
   if (pZip->m_total_files) {
     mz_uint i, n;
-
-    // Read the entire central directory into a heap block, and allocate another
-    // heap block to hold the unsorted central dir file record offsets, and
-    // another to hold the sorted indices.
+    /* Read the entire central directory into a heap block, and allocate another
+     * heap block to hold the unsorted central dir file record offsets, and
+     * possibly another to hold the sorted indices. */
     if ((!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size,
                               MZ_FALSE)) ||
         (!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets,
                               pZip->m_total_files, MZ_FALSE)))
-      return MZ_FALSE;
+      return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
 
     if (sort_central_dir) {
       if (!mz_zip_array_resize(pZip,
                                &pZip->m_pState->m_sorted_central_dir_offsets,
                                pZip->m_total_files, MZ_FALSE))
-        return MZ_FALSE;
+        return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
     }
 
     if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs,
                       pZip->m_pState->m_central_dir.m_p,
                       cdir_size) != cdir_size)
-      return MZ_FALSE;
+      return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
 
-    // Now create an index into the central directory file records, do some
-    // basic sanity checking on each record, and check for zip64 entries (which
-    // are not yet supported).
+    /* Now create an index into the central directory file records, do some
+     * basic sanity checking on each record */
     p = (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p;
     for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i) {
-      mz_uint total_header_size, comp_size, decomp_size, disk_index;
+      mz_uint total_header_size, disk_index, bit_flags, filename_size,
+          ext_data_size;
+      mz_uint64 comp_size, decomp_size, local_header_ofs;
+
       if ((n < MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) ||
           (MZ_READ_LE32(p) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG))
-        return MZ_FALSE;
+        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+
       MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32,
                            i) =
           (mz_uint32)(p - (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p);
+
       if (sort_central_dir)
         MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets,
                              mz_uint32, i) = i;
+
       comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS);
       decomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS);
-      if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) &&
-           (decomp_size != comp_size)) ||
-          (decomp_size && !comp_size) || (decomp_size == 0xFFFFFFFF) ||
-          (comp_size == 0xFFFFFFFF))
-        return MZ_FALSE;
+      local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS);
+      filename_size = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS);
+      ext_data_size = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS);
+
+      if ((!pZip->m_pState->m_zip64_has_extended_info_fields) &&
+          (ext_data_size) &&
+          (MZ_MAX(MZ_MAX(comp_size, decomp_size), local_header_ofs) ==
+           MZ_UINT32_MAX)) {
+        /* Attempt to find zip64 extended information field in the entry's extra
+         * data */
+        mz_uint32 extra_size_remaining = ext_data_size;
+
+        if (extra_size_remaining) {
+          const mz_uint8 *pExtra_data;
+          void *buf = NULL;
+
+          if (MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + ext_data_size >
+              n) {
+            buf = MZ_MALLOC(ext_data_size);
+            if (buf == NULL)
+              return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
+
+            if (pZip->m_pRead(pZip->m_pIO_opaque,
+                              cdir_ofs + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE +
+                                  filename_size,
+                              buf, ext_data_size) != ext_data_size) {
+              MZ_FREE(buf);
+              return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
+            }
+
+            pExtra_data = (mz_uint8 *)buf;
+          } else {
+            pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size;
+          }
+
+          do {
+            mz_uint32 field_id;
+            mz_uint32 field_data_size;
+
+            if (extra_size_remaining < (sizeof(mz_uint16) * 2)) {
+              MZ_FREE(buf);
+              return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+            }
+
+            field_id = MZ_READ_LE16(pExtra_data);
+            field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16));
+
+            if ((field_data_size + sizeof(mz_uint16) * 2) >
+                extra_size_remaining) {
+              MZ_FREE(buf);
+              return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+            }
+
+            if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) {
+              /* Ok, the archive didn't have any zip64 headers but it uses a
+               * zip64 extended information field so mark it as zip64 anyway
+               * (this can occur with infozip's zip util when it reads
+               * compresses files from stdin). */
+              pZip->m_pState->m_zip64 = MZ_TRUE;
+              pZip->m_pState->m_zip64_has_extended_info_fields = MZ_TRUE;
+              break;
+            }
+
+            pExtra_data += sizeof(mz_uint16) * 2 + field_data_size;
+            extra_size_remaining =
+                extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size;
+          } while (extra_size_remaining);
+
+          MZ_FREE(buf);
+        }
+      }
+
+      /* I've seen archives that aren't marked as zip64 that uses zip64 ext
+       * data, argh */
+      if ((comp_size != MZ_UINT32_MAX) && (decomp_size != MZ_UINT32_MAX)) {
+        if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) &&
+             (decomp_size != comp_size)) ||
+            (decomp_size && !comp_size))
+          return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+      }
+
       disk_index = MZ_READ_LE16(p + MZ_ZIP_CDH_DISK_START_OFS);
-      if ((disk_index != num_this_disk) && (disk_index != 1))
-        return MZ_FALSE;
-      if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) +
-           MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size)
-        return MZ_FALSE;
+      if ((disk_index == MZ_UINT16_MAX) ||
+          ((disk_index != num_this_disk) && (disk_index != 1)))
+        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK);
+
+      if (comp_size != MZ_UINT32_MAX) {
+        if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) +
+             MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size)
+          return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+      }
+
+      bit_flags = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS);
+      if (bit_flags & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED)
+        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION);
+
       if ((total_header_size = MZ_ZIP_CENTRAL_DIR_HEADER_SIZE +
                                MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) +
                                MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS) +
                                MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS)) >
           n)
-        return MZ_FALSE;
+        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+
       n -= total_header_size;
       p += total_header_size;
     }

+ 40 - 22
contrib/zip/src/zip.c

@@ -24,7 +24,6 @@
   ((((P)[0] >= 'A' && (P)[0] <= 'Z') || ((P)[0] >= 'a' && (P)[0] <= 'z')) &&   \
    (P)[1] == ':')
 #define FILESYSTEM_PREFIX_LEN(P) (HAS_DEVICE(P) ? 2 : 0)
-#define ISSLASH(C) ((C) == '/' || (C) == '\\')
 
 #else
 
@@ -48,7 +47,7 @@ int symlink(const char *target, const char *linkpath); // needed on Linux
 #endif
 
 #ifndef ISSLASH
-#define ISSLASH(C) ((C) == '/')
+#define ISSLASH(C) ((C) == '/' || (C) == '\\')
 #endif
 
 #define CLEANUP(ptr)                                                           \
@@ -78,26 +77,34 @@ static const char *base_name(const char *name) {
   return base;
 }
 
-static int mkpath(const char *path) {
-  char const *p;
+static int mkpath(char *path) {
+  char *p;
   char npath[MAX_PATH + 1];
   int len = 0;
   int has_device = HAS_DEVICE(path);
 
   memset(npath, 0, MAX_PATH + 1);
-
-#ifdef _WIN32
-  // only on windows fix the path
-  npath[0] = path[0];
-  npath[1] = path[1];
-  len = 2;
-#endif // _WIN32
-    
+  if (has_device) {
+    // only on windows
+    npath[0] = path[0];
+    npath[1] = path[1];
+    len = 2;
+  }
   for (p = path + len; *p && len < MAX_PATH; p++) {
     if (ISSLASH(*p) && ((!has_device && len > 0) || (has_device && len > 2))) {
-      if (MKDIR(npath) == -1)
-        if (errno != EEXIST)
+#if defined(_WIN32) || defined(__WIN32__) || defined(_MSC_VER) ||              \
+    defined(__MINGW32__)
+#else
+      if ('\\' == *p) {
+        *p = '/';
+      }
+#endif
+
+      if (MKDIR(npath) == -1) {
+        if (errno != EEXIST) {
           return -1;
+        }
+      }
     }
     npath[len++] = *p;
   }
@@ -279,7 +286,14 @@ int zip_entry_open(struct zip_t *zip, const char *entryname) {
   zip->entry.header_offset = zip->archive.m_archive_size;
   memset(zip->entry.header, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE * sizeof(mz_uint8));
   zip->entry.method = 0;
+
+  // UNIX or APPLE
+#if MZ_PLATFORM == 3 || MZ_PLATFORM == 19
+  // regular file with rw-r--r-- persmissions
+  zip->entry.external_attr = (mz_uint32)(0100644) << 16;
+#else
   zip->entry.external_attr = 0;
+#endif
 
   num_alignment_padding_bytes =
       mz_zip_writer_compute_padding_needed_for_file_alignment(pzip);
@@ -660,7 +674,7 @@ ssize_t zip_entry_noallocread(struct zip_t *zip, void *buf, size_t bufsize) {
   }
 
   if (!mz_zip_reader_extract_to_mem_no_alloc(pzip, (mz_uint)zip->entry.index,
-  buf, bufsize, 0, NULL,  0)) {
+                                             buf, bufsize, 0, NULL, 0)) {
     return -1;
   }
 
@@ -670,10 +684,7 @@ ssize_t zip_entry_noallocread(struct zip_t *zip, void *buf, size_t bufsize) {
 int zip_entry_fread(struct zip_t *zip, const char *filename) {
   mz_zip_archive *pzip = NULL;
   mz_uint idx;
-#if defined(_MSC_VER)
-#else
   mz_uint32 xattr = 0;
-#endif
   mz_zip_archive_file_stat info;
 
   if (!zip) {
@@ -875,12 +886,19 @@ int zip_extract(const char *zipname, const char *dir,
       goto out;
     }
 
-    if ((((info.m_version_made_by >> 8) == 3) || ((info.m_version_made_by >> 8) == 19)) // if zip is produced on Unix or macOS (3 and 19 from section 4.4.2.2 of zip standard)
-        && info.m_external_attr & (0x20 << 24)) { // and has sym link attribute (0x80 is file, 0x40 is directory)
+    if ((((info.m_version_made_by >> 8) == 3) ||
+         ((info.m_version_made_by >> 8) ==
+          19)) // if zip is produced on Unix or macOS (3 and 19 from
+               // section 4.4.2.2 of zip standard)
+        && info.m_external_attr &
+               (0x20 << 24)) { // and has sym link attribute (0x80 is file, 0x40
+                               // is directory)
 #if defined(_WIN32) || defined(__WIN32__) || defined(_MSC_VER) ||              \
     defined(__MINGW32__)
-#else      
-      if (info.m_uncomp_size > MAX_PATH || !mz_zip_reader_extract_to_mem_no_alloc(&zip_archive, i, symlink_to, MAX_PATH, 0, NULL, 0)) {
+#else
+      if (info.m_uncomp_size > MAX_PATH ||
+          !mz_zip_reader_extract_to_mem_no_alloc(&zip_archive, i, symlink_to,
+                                                 MAX_PATH, 0, NULL, 0)) {
         goto out;
       }
       symlink_to[info.m_uncomp_size] = '\0';

+ 226 - 231
contrib/zip/src/zip.h

@@ -20,241 +20,240 @@ extern "C" {
 #endif
 
 #if !defined(_SSIZE_T_DEFINED) && !defined(_SSIZE_T_DEFINED_) &&               \
-    !defined(_SSIZE_T) && !defined(_SSIZE_T_) && !defined(__ssize_t_defined)
-#define _SSIZE_T
+    !defined(__DEFINED_ssize_t) && !defined(__ssize_t_defined) &&              \
+    !defined(_SSIZE_T) && !defined(_SSIZE_T_)
+
 // 64-bit Windows is the only mainstream platform
 // where sizeof(long) != sizeof(void*)
 #ifdef _WIN64
-typedef long long  ssize_t;  /* byte count or error */
+typedef long long ssize_t; /* byte count or error */
 #else
-typedef long  ssize_t;  /* byte count or error */
+typedef long ssize_t; /* byte count or error */
 #endif
+
+#define _SSIZE_T_DEFINED
+#define _SSIZE_T_DEFINED_
+#define __DEFINED_ssize_t
+#define __ssize_t_defined
+#define _SSIZE_T
+#define _SSIZE_T_
+
 #endif
 
 #ifndef MAX_PATH
 #define MAX_PATH 32767 /* # chars in a path name including NULL */
 #endif
 
+/**
+ * @mainpage
+ *
+ * Documenation for @ref zip.
+ */
+
+/**
+ * @addtogroup zip
+ * @{
+ */
+
+/**
+ * Default zip compression level.
+ */
+
 #define ZIP_DEFAULT_COMPRESSION_LEVEL 6
 
-/*
-  This data structure is used throughout the library to represent zip archive
-  - forward declaration.
-*/
+/**
+ * @struct zip_t
+ *
+ * This data structure is used throughout the library to represent zip archive -
+ * forward declaration.
+ */
 struct zip_t;
 
-/*
-  Opens zip archive with compression level using the given mode.
-
-  Args:
-    zipname: zip archive file name.
-    level: compression level (0-9 are the standard zlib-style levels).
-    mode: file access mode.
-        'r': opens a file for reading/extracting (the file must exists).
-        'w': creates an empty file for writing.
-        'a': appends to an existing archive.
-
-  Returns:
-    The zip archive handler or NULL on error
-*/
+/**
+ * Opens zip archive with compression level using the given mode.
+ *
+ * @param zipname zip archive file name.
+ * @param level compression level (0-9 are the standard zlib-style levels).
+ * @param mode file access mode.
+ *        - 'r': opens a file for reading/extracting (the file must exists).
+ *        - 'w': creates an empty file for writing.
+ *        - 'a': appends to an existing archive.
+ *
+ * @return the zip archive handler or NULL on error
+ */
 extern struct zip_t *zip_open(const char *zipname, int level, char mode);
 
-/*
-  Closes the zip archive, releases resources - always finalize.
-
-  Args:
-    zip: zip archive handler.
-*/
+/**
+ * Closes the zip archive, releases resources - always finalize.
+ *
+ * @param zip zip archive handler.
+ */
 extern void zip_close(struct zip_t *zip);
 
-/*
-  Opens an entry by name in the zip archive.
-  For zip archive opened in 'w' or 'a' mode the function will append
-  a new entry. In readonly mode the function tries to locate the entry
-  in global dictionary.
-
-  Args:
-    zip: zip archive handler.
-    entryname: an entry name in local dictionary.
-
-  Returns:
-    The return code - 0 on success, negative number (< 0) on error.
-*/
+/**
+ * Opens an entry by name in the zip archive.
+ *
+ * For zip archive opened in 'w' or 'a' mode the function will append
+ * a new entry. In readonly mode the function tries to locate the entry
+ * in global dictionary.
+ *
+ * @param zip zip archive handler.
+ * @param entryname an entry name in local dictionary.
+ *
+ * @return the return code - 0 on success, negative number (< 0) on error.
+ */
 extern int zip_entry_open(struct zip_t *zip, const char *entryname);
 
-/*
-  Opens a new entry by index in the zip archive.
-  This function is only valid if zip archive was opened in 'r' (readonly) mode.
-
-  Args:
-    zip: zip archive handler.
-    index: index in local dictionary.
-
-  Returns:
-    The return code - 0 on success, negative number (< 0) on error.
-*/
+/**
+ * Opens a new entry by index in the zip archive.
+ *
+ * This function is only valid if zip archive was opened in 'r' (readonly) mode.
+ *
+ * @param zip zip archive handler.
+ * @param index index in local dictionary.
+ *
+ * @return the return code - 0 on success, negative number (< 0) on error.
+ */
 extern int zip_entry_openbyindex(struct zip_t *zip, int index);
 
-/*
-  Closes a zip entry, flushes buffer and releases resources.
-
-  Args:
-    zip: zip archive handler.
-
-  Returns:
-    The return code - 0 on success, negative number (< 0) on error.
-*/
+/**
+ * Closes a zip entry, flushes buffer and releases resources.
+ *
+ * @param zip zip archive handler.
+ *
+ * @return the return code - 0 on success, negative number (< 0) on error.
+ */
 extern int zip_entry_close(struct zip_t *zip);
 
-/*
-  Returns a local name of the current zip entry.
-  The main difference between user's entry name and local entry name
-  is optional relative path.
-  Following .ZIP File Format Specification - the path stored MUST not contain
-  a drive or device letter, or a leading slash.
-  All slashes MUST be forward slashes '/' as opposed to backwards slashes '\'
-  for compatibility with Amiga and UNIX file systems etc.
-
-  Args:
-    zip: zip archive handler.
-
-  Returns:
-    The pointer to the current zip entry name, or NULL on error.
-*/
+/**
+ * Returns a local name of the current zip entry.
+ *
+ * The main difference between user's entry name and local entry name
+ * is optional relative path.
+ * Following .ZIP File Format Specification - the path stored MUST not contain
+ * a drive or device letter, or a leading slash.
+ * All slashes MUST be forward slashes '/' as opposed to backwards slashes '\'
+ * for compatibility with Amiga and UNIX file systems etc.
+ *
+ * @param zip: zip archive handler.
+ *
+ * @return the pointer to the current zip entry name, or NULL on error.
+ */
 extern const char *zip_entry_name(struct zip_t *zip);
 
-/*
-  Returns an index of the current zip entry.
-
-  Args:
-    zip: zip archive handler.
-
-  Returns:
-    The index on success, negative number (< 0) on error.
-*/
+/**
+ * Returns an index of the current zip entry.
+ *
+ * @param zip zip archive handler.
+ *
+ * @return the index on success, negative number (< 0) on error.
+ */
 extern int zip_entry_index(struct zip_t *zip);
 
-/*
-  Determines if the current zip entry is a directory entry.
-
-  Args:
-    zip: zip archive handler.
-
-  Returns:
-    The return code - 1 (true), 0 (false), negative number (< 0) on error.
-*/
+/**
+ * Determines if the current zip entry is a directory entry.
+ *
+ * @param zip zip archive handler.
+ *
+ * @return the return code - 1 (true), 0 (false), negative number (< 0) on
+ *         error.
+ */
 extern int zip_entry_isdir(struct zip_t *zip);
 
-/*
-  Returns an uncompressed size of the current zip entry.
-
-  Args:
-    zip: zip archive handler.
-
-  Returns:
-    The uncompressed size in bytes.
-*/
+/**
+ * Returns an uncompressed size of the current zip entry.
+ *
+ * @param zip zip archive handler.
+ *
+ * @return the uncompressed size in bytes.
+ */
 extern unsigned long long zip_entry_size(struct zip_t *zip);
 
-/*
-  Returns CRC-32 checksum of the current zip entry.
-
-  Args:
-    zip: zip archive handler.
-
-  Returns:
-    The CRC-32 checksum.
-*/
+/**
+ * Returns CRC-32 checksum of the current zip entry.
+ *
+ * @param zip zip archive handler.
+ *
+ * @return the CRC-32 checksum.
+ */
 extern unsigned int zip_entry_crc32(struct zip_t *zip);
 
-/*
-  Compresses an input buffer for the current zip entry.
-
-  Args:
-    zip: zip archive handler.
-    buf: input buffer.
-    bufsize: input buffer size (in bytes).
-
-  Returns:
-    The return code - 0 on success, negative number (< 0) on error.
-*/
+/**
+ * Compresses an input buffer for the current zip entry.
+ *
+ * @param zip zip archive handler.
+ * @param buf input buffer.
+ * @param bufsize input buffer size (in bytes).
+ *
+ * @return the return code - 0 on success, negative number (< 0) on error.
+ */
 extern int zip_entry_write(struct zip_t *zip, const void *buf, size_t bufsize);
 
-/*
-  Compresses a file for the current zip entry.
-
-  Args:
-    zip: zip archive handler.
-    filename: input file.
-
-  Returns:
-    The return code - 0 on success, negative number (< 0) on error.
-*/
+/**
+ * Compresses a file for the current zip entry.
+ *
+ * @param zip zip archive handler.
+ * @param filename input file.
+ *
+ * @return the return code - 0 on success, negative number (< 0) on error.
+ */
 extern int zip_entry_fwrite(struct zip_t *zip, const char *filename);
 
-/*
-  Extracts the current zip entry into output buffer.
-  The function allocates sufficient memory for a output buffer.
-
-  Args:
-    zip: zip archive handler.
-    buf: output buffer.
-    bufsize: output buffer size (in bytes).
-
-  Note:
-    - remember to release memory allocated for a output buffer.
-    - for large entries, please take a look at zip_entry_extract function.
-
-  Returns:
-    The return code - the number of bytes actually read on success.
-    Otherwise a -1 on error.
-*/
+/**
+ * Extracts the current zip entry into output buffer.
+ *
+ * The function allocates sufficient memory for a output buffer.
+ *
+ * @param zip zip archive handler.
+ * @param buf output buffer.
+ * @param bufsize output buffer size (in bytes).
+ *
+ * @note remember to release memory allocated for a output buffer.
+ *       for large entries, please take a look at zip_entry_extract function.
+ *
+ * @return the return code - the number of bytes actually read on success.
+ *         Otherwise a -1 on error.
+ */
 extern ssize_t zip_entry_read(struct zip_t *zip, void **buf, size_t *bufsize);
 
-/*
-  Extracts the current zip entry into a memory buffer using no memory
-  allocation.
-
-  Args:
-    zip: zip archive handler.
-    buf: preallocated output buffer.
-    bufsize: output buffer size (in bytes).
-
-  Note:
-    - ensure supplied output buffer is large enough.
-    - zip_entry_size function (returns uncompressed size for the current entry)
-      can be handy to estimate how big buffer is needed.
-    - for large entries, please take a look at zip_entry_extract function.
-
-  Returns:
-    The return code - the number of bytes actually read on success.
-    Otherwise a -1 on error (e.g. bufsize is not large enough).
-*/
-extern ssize_t zip_entry_noallocread(struct zip_t *zip, void *buf, size_t bufsize);
-
-/*
-  Extracts the current zip entry into output file.
-
-  Args:
-    zip: zip archive handler.
-    filename: output file.
-
-  Returns:
-    The return code - 0 on success, negative number (< 0) on error.
-*/
+/**
+ * Extracts the current zip entry into a memory buffer using no memory
+ * allocation.
+ *
+ * @param zip zip archive handler.
+ * @param buf preallocated output buffer.
+ * @param bufsize output buffer size (in bytes).
+ *
+ * @note ensure supplied output buffer is large enough.
+ *       zip_entry_size function (returns uncompressed size for the current
+ *       entry) can be handy to estimate how big buffer is needed. for large
+ * entries, please take a look at zip_entry_extract function.
+ *
+ * @return the return code - the number of bytes actually read on success.
+ *         Otherwise a -1 on error (e.g. bufsize is not large enough).
+ */
+extern ssize_t zip_entry_noallocread(struct zip_t *zip, void *buf,
+                                     size_t bufsize);
+
+/**
+ * Extracts the current zip entry into output file.
+ *
+ * @param zip zip archive handler.
+ * @param filename output file.
+ *
+ * @return the return code - 0 on success, negative number (< 0) on error.
+ */
 extern int zip_entry_fread(struct zip_t *zip, const char *filename);
 
-/*
-  Extracts the current zip entry using a callback function (on_extract).
-
-  Args:
-    zip: zip archive handler.
-    on_extract: callback function.
-    arg: opaque pointer (optional argument,
-                         which you can pass to the on_extract callback)
-
-   Returns:
-    The return code - 0 on success, negative number (< 0) on error.
+/**
+ * Extracts the current zip entry using a callback function (on_extract).
+ *
+ * @param zip zip archive handler.
+ * @param on_extract callback function.
+ * @param arg opaque pointer (optional argument, which you can pass to the
+ *        on_extract callback)
+ *
+ * @return the return code - 0 on success, negative number (< 0) on error.
  */
 extern int
 zip_entry_extract(struct zip_t *zip,
@@ -262,53 +261,49 @@ zip_entry_extract(struct zip_t *zip,
                                        const void *data, size_t size),
                   void *arg);
 
-/*
-  Returns the number of all entries (files and directories) in the zip archive.
-
-  Args:
-    zip: zip archive handler.
-
-  Returns:
-    The return code - the number of entries on success,
-    negative number (< 0) on error.
-*/
+/**
+ * Returns the number of all entries (files and directories) in the zip archive.
+ *
+ * @param zip zip archive handler.
+ *
+ * @return the return code - the number of entries on success, negative number
+ *         (< 0) on error.
+ */
 extern int zip_total_entries(struct zip_t *zip);
 
-/*
-  Creates a new archive and puts files into a single zip archive.
-
-  Args:
-    zipname: zip archive file.
-    filenames: input files.
-    len: number of input files.
-
-  Returns:
-    The return code - 0 on success, negative number (< 0) on error.
-*/
+/**
+ * Creates a new archive and puts files into a single zip archive.
+ *
+ * @param zipname zip archive file.
+ * @param filenames input files.
+ * @param len: number of input files.
+ *
+ * @return the return code - 0 on success, negative number (< 0) on error.
+ */
 extern int zip_create(const char *zipname, const char *filenames[], size_t len);
 
-/*
-  Extracts a zip archive file into directory.
-
-  If on_extract_entry is not NULL, the callback will be called after
-  successfully extracted each zip entry.
-  Returning a negative value from the callback will cause abort and return an
-  error. The last argument (void *arg) is optional, which you can use to pass
-  data to the on_extract_entry callback.
-
-  Args:
-    zipname: zip archive file.
-    dir: output directory.
-    on_extract_entry: on extract callback.
-    arg: opaque pointer.
-
-  Returns:
-    The return code - 0 on success, negative number (< 0) on error.
-*/
+/**
+ * Extracts a zip archive file into directory.
+ *
+ * If on_extract_entry is not NULL, the callback will be called after
+ * successfully extracted each zip entry.
+ * Returning a negative value from the callback will cause abort and return an
+ * error. The last argument (void *arg) is optional, which you can use to pass
+ * data to the on_extract_entry callback.
+ *
+ * @param zipname zip archive file.
+ * @param dir output directory.
+ * @param on_extract_entry on extract callback.
+ * @param arg opaque pointer.
+ *
+ * @return the return code - 0 on success, negative number (< 0) on error.
+ */
 extern int zip_extract(const char *zipname, const char *dir,
                        int (*on_extract_entry)(const char *filename, void *arg),
                        void *arg);
 
+/** @} */
+
 #ifdef __cplusplus
 }
 #endif

+ 12 - 15
contrib/zip/test/CMakeLists.txt

@@ -1,19 +1,16 @@
 cmake_minimum_required(VERSION 2.8)
 
-if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang")
-  if(ENABLE_COVERAGE)
-    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g ")
-    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0")
-    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-arcs")
-    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ftest-coverage")
-    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage")
-  endif()
-endif ()
-
 # test
-include_directories(../src)
-add_executable(test.exe test.c ../src/zip.c)
-add_executable(test_miniz.exe test_miniz.c)
+set(test_out test.out)
+set(test_miniz_out test_miniz.out)
+
+add_executable(${test_out} test.c)
+target_link_libraries(${test_out} zip)
+add_executable(${test_miniz_out} test_miniz.c)
+target_link_libraries(${test_miniz_out} zip)
+
+add_test(NAME ${test_out} COMMAND ${test_out})
+add_test(NAME ${test_miniz_out} COMMAND ${test_miniz_out})
 
-add_test(NAME test COMMAND test.exe)
-add_test(NAME test_miniz COMMAND test_miniz.exe)
+set(test_out ${test_out} PARENT_SCOPE)
+set(test_miniz_out ${test_miniz_out} PARENT_SCOPE)

+ 36 - 2
contrib/zip/test/test.c

@@ -29,6 +29,8 @@
 #define XFILE "7.txt\0"
 #define XMODE 0100777
 
+#define UNIXMODE 0100644
+
 #define UNUSED(x) (void)x
 
 static int total_entries = 0;
@@ -102,7 +104,8 @@ static void test_read(void) {
   assert(0 == zip_entry_close(zip));
   free(buf);
   buf = NULL;
-  
+  bufsize = 0;
+
   assert(0 == zip_entry_open(zip, "test/test-2.txt"));
   assert(strlen(TESTDATA2) == zip_entry_size(zip));
   assert(CRC32DATA2 == zip_entry_crc32(zip));
@@ -131,7 +134,8 @@ static void test_read(void) {
   assert(0 == zip_entry_close(zip));
   free(buf);
   buf = NULL;
-  
+  bufsize = 0;
+
   buftmp = strlen(TESTDATA1);
   buf = calloc(buftmp, sizeof(char));
   assert(0 == zip_entry_open(zip, "test/test-1.txt"));
@@ -433,6 +437,35 @@ static void test_mtime(void) {
   remove(ZIPNAME);
 }
 
+static void test_unix_permissions(void) {
+#if defined(_WIN64) || defined(_WIN32) || defined(__WIN32__)
+#else
+  // UNIX or APPLE
+  struct MZ_FILE_STAT_STRUCT file_stats;
+
+  remove(ZIPNAME);
+
+  struct zip_t *zip = zip_open(ZIPNAME, ZIP_DEFAULT_COMPRESSION_LEVEL, 'w');
+  assert(zip != NULL);
+
+  assert(0 == zip_entry_open(zip, RFILE));
+  assert(0 == zip_entry_write(zip, TESTDATA1, strlen(TESTDATA1)));
+  assert(0 == zip_entry_close(zip));
+
+  zip_close(zip);
+
+  remove(RFILE);
+
+  assert(0 == zip_extract(ZIPNAME, ".", NULL, NULL));
+
+  assert(0 == MZ_FILE_STAT(RFILE, &file_stats));
+  assert(UNIXMODE == file_stats.st_mode);
+
+  remove(RFILE);
+  remove(ZIPNAME);
+#endif
+}
+
 int main(int argc, char *argv[]) {
   UNUSED(argc);
   UNUSED(argv);
@@ -453,6 +486,7 @@ int main(int argc, char *argv[]) {
   test_write_permissions();
   test_exe_permissions();
   test_mtime();
+  test_unix_permissions();
 
   remove(ZIPNAME);
   return 0;

+ 24 - 1
contrib/zip/test/test_miniz.c

@@ -23,16 +23,39 @@ int main(int argc, char *argv[]) {
   uint step = 0;
   int cmp_status;
   uLong src_len = (uLong)strlen(s_pStr);
-  uLong cmp_len = compressBound(src_len);
   uLong uncomp_len = src_len;
+  uLong cmp_len;
   uint8 *pCmp, *pUncomp;
+  size_t sz;
   uint total_succeeded = 0;
   (void)argc, (void)argv;
 
   printf("miniz.c version: %s\n", MZ_VERSION);
 
   do {
+    pCmp = (uint8 *)tdefl_compress_mem_to_heap(s_pStr, src_len, &cmp_len, 0);
+    if (!pCmp) {
+      printf("tdefl_compress_mem_to_heap failed\n");
+      return EXIT_FAILURE;
+    }
+    if (src_len <= cmp_len) {
+      printf("tdefl_compress_mem_to_heap failed: from %u to %u bytes\n",
+             (mz_uint32)uncomp_len, (mz_uint32)cmp_len);
+      free(pCmp);
+      return EXIT_FAILURE;
+    }
+
+    sz = tdefl_compress_mem_to_mem(pCmp, cmp_len, s_pStr, src_len, 0);
+    if (sz != cmp_len) {
+      printf("tdefl_compress_mem_to_mem failed: expected %u, got %u\n",
+             (mz_uint32)cmp_len, (mz_uint32)sz);
+      free(pCmp);
+      return EXIT_FAILURE;
+    }
+
     // Allocate buffers to hold compressed and uncompressed data.
+    free(pCmp);
+    cmp_len = compressBound(src_len);
     pCmp = (mz_uint8 *)malloc((size_t)cmp_len);
     pUncomp = (mz_uint8 *)malloc((size_t)src_len);
     if ((!pCmp) || (!pUncomp)) {

+ 7 - 0
include/assimp/version.h

@@ -62,6 +62,13 @@ extern "C" {
  */
 ASSIMP_API const char*  aiGetLegalString  (void);
 
+// ---------------------------------------------------------------------------
+/** @brief Returns the current patch version number of Assimp.
+ *  @return Patch version of the Assimp runtime the application was
+ *    linked/built against
+ */
+ASSIMP_API unsigned int aiGetVersionPatch(void);
+
 // ---------------------------------------------------------------------------
 /** @brief Returns the current minor version number of Assimp.
  *  @return Minor version of the Assimp runtime the application was

+ 46 - 0
samples/SimpleTexturedDirectx11/CMakeLists.txt

@@ -0,0 +1,46 @@
+FIND_PACKAGE(DirectX)
+
+IF ( MSVC )
+  SET(M_LIB)
+ENDIF ( MSVC )
+
+if ( MSVC )
+  ADD_DEFINITIONS( -D_SCL_SECURE_NO_WARNINGS )
+  ADD_DEFINITIONS( -D_CRT_SECURE_NO_WARNINGS )
+  REMOVE_DEFINITIONS( -DUNICODE -D_UNICODE )
+endif ( MSVC )
+
+INCLUDE_DIRECTORIES(
+  ${Assimp_SOURCE_DIR}/include
+  ${Assimp_SOURCE_DIR}/code
+  ${OPENGL_INCLUDE_DIR}
+  ${GLUT_INCLUDE_DIR}
+  ${Assimp_SOURCE_DIR}/samples/freeglut/include
+)
+
+LINK_DIRECTORIES(
+  ${Assimp_BINARY_DIR}
+  ${Assimp_BINARY_DIR}/lib
+)
+
+ADD_EXECUTABLE( assimp_simpletextureddirectx11 WIN32
+  SimpleTexturedDirectx11/Mesh.h 
+  SimpleTexturedDirectx11/ModelLoader.cpp
+  SimpleTexturedDirectx11/ModelLoader.h
+  #SimpleTexturedDirectx11/PixelShader.hlsl
+  SimpleTexturedDirectx11/TextureLoader.cpp
+  SimpleTexturedDirectx11/TextureLoader.h 
+  #SimpleTexturedDirectx11/VertexShader.hlsl  
+  SimpleTexturedDirectx11/main.cpp
+)
+
+SET_PROPERTY(TARGET assimp_simpletextureddirectx11 PROPERTY DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX})
+
+TARGET_LINK_LIBRARIES( assimp_simpletextureddirectx11 assimp ${DirectX_LIBRARY} comctl32.lib winmm.lib )
+SET_TARGET_PROPERTIES( assimp_simpletextureddirectx11 PROPERTIES
+  OUTPUT_NAME assimp_simpletextureddirectx11
+)
+
+INSTALL( TARGETS assimp_simpletextureddirectx11
+  DESTINATION "${ASSIMP_BIN_INSTALL_DIR}" COMPONENT assimp-dev
+)

+ 0 - 28
samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11.sln

@@ -1,28 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.26228.9
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SimpleTexturedDirectx11", "SimpleTexturedDirectx11\SimpleTexturedDirectx11.vcxproj", "{E3B160B5-E71F-4F3F-9310-B8F156F736D8}"
-EndProject
-Global
-	GlobalSection(SolutionConfigurationPlatforms) = preSolution
-		Debug|x64 = Debug|x64
-		Debug|x86 = Debug|x86
-		Release|x64 = Release|x64
-		Release|x86 = Release|x86
-	EndGlobalSection
-	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{E3B160B5-E71F-4F3F-9310-B8F156F736D8}.Debug|x64.ActiveCfg = Debug|x64
-		{E3B160B5-E71F-4F3F-9310-B8F156F736D8}.Debug|x64.Build.0 = Debug|x64
-		{E3B160B5-E71F-4F3F-9310-B8F156F736D8}.Debug|x86.ActiveCfg = Debug|Win32
-		{E3B160B5-E71F-4F3F-9310-B8F156F736D8}.Debug|x86.Build.0 = Debug|Win32
-		{E3B160B5-E71F-4F3F-9310-B8F156F736D8}.Release|x64.ActiveCfg = Release|x64
-		{E3B160B5-E71F-4F3F-9310-B8F156F736D8}.Release|x64.Build.0 = Release|x64
-		{E3B160B5-E71F-4F3F-9310-B8F156F736D8}.Release|x86.ActiveCfg = Release|Win32
-		{E3B160B5-E71F-4F3F-9310-B8F156F736D8}.Release|x86.Build.0 = Release|Win32
-	EndGlobalSection
-	GlobalSection(SolutionProperties) = preSolution
-		HideSolutionNode = FALSE
-	EndGlobalSection
-EndGlobal

+ 2 - 0
samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/ModelLoader.cpp

@@ -180,6 +180,8 @@ string ModelLoader::determineTextureType(const aiScene * scene, aiMaterial * mat
 	{
 		return "textures are on disk";
 	}
+
+    return ".";
 }
 
 int ModelLoader::getTextureIndex(aiString * str)

+ 0 - 146
samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/SimpleTexturedDirectx11.vcxproj

@@ -1,146 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <ItemGroup Label="ProjectConfigurations">
-    <ProjectConfiguration Include="Debug|Win32">
-      <Configuration>Debug</Configuration>
-      <Platform>Win32</Platform>
-    </ProjectConfiguration>
-    <ProjectConfiguration Include="Release|Win32">
-      <Configuration>Release</Configuration>
-      <Platform>Win32</Platform>
-    </ProjectConfiguration>
-    <ProjectConfiguration Include="Debug|x64">
-      <Configuration>Debug</Configuration>
-      <Platform>x64</Platform>
-    </ProjectConfiguration>
-    <ProjectConfiguration Include="Release|x64">
-      <Configuration>Release</Configuration>
-      <Platform>x64</Platform>
-    </ProjectConfiguration>
-  </ItemGroup>
-  <PropertyGroup Label="Globals">
-    <VCProjectVersion>15.0</VCProjectVersion>
-    <ProjectGuid>{E3B160B5-E71F-4F3F-9310-B8F156F736D8}</ProjectGuid>
-    <RootNamespace>SimpleTexturedDirectx11</RootNamespace>
-    <WindowsTargetPlatformVersion>10.0.14393.0</WindowsTargetPlatformVersion>
-  </PropertyGroup>
-  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
-    <ConfigurationType>Application</ConfigurationType>
-    <UseDebugLibraries>true</UseDebugLibraries>
-    <PlatformToolset>v141</PlatformToolset>
-    <CharacterSet>MultiByte</CharacterSet>
-  </PropertyGroup>
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
-    <ConfigurationType>Application</ConfigurationType>
-    <UseDebugLibraries>false</UseDebugLibraries>
-    <PlatformToolset>v141</PlatformToolset>
-    <WholeProgramOptimization>true</WholeProgramOptimization>
-    <CharacterSet>MultiByte</CharacterSet>
-  </PropertyGroup>
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
-    <ConfigurationType>Application</ConfigurationType>
-    <UseDebugLibraries>true</UseDebugLibraries>
-    <PlatformToolset>v141</PlatformToolset>
-    <CharacterSet>MultiByte</CharacterSet>
-  </PropertyGroup>
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
-    <ConfigurationType>Application</ConfigurationType>
-    <UseDebugLibraries>false</UseDebugLibraries>
-    <PlatformToolset>v141</PlatformToolset>
-    <WholeProgramOptimization>true</WholeProgramOptimization>
-    <CharacterSet>MultiByte</CharacterSet>
-  </PropertyGroup>
-  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
-  <ImportGroup Label="ExtensionSettings">
-  </ImportGroup>
-  <ImportGroup Label="Shared">
-  </ImportGroup>
-  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
-    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
-  </ImportGroup>
-  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
-    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
-  </ImportGroup>
-  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
-    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
-  </ImportGroup>
-  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
-    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
-  </ImportGroup>
-  <PropertyGroup Label="UserMacros" />
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
-    <IncludePath>$(IncludePath);E:\OpenGL VS Files\include</IncludePath>
-    <LibraryPath>$(LibraryPath);E:\OpenGL VS Files\lib</LibraryPath>
-  </PropertyGroup>
-  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
-    <ClCompile>
-      <WarningLevel>Level3</WarningLevel>
-      <Optimization>Disabled</Optimization>
-      <SDLCheck>true</SDLCheck>
-    </ClCompile>
-    <Link>
-      <AdditionalDependencies>assimp-vc140-mt.lib;%(AdditionalDependencies)</AdditionalDependencies>
-    </Link>
-  </ItemDefinitionGroup>
-  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
-    <ClCompile>
-      <WarningLevel>Level3</WarningLevel>
-      <Optimization>Disabled</Optimization>
-      <SDLCheck>true</SDLCheck>
-    </ClCompile>
-  </ItemDefinitionGroup>
-  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
-    <ClCompile>
-      <WarningLevel>Level3</WarningLevel>
-      <Optimization>MaxSpeed</Optimization>
-      <FunctionLevelLinking>true</FunctionLevelLinking>
-      <IntrinsicFunctions>true</IntrinsicFunctions>
-      <SDLCheck>true</SDLCheck>
-    </ClCompile>
-    <Link>
-      <EnableCOMDATFolding>true</EnableCOMDATFolding>
-      <OptimizeReferences>true</OptimizeReferences>
-    </Link>
-  </ItemDefinitionGroup>
-  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
-    <ClCompile>
-      <WarningLevel>Level3</WarningLevel>
-      <Optimization>MaxSpeed</Optimization>
-      <FunctionLevelLinking>true</FunctionLevelLinking>
-      <IntrinsicFunctions>true</IntrinsicFunctions>
-      <SDLCheck>true</SDLCheck>
-    </ClCompile>
-    <Link>
-      <EnableCOMDATFolding>true</EnableCOMDATFolding>
-      <OptimizeReferences>true</OptimizeReferences>
-    </Link>
-  </ItemDefinitionGroup>
-  <ItemGroup>
-    <ClCompile Include="main.cpp" />
-    <ClCompile Include="ModelLoader.cpp" />
-    <ClCompile Include="TextureLoader.cpp" />
-  </ItemGroup>
-  <ItemGroup>
-    <FxCompile Include="PixelShader.hlsl">
-      <ShaderType Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Pixel</ShaderType>
-      <ShaderType Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Pixel</ShaderType>
-      <ShaderType Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Pixel</ShaderType>
-      <ShaderType Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Pixel</ShaderType>
-    </FxCompile>
-    <FxCompile Include="VertexShader.hlsl">
-      <ShaderType Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Vertex</ShaderType>
-      <ShaderType Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Vertex</ShaderType>
-      <ShaderType Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Vertex</ShaderType>
-      <ShaderType Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Vertex</ShaderType>
-    </FxCompile>
-  </ItemGroup>
-  <ItemGroup>
-    <ClInclude Include="Mesh.h" />
-    <ClInclude Include="ModelLoader.h" />
-    <ClInclude Include="TextureLoader.h" />
-  </ItemGroup>
-  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
-  <ImportGroup Label="ExtensionTargets">
-  </ImportGroup>
-</Project>

+ 0 - 50
samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/SimpleTexturedDirectx11.vcxproj.filters

@@ -1,50 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <ItemGroup>
-    <Filter Include="Source Files">
-      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
-      <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
-    </Filter>
-    <Filter Include="Header Files">
-      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
-      <Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
-    </Filter>
-    <Filter Include="Resource Files">
-      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
-      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
-    </Filter>
-    <Filter Include="Shaders">
-      <UniqueIdentifier>{b6a86d3e-70a5-4d1e-ba05-c20902300206}</UniqueIdentifier>
-    </Filter>
-  </ItemGroup>
-  <ItemGroup>
-    <ClCompile Include="main.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
-    <ClCompile Include="ModelLoader.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
-    <ClCompile Include="TextureLoader.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
-  </ItemGroup>
-  <ItemGroup>
-    <FxCompile Include="VertexShader.hlsl">
-      <Filter>Shaders</Filter>
-    </FxCompile>
-    <FxCompile Include="PixelShader.hlsl">
-      <Filter>Shaders</Filter>
-    </FxCompile>
-  </ItemGroup>
-  <ItemGroup>
-    <ClInclude Include="ModelLoader.h">
-      <Filter>Header Files</Filter>
-    </ClInclude>
-    <ClInclude Include="Mesh.h">
-      <Filter>Header Files</Filter>
-    </ClInclude>
-    <ClInclude Include="TextureLoader.h">
-      <Filter>Header Files</Filter>
-    </ClInclude>
-  </ItemGroup>
-</Project>

BIN
test/models/glTF2/textureTransform/Arrow.png


BIN
test/models/glTF2/textureTransform/Correct.png


BIN
test/models/glTF2/textureTransform/Error.png


+ 0 - 0
test/models/glTF2/textureTransform/License.txt


BIN
test/models/glTF2/textureTransform/NotSupported.png


BIN
test/models/glTF2/textureTransform/TextureTransformTest.bin


+ 540 - 0
test/models/glTF2/textureTransform/TextureTransformTest.gltf

@@ -0,0 +1,540 @@
+{
+  "accessors": [
+    {
+      "bufferView": 0,
+      "componentType": 5126,
+      "count": 4,
+      "type": "VEC3",
+      "max": [
+        0.5,
+        0.5,
+        0.0
+      ],
+      "min": [
+        -0.5,
+        -0.5,
+        0.0
+      ],
+      "name": "Positions"
+    },
+    {
+      "bufferView": 1,
+      "componentType": 5126,
+      "count": 4,
+      "type": "VEC2",
+      "name": "UV0"
+    },
+    {
+      "bufferView": 2,
+      "componentType": 5126,
+      "count": 4,
+      "type": "VEC2",
+      "name": "UV1"
+    },
+    {
+      "bufferView": 3,
+      "componentType": 5125,
+      "count": 6,
+      "type": "SCALAR",
+      "name": "Indices"
+    }
+  ],
+  "asset": {
+    "version": "2.0"
+  },
+  "buffers": [
+    {
+      "uri": "TextureTransformTest.bin",
+      "byteLength": 136
+    }
+  ],
+  "bufferViews": [
+    {
+      "buffer": 0,
+      "byteLength": 48,
+      "name": "Positions"
+    },
+    {
+      "buffer": 0,
+      "byteOffset": 48,
+      "byteLength": 32,
+      "name": "UV0"
+    },
+    {
+      "buffer": 0,
+      "byteOffset": 80,
+      "byteLength": 32,
+      "name": "UV1"
+    },
+    {
+      "buffer": 0,
+      "byteOffset": 112,
+      "byteLength": 24,
+      "name": "Indices"
+    }
+  ],
+  "extensionsUsed": [
+    "KHR_texture_transform"
+  ],
+  "images": [
+    {
+      "uri": "UV.png"
+    },
+    {
+      "uri": "Arrow.png"
+    },
+    {
+      "uri": "Correct.png"
+    },
+    {
+      "uri": "NotSupported.png"
+    },
+    {
+      "uri": "Error.png"
+    }
+  ],
+  "materials": [
+    {
+      "name": "Offset U",
+      "pbrMetallicRoughness": {
+        "baseColorTexture": {
+          "index": 0,
+          "extensions": {
+            "KHR_texture_transform": {
+              "offset": [
+                0.5,
+                0.0
+              ]
+            }
+          }
+        },
+        "metallicFactor": 0
+      }
+    },
+    {
+      "name": "Offset V",
+      "pbrMetallicRoughness": {
+        "baseColorTexture": {
+          "index": 0,
+          "extensions": {
+            "KHR_texture_transform": {
+              "offset": [
+                0.0,
+                0.5
+              ]
+            }
+          }
+        },
+        "metallicFactor": 0
+      }
+    },
+    {
+      "name": "Offset UV",
+      "pbrMetallicRoughness": {
+        "baseColorTexture": {
+          "index": 0,
+          "extensions": {
+            "KHR_texture_transform": {
+              "offset": [
+                0.5,
+                0.5
+              ]
+            }
+          }
+        },
+        "metallicFactor": 0
+      }
+    },
+    {
+      "name": "Rotation",
+      "pbrMetallicRoughness": {
+        "baseColorTexture": {
+          "index": 1,
+          "extensions": {
+            "KHR_texture_transform": {
+              "rotation": 0.39269908169872415480783042290994
+            }
+          }
+        },
+        "metallicFactor": 0
+      }
+    },
+    {
+      "name": "Scale",
+      "pbrMetallicRoughness": {
+        "baseColorTexture": {
+          "index": 1,
+          "extensions": {
+            "KHR_texture_transform": {
+              "scale": [
+                1.5,
+                1.5
+              ]
+            }
+          }
+        },
+        "metallicFactor": 0
+      }
+    },
+    {
+      "name": "All",
+      "pbrMetallicRoughness": {
+        "baseColorTexture": {
+          "index": 1,
+          "extensions": {
+            "KHR_texture_transform": {
+              "offset": [
+                -0.2,
+                -0.1
+              ],
+              "rotation": 0.3,
+              "scale": [
+                1.5,
+                1.5
+              ]
+            }
+          }
+        },
+        "metallicFactor": 0
+      }
+    },
+    {
+      "name": "Correct",
+      "pbrMetallicRoughness": {
+        "baseColorTexture": {
+          "index": 2
+        },
+        "metallicFactor": 0
+      }
+    },
+    {
+      "name": "NotSupported",
+      "pbrMetallicRoughness": {
+        "baseColorTexture": {
+          "index": 3
+        },
+        "metallicFactor": 0
+      }
+    },
+    {
+      "name": "Error",
+      "pbrMetallicRoughness": {
+        "baseColorTexture": {
+          "index": 4
+        },
+        "metallicFactor": 0
+      }
+    }
+  ],
+  "meshes": [
+    {
+      "name": "Offset U",
+      "primitives": [
+        {
+          "attributes": {
+            "POSITION": 0,
+            "TEXCOORD_0": 2
+          },
+          "indices": 3,
+          "material": 0
+        }
+      ]
+    },
+    {
+      "name": "Offset V",
+      "primitives": [
+        {
+          "attributes": {
+            "POSITION": 0,
+            "TEXCOORD_0": 2
+          },
+          "indices": 3,
+          "material": 1
+        }
+      ]
+    },
+    {
+      "name": "Offset UV",
+      "primitives": [
+        {
+          "attributes": {
+            "POSITION": 0,
+            "TEXCOORD_0": 2
+          },
+          "indices": 3,
+          "material": 2
+        }
+      ]
+    },
+    {
+      "name": "Rotation",
+      "primitives": [
+        {
+          "attributes": {
+            "POSITION": 0,
+            "TEXCOORD_0": 1
+          },
+          "indices": 3,
+          "material": 3
+        }
+      ]
+    },
+    {
+      "name": "Scale",
+      "primitives": [
+        {
+          "attributes": {
+            "POSITION": 0,
+            "TEXCOORD_0": 1
+          },
+          "indices": 3,
+          "material": 4
+        }
+      ]
+    },
+    {
+      "name": "All",
+      "primitives": [
+        {
+          "attributes": {
+            "POSITION": 0,
+            "TEXCOORD_0": 1
+          },
+          "indices": 3,
+          "material": 5
+        }
+      ]
+    },
+    {
+      "name": "Correct Marker",
+      "primitives": [
+        {
+          "attributes": {
+            "POSITION": 0,
+            "TEXCOORD_0": 1
+          },
+          "indices": 3,
+          "material": 6
+        }
+      ]
+    },
+    {
+      "name": "Not Supported Marker",
+      "primitives": [
+        {
+          "attributes": {
+            "POSITION": 0,
+            "TEXCOORD_0": 1
+          },
+          "indices": 3,
+          "material": 7
+        }
+      ]
+    },
+    {
+      "name": "Error Marker",
+      "primitives": [
+        {
+          "attributes": {
+            "POSITION": 0,
+            "TEXCOORD_0": 1
+          },
+          "indices": 3,
+          "material": 8
+        }
+      ]
+    }
+  ],
+  "nodes": [
+    {
+      "name": "Offset U",
+      "mesh": 0,
+      "translation": [
+        -1.1,
+        0.55,
+        0
+      ]
+    },
+    {
+      "name": "Offset V",
+      "mesh": 1,
+      "translation": [
+        0,
+        0.55,
+        0
+      ]
+    },
+    {
+      "name": "Offset UV",
+      "mesh": 2,
+      "translation": [
+        1.1,
+        0.55,
+        0
+      ]
+    },
+    {
+      "name": "Rotation",
+      "mesh": 3,
+      "translation": [
+        -1.1,
+        -0.55,
+        0
+      ],
+      "children": [
+        4,
+        5,
+        6
+      ]
+    },
+    {
+      "name": "Rotation - Correct",
+      "mesh": 6,
+      "translation": [
+        -0.07904822439840125109869401756656,
+        -0.51626748576241543174100150833647,
+        0.01
+      ],
+      "scale": [
+        0.15,
+        0.15,
+        0.15
+      ]
+    },
+    {
+      "name": "Rotation - Not Supported",
+      "mesh": 7,
+      "translation": [
+        0.27781745930520227684092879831533,
+        -0.27781745930520227684092879831533,
+        0.01
+      ],
+      "scale": [
+        0.15,
+        0.15,
+        0.15
+      ]
+    },
+    {
+      "name": "Rotation - Error",
+      "mesh": 8,
+      "translation": [
+        0.51626748576241543174100150833647,
+        0.07904822439840125109869401756656,
+        0.01
+      ],
+      "scale": [
+        0.15,
+        0.15,
+        0.15
+      ]
+    },
+    {
+      "name": "Scale",
+      "mesh": 4,
+      "translation": [
+        0,
+        -0.55,
+        0
+      ],
+      "children": [
+        8,
+        9
+      ]
+    },
+    {
+      "name": "Scale - Correct",
+      "mesh": 6,
+      "translation": [
+        0.01854497287013485122728586554355,
+        -0.01854497287013485122728586554355,
+        0.01
+      ],
+      "scale": [
+        0.1,
+        0.1,
+        0.1
+      ]
+    },
+    {
+      "name": "Scale - Not Supported",
+      "mesh": 7,
+      "translation": [
+        0.27781745930520227684092879831533,
+        -0.27781745930520227684092879831533,
+        0.01
+      ],
+      "scale": [
+        0.15,
+        0.15,
+        0.15
+      ]
+    },
+    {
+      "name": "All",
+      "mesh": 5,
+      "translation": [
+        1.1,
+        -0.55,
+        0
+      ],
+      "children": [
+        11
+      ]
+    },
+    {
+      "name": "All - Correct",
+      "mesh": 6,
+      "translation": [
+        -0.07,
+        -0.25,
+        0.01
+      ],
+      "scale": [
+        0.1,
+        0.1,
+        0.1
+      ]
+    }
+  ],
+  "scene": 0,
+  "scenes": [
+    {
+      "nodes": [
+        0,
+        1,
+        2,
+        3,
+        7,
+        10
+      ]
+    }
+  ],
+  "textures": [
+    {
+      "source": 0,
+      "sampler": 0
+    },
+    {
+      "source": 1,
+      "sampler": 0
+    },
+    {
+      "source": 2
+    },
+    {
+      "source": 3
+    },
+    {
+      "source": 4
+    }
+  ],
+  "samplers": [
+    {
+      "wrapS": 33071,
+      "wrapT": 33071,
+      "magFilter": 9729,
+      "minFilter": 9729
+    }
+  ]
+}

BIN
test/models/glTF2/textureTransform/UV.png


+ 1 - 1
test/unit/utM3DImportExport.cpp

@@ -54,7 +54,7 @@ class utM3DImportExport : public AbstractImportExportBase {
 public:
     virtual bool importerTest() {
         Assimp::Importer importer;
-        const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/M3D/WusonBlitz0.m3d", aiProcess_ValidateDataStructure );
+        const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/M3D/cube_normals.m3d", aiProcess_ValidateDataStructure );
 #ifndef ASSIMP_BUILD_NO_M3D_IMPORTER
         return nullptr != scene;
 #else

+ 7 - 0
test/unit/utglTF2ImportExport.cpp

@@ -405,6 +405,13 @@ TEST_F(utglTF2ImportExport, incorrect_vertex_arrays) {
     EXPECT_EQ(scene->mMeshes[7]->mNumFaces, 17u);
 }
 
+TEST_F( utglTF2ImportExport, texture_transform_test ) {
+	Assimp::Importer importer;
+	const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/textureTransform/TextureTransformTest.gltf",
+			aiProcess_ValidateDataStructure);
+	EXPECT_NE(nullptr, scene);
+}
+
 #ifndef ASSIMP_BUILD_NO_EXPORT
 TEST_F( utglTF2ImportExport, exportglTF2FromFileTest ) {
     EXPECT_TRUE( exporterTest() );

Some files were not shown because too many files changed in this diff