浏览代码

need to unhide OBJ for this one

AzaezelX 3 年之前
父节点
当前提交
7716663346

+ 1 - 1
.gitignore

@@ -108,7 +108,7 @@ publish
 
 # Others
 [Bb]in
-[Oo]bj
+# [Oo]bj
 sql
 TestResults
 *.Cache

+ 414 - 0
Engine/lib/assimp/code/AssetLib/Obj/ObjExporter.cpp

@@ -0,0 +1,414 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2020, assimp team
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+#ifndef ASSIMP_BUILD_NO_EXPORT
+#ifndef ASSIMP_BUILD_NO_OBJ_EXPORTER
+
+#include "ObjExporter.h"
+#include <assimp/Exceptional.h>
+#include <assimp/StringComparison.h>
+#include <assimp/version.h>
+#include <assimp/IOSystem.hpp>
+#include <assimp/Exporter.hpp>
+#include <assimp/material.h>
+#include <assimp/scene.h>
+#include <memory>
+
+using namespace Assimp;
+
+namespace Assimp {
+
+// ------------------------------------------------------------------------------------------------
+// Worker function for exporting a scene to Wavefront OBJ. Prototyped and registered in Exporter.cpp
+void ExportSceneObj(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/) {
+    // invoke the exporter
+    ObjExporter exporter(pFile, pScene);
+
+    if (exporter.mOutput.fail() || exporter.mOutputMat.fail()) {
+        throw DeadlyExportError("output data creation failed. Most likely the file became too large: " + std::string(pFile));
+    }
+
+    // we're still here - export successfully completed. Write both the main OBJ file and the material script
+    {
+        std::unique_ptr<IOStream> outfile (pIOSystem->Open(pFile,"wt"));
+        if (outfile == nullptr) {
+            throw DeadlyExportError("could not open output .obj file: " + std::string(pFile));
+        }
+        outfile->Write( exporter.mOutput.str().c_str(), static_cast<size_t>(exporter.mOutput.tellp()),1);
+    }
+    {
+        std::unique_ptr<IOStream> outfile (pIOSystem->Open(exporter.GetMaterialLibFileName(),"wt"));
+        if (outfile == nullptr) {
+            throw DeadlyExportError("could not open output .mtl file: " + std::string(exporter.GetMaterialLibFileName()));
+        }
+        outfile->Write( exporter.mOutputMat.str().c_str(), static_cast<size_t>(exporter.mOutputMat.tellp()),1);
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Worker function for exporting a scene to Wavefront OBJ without the material file. Prototyped and registered in Exporter.cpp
+void ExportSceneObjNoMtl(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* ) {
+    // invoke the exporter
+    ObjExporter exporter(pFile, pScene, true);
+
+    if (exporter.mOutput.fail() || exporter.mOutputMat.fail()) {
+        throw DeadlyExportError("output data creation failed. Most likely the file became too large: " + std::string(pFile));
+    }
+
+    // we're still here - export successfully completed. Write both the main OBJ file and the material script
+    {
+        std::unique_ptr<IOStream> outfile (pIOSystem->Open(pFile,"wt"));
+        if (outfile == nullptr) {
+            throw DeadlyExportError("could not open output .obj file: " + std::string(pFile));
+        }
+        outfile->Write( exporter.mOutput.str().c_str(), static_cast<size_t>(exporter.mOutput.tellp()),1);
+    }
+
+
+}
+
+} // end of namespace Assimp
+
+static const std::string MaterialExt = ".mtl";
+
+// ------------------------------------------------------------------------------------------------
+ObjExporter::ObjExporter(const char* _filename, const aiScene* pScene, bool noMtl)
+: filename(_filename)
+, pScene(pScene)
+, vn()
+, vt()
+, vp()
+, useVc(false)
+, mVnMap()
+, mVtMap()
+, mVpMap()
+, mMeshes()
+, endl("\n") {
+    // make sure that all formatting happens using the standard, C locale and not the user's current locale
+    const std::locale& l = std::locale("C");
+    mOutput.imbue(l);
+    mOutput.precision(ASSIMP_AI_REAL_TEXT_PRECISION);
+    mOutputMat.imbue(l);
+    mOutputMat.precision(ASSIMP_AI_REAL_TEXT_PRECISION);
+
+    WriteGeometryFile(noMtl);
+    if ( !noMtl ) {
+        WriteMaterialFile();
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+ObjExporter::~ObjExporter() {
+    // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+std::string ObjExporter::GetMaterialLibName() {
+    // within the Obj file, we use just the relative file name with the path stripped
+    const std::string& s = GetMaterialLibFileName();
+    std::string::size_type il = s.find_last_of("/\\");
+    if (il != std::string::npos) {
+        return s.substr(il + 1);
+    }
+
+    return s;
+}
+
+// ------------------------------------------------------------------------------------------------
+std::string ObjExporter::GetMaterialLibFileName() {
+    // Remove existing .obj file extension so that the final material file name will be fileName.mtl and not fileName.obj.mtl
+    size_t lastdot = filename.find_last_of('.');
+    if ( lastdot != std::string::npos ) {
+        return filename.substr( 0, lastdot ) + MaterialExt;
+    }
+
+    return filename + MaterialExt;
+}
+
+// ------------------------------------------------------------------------------------------------
+void ObjExporter::WriteHeader(std::ostringstream& out) {
+    out << "# File produced by Open Asset Import Library (http://www.assimp.sf.net)" << endl;
+    out << "# (assimp v" << aiGetVersionMajor() << '.' << aiGetVersionMinor() << '.'
+        << aiGetVersionRevision() << ")" << endl  << endl;
+}
+
+// ------------------------------------------------------------------------------------------------
+std::string ObjExporter::GetMaterialName(unsigned int index) {
+    const aiMaterial* const mat = pScene->mMaterials[index];
+    if ( nullptr == mat ) {
+        static const std::string EmptyStr;
+        return EmptyStr;
+    }
+
+    aiString s;
+    if(AI_SUCCESS == mat->Get(AI_MATKEY_NAME,s)) {
+        return std::string(s.data,s.length);
+    }
+
+    char number[ sizeof(unsigned int) * 3 + 1 ];
+    ASSIMP_itoa10(number,index);
+    return "$Material_" + std::string(number);
+}
+
+// ------------------------------------------------------------------------------------------------
+void ObjExporter::WriteMaterialFile() {
+    WriteHeader(mOutputMat);
+
+    for(unsigned int i = 0; i < pScene->mNumMaterials; ++i) {
+        const aiMaterial* const mat = pScene->mMaterials[i];
+
+        int illum = 1;
+        mOutputMat << "newmtl " << GetMaterialName(i)  << endl;
+
+        aiColor4D c;
+        if(AI_SUCCESS == mat->Get(AI_MATKEY_COLOR_DIFFUSE,c)) {
+            mOutputMat << "Kd " << c.r << " " << c.g << " " << c.b << endl;
+        }
+        if(AI_SUCCESS == mat->Get(AI_MATKEY_COLOR_AMBIENT,c)) {
+            mOutputMat << "Ka " << c.r << " " << c.g << " " << c.b << endl;
+        }
+        if(AI_SUCCESS == mat->Get(AI_MATKEY_COLOR_SPECULAR,c)) {
+            mOutputMat << "Ks " << c.r << " " << c.g << " " << c.b << endl;
+        }
+        if(AI_SUCCESS == mat->Get(AI_MATKEY_COLOR_EMISSIVE,c)) {
+            mOutputMat << "Ke " << c.r << " " << c.g << " " << c.b << endl;
+        }
+        if(AI_SUCCESS == mat->Get(AI_MATKEY_COLOR_TRANSPARENT,c)) {
+            mOutputMat << "Tf " << c.r << " " << c.g << " " << c.b << endl;
+        }
+
+        ai_real o;
+        if(AI_SUCCESS == mat->Get(AI_MATKEY_OPACITY,o)) {
+            mOutputMat << "d " << o << endl;
+        }
+        if(AI_SUCCESS == mat->Get(AI_MATKEY_REFRACTI,o)) {
+            mOutputMat << "Ni " << o << endl;
+        }
+
+        if(AI_SUCCESS == mat->Get(AI_MATKEY_SHININESS,o) && o) {
+            mOutputMat << "Ns " << o << endl;
+            illum = 2;
+        }
+
+        mOutputMat << "illum " << illum << endl;
+
+        aiString s;
+        if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_DIFFUSE(0),s)) {
+            mOutputMat << "map_Kd " << s.data << endl;
+        }
+        if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_AMBIENT(0),s)) {
+            mOutputMat << "map_Ka " << s.data << endl;
+        }
+        if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_SPECULAR(0),s)) {
+            mOutputMat << "map_Ks " << s.data << endl;
+        }
+        if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_SHININESS(0),s)) {
+            mOutputMat << "map_Ns " << s.data << endl;
+        }
+        if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_OPACITY(0),s)) {
+            mOutputMat << "map_d " << s.data << endl;
+        }
+        if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_HEIGHT(0),s) || AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_NORMALS(0),s)) {
+            // implementations seem to vary here, so write both variants
+            mOutputMat << "bump " << s.data << endl;
+            mOutputMat << "map_bump " << s.data << endl;
+        }
+
+        mOutputMat << endl;
+    }
+}
+
+void ObjExporter::WriteGeometryFile(bool noMtl) {
+    WriteHeader(mOutput);
+    if (!noMtl)
+        mOutput << "mtllib "  << GetMaterialLibName() << endl << endl;
+
+    // collect mesh geometry
+    aiMatrix4x4 mBase;
+    AddNode(pScene->mRootNode, mBase);
+
+    // write vertex positions with colors, if any
+    mVpMap.getKeys( vp );
+    if ( !useVc ) {
+        mOutput << "# " << vp.size() << " vertex positions" << endl;
+        for ( const vertexData& v : vp ) {
+            mOutput << "v  " << v.vp.x << " " << v.vp.y << " " << v.vp.z << endl;
+        }
+    } else {
+        mOutput << "# " << vp.size() << " vertex positions and colors" << endl;
+        for ( const vertexData& v : vp ) {
+            mOutput << "v  " << v.vp.x << " " << v.vp.y << " " << v.vp.z << " " << v.vc.r << " " << v.vc.g << " " << v.vc.b << endl;
+        }
+    }
+    mOutput << endl;
+
+    // write uv coordinates
+    mVtMap.getKeys(vt);
+    mOutput << "# " << vt.size() << " UV coordinates" << endl;
+    for(const aiVector3D& v : vt) {
+        mOutput << "vt " << v.x << " " << v.y << " " << v.z << endl;
+    }
+    mOutput << endl;
+
+    // write vertex normals
+    mVnMap.getKeys(vn);
+    mOutput << "# " << vn.size() << " vertex normals" << endl;
+    for(const aiVector3D& v : vn) {
+        mOutput << "vn " << v.x << " " << v.y << " " << v.z << endl;
+    }
+    mOutput << endl;
+
+    // now write all mesh instances
+    for(const MeshInstance& m : mMeshes) {
+        mOutput << "# Mesh \'" << m.name << "\' with " << m.faces.size() << " faces" << endl;
+        if (!m.name.empty()) {
+            mOutput << "g " << m.name << endl;
+        }
+        if ( !noMtl ) {
+            mOutput << "usemtl " << m.matname << endl;
+        }
+
+        for(const Face& f : m.faces) {
+            mOutput << f.kind << ' ';
+            for(const FaceVertex& fv : f.indices) {
+                mOutput << ' ' << fv.vp;
+
+                if (f.kind != 'p') {
+                    if (fv.vt || f.kind == 'f') {
+                        mOutput << '/';
+                    }
+                    if (fv.vt) {
+                        mOutput << fv.vt;
+                    }
+                    if (f.kind == 'f' && fv.vn) {
+                        mOutput << '/' << fv.vn;
+                    }
+                }
+            }
+
+            mOutput << endl;
+        }
+        mOutput << endl;
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void ObjExporter::AddMesh(const aiString& name, const aiMesh* m, const aiMatrix4x4& mat) {
+    mMeshes.push_back(MeshInstance() );
+    MeshInstance& mesh = mMeshes.back();
+
+    if ( nullptr != m->mColors[ 0 ] ) {
+        useVc = true;
+    }
+
+    mesh.name = std::string( name.data, name.length );
+    mesh.matname = GetMaterialName(m->mMaterialIndex);
+
+    mesh.faces.resize(m->mNumFaces);
+
+    for(unsigned int i = 0; i < m->mNumFaces; ++i) {
+        const aiFace& f = m->mFaces[i];
+
+        Face& face = mesh.faces[i];
+        switch (f.mNumIndices) {
+            case 1:
+                face.kind = 'p';
+                break;
+            case 2:
+                face.kind = 'l';
+                break;
+            default:
+                face.kind = 'f';
+        }
+        face.indices.resize(f.mNumIndices);
+
+        for(unsigned int a = 0; a < f.mNumIndices; ++a) {
+            const unsigned int idx = f.mIndices[a];
+
+            aiVector3D vert = mat * m->mVertices[idx];
+
+            if ( nullptr != m->mColors[ 0 ] ) {
+                aiColor4D col4 = m->mColors[ 0 ][ idx ];
+                face.indices[a].vp = mVpMap.getIndex({vert, aiColor3D(col4.r, col4.g, col4.b)});
+            } else {
+                face.indices[a].vp = mVpMap.getIndex({vert, aiColor3D(0,0,0)});
+            }
+
+            if (m->mNormals) {
+                aiVector3D norm = aiMatrix3x3(mat) * m->mNormals[idx];
+                face.indices[a].vn = mVnMap.getIndex(norm);
+            } else {
+                face.indices[a].vn = 0;
+            }
+
+            if ( m->mTextureCoords[ 0 ] ) {
+                face.indices[a].vt = mVtMap.getIndex(m->mTextureCoords[0][idx]);
+            } else {
+                face.indices[a].vt = 0;
+            }
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void ObjExporter::AddNode(const aiNode* nd, const aiMatrix4x4& mParent) {
+    const aiMatrix4x4& mAbs = mParent * nd->mTransformation;
+
+    aiMesh *cm( nullptr );
+    for(unsigned int i = 0; i < nd->mNumMeshes; ++i) {
+        cm = pScene->mMeshes[nd->mMeshes[i]];
+        if (nullptr != cm) {
+            AddMesh(cm->mName, pScene->mMeshes[nd->mMeshes[i]], mAbs);
+        } else {
+            AddMesh(nd->mName, pScene->mMeshes[nd->mMeshes[i]], mAbs);
+        }
+    }
+
+    for(unsigned int i = 0; i < nd->mNumChildren; ++i) {
+        AddNode(nd->mChildren[i], mAbs);
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+
+#endif // ASSIMP_BUILD_NO_OBJ_EXPORTER
+#endif // ASSIMP_BUILD_NO_EXPORT

+ 190 - 0
Engine/lib/assimp/code/AssetLib/Obj/ObjExporter.h

@@ -0,0 +1,190 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2020, assimp team
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file ObjExporter.h
+ * Declares the exporter class to write a scene to a Collada file
+ */
+#ifndef AI_OBJEXPORTER_H_INC
+#define AI_OBJEXPORTER_H_INC
+
+#include <assimp/types.h>
+#include <sstream>
+#include <vector>
+#include <map>
+
+struct aiScene;
+struct aiNode;
+struct aiMesh;
+
+namespace Assimp {
+
+// ------------------------------------------------------------------------------------------------
+/** Helper class to export a given scene to an OBJ file. */
+// ------------------------------------------------------------------------------------------------
+class ObjExporter {
+public:
+    /// Constructor for a specific scene to export
+    ObjExporter(const char* filename, const aiScene* pScene, bool noMtl=false);
+    ~ObjExporter();
+    std::string GetMaterialLibName();
+    std::string GetMaterialLibFileName();
+
+    /// public string-streams to write all output into
+    std::ostringstream mOutput, mOutputMat;
+
+private:
+    // intermediate data structures
+    struct FaceVertex {
+        FaceVertex()
+        : vp()
+        , vn()
+        , vt() {
+            // empty
+        }
+
+        // one-based, 0 means: 'does not exist'
+        unsigned int vp, vn, vt;
+    };
+
+    struct Face {
+        char kind;
+        std::vector<FaceVertex> indices;
+    };
+
+    struct MeshInstance {
+        std::string name, matname;
+        std::vector<Face> faces;
+    };
+
+    void WriteHeader(std::ostringstream& out);
+    void WriteMaterialFile();
+    void WriteGeometryFile(bool noMtl=false);
+    std::string GetMaterialName(unsigned int index);
+    void AddMesh(const aiString& name, const aiMesh* m, const aiMatrix4x4& mat);
+    void AddNode(const aiNode* nd, const aiMatrix4x4& mParent);
+
+private:
+    std::string filename;
+    const aiScene* const pScene;
+
+    struct vertexData {
+        aiVector3D vp;
+        aiColor3D vc; // OBJ does not support 4D color
+    };
+
+    std::vector<aiVector3D> vn, vt;
+    std::vector<aiColor4D> vc;
+    std::vector<vertexData> vp;
+    bool useVc;
+
+    struct vertexDataCompare {
+        bool operator() ( const vertexData& a, const vertexData& b ) const {
+            // position
+            if (a.vp.x < b.vp.x) return true;
+            if (a.vp.x > b.vp.x) return false;
+            if (a.vp.y < b.vp.y) return true;
+            if (a.vp.y > b.vp.y) return false;
+            if (a.vp.z < b.vp.z) return true;
+            if (a.vp.z > b.vp.z) return false;
+
+            // color
+            if (a.vc.r < b.vc.r) return true;
+            if (a.vc.r > b.vc.r) return false;
+            if (a.vc.g < b.vc.g) return true;
+            if (a.vc.g > b.vc.g) return false;
+            if (a.vc.b < b.vc.b) return true;
+            if (a.vc.b > b.vc.b) return false;
+            return false;
+        }
+    };
+
+    struct aiVectorCompare {
+        bool operator() (const aiVector3D& a, const aiVector3D& b) const {
+            if(a.x < b.x) return true;
+            if(a.x > b.x) return false;
+            if(a.y < b.y) return true;
+            if(a.y > b.y) return false;
+            if(a.z < b.z) return true;
+            return false;
+        }
+    };
+
+    template <class T, class Compare = std::less<T>>
+    class indexMap {
+        int mNextIndex;
+        typedef std::map<T, int, Compare> dataType;
+        dataType vecMap;
+
+    public:
+        indexMap()
+        : mNextIndex(1) {
+            // empty
+        }
+
+        int getIndex(const T& key) {
+            typename dataType::iterator vertIt = vecMap.find(key);
+            // vertex already exists, so reference it
+            if(vertIt != vecMap.end()){
+                return vertIt->second;
+            }
+            return vecMap[key] = mNextIndex++;
+        };
+
+        void getKeys( std::vector<T>& keys ) {
+            keys.resize(vecMap.size());
+            for(typename dataType::iterator it = vecMap.begin(); it != vecMap.end(); ++it){
+                keys[it->second-1] = it->first;
+            }
+        };
+    };
+
+    indexMap<aiVector3D, aiVectorCompare> mVnMap, mVtMap;
+    indexMap<vertexData, vertexDataCompare> mVpMap;
+    std::vector<MeshInstance> mMeshes;
+
+    // this endl() doesn't flush() the stream
+    const std::string endl;
+};
+
+}
+
+#endif

+ 344 - 0
Engine/lib/assimp/code/AssetLib/Obj/ObjFileData.h

@@ -0,0 +1,344 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2020, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+#pragma once
+#ifndef OBJ_FILEDATA_H_INC
+#define OBJ_FILEDATA_H_INC
+
+#include <assimp/mesh.h>
+#include <assimp/types.h>
+#include <map>
+#include <vector>
+
+namespace Assimp {
+namespace ObjFile {
+
+struct Object;
+struct Face;
+struct Material;
+
+// ------------------------------------------------------------------------------------------------
+//! \struct Face
+//! \brief  Data structure for a simple obj-face, describes discredit,l.ation and materials
+// ------------------------------------------------------------------------------------------------
+struct Face {
+    using IndexArray = std::vector<unsigned int>;
+
+    //! Primitive type
+    aiPrimitiveType m_PrimitiveType;
+    //! Vertex indices
+    IndexArray m_vertices;
+    //! Normal indices
+    IndexArray m_normals;
+    //! Texture coordinates indices
+    IndexArray m_texturCoords;
+    //! Pointer to assigned material
+    Material *m_pMaterial;
+
+    //! \brief  Default constructor
+    Face(aiPrimitiveType pt = aiPrimitiveType_POLYGON) :
+            m_PrimitiveType(pt), m_vertices(), m_normals(), m_texturCoords(), m_pMaterial(0L) {
+        // empty
+    }
+
+    //! \brief  Destructor
+    ~Face() {
+        // empty
+    }
+};
+
+// ------------------------------------------------------------------------------------------------
+//! \struct Object
+//! \brief  Stores all objects of an obj-file object definition
+// ------------------------------------------------------------------------------------------------
+struct Object {
+    enum ObjectType {
+        ObjType,
+        GroupType
+    };
+
+    //! Object name
+    std::string m_strObjName;
+    //! Transformation matrix, stored in OpenGL format
+    aiMatrix4x4 m_Transformation;
+    //! All sub-objects referenced by this object
+    std::vector<Object *> m_SubObjects;
+    /// Assigned meshes
+    std::vector<unsigned int> m_Meshes;
+
+    //! \brief  Default constructor
+    Object() = default;
+
+    //! \brief  Destructor
+    ~Object() {
+        for (std::vector<Object *>::iterator it = m_SubObjects.begin(); it != m_SubObjects.end(); ++it) {
+            delete *it;
+        }
+    }
+};
+
+// ------------------------------------------------------------------------------------------------
+//! \struct Material
+//! \brief  Data structure to store all material specific data
+// ------------------------------------------------------------------------------------------------
+struct Material {
+    //! Name of material description
+    aiString MaterialName;
+    //! Texture names
+    aiString texture;
+    aiString textureSpecular;
+    aiString textureAmbient;
+    aiString textureEmissive;
+    aiString textureBump;
+    aiString textureNormal;
+    aiString textureReflection[6];
+    aiString textureSpecularity;
+    aiString textureOpacity;
+    aiString textureDisp;
+    aiString textureRoughness;
+    aiString textureMetallic;
+    aiString textureSheen;
+    aiString textureRMA;
+
+    enum TextureType {
+        TextureDiffuseType = 0,
+        TextureSpecularType,
+        TextureAmbientType,
+        TextureEmissiveType,
+        TextureBumpType,
+        TextureNormalType,
+        TextureReflectionSphereType,
+        TextureReflectionCubeTopType,
+        TextureReflectionCubeBottomType,
+        TextureReflectionCubeFrontType,
+        TextureReflectionCubeBackType,
+        TextureReflectionCubeLeftType,
+        TextureReflectionCubeRightType,
+        TextureSpecularityType,
+        TextureOpacityType,
+        TextureDispType,
+        TextureRoughnessType,
+        TextureMetallicType,
+        TextureSheenType,
+        TextureRMAType,
+        TextureTypeCount
+    };
+    bool clamp[TextureTypeCount];
+
+    //! Ambient color
+    aiColor3D ambient;
+    //! Diffuse color
+    aiColor3D diffuse;
+    //! Specular color
+    aiColor3D specular;
+    //! Emissive color
+    aiColor3D emissive;
+    //! Alpha value
+    ai_real alpha;
+    //! Shineness factor
+    ai_real shineness;
+    //! Illumination model
+    int illumination_model;
+    //! Index of refraction
+    ai_real ior;
+    //! Transparency color
+    aiColor3D transparent;
+
+    //! PBR Roughness
+    ai_real roughness;
+    //! PBR Metallic
+    ai_real metallic;
+    //! PBR Metallic
+    aiColor3D sheen;
+    //! PBR Clearcoat Thickness
+    ai_real clearcoat_thickness;
+    //! PBR Clearcoat Rougness
+    ai_real clearcoat_roughness;
+    //! PBR Anisotropy
+    ai_real anisotropy;
+
+    //! bump map multipler (normal map scalar)(-bm)
+    ai_real bump_multiplier;
+
+    //! Constructor
+    Material() :
+            diffuse(ai_real(0.6), ai_real(0.6), ai_real(0.6)),
+            alpha(ai_real(1.0)),
+            shineness(ai_real(0.0)),
+            illumination_model(1),
+            ior(ai_real(1.0)),
+            transparent(ai_real(1.0), ai_real(1.0), ai_real(1.0)),
+            roughness(ai_real(1.0)),
+            metallic(ai_real(0.0)),
+            sheen(ai_real(1.0), ai_real(1.0), ai_real(1.0)),
+            clearcoat_thickness(ai_real(0.0)),
+            clearcoat_roughness(ai_real(0.0)),
+            anisotropy(ai_real(0.0)),
+            bump_multiplier(ai_real(1.0)) {
+        std::fill_n(clamp, static_cast<unsigned int>(TextureTypeCount), false);
+    }
+
+    // Destructor
+    ~Material() = default;
+};
+
+// ------------------------------------------------------------------------------------------------
+//! \struct Mesh
+//! \brief  Data structure to store a mesh
+// ------------------------------------------------------------------------------------------------
+struct Mesh {
+    static const unsigned int NoMaterial = ~0u;
+    /// The name for the mesh
+    std::string m_name;
+    /// Array with pointer to all stored faces
+    std::vector<Face *> m_Faces;
+    /// Assigned material
+    Material *m_pMaterial;
+    /// Number of stored indices.
+    unsigned int m_uiNumIndices;
+    /// Number of UV
+    unsigned int m_uiUVCoordinates[AI_MAX_NUMBER_OF_TEXTURECOORDS];
+    /// Material index.
+    unsigned int m_uiMaterialIndex;
+    /// True, if normals are stored.
+    bool m_hasNormals;
+    /// True, if vertex colors are stored.
+    bool m_hasVertexColors;
+
+    /// Constructor
+    explicit Mesh(const std::string &name) :
+            m_name(name),
+            m_pMaterial(nullptr),
+            m_uiNumIndices(0),
+            m_uiMaterialIndex(NoMaterial),
+            m_hasNormals(false) {
+        memset(m_uiUVCoordinates, 0, sizeof(unsigned int) * AI_MAX_NUMBER_OF_TEXTURECOORDS);
+    }
+
+    /// Destructor
+    ~Mesh() {
+        for (std::vector<Face *>::iterator it = m_Faces.begin();
+                it != m_Faces.end(); ++it) {
+            delete *it;
+        }
+    }
+};
+
+// ------------------------------------------------------------------------------------------------
+//! \struct Model
+//! \brief  Data structure to store all obj-specific model data
+// ------------------------------------------------------------------------------------------------
+struct Model {
+    using GroupMap = std::map<std::string, std::vector<unsigned int> *>;
+    using GroupMapIt = std::map<std::string, std::vector<unsigned int> *>::iterator;
+    using ConstGroupMapIt = std::map<std::string, std::vector<unsigned int> *>::const_iterator;
+
+    //! Model name
+    std::string m_ModelName;
+    //! List ob assigned objects
+    std::vector<Object *> m_Objects;
+    //! Pointer to current object
+    ObjFile::Object *m_pCurrent;
+    //! Pointer to current material
+    ObjFile::Material *m_pCurrentMaterial;
+    //! Pointer to default material
+    ObjFile::Material *m_pDefaultMaterial;
+    //! Vector with all generated materials
+    std::vector<std::string> m_MaterialLib;
+    //! Vector with all generated vertices
+    std::vector<aiVector3D> m_Vertices;
+    //! vector with all generated normals
+    std::vector<aiVector3D> m_Normals;
+    //! vector with all vertex colors
+    std::vector<aiVector3D> m_VertexColors;
+    //! Group map
+    GroupMap m_Groups;
+    //! Group to face id assignment
+    std::vector<unsigned int> *m_pGroupFaceIDs;
+    //! Active group
+    std::string m_strActiveGroup;
+    //! Vector with generated texture coordinates
+    std::vector<aiVector3D> m_TextureCoord;
+    //! Maximum dimension of texture coordinates
+    unsigned int m_TextureCoordDim;
+    //! Current mesh instance
+    Mesh *m_pCurrentMesh;
+    //! Vector with stored meshes
+    std::vector<Mesh *> m_Meshes;
+    //! Material map
+    std::map<std::string, Material *> m_MaterialMap;
+
+    //! \brief  The default class constructor
+    Model() :
+            m_ModelName(),
+            m_pCurrent(nullptr),
+            m_pCurrentMaterial(nullptr),
+            m_pDefaultMaterial(nullptr),
+            m_pGroupFaceIDs(nullptr),
+            m_strActiveGroup(),
+            m_TextureCoordDim(0),
+            m_pCurrentMesh(nullptr) {
+        // empty
+    }
+
+    //! \brief  The class destructor
+    ~Model() {
+        for (auto & it : m_Objects) {
+            delete it;
+        }
+        for (auto & Meshe : m_Meshes) {
+            delete Meshe;
+        }
+        for (auto & Group : m_Groups) {
+            delete Group.second;
+        }
+        for (auto & it : m_MaterialMap) {
+            delete it.second;
+        }
+    }
+};
+
+// ------------------------------------------------------------------------------------------------
+
+} // Namespace ObjFile
+} // Namespace Assimp
+
+#endif // OBJ_FILEDATA_H_INC

+ 783 - 0
Engine/lib/assimp/code/AssetLib/Obj/ObjFileImporter.cpp

@@ -0,0 +1,783 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2020, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+
+#ifndef ASSIMP_BUILD_NO_OBJ_IMPORTER
+
+#include "ObjFileImporter.h"
+#include "ObjFileData.h"
+#include "ObjFileParser.h"
+#include <assimp/DefaultIOSystem.h>
+#include <assimp/IOStreamBuffer.h>
+#include <assimp/ai_assert.h>
+#include <assimp/importerdesc.h>
+#include <assimp/scene.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/Importer.hpp>
+#include <assimp/ObjMaterial.h>
+#include <memory>
+
+static const aiImporterDesc desc = {
+    "Wavefront Object Importer",
+    "",
+    "",
+    "surfaces not supported",
+    aiImporterFlags_SupportTextFlavour,
+    0,
+    0,
+    0,
+    0,
+    "obj"
+};
+
+static const unsigned int ObjMinSize = 16;
+
+namespace Assimp {
+
+using namespace std;
+
+// ------------------------------------------------------------------------------------------------
+//  Default constructor
+ObjFileImporter::ObjFileImporter() :
+        m_Buffer(),
+        m_pRootObject(nullptr),
+        m_strAbsPath(std::string(1, DefaultIOSystem().getOsSeparator())) {}
+
+// ------------------------------------------------------------------------------------------------
+//  Destructor.
+ObjFileImporter::~ObjFileImporter() {
+    delete m_pRootObject;
+    m_pRootObject = nullptr;
+}
+
+// ------------------------------------------------------------------------------------------------
+//  Returns true if file is an obj file.
+bool ObjFileImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
+    static const char *tokens[] = { "mtllib", "usemtl", "v ", "vt ", "vn ", "o ", "g ", "s ", "f " };
+    return BaseImporter::SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens), 200, false, true);
+}
+
+// ------------------------------------------------------------------------------------------------
+const aiImporterDesc *ObjFileImporter::GetInfo() const {
+    return &desc;
+}
+
+// ------------------------------------------------------------------------------------------------
+//  Obj-file import implementation
+void ObjFileImporter::InternReadFile(const std::string &file, aiScene *pScene, IOSystem *pIOHandler) {
+    // Read file into memory
+    static const std::string mode = "rb";
+    auto streamCloser = [&](IOStream *pStream) {
+        pIOHandler->Close(pStream);
+    };
+    std::unique_ptr<IOStream, decltype(streamCloser)> fileStream(pIOHandler->Open(file, mode), streamCloser);
+    if (!fileStream.get()) {
+        throw DeadlyImportError("Failed to open file ", file, ".");
+    }
+
+    // Get the file-size and validate it, throwing an exception when fails
+    size_t fileSize = fileStream->FileSize();
+    if (fileSize < ObjMinSize) {
+        throw DeadlyImportError("OBJ-file is too small.");
+    }
+
+    IOStreamBuffer<char> streamedBuffer;
+    streamedBuffer.open(fileStream.get());
+
+    // Allocate buffer and read file into it
+    //TextFileToBuffer( fileStream.get(),m_Buffer);
+
+    // Get the model name
+    std::string modelName, folderName;
+    std::string::size_type pos = file.find_last_of("\\/");
+    if (pos != std::string::npos) {
+        modelName = file.substr(pos + 1, file.size() - pos - 1);
+        folderName = file.substr(0, pos);
+        if (!folderName.empty()) {
+            pIOHandler->PushDirectory(folderName);
+        }
+    } else {
+        modelName = file;
+    }
+
+    // parse the file into a temporary representation
+    ObjFileParser parser(streamedBuffer, modelName, pIOHandler, m_progress, file);
+
+    // And create the proper return structures out of it
+    CreateDataFromImport(parser.GetModel(), pScene);
+
+    streamedBuffer.close();
+
+    // Clean up allocated storage for the next import
+    m_Buffer.clear();
+
+    // Pop directory stack
+    if (pIOHandler->StackSize() > 0) {
+        pIOHandler->PopDirectory();
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+//  Create the data from parsed obj-file
+void ObjFileImporter::CreateDataFromImport(const ObjFile::Model *pModel, aiScene *pScene) {
+    if (nullptr == pModel) {
+        return;
+    }
+
+    // Create the root node of the scene
+    pScene->mRootNode = new aiNode;
+    if (!pModel->m_ModelName.empty()) {
+        // Set the name of the scene
+        pScene->mRootNode->mName.Set(pModel->m_ModelName);
+    } else {
+        // This is a fatal error, so break down the application
+        ai_assert(false);
+    }
+
+    if (!pModel->m_Objects.empty()) {
+
+        unsigned int meshCount = 0;
+        unsigned int childCount = 0;
+
+        for (auto object : pModel->m_Objects) {
+            if (object) {
+                ++childCount;
+                meshCount += (unsigned int)object->m_Meshes.size();
+            }
+        }
+
+        // Allocate space for the child nodes on the root node
+        pScene->mRootNode->mChildren = new aiNode *[childCount];
+
+        // Create nodes for the whole scene
+        std::vector<aiMesh *> MeshArray;
+        MeshArray.reserve(meshCount);
+        for (size_t index = 0; index < pModel->m_Objects.size(); ++index) {
+            createNodes(pModel, pModel->m_Objects[index], pScene->mRootNode, pScene, MeshArray);
+        }
+
+        ai_assert(pScene->mRootNode->mNumChildren == childCount);
+
+        // Create mesh pointer buffer for this scene
+        if (pScene->mNumMeshes > 0) {
+            pScene->mMeshes = new aiMesh *[MeshArray.size()];
+            for (size_t index = 0; index < MeshArray.size(); ++index) {
+                pScene->mMeshes[index] = MeshArray[index];
+            }
+        }
+
+        // Create all materials
+        createMaterials(pModel, pScene);
+    } else {
+        if (pModel->m_Vertices.empty()) {
+            return;
+        }
+
+        std::unique_ptr<aiMesh> mesh(new aiMesh);
+        mesh->mPrimitiveTypes = aiPrimitiveType_POINT;
+        unsigned int n = (unsigned int)pModel->m_Vertices.size();
+        mesh->mNumVertices = n;
+
+        mesh->mVertices = new aiVector3D[n];
+        memcpy(mesh->mVertices, pModel->m_Vertices.data(), n * sizeof(aiVector3D));
+
+        if (!pModel->m_Normals.empty()) {
+            mesh->mNormals = new aiVector3D[n];
+            if (pModel->m_Normals.size() < n) {
+                throw DeadlyImportError("OBJ: vertex normal index out of range");
+            }
+            memcpy(mesh->mNormals, pModel->m_Normals.data(), n * sizeof(aiVector3D));
+        }
+
+        if (!pModel->m_VertexColors.empty()) {
+            mesh->mColors[0] = new aiColor4D[mesh->mNumVertices];
+            for (unsigned int i = 0; i < n; ++i) {
+                if (i < pModel->m_VertexColors.size()) {
+                    const aiVector3D &color = pModel->m_VertexColors[i];
+                    mesh->mColors[0][i] = aiColor4D(color.x, color.y, color.z, 1.0);
+                } else {
+                    throw DeadlyImportError("OBJ: vertex color index out of range");
+                }
+            }
+        }
+
+        pScene->mRootNode->mNumMeshes = 1;
+        pScene->mRootNode->mMeshes = new unsigned int[1];
+        pScene->mRootNode->mMeshes[0] = 0;
+        pScene->mMeshes = new aiMesh *[1];
+        pScene->mNumMeshes = 1;
+        pScene->mMeshes[0] = mesh.release();
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+//  Creates all nodes of the model
+aiNode *ObjFileImporter::createNodes(const ObjFile::Model *pModel, const ObjFile::Object *pObject,
+        aiNode *pParent, aiScene *pScene,
+        std::vector<aiMesh *> &MeshArray) {
+    ai_assert(nullptr != pModel);
+    if (nullptr == pObject) {
+        return nullptr;
+    }
+
+    // Store older mesh size to be able to computes mesh offsets for new mesh instances
+    const size_t oldMeshSize = MeshArray.size();
+    aiNode *pNode = new aiNode;
+
+    pNode->mName = pObject->m_strObjName;
+
+    // If we have a parent node, store it
+    ai_assert(nullptr != pParent);
+    appendChildToParentNode(pParent, pNode);
+
+    for (size_t i = 0; i < pObject->m_Meshes.size(); ++i) {
+        unsigned int meshId = pObject->m_Meshes[i];
+        aiMesh *pMesh = createTopology(pModel, pObject, meshId);
+        if (pMesh) {
+            if (pMesh->mNumFaces > 0) {
+                MeshArray.push_back(pMesh);
+            } else {
+                delete pMesh;
+            }
+        }
+    }
+
+    // Create all nodes from the sub-objects stored in the current object
+    if (!pObject->m_SubObjects.empty()) {
+        size_t numChilds = pObject->m_SubObjects.size();
+        pNode->mNumChildren = static_cast<unsigned int>(numChilds);
+        pNode->mChildren = new aiNode *[numChilds];
+        pNode->mNumMeshes = 1;
+        pNode->mMeshes = new unsigned int[1];
+    }
+
+    // Set mesh instances into scene- and node-instances
+    const size_t meshSizeDiff = MeshArray.size() - oldMeshSize;
+    if (meshSizeDiff > 0) {
+        pNode->mMeshes = new unsigned int[meshSizeDiff];
+        pNode->mNumMeshes = static_cast<unsigned int>(meshSizeDiff);
+        size_t index = 0;
+        for (size_t i = oldMeshSize; i < MeshArray.size(); ++i) {
+            pNode->mMeshes[index] = pScene->mNumMeshes;
+            pScene->mNumMeshes++;
+            ++index;
+        }
+    }
+
+    return pNode;
+}
+
+// ------------------------------------------------------------------------------------------------
+//  Create topology data
+aiMesh *ObjFileImporter::createTopology(const ObjFile::Model *pModel, const ObjFile::Object *pData, unsigned int meshIndex) {
+    // Checking preconditions
+    ai_assert(nullptr != pModel);
+
+    if (nullptr == pData) {
+        return nullptr;
+    }
+
+    // Create faces
+    ObjFile::Mesh *pObjMesh = pModel->m_Meshes[meshIndex];
+    if (!pObjMesh) {
+        return nullptr;
+    }
+
+    if (pObjMesh->m_Faces.empty()) {
+        return nullptr;
+    }
+
+    std::unique_ptr<aiMesh> pMesh(new aiMesh);
+    if (!pObjMesh->m_name.empty()) {
+        pMesh->mName.Set(pObjMesh->m_name);
+    }
+
+    for (size_t index = 0; index < pObjMesh->m_Faces.size(); index++) {
+        ObjFile::Face *const inp = pObjMesh->m_Faces[index];
+        ai_assert(nullptr != inp);
+
+        if (inp->m_PrimitiveType == aiPrimitiveType_LINE) {
+            pMesh->mNumFaces += static_cast<unsigned int>(inp->m_vertices.size() - 1);
+            pMesh->mPrimitiveTypes |= aiPrimitiveType_LINE;
+        } else if (inp->m_PrimitiveType == aiPrimitiveType_POINT) {
+            pMesh->mNumFaces += static_cast<unsigned int>(inp->m_vertices.size());
+            pMesh->mPrimitiveTypes |= aiPrimitiveType_POINT;
+        } else {
+            ++pMesh->mNumFaces;
+            if (inp->m_vertices.size() > 3) {
+                pMesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON;
+            } else {
+                pMesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE;
+            }
+        }
+    }
+
+    unsigned int uiIdxCount(0u);
+    if (pMesh->mNumFaces > 0) {
+        pMesh->mFaces = new aiFace[pMesh->mNumFaces];
+        if (pObjMesh->m_uiMaterialIndex != ObjFile::Mesh::NoMaterial) {
+            pMesh->mMaterialIndex = pObjMesh->m_uiMaterialIndex;
+        }
+
+        unsigned int outIndex(0);
+
+        // Copy all data from all stored meshes
+        for (auto &face : pObjMesh->m_Faces) {
+            ObjFile::Face *const inp = face;
+            if (inp->m_PrimitiveType == aiPrimitiveType_LINE) {
+                for (size_t i = 0; i < inp->m_vertices.size() - 1; ++i) {
+                    aiFace &f = pMesh->mFaces[outIndex++];
+                    uiIdxCount += f.mNumIndices = 2;
+                    f.mIndices = new unsigned int[2];
+                }
+                continue;
+            } else if (inp->m_PrimitiveType == aiPrimitiveType_POINT) {
+                for (size_t i = 0; i < inp->m_vertices.size(); ++i) {
+                    aiFace &f = pMesh->mFaces[outIndex++];
+                    uiIdxCount += f.mNumIndices = 1;
+                    f.mIndices = new unsigned int[1];
+                }
+                continue;
+            }
+
+            aiFace *pFace = &pMesh->mFaces[outIndex++];
+            const unsigned int uiNumIndices = (unsigned int)face->m_vertices.size();
+            uiIdxCount += pFace->mNumIndices = (unsigned int)uiNumIndices;
+            if (pFace->mNumIndices > 0) {
+                pFace->mIndices = new unsigned int[uiNumIndices];
+            }
+        }
+    }
+
+    // Create mesh vertices
+    createVertexArray(pModel, pData, meshIndex, pMesh.get(), uiIdxCount);
+
+    return pMesh.release();
+}
+
+// ------------------------------------------------------------------------------------------------
+//  Creates a vertex array
+void ObjFileImporter::createVertexArray(const ObjFile::Model *pModel,
+        const ObjFile::Object *pCurrentObject,
+        unsigned int uiMeshIndex,
+        aiMesh *pMesh,
+        unsigned int numIndices) {
+    // Checking preconditions
+    ai_assert(nullptr != pCurrentObject);
+
+    // Break, if no faces are stored in object
+    if (pCurrentObject->m_Meshes.empty())
+        return;
+
+    // Get current mesh
+    ObjFile::Mesh *pObjMesh = pModel->m_Meshes[uiMeshIndex];
+    if (nullptr == pObjMesh || pObjMesh->m_uiNumIndices < 1) {
+        return;
+    }
+
+    // Copy vertices of this mesh instance
+    pMesh->mNumVertices = numIndices;
+    if (pMesh->mNumVertices == 0) {
+        throw DeadlyImportError("OBJ: no vertices");
+    } else if (pMesh->mNumVertices > AI_MAX_VERTICES) {
+        throw DeadlyImportError("OBJ: Too many vertices");
+    }
+    pMesh->mVertices = new aiVector3D[pMesh->mNumVertices];
+
+    // Allocate buffer for normal vectors
+    if (!pModel->m_Normals.empty() && pObjMesh->m_hasNormals)
+        pMesh->mNormals = new aiVector3D[pMesh->mNumVertices];
+
+    // Allocate buffer for vertex-color vectors
+    if (!pModel->m_VertexColors.empty())
+        pMesh->mColors[0] = new aiColor4D[pMesh->mNumVertices];
+
+    // Allocate buffer for texture coordinates
+    if (!pModel->m_TextureCoord.empty() && pObjMesh->m_uiUVCoordinates[0]) {
+        pMesh->mNumUVComponents[0] = pModel->m_TextureCoordDim;
+        pMesh->mTextureCoords[0] = new aiVector3D[pMesh->mNumVertices];
+    }
+
+    // Copy vertices, normals and textures into aiMesh instance
+    bool normalsok = true, uvok = true;
+    unsigned int newIndex = 0, outIndex = 0;
+    for (auto sourceFace : pObjMesh->m_Faces) {
+        // Copy all index arrays
+        for (size_t vertexIndex = 0, outVertexIndex = 0; vertexIndex < sourceFace->m_vertices.size(); vertexIndex++) {
+            const unsigned int vertex = sourceFace->m_vertices.at(vertexIndex);
+            if (vertex >= pModel->m_Vertices.size()) {
+                throw DeadlyImportError("OBJ: vertex index out of range");
+            }
+
+            if (pMesh->mNumVertices <= newIndex) {
+                throw DeadlyImportError("OBJ: bad vertex index");
+            }
+
+            pMesh->mVertices[newIndex] = pModel->m_Vertices[vertex];
+
+            // Copy all normals
+            if (normalsok && !pModel->m_Normals.empty() && vertexIndex < sourceFace->m_normals.size()) {
+                const unsigned int normal = sourceFace->m_normals.at(vertexIndex);
+                if (normal >= pModel->m_Normals.size()) {
+                    normalsok = false;
+                } else {
+                    pMesh->mNormals[newIndex] = pModel->m_Normals[normal];
+                }
+            }
+
+            // Copy all vertex colors
+            if (vertex < pModel->m_VertexColors.size()) {
+                const aiVector3D &color = pModel->m_VertexColors[vertex];
+                pMesh->mColors[0][newIndex] = aiColor4D(color.x, color.y, color.z, 1.0);
+            }
+
+            // Copy all texture coordinates
+            if (uvok && !pModel->m_TextureCoord.empty() && vertexIndex < sourceFace->m_texturCoords.size()) {
+                const unsigned int tex = sourceFace->m_texturCoords.at(vertexIndex);
+
+                if (tex >= pModel->m_TextureCoord.size()) {
+                    uvok = false;
+                } else {
+                    const aiVector3D &coord3d = pModel->m_TextureCoord[tex];
+                    pMesh->mTextureCoords[0][newIndex] = aiVector3D(coord3d.x, coord3d.y, coord3d.z);
+                }
+            }
+
+            // Get destination face
+            aiFace *pDestFace = &pMesh->mFaces[outIndex];
+
+            const bool last = (vertexIndex == sourceFace->m_vertices.size() - 1);
+            if (sourceFace->m_PrimitiveType != aiPrimitiveType_LINE || !last) {
+                pDestFace->mIndices[outVertexIndex] = newIndex;
+                outVertexIndex++;
+            }
+
+            if (sourceFace->m_PrimitiveType == aiPrimitiveType_POINT) {
+                outIndex++;
+                outVertexIndex = 0;
+            } else if (sourceFace->m_PrimitiveType == aiPrimitiveType_LINE) {
+                outVertexIndex = 0;
+
+                if (!last)
+                    outIndex++;
+
+                if (vertexIndex) {
+                    if (!last) {
+                        pMesh->mVertices[newIndex + 1] = pMesh->mVertices[newIndex];
+                        if (!sourceFace->m_normals.empty() && !pModel->m_Normals.empty()) {
+                            pMesh->mNormals[newIndex + 1] = pMesh->mNormals[newIndex];
+                        }
+                        if (!pModel->m_TextureCoord.empty()) {
+                            for (size_t i = 0; i < pMesh->GetNumUVChannels(); i++) {
+                                pMesh->mTextureCoords[i][newIndex + 1] = pMesh->mTextureCoords[i][newIndex];
+                            }
+                        }
+                        ++newIndex;
+                    }
+
+                    pDestFace[-1].mIndices[1] = newIndex;
+                }
+            } else if (last) {
+                outIndex++;
+            }
+            ++newIndex;
+        }
+    }
+
+    if (!normalsok) {
+        delete[] pMesh->mNormals;
+        pMesh->mNormals = nullptr;
+    }
+
+    if (!uvok) {
+        delete[] pMesh->mTextureCoords[0];
+        pMesh->mTextureCoords[0] = nullptr;
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+//  Counts all stored meshes
+void ObjFileImporter::countObjects(const std::vector<ObjFile::Object *> &rObjects, int &iNumMeshes) {
+    iNumMeshes = 0;
+    if (rObjects.empty())
+        return;
+
+    iNumMeshes += static_cast<unsigned int>(rObjects.size());
+    for (auto object : rObjects) {
+        if (!object->m_SubObjects.empty()) {
+            countObjects(object->m_SubObjects, iNumMeshes);
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+//   Add clamp mode property to material if necessary
+void ObjFileImporter::addTextureMappingModeProperty(aiMaterial *mat, aiTextureType type, int clampMode, int index) {
+    if (nullptr == mat) {
+        return;
+    }
+
+    mat->AddProperty<int>(&clampMode, 1, AI_MATKEY_MAPPINGMODE_U(type, index));
+    mat->AddProperty<int>(&clampMode, 1, AI_MATKEY_MAPPINGMODE_V(type, index));
+}
+
+// ------------------------------------------------------------------------------------------------
+//  Creates the material
+void ObjFileImporter::createMaterials(const ObjFile::Model *pModel, aiScene *pScene) {
+    if (nullptr == pScene) {
+        return;
+    }
+
+    const unsigned int numMaterials = (unsigned int)pModel->m_MaterialLib.size();
+    pScene->mNumMaterials = 0;
+    if (pModel->m_MaterialLib.empty()) {
+        ASSIMP_LOG_DEBUG("OBJ: no materials specified");
+        return;
+    }
+
+    pScene->mMaterials = new aiMaterial *[numMaterials];
+    for (unsigned int matIndex = 0; matIndex < numMaterials; matIndex++) {
+        // Store material name
+        std::map<std::string, ObjFile::Material *>::const_iterator it;
+        it = pModel->m_MaterialMap.find(pModel->m_MaterialLib[matIndex]);
+
+        // No material found, use the default material
+        if (pModel->m_MaterialMap.end() == it)
+            continue;
+
+        aiMaterial *mat = new aiMaterial;
+        ObjFile::Material *pCurrentMaterial = (*it).second;
+        mat->AddProperty(&pCurrentMaterial->MaterialName, AI_MATKEY_NAME);
+
+        // convert illumination model
+        int sm = 0;
+        switch (pCurrentMaterial->illumination_model) {
+        case 0:
+            sm = aiShadingMode_NoShading;
+            break;
+        case 1:
+            sm = aiShadingMode_Gouraud;
+            break;
+        case 2:
+            sm = aiShadingMode_Phong;
+            break;
+        default:
+            sm = aiShadingMode_Gouraud;
+            ASSIMP_LOG_ERROR("OBJ: unexpected illumination model (0-2 recognized)");
+        }
+
+        mat->AddProperty<int>(&sm, 1, AI_MATKEY_SHADING_MODEL);
+
+        // Preserve the original illum value
+        mat->AddProperty<int>(&pCurrentMaterial->illumination_model, 1, AI_MATKEY_OBJ_ILLUM);
+
+        // Adding material colors
+        mat->AddProperty(&pCurrentMaterial->ambient, 1, AI_MATKEY_COLOR_AMBIENT);
+        mat->AddProperty(&pCurrentMaterial->diffuse, 1, AI_MATKEY_COLOR_DIFFUSE);
+        mat->AddProperty(&pCurrentMaterial->specular, 1, AI_MATKEY_COLOR_SPECULAR);
+        mat->AddProperty(&pCurrentMaterial->emissive, 1, AI_MATKEY_COLOR_EMISSIVE);
+        mat->AddProperty(&pCurrentMaterial->shineness, 1, AI_MATKEY_SHININESS);
+        mat->AddProperty(&pCurrentMaterial->alpha, 1, AI_MATKEY_OPACITY);
+        mat->AddProperty(&pCurrentMaterial->transparent, 1, AI_MATKEY_COLOR_TRANSPARENT);
+        mat->AddProperty(&pCurrentMaterial->roughness, 1, AI_MATKEY_ROUGHNESS_FACTOR);
+        mat->AddProperty(&pCurrentMaterial->metallic, 1, AI_MATKEY_METALLIC_FACTOR);
+        mat->AddProperty(&pCurrentMaterial->sheen, 1, AI_MATKEY_SHEEN_COLOR_FACTOR);
+        mat->AddProperty(&pCurrentMaterial->clearcoat_thickness, 1, AI_MATKEY_CLEARCOAT_FACTOR);
+        mat->AddProperty(&pCurrentMaterial->clearcoat_roughness, 1, AI_MATKEY_CLEARCOAT_ROUGHNESS_FACTOR);
+        mat->AddProperty(&pCurrentMaterial->anisotropy, 1, AI_MATKEY_ANISOTROPY_FACTOR);
+
+        // Adding refraction index
+        mat->AddProperty(&pCurrentMaterial->ior, 1, AI_MATKEY_REFRACTI);
+
+        // Adding textures
+        const int uvwIndex = 0;
+
+        if (0 != pCurrentMaterial->texture.length) {
+            mat->AddProperty(&pCurrentMaterial->texture, AI_MATKEY_TEXTURE_DIFFUSE(0));
+            mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_DIFFUSE(0));
+            if (pCurrentMaterial->clamp[ObjFile::Material::TextureDiffuseType]) {
+                addTextureMappingModeProperty(mat, aiTextureType_DIFFUSE);
+            }
+        }
+
+        if (0 != pCurrentMaterial->textureAmbient.length) {
+            mat->AddProperty(&pCurrentMaterial->textureAmbient, AI_MATKEY_TEXTURE_AMBIENT(0));
+            mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_AMBIENT(0));
+            if (pCurrentMaterial->clamp[ObjFile::Material::TextureAmbientType]) {
+                addTextureMappingModeProperty(mat, aiTextureType_AMBIENT);
+            }
+        }
+
+        if (0 != pCurrentMaterial->textureEmissive.length) {
+            mat->AddProperty(&pCurrentMaterial->textureEmissive, AI_MATKEY_TEXTURE_EMISSIVE(0));
+            mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_EMISSIVE(0));
+        }
+
+        if (0 != pCurrentMaterial->textureSpecular.length) {
+            mat->AddProperty(&pCurrentMaterial->textureSpecular, AI_MATKEY_TEXTURE_SPECULAR(0));
+            mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_SPECULAR(0));
+            if (pCurrentMaterial->clamp[ObjFile::Material::TextureSpecularType]) {
+                addTextureMappingModeProperty(mat, aiTextureType_SPECULAR);
+            }
+        }
+
+        if (0 != pCurrentMaterial->textureBump.length) {
+            mat->AddProperty(&pCurrentMaterial->textureBump, AI_MATKEY_TEXTURE_HEIGHT(0));
+            mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_HEIGHT(0));
+            if (pCurrentMaterial->bump_multiplier != 1.0) {
+                mat->AddProperty(&pCurrentMaterial->bump_multiplier, 1, AI_MATKEY_OBJ_BUMPMULT_HEIGHT(0));
+            }
+            if (pCurrentMaterial->clamp[ObjFile::Material::TextureBumpType]) {
+                addTextureMappingModeProperty(mat, aiTextureType_HEIGHT);
+            }
+        }
+
+        if (0 != pCurrentMaterial->textureNormal.length) {
+            mat->AddProperty(&pCurrentMaterial->textureNormal, AI_MATKEY_TEXTURE_NORMALS(0));
+            mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_NORMALS(0));
+            if (pCurrentMaterial->bump_multiplier != 1.0) {
+                mat->AddProperty(&pCurrentMaterial->bump_multiplier, 1, AI_MATKEY_OBJ_BUMPMULT_NORMALS(0));
+            }
+            if (pCurrentMaterial->clamp[ObjFile::Material::TextureNormalType]) {
+                addTextureMappingModeProperty(mat, aiTextureType_NORMALS);
+            }
+        }
+
+        if (0 != pCurrentMaterial->textureReflection[0].length) {
+            ObjFile::Material::TextureType type = 0 != pCurrentMaterial->textureReflection[1].length ?
+                                                          ObjFile::Material::TextureReflectionCubeTopType :
+                                                          ObjFile::Material::TextureReflectionSphereType;
+
+            unsigned count = type == ObjFile::Material::TextureReflectionSphereType ? 1 : 6;
+            for (unsigned i = 0; i < count; i++) {
+                mat->AddProperty(&pCurrentMaterial->textureReflection[i], AI_MATKEY_TEXTURE_REFLECTION(i));
+                mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_REFLECTION(i));
+
+                if (pCurrentMaterial->clamp[type])
+                    addTextureMappingModeProperty(mat, aiTextureType_REFLECTION, 1, i);
+            }
+        }
+
+        if (0 != pCurrentMaterial->textureDisp.length) {
+            mat->AddProperty(&pCurrentMaterial->textureDisp, AI_MATKEY_TEXTURE_DISPLACEMENT(0));
+            mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_DISPLACEMENT(0));
+            if (pCurrentMaterial->clamp[ObjFile::Material::TextureDispType]) {
+                addTextureMappingModeProperty(mat, aiTextureType_DISPLACEMENT);
+            }
+        }
+
+        if (0 != pCurrentMaterial->textureOpacity.length) {
+            mat->AddProperty(&pCurrentMaterial->textureOpacity, AI_MATKEY_TEXTURE_OPACITY(0));
+            mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_OPACITY(0));
+            if (pCurrentMaterial->clamp[ObjFile::Material::TextureOpacityType]) {
+                addTextureMappingModeProperty(mat, aiTextureType_OPACITY);
+            }
+        }
+
+        if (0 != pCurrentMaterial->textureSpecularity.length) {
+            mat->AddProperty(&pCurrentMaterial->textureSpecularity, AI_MATKEY_TEXTURE_SHININESS(0));
+            mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_SHININESS(0));
+            if (pCurrentMaterial->clamp[ObjFile::Material::TextureSpecularityType]) {
+                addTextureMappingModeProperty(mat, aiTextureType_SHININESS);
+            }
+        }
+
+        if (0 != pCurrentMaterial->textureRoughness.length) {
+            mat->AddProperty(&pCurrentMaterial->textureRoughness, _AI_MATKEY_TEXTURE_BASE, aiTextureType_DIFFUSE_ROUGHNESS, 0);
+            mat->AddProperty(&uvwIndex, 1, _AI_MATKEY_UVWSRC_BASE, aiTextureType_DIFFUSE_ROUGHNESS, 0 );
+            if (pCurrentMaterial->clamp[ObjFile::Material::TextureRoughnessType]) {
+                addTextureMappingModeProperty(mat, aiTextureType_DIFFUSE_ROUGHNESS);
+            }
+        }
+
+        if (0 != pCurrentMaterial->textureMetallic.length) {
+            mat->AddProperty(&pCurrentMaterial->textureMetallic, _AI_MATKEY_TEXTURE_BASE, aiTextureType_METALNESS, 0);
+            mat->AddProperty(&uvwIndex, 1, _AI_MATKEY_UVWSRC_BASE, aiTextureType_METALNESS, 0 );
+            if (pCurrentMaterial->clamp[ObjFile::Material::TextureMetallicType]) {
+                addTextureMappingModeProperty(mat, aiTextureType_METALNESS);
+            }
+        }
+
+        if (0 != pCurrentMaterial->textureSheen.length) {
+            mat->AddProperty(&pCurrentMaterial->textureSheen, _AI_MATKEY_TEXTURE_BASE, aiTextureType_SHEEN, 0);
+            mat->AddProperty(&uvwIndex, 1, _AI_MATKEY_UVWSRC_BASE, aiTextureType_SHEEN, 0 );
+            if (pCurrentMaterial->clamp[ObjFile::Material::TextureSheenType]) {
+                addTextureMappingModeProperty(mat, aiTextureType_SHEEN);
+            }
+        }
+
+        if (0 != pCurrentMaterial->textureRMA.length) {
+            // NOTE: glTF importer places Rough/Metal/AO texture in Unknown so doing the same here for consistency.
+            mat->AddProperty(&pCurrentMaterial->textureRMA, _AI_MATKEY_TEXTURE_BASE, aiTextureType_UNKNOWN, 0);
+            mat->AddProperty(&uvwIndex, 1, _AI_MATKEY_UVWSRC_BASE, aiTextureType_UNKNOWN, 0 );
+            if (pCurrentMaterial->clamp[ObjFile::Material::TextureRMAType]) {
+                addTextureMappingModeProperty(mat, aiTextureType_UNKNOWN);
+            }
+        }
+
+        // Store material property info in material array in scene
+        pScene->mMaterials[pScene->mNumMaterials] = mat;
+        pScene->mNumMaterials++;
+    }
+
+    // Test number of created materials.
+    ai_assert(pScene->mNumMaterials == numMaterials);
+}
+
+// ------------------------------------------------------------------------------------------------
+//  Appends this node to the parent node
+void ObjFileImporter::appendChildToParentNode(aiNode *pParent, aiNode *pChild) {
+    // Checking preconditions
+    ai_assert(nullptr != pParent);
+    ai_assert(nullptr != pChild);
+
+    // Assign parent to child
+    pChild->mParent = pParent;
+
+    // Copy node instances into parent node
+    pParent->mNumChildren++;
+    pParent->mChildren[pParent->mNumChildren - 1] = pChild;
+}
+
+// ------------------------------------------------------------------------------------------------
+
+} // Namespace Assimp
+
+#endif // !! ASSIMP_BUILD_NO_OBJ_IMPORTER

+ 122 - 0
Engine/lib/assimp/code/AssetLib/Obj/ObjFileImporter.h

@@ -0,0 +1,122 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2020, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+#pragma once
+#ifndef OBJ_FILE_IMPORTER_H_INC
+#define OBJ_FILE_IMPORTER_H_INC
+
+#include <assimp/BaseImporter.h>
+#include <assimp/material.h>
+#include <vector>
+
+struct aiMesh;
+struct aiNode;
+
+namespace Assimp {
+
+namespace ObjFile {
+struct Object;
+struct Model;
+} // namespace ObjFile
+
+// ------------------------------------------------------------------------------------------------
+/// \class  ObjFileImporter
+/// \brief  Imports a waveform obj file
+// ------------------------------------------------------------------------------------------------
+class ObjFileImporter : public BaseImporter {
+public:
+    /// \brief  Default constructor
+    ObjFileImporter();
+
+    /// \brief  Destructor
+    ~ObjFileImporter() override;
+
+    /// \brief  Returns whether the class can handle the format of the given file.
+    /// \remark See BaseImporter::CanRead() for details.
+    bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const override;
+
+protected:
+    //! \brief  Appends the supported extension.
+    const aiImporterDesc *GetInfo() const override;
+
+    //! \brief  File import implementation.
+    void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override;
+
+    //! \brief  Create the data from imported content.
+    void CreateDataFromImport(const ObjFile::Model *pModel, aiScene *pScene);
+
+    //! \brief  Creates all nodes stored in imported content.
+    aiNode *createNodes(const ObjFile::Model *pModel, const ObjFile::Object *pData,
+            aiNode *pParent, aiScene *pScene, std::vector<aiMesh *> &MeshArray);
+
+    //! \brief  Creates topology data like faces and meshes for the geometry.
+    aiMesh *createTopology(const ObjFile::Model *pModel, const ObjFile::Object *pData,
+            unsigned int uiMeshIndex);
+
+    //! \brief  Creates vertices from model.
+    void createVertexArray(const ObjFile::Model *pModel, const ObjFile::Object *pCurrentObject,
+            unsigned int uiMeshIndex, aiMesh *pMesh, unsigned int numIndices);
+
+    //! \brief  Object counter helper method.
+    void countObjects(const std::vector<ObjFile::Object *> &rObjects, int &iNumMeshes);
+
+    //! \brief  Material creation.
+    void createMaterials(const ObjFile::Model *pModel, aiScene *pScene);
+
+    /// @brief  Adds special property for the used texture mapping mode of the model.
+    void addTextureMappingModeProperty(aiMaterial *mat, aiTextureType type, int clampMode = 1, int index = 0);
+
+    //! \brief  Appends a child node to a parent node and updates the data structures.
+    void appendChildToParentNode(aiNode *pParent, aiNode *pChild);
+
+private:
+    //! Data buffer
+    std::vector<char> m_Buffer;
+    //! Pointer to root object instance
+    ObjFile::Object *m_pRootObject;
+    //! Absolute pathname of model in file system
+    std::string m_strAbsPath;
+};
+
+// ------------------------------------------------------------------------------------------------
+
+} // Namespace Assimp
+
+#endif

+ 498 - 0
Engine/lib/assimp/code/AssetLib/Obj/ObjFileMtlImporter.cpp

@@ -0,0 +1,498 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2020, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+
+#ifndef ASSIMP_BUILD_NO_OBJ_IMPORTER
+
+#include "ObjFileMtlImporter.h"
+#include "ObjFileData.h"
+#include "ObjTools.h"
+#include <assimp/ParsingUtils.h>
+#include <assimp/fast_atof.h>
+#include <assimp/material.h>
+#include <stdlib.h>
+#include <assimp/DefaultLogger.hpp>
+
+namespace Assimp {
+
+// Material specific token (case insensitive compare)
+static const std::string DiffuseTexture = "map_Kd";
+static const std::string AmbientTexture = "map_Ka";
+static const std::string SpecularTexture = "map_Ks";
+static const std::string OpacityTexture = "map_d";
+static const std::string EmissiveTexture1 = "map_emissive";
+static const std::string EmissiveTexture2 = "map_Ke";
+static const std::string BumpTexture1 = "map_bump";
+static const std::string BumpTexture2 = "bump";
+static const std::string NormalTextureV1 = "map_Kn";
+static const std::string NormalTextureV2 = "norm";
+static const std::string ReflectionTexture = "refl";
+static const std::string DisplacementTexture1 = "map_disp";
+static const std::string DisplacementTexture2 = "disp";
+static const std::string SpecularityTexture = "map_ns";
+static const std::string RoughnessTexture = "map_Pr";
+static const std::string MetallicTexture = "map_Pm";
+static const std::string SheenTexture = "map_Ps";
+static const std::string RMATexture = "map_Ps";
+
+// texture option specific token
+static const std::string BlendUOption = "-blendu";
+static const std::string BlendVOption = "-blendv";
+static const std::string BoostOption = "-boost";
+static const std::string ModifyMapOption = "-mm";
+static const std::string OffsetOption = "-o";
+static const std::string ScaleOption = "-s";
+static const std::string TurbulenceOption = "-t";
+static const std::string ResolutionOption = "-texres";
+static const std::string ClampOption = "-clamp";
+static const std::string BumpOption = "-bm";
+static const std::string ChannelOption = "-imfchan";
+static const std::string TypeOption = "-type";
+
+// -------------------------------------------------------------------
+//  Constructor
+ObjFileMtlImporter::ObjFileMtlImporter(std::vector<char> &buffer,
+        const std::string &,
+        ObjFile::Model *pModel) :
+        m_DataIt(buffer.begin()),
+        m_DataItEnd(buffer.end()),
+        m_pModel(pModel),
+        m_uiLine(0),
+        m_buffer() {
+    ai_assert(nullptr != m_pModel);
+    m_buffer.resize(BUFFERSIZE);
+    std::fill(m_buffer.begin(), m_buffer.end(), '\0');
+    if (nullptr == m_pModel->m_pDefaultMaterial) {
+        m_pModel->m_pDefaultMaterial = new ObjFile::Material;
+        m_pModel->m_pDefaultMaterial->MaterialName.Set("default");
+    }
+    load();
+}
+
+// -------------------------------------------------------------------
+//  Destructor
+ObjFileMtlImporter::~ObjFileMtlImporter() {
+    // empty
+}
+
+// -------------------------------------------------------------------
+//  Loads the material description
+void ObjFileMtlImporter::load() {
+    if (m_DataIt == m_DataItEnd)
+        return;
+
+    while (m_DataIt != m_DataItEnd) {
+        switch (*m_DataIt) {
+            case 'k':
+            case 'K': {
+                ++m_DataIt;
+                if (*m_DataIt == 'a') // Ambient color
+                {
+                    ++m_DataIt;
+                    getColorRGBA(&m_pModel->m_pCurrentMaterial->ambient);
+                } else if (*m_DataIt == 'd') {
+                    // Diffuse color
+                    ++m_DataIt;
+                    getColorRGBA(&m_pModel->m_pCurrentMaterial->diffuse);
+                } else if (*m_DataIt == 's') {
+                    ++m_DataIt;
+                    getColorRGBA(&m_pModel->m_pCurrentMaterial->specular);
+                } else if (*m_DataIt == 'e') {
+                    ++m_DataIt;
+                    getColorRGBA(&m_pModel->m_pCurrentMaterial->emissive);
+                }
+                m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
+            } break;
+            case 'T': {
+                ++m_DataIt;
+                // Material transmission color
+                if (*m_DataIt == 'f')  {
+                    ++m_DataIt;
+                    getColorRGBA(&m_pModel->m_pCurrentMaterial->transparent);
+                } else if (*m_DataIt == 'r')  {
+                    // Material transmission alpha value
+                    ++m_DataIt;
+                    ai_real d;
+                    getFloatValue(d);
+                    m_pModel->m_pCurrentMaterial->alpha = static_cast<ai_real>(1.0) - d;
+                }
+                m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
+            } break;
+            case 'd': {
+                if (*(m_DataIt + 1) == 'i' && *(m_DataIt + 2) == 's' && *(m_DataIt + 3) == 'p') {
+                    // A displacement map
+                    getTexture();
+                } else {
+                    // Alpha value
+                    ++m_DataIt;
+                    getFloatValue(m_pModel->m_pCurrentMaterial->alpha);
+                    m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
+                }
+            } break;
+
+            case 'N':
+            case 'n': {
+                ++m_DataIt;
+                switch (*m_DataIt) {
+                    case 's': // Specular exponent
+                        ++m_DataIt;
+                        getFloatValue(m_pModel->m_pCurrentMaterial->shineness);
+                        break;
+                    case 'i': // Index Of refraction
+                        ++m_DataIt;
+                        getFloatValue(m_pModel->m_pCurrentMaterial->ior);
+                        break;
+                    case 'e': // New material
+                        createMaterial();
+                        break;
+                    case 'o': // Norm texture
+                        --m_DataIt;
+                        getTexture();
+                        break;
+                }
+                m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
+            } break;
+
+            case 'P':
+                {
+                    ++m_DataIt;
+                    switch(*m_DataIt)
+                    {
+                    case 'r':
+                        ++m_DataIt;
+                        getFloatValue(m_pModel->m_pCurrentMaterial->roughness);
+                        break;
+                    case 'm':
+                        ++m_DataIt;
+                        getFloatValue(m_pModel->m_pCurrentMaterial->metallic);
+                        break;
+                    case 's':
+                        ++m_DataIt;
+                        getColorRGBA(&m_pModel->m_pCurrentMaterial->sheen);
+                        break;
+                    case 'c':
+                        ++m_DataIt;
+                        if (*m_DataIt == 'r') {
+                            ++m_DataIt;
+                            getFloatValue(m_pModel->m_pCurrentMaterial->clearcoat_roughness);
+                        } else {
+                            getFloatValue(m_pModel->m_pCurrentMaterial->clearcoat_thickness);
+                        }
+                        break;
+                    }
+                    m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
+                }
+                break;
+
+            case 'm': // Texture
+            case 'b': // quick'n'dirty - for 'bump' sections
+            case 'r': // quick'n'dirty - for 'refl' sections
+            {
+                getTexture();
+                m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
+            } break;
+
+            case 'i': // Illumination model
+            {
+                m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
+                getIlluminationModel(m_pModel->m_pCurrentMaterial->illumination_model);
+                m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
+            } break;
+
+            case 'a': // Anisotropy
+            {
+                ++m_DataIt;
+                getFloatValue(m_pModel->m_pCurrentMaterial->anisotropy);
+                m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
+            } break;
+
+            default: {
+                m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
+            } break;
+        }
+    }
+}
+
+// -------------------------------------------------------------------
+//  Loads a color definition
+void ObjFileMtlImporter::getColorRGBA(aiColor3D *pColor) {
+    ai_assert(nullptr != pColor);
+
+    ai_real r(0.0), g(0.0), b(0.0);
+    m_DataIt = getFloat<DataArrayIt>(m_DataIt, m_DataItEnd, r);
+    pColor->r = r;
+
+    // we have to check if color is default 0 with only one token
+    if (!IsLineEnd(*m_DataIt)) {
+        m_DataIt = getFloat<DataArrayIt>(m_DataIt, m_DataItEnd, g);
+        m_DataIt = getFloat<DataArrayIt>(m_DataIt, m_DataItEnd, b);
+    }
+    pColor->g = g;
+    pColor->b = b;
+}
+
+// -------------------------------------------------------------------
+//  Loads the kind of illumination model.
+void ObjFileMtlImporter::getIlluminationModel(int &illum_model) {
+    m_DataIt = CopyNextWord<DataArrayIt>(m_DataIt, m_DataItEnd, &m_buffer[0], BUFFERSIZE);
+    illum_model = atoi(&m_buffer[0]);
+}
+
+// -------------------------------------------------------------------
+//  Loads a single float value.
+void ObjFileMtlImporter::getFloatValue(ai_real &value) {
+    m_DataIt = CopyNextWord<DataArrayIt>(m_DataIt, m_DataItEnd, &m_buffer[0], BUFFERSIZE);
+    size_t len = std::strlen(&m_buffer[0]);
+    if (0 == len) {
+        value = 0.0f;
+        return;
+    }
+    
+    value = (ai_real)fast_atof(&m_buffer[0]);
+}
+
+// -------------------------------------------------------------------
+//  Creates a material from loaded data.
+void ObjFileMtlImporter::createMaterial() {
+    std::string line;
+    while (!IsLineEnd(*m_DataIt)) {
+        line += *m_DataIt;
+        ++m_DataIt;
+    }
+
+    std::vector<std::string> token;
+    const unsigned int numToken = tokenize<std::string>(line, token, " \t");
+    std::string name;
+    if (numToken == 1) {
+        name = AI_DEFAULT_MATERIAL_NAME;
+    } else {
+        // skip newmtl and all following white spaces
+        std::size_t first_ws_pos = line.find_first_of(" \t");
+        std::size_t first_non_ws_pos = line.find_first_not_of(" \t", first_ws_pos);
+        if (first_non_ws_pos != std::string::npos) {
+            name = line.substr(first_non_ws_pos);
+        }
+    }
+
+    name = trim_whitespaces(name);
+
+    std::map<std::string, ObjFile::Material *>::iterator it = m_pModel->m_MaterialMap.find(name);
+    if (m_pModel->m_MaterialMap.end() == it) {
+        // New Material created
+        m_pModel->m_pCurrentMaterial = new ObjFile::Material();
+        m_pModel->m_pCurrentMaterial->MaterialName.Set(name);
+        m_pModel->m_MaterialLib.push_back(name);
+        m_pModel->m_MaterialMap[name] = m_pModel->m_pCurrentMaterial;
+
+        if (m_pModel->m_pCurrentMesh) {
+            m_pModel->m_pCurrentMesh->m_uiMaterialIndex = static_cast<unsigned int>(m_pModel->m_MaterialLib.size() - 1);
+        }
+    } else {
+        // Use older material
+        m_pModel->m_pCurrentMaterial = (*it).second;
+    }
+}
+
+// -------------------------------------------------------------------
+//  Gets a texture name from data.
+void ObjFileMtlImporter::getTexture() {
+    aiString *out(nullptr);
+    int clampIndex = -1;
+
+    const char *pPtr(&(*m_DataIt));
+    if (!ASSIMP_strincmp(pPtr, DiffuseTexture.c_str(), static_cast<unsigned int>(DiffuseTexture.size()))) {
+        // Diffuse texture
+        out = &m_pModel->m_pCurrentMaterial->texture;
+        clampIndex = ObjFile::Material::TextureDiffuseType;
+    } else if (!ASSIMP_strincmp(pPtr, AmbientTexture.c_str(), static_cast<unsigned int>(AmbientTexture.size()))) {
+        // Ambient texture
+        out = &m_pModel->m_pCurrentMaterial->textureAmbient;
+        clampIndex = ObjFile::Material::TextureAmbientType;
+    } else if (!ASSIMP_strincmp(pPtr, SpecularTexture.c_str(), static_cast<unsigned int>(SpecularTexture.size()))) {
+        // Specular texture
+        out = &m_pModel->m_pCurrentMaterial->textureSpecular;
+        clampIndex = ObjFile::Material::TextureSpecularType;
+    } else if (!ASSIMP_strincmp(pPtr, DisplacementTexture1.c_str(), static_cast<unsigned int>(DisplacementTexture1.size())) ||
+               !ASSIMP_strincmp(pPtr, DisplacementTexture2.c_str(), static_cast<unsigned int>(DisplacementTexture2.size()))) {
+        // Displacement texture
+        out = &m_pModel->m_pCurrentMaterial->textureDisp;
+        clampIndex = ObjFile::Material::TextureDispType;
+    } else if (!ASSIMP_strincmp(pPtr, OpacityTexture.c_str(), static_cast<unsigned int>(OpacityTexture.size()))) {
+        // Opacity texture
+        out = &m_pModel->m_pCurrentMaterial->textureOpacity;
+        clampIndex = ObjFile::Material::TextureOpacityType;
+    } else if (!ASSIMP_strincmp(pPtr, EmissiveTexture1.c_str(), static_cast<unsigned int>(EmissiveTexture1.size())) ||
+               !ASSIMP_strincmp(pPtr, EmissiveTexture2.c_str(), static_cast<unsigned int>(EmissiveTexture2.size()))) {
+        // Emissive texture
+        out = &m_pModel->m_pCurrentMaterial->textureEmissive;
+        clampIndex = ObjFile::Material::TextureEmissiveType;
+    } else if (!ASSIMP_strincmp(pPtr, BumpTexture1.c_str(), static_cast<unsigned int>(BumpTexture1.size())) ||
+               !ASSIMP_strincmp(pPtr, BumpTexture2.c_str(), static_cast<unsigned int>(BumpTexture2.size()))) {
+        // Bump texture
+        out = &m_pModel->m_pCurrentMaterial->textureBump;
+        clampIndex = ObjFile::Material::TextureBumpType;
+    } else if (!ASSIMP_strincmp(pPtr, NormalTextureV1.c_str(), static_cast<unsigned int>(NormalTextureV1.size())) || !ASSIMP_strincmp(pPtr, NormalTextureV2.c_str(), static_cast<unsigned int>(NormalTextureV2.size()))) {
+        // Normal map
+        out = &m_pModel->m_pCurrentMaterial->textureNormal;
+        clampIndex = ObjFile::Material::TextureNormalType;
+    } else if (!ASSIMP_strincmp(pPtr, ReflectionTexture.c_str(), static_cast<unsigned int>(ReflectionTexture.size()))) {
+        // Reflection texture(s)
+        //Do nothing here
+        return;
+    } else if (!ASSIMP_strincmp(pPtr, SpecularityTexture.c_str(), static_cast<unsigned int>(SpecularityTexture.size()))) {
+        // Specularity scaling (glossiness)
+        out = &m_pModel->m_pCurrentMaterial->textureSpecularity;
+        clampIndex = ObjFile::Material::TextureSpecularityType;
+    } else if ( !ASSIMP_strincmp( pPtr, RoughnessTexture.c_str(), static_cast<unsigned int>(RoughnessTexture.size()))) {
+        // PBR Roughness texture
+        out = & m_pModel->m_pCurrentMaterial->textureRoughness;
+        clampIndex = ObjFile::Material::TextureRoughnessType;
+    } else if ( !ASSIMP_strincmp( pPtr, MetallicTexture.c_str(), static_cast<unsigned int>(MetallicTexture.size()))) {
+        // PBR Metallic texture
+        out = & m_pModel->m_pCurrentMaterial->textureMetallic;
+        clampIndex = ObjFile::Material::TextureMetallicType;
+    } else if (!ASSIMP_strincmp( pPtr, SheenTexture.c_str(), static_cast<unsigned int>(SheenTexture.size()))) {
+        // PBR Sheen (reflectance) texture
+        out = & m_pModel->m_pCurrentMaterial->textureSheen;
+        clampIndex = ObjFile::Material::TextureSheenType;
+    } else if (!ASSIMP_strincmp( pPtr, RMATexture.c_str(), static_cast<unsigned int>(RMATexture.size()))) {
+        // PBR Rough/Metal/AO texture
+        out = & m_pModel->m_pCurrentMaterial->textureRMA;
+        clampIndex = ObjFile::Material::TextureRMAType;
+    } else {
+        ASSIMP_LOG_ERROR("OBJ/MTL: Encountered unknown texture type");
+        return;
+    }
+
+    bool clamp = false;
+    getTextureOption(clamp, clampIndex, out);
+    m_pModel->m_pCurrentMaterial->clamp[clampIndex] = clamp;
+
+    std::string texture;
+    m_DataIt = getName<DataArrayIt>(m_DataIt, m_DataItEnd, texture);
+    if (nullptr != out) {
+        out->Set(texture);
+    }
+}
+
+/* /////////////////////////////////////////////////////////////////////////////
+ * Texture Option
+ * /////////////////////////////////////////////////////////////////////////////
+ * According to http://en.wikipedia.org/wiki/Wavefront_.obj_file#Texture_options
+ * Texture map statement can contains various texture option, for example:
+ *
+ *  map_Ka -o 1 1 1 some.png
+ *  map_Kd -clamp on some.png
+ *
+ * So we need to parse and skip these options, and leave the last part which is
+ * the url of image, otherwise we will get a wrong url like "-clamp on some.png".
+ *
+ * Because aiMaterial supports clamp option, so we also want to return it
+ * /////////////////////////////////////////////////////////////////////////////
+ */
+void ObjFileMtlImporter::getTextureOption(bool &clamp, int &clampIndex, aiString *&out) {
+    m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
+
+    // If there is any more texture option
+    while (!isEndOfBuffer(m_DataIt, m_DataItEnd) && *m_DataIt == '-') {
+        const char *pPtr(&(*m_DataIt));
+        //skip option key and value
+        int skipToken = 1;
+
+        if (!ASSIMP_strincmp(pPtr, ClampOption.c_str(), static_cast<unsigned int>(ClampOption.size()))) {
+            DataArrayIt it = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
+            char value[3];
+            CopyNextWord(it, m_DataItEnd, value, sizeof(value) / sizeof(*value));
+            if (!ASSIMP_strincmp(value, "on", 2)) {
+                clamp = true;
+            }
+
+            skipToken = 2;
+        } else if (!ASSIMP_strincmp(pPtr, TypeOption.c_str(), static_cast<unsigned int>(TypeOption.size()))) {
+            DataArrayIt it = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
+            char value[12];
+            CopyNextWord(it, m_DataItEnd, value, sizeof(value) / sizeof(*value));
+            if (!ASSIMP_strincmp(value, "cube_top", 8)) {
+                clampIndex = ObjFile::Material::TextureReflectionCubeTopType;
+                out = &m_pModel->m_pCurrentMaterial->textureReflection[0];
+            } else if (!ASSIMP_strincmp(value, "cube_bottom", 11)) {
+                clampIndex = ObjFile::Material::TextureReflectionCubeBottomType;
+                out = &m_pModel->m_pCurrentMaterial->textureReflection[1];
+            } else if (!ASSIMP_strincmp(value, "cube_front", 10)) {
+                clampIndex = ObjFile::Material::TextureReflectionCubeFrontType;
+                out = &m_pModel->m_pCurrentMaterial->textureReflection[2];
+            } else if (!ASSIMP_strincmp(value, "cube_back", 9)) {
+                clampIndex = ObjFile::Material::TextureReflectionCubeBackType;
+                out = &m_pModel->m_pCurrentMaterial->textureReflection[3];
+            } else if (!ASSIMP_strincmp(value, "cube_left", 9)) {
+                clampIndex = ObjFile::Material::TextureReflectionCubeLeftType;
+                out = &m_pModel->m_pCurrentMaterial->textureReflection[4];
+            } else if (!ASSIMP_strincmp(value, "cube_right", 10)) {
+                clampIndex = ObjFile::Material::TextureReflectionCubeRightType;
+                out = &m_pModel->m_pCurrentMaterial->textureReflection[5];
+            } else if (!ASSIMP_strincmp(value, "sphere", 6)) {
+                clampIndex = ObjFile::Material::TextureReflectionSphereType;
+                out = &m_pModel->m_pCurrentMaterial->textureReflection[0];
+            }
+
+            skipToken = 2;
+        } else if (!ASSIMP_strincmp(pPtr, BumpOption.c_str(), static_cast<unsigned int>(BumpOption.size()))) {
+            DataArrayIt it = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
+            getFloat(it, m_DataItEnd, m_pModel->m_pCurrentMaterial->bump_multiplier);
+            skipToken = 2;
+        } else if (!ASSIMP_strincmp(pPtr, BlendUOption.c_str(), static_cast<unsigned int>(BlendUOption.size())) || !ASSIMP_strincmp(pPtr, BlendVOption.c_str(), static_cast<unsigned int>(BlendVOption.size())) || !ASSIMP_strincmp(pPtr, BoostOption.c_str(), static_cast<unsigned int>(BoostOption.size())) || !ASSIMP_strincmp(pPtr, ResolutionOption.c_str(), static_cast<unsigned int>(ResolutionOption.size())) || !ASSIMP_strincmp(pPtr, ChannelOption.c_str(), static_cast<unsigned int>(ChannelOption.size()))) {
+            skipToken = 2;
+        } else if (!ASSIMP_strincmp(pPtr, ModifyMapOption.c_str(), static_cast<unsigned int>(ModifyMapOption.size()))) {
+            skipToken = 3;
+        } else if (!ASSIMP_strincmp(pPtr, OffsetOption.c_str(), static_cast<unsigned int>(OffsetOption.size())) || !ASSIMP_strincmp(pPtr, ScaleOption.c_str(), static_cast<unsigned int>(ScaleOption.size())) || !ASSIMP_strincmp(pPtr, TurbulenceOption.c_str(), static_cast<unsigned int>(TurbulenceOption.size()))) {
+            skipToken = 4;
+        }
+
+        for (int i = 0; i < skipToken; ++i) {
+            m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
+        }
+    }
+}
+
+// -------------------------------------------------------------------
+
+} // Namespace Assimp
+
+#endif // !! ASSIMP_BUILD_NO_OBJ_IMPORTER

+ 113 - 0
Engine/lib/assimp/code/AssetLib/Obj/ObjFileMtlImporter.h

@@ -0,0 +1,113 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2020, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------*/
+#ifndef OBJFILEMTLIMPORTER_H_INC
+#define OBJFILEMTLIMPORTER_H_INC
+
+#include <assimp/defs.h>
+#include <string>
+#include <vector>
+
+struct aiColor3D;
+struct aiString;
+
+namespace Assimp {
+
+namespace ObjFile {
+struct Model;
+struct Material;
+} // namespace ObjFile
+
+/**
+ *  @class  ObjFileMtlImporter
+ *  @brief  Loads the material description from a mtl file.
+ */
+class ObjFileMtlImporter {
+public:
+    static const size_t BUFFERSIZE = 2048;
+    using DataArray = std::vector<char>;
+    using DataArrayIt = std::vector<char>::iterator;
+    using ConstDataArrayIt = std::vector<char>::const_iterator;
+
+    //! \brief  The class default constructor
+    ObjFileMtlImporter(std::vector<char> &buffer, const std::string &strAbsPath,
+            ObjFile::Model *pModel);
+
+    //! \brief  The class destructor
+    ~ObjFileMtlImporter();
+
+    ObjFileMtlImporter(const ObjFileMtlImporter &rOther) = delete;
+    ObjFileMtlImporter &operator=(const ObjFileMtlImporter &rOther) = delete;
+
+private:
+    /// Copy constructor, empty.
+    /// Load the whole material description
+    void load();
+    /// Get color data.
+    void getColorRGBA(aiColor3D *pColor);
+    /// Get illumination model from loaded data
+    void getIlluminationModel(int &illum_model);
+    /// Gets a float value from data.
+    void getFloatValue(ai_real &value);
+    /// Creates a new material from loaded data.
+    void createMaterial();
+    /// Get texture name from loaded data.
+    void getTexture();
+    void getTextureOption(bool &clamp, int &clampIndex, aiString *&out);
+
+private:
+    //! Absolute pathname
+    std::string m_strAbsPath;
+    //! Data iterator showing to the current position in data buffer
+    DataArrayIt m_DataIt;
+    //! Data iterator to end of buffer
+    DataArrayIt m_DataItEnd;
+    //! USed model instance
+    ObjFile::Model *m_pModel;
+    //! Current line in file
+    unsigned int m_uiLine;
+    //! Helper buffer
+    std::vector<char> m_buffer;
+};
+
+// ------------------------------------------------------------------------------------------------
+
+} // Namespace Assimp
+
+#endif // OBJFILEMTLIMPORTER_H_INC

+ 859 - 0
Engine/lib/assimp/code/AssetLib/Obj/ObjFileParser.cpp

@@ -0,0 +1,859 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2020, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+#ifndef ASSIMP_BUILD_NO_OBJ_IMPORTER
+
+#include "ObjFileParser.h"
+#include "ObjFileData.h"
+#include "ObjFileMtlImporter.h"
+#include "ObjTools.h"
+#include <assimp/BaseImporter.h>
+#include <assimp/DefaultIOSystem.h>
+#include <assimp/ParsingUtils.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/Importer.hpp>
+#include <cstdlib>
+#include <memory>
+#include <utility>
+
+namespace Assimp {
+
+constexpr char ObjFileParser::DEFAULT_MATERIAL[];
+
+ObjFileParser::ObjFileParser() :
+        m_DataIt(),
+        m_DataItEnd(),
+        m_pModel(nullptr),
+        m_uiLine(0),
+        m_buffer(),
+        m_pIO(nullptr),
+        m_progress(nullptr),
+        m_originalObjFileName() {
+    std::fill_n(m_buffer, Buffersize, '\0');
+}
+
+ObjFileParser::ObjFileParser(IOStreamBuffer<char> &streamBuffer, const std::string &modelName,
+        IOSystem *io, ProgressHandler *progress,
+        const std::string &originalObjFileName) :
+        m_DataIt(),
+        m_DataItEnd(),
+        m_pModel(nullptr),
+        m_uiLine(0),
+        m_buffer(),
+        m_pIO(io),
+        m_progress(progress),
+        m_originalObjFileName(originalObjFileName) {
+    std::fill_n(m_buffer, Buffersize, '\0');
+
+    // Create the model instance to store all the data
+    m_pModel.reset(new ObjFile::Model());
+    m_pModel->m_ModelName = modelName;
+
+    // create default material and store it
+    m_pModel->m_pDefaultMaterial = new ObjFile::Material;
+    m_pModel->m_pDefaultMaterial->MaterialName.Set(DEFAULT_MATERIAL);
+    m_pModel->m_MaterialLib.push_back(DEFAULT_MATERIAL);
+    m_pModel->m_MaterialMap[DEFAULT_MATERIAL] = m_pModel->m_pDefaultMaterial;
+
+    // Start parsing the file
+    parseFile(streamBuffer);
+}
+
+ObjFileParser::~ObjFileParser() {
+}
+
+void ObjFileParser::setBuffer(std::vector<char> &buffer) {
+    m_DataIt = buffer.begin();
+    m_DataItEnd = buffer.end();
+}
+
+ObjFile::Model *ObjFileParser::GetModel() const {
+    return m_pModel.get();
+}
+
+void ObjFileParser::parseFile(IOStreamBuffer<char> &streamBuffer) {
+    // only update every 100KB or it'll be too slow
+    //const unsigned int updateProgressEveryBytes = 100 * 1024;
+    unsigned int progressCounter = 0;
+    const unsigned int bytesToProcess = static_cast<unsigned int>(streamBuffer.size());
+    const unsigned int progressTotal = bytesToProcess;
+    unsigned int processed = 0;
+    size_t lastFilePos(0);
+
+    bool insideCstype = false;
+    std::vector<char> buffer;
+    while (streamBuffer.getNextDataLine(buffer, '\\')) {
+        m_DataIt = buffer.begin();
+        m_DataItEnd = buffer.end();
+
+        // Handle progress reporting
+        const size_t filePos(streamBuffer.getFilePos());
+        if (lastFilePos < filePos) {
+            processed = static_cast<unsigned int>(filePos);
+            lastFilePos = filePos;
+            progressCounter++;
+            m_progress->UpdateFileRead(processed, progressTotal);
+        }
+
+        // handle cstype section end (http://paulbourke.net/dataformats/obj/)
+        if (insideCstype) {
+            switch (*m_DataIt) {
+            case 'e': {
+                std::string name;
+                getNameNoSpace(m_DataIt, m_DataItEnd, name);
+                insideCstype = name != "end";
+            } break;
+            }
+            goto pf_skip_line;
+        }
+
+        // parse line
+        switch (*m_DataIt) {
+        case 'v': // Parse a vertex texture coordinate
+        {
+            ++m_DataIt;
+            if (*m_DataIt == ' ' || *m_DataIt == '\t') {
+                size_t numComponents = getNumComponentsInDataDefinition();
+                if (numComponents == 3) {
+                    // read in vertex definition
+                    getVector3(m_pModel->m_Vertices);
+                } else if (numComponents == 4) {
+                    // read in vertex definition (homogeneous coords)
+                    getHomogeneousVector3(m_pModel->m_Vertices);
+                } else if (numComponents == 6) {
+                    // read vertex and vertex-color
+                    getTwoVectors3(m_pModel->m_Vertices, m_pModel->m_VertexColors);
+                }
+            } else if (*m_DataIt == 't') {
+                // read in texture coordinate ( 2D or 3D )
+                ++m_DataIt;
+                size_t dim = getTexCoordVector(m_pModel->m_TextureCoord);
+                m_pModel->m_TextureCoordDim = std::max(m_pModel->m_TextureCoordDim, (unsigned int)dim);
+            } else if (*m_DataIt == 'n') {
+                // Read in normal vector definition
+                ++m_DataIt;
+                getVector3(m_pModel->m_Normals);
+            }
+        } break;
+
+        case 'p': // Parse a face, line or point statement
+        case 'l':
+        case 'f': {
+            getFace(*m_DataIt == 'f' ? aiPrimitiveType_POLYGON : (*m_DataIt == 'l' ? aiPrimitiveType_LINE : aiPrimitiveType_POINT));
+        } break;
+
+        case '#': // Parse a comment
+        {
+            getComment();
+        } break;
+
+        case 'u': // Parse a material desc. setter
+        {
+            std::string name;
+
+            getNameNoSpace(m_DataIt, m_DataItEnd, name);
+
+            size_t nextSpace = name.find(' ');
+            if (nextSpace != std::string::npos)
+                name = name.substr(0, nextSpace);
+
+            if (name == "usemtl") {
+                getMaterialDesc();
+            }
+        } break;
+
+        case 'm': // Parse a material library or merging group ('mg')
+        {
+            std::string name;
+
+            getNameNoSpace(m_DataIt, m_DataItEnd, name);
+
+            size_t nextSpace = name.find(' ');
+            if (nextSpace != std::string::npos)
+                name = name.substr(0, nextSpace);
+
+            if (name == "mg")
+                getGroupNumberAndResolution();
+            else if (name == "mtllib")
+                getMaterialLib();
+            else
+                goto pf_skip_line;
+        } break;
+
+        case 'g': // Parse group name
+        {
+            getGroupName();
+        } break;
+
+        case 's': // Parse group number
+        {
+            getGroupNumber();
+        } break;
+
+        case 'o': // Parse object name
+        {
+            getObjectName();
+        } break;
+
+        case 'c': // handle cstype section start
+        {
+            std::string name;
+            getNameNoSpace(m_DataIt, m_DataItEnd, name);
+            insideCstype = name == "cstype";
+            goto pf_skip_line;
+        } break;
+
+        default: {
+        pf_skip_line:
+            m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
+        } break;
+        }
+    }
+}
+
+void ObjFileParser::copyNextWord(char *pBuffer, size_t length) {
+    size_t index = 0;
+    m_DataIt = getNextWord<DataArrayIt>(m_DataIt, m_DataItEnd);
+    if (*m_DataIt == '\\') {
+        ++m_DataIt;
+        ++m_DataIt;
+        m_DataIt = getNextWord<DataArrayIt>(m_DataIt, m_DataItEnd);
+    }
+    while (m_DataIt != m_DataItEnd && !IsSpaceOrNewLine(*m_DataIt)) {
+        pBuffer[index] = *m_DataIt;
+        index++;
+        if (index == length - 1) {
+            break;
+        }
+        ++m_DataIt;
+    }
+
+    ai_assert(index < length);
+    pBuffer[index] = '\0';
+}
+
+static bool isDataDefinitionEnd(const char *tmp) {
+    if (*tmp == '\\') {
+        tmp++;
+        if (IsLineEnd(*tmp)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+static bool isNanOrInf(const char *in) {
+    // Look for "nan" or "inf", case insensitive
+    return ((in[0] == 'N' || in[0] == 'n') && ASSIMP_strincmp(in, "nan", 3) == 0) ||
+           ((in[0] == 'I' || in[0] == 'i') && ASSIMP_strincmp(in, "inf", 3) == 0);
+}
+
+size_t ObjFileParser::getNumComponentsInDataDefinition() {
+    size_t numComponents(0);
+    const char *tmp(&m_DataIt[0]);
+    bool end_of_definition = false;
+    while (!end_of_definition) {
+        if (isDataDefinitionEnd(tmp)) {
+            tmp += 2;
+        } else if (IsLineEnd(*tmp)) {
+            end_of_definition = true;
+        }
+        if (!SkipSpaces(&tmp)) {
+            break;
+        }
+        const bool isNum(IsNumeric(*tmp) || isNanOrInf(tmp));
+        SkipToken(tmp);
+        if (isNum) {
+            ++numComponents;
+        }
+        if (!SkipSpaces(&tmp)) {
+            break;
+        }
+    }
+    return numComponents;
+}
+
+size_t ObjFileParser::getTexCoordVector(std::vector<aiVector3D> &point3d_array) {
+    size_t numComponents = getNumComponentsInDataDefinition();
+    ai_real x, y, z;
+    if (2 == numComponents) {
+        copyNextWord(m_buffer, Buffersize);
+        x = (ai_real)fast_atof(m_buffer);
+
+        copyNextWord(m_buffer, Buffersize);
+        y = (ai_real)fast_atof(m_buffer);
+        z = 0.0;
+    } else if (3 == numComponents) {
+        copyNextWord(m_buffer, Buffersize);
+        x = (ai_real)fast_atof(m_buffer);
+
+        copyNextWord(m_buffer, Buffersize);
+        y = (ai_real)fast_atof(m_buffer);
+
+        copyNextWord(m_buffer, Buffersize);
+        z = (ai_real)fast_atof(m_buffer);
+    } else {
+        throw DeadlyImportError("OBJ: Invalid number of components");
+    }
+
+    // Coerce nan and inf to 0 as is the OBJ default value
+    if (!std::isfinite(x))
+        x = 0;
+
+    if (!std::isfinite(y))
+        y = 0;
+
+    if (!std::isfinite(z))
+        z = 0;
+
+    point3d_array.emplace_back(x, y, z);
+    m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
+    return numComponents;
+}
+
+void ObjFileParser::getVector3(std::vector<aiVector3D> &point3d_array) {
+    ai_real x, y, z;
+    copyNextWord(m_buffer, Buffersize);
+    x = (ai_real)fast_atof(m_buffer);
+
+    copyNextWord(m_buffer, Buffersize);
+    y = (ai_real)fast_atof(m_buffer);
+
+    copyNextWord(m_buffer, Buffersize);
+    z = (ai_real)fast_atof(m_buffer);
+
+    point3d_array.emplace_back(x, y, z);
+    m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
+}
+
+void ObjFileParser::getHomogeneousVector3(std::vector<aiVector3D> &point3d_array) {
+    ai_real x, y, z, w;
+    copyNextWord(m_buffer, Buffersize);
+    x = (ai_real)fast_atof(m_buffer);
+
+    copyNextWord(m_buffer, Buffersize);
+    y = (ai_real)fast_atof(m_buffer);
+
+    copyNextWord(m_buffer, Buffersize);
+    z = (ai_real)fast_atof(m_buffer);
+
+    copyNextWord(m_buffer, Buffersize);
+    w = (ai_real)fast_atof(m_buffer);
+
+    if (w == 0)
+        throw DeadlyImportError("OBJ: Invalid component in homogeneous vector (Division by zero)");
+
+    point3d_array.emplace_back(x / w, y / w, z / w);
+    m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
+}
+
+void ObjFileParser::getTwoVectors3(std::vector<aiVector3D> &point3d_array_a, std::vector<aiVector3D> &point3d_array_b) {
+    ai_real x, y, z;
+    copyNextWord(m_buffer, Buffersize);
+    x = (ai_real)fast_atof(m_buffer);
+
+    copyNextWord(m_buffer, Buffersize);
+    y = (ai_real)fast_atof(m_buffer);
+
+    copyNextWord(m_buffer, Buffersize);
+    z = (ai_real)fast_atof(m_buffer);
+
+    point3d_array_a.emplace_back(x, y, z);
+
+    copyNextWord(m_buffer, Buffersize);
+    x = (ai_real)fast_atof(m_buffer);
+
+    copyNextWord(m_buffer, Buffersize);
+    y = (ai_real)fast_atof(m_buffer);
+
+    copyNextWord(m_buffer, Buffersize);
+    z = (ai_real)fast_atof(m_buffer);
+
+    point3d_array_b.emplace_back(x, y, z);
+
+    m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
+}
+
+void ObjFileParser::getVector2(std::vector<aiVector2D> &point2d_array) {
+    ai_real x, y;
+    copyNextWord(m_buffer, Buffersize);
+    x = (ai_real)fast_atof(m_buffer);
+
+    copyNextWord(m_buffer, Buffersize);
+    y = (ai_real)fast_atof(m_buffer);
+
+    point2d_array.emplace_back(x, y);
+
+    m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
+}
+
+static const std::string DefaultObjName = "defaultobject";
+
+void ObjFileParser::getFace(aiPrimitiveType type) {
+    m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
+    if (m_DataIt == m_DataItEnd || *m_DataIt == '\0') {
+        return;
+    }
+
+    ObjFile::Face *face = new ObjFile::Face(type);
+    bool hasNormal = false;
+
+    const int vSize = static_cast<unsigned int>(m_pModel->m_Vertices.size());
+    const int vtSize = static_cast<unsigned int>(m_pModel->m_TextureCoord.size());
+    const int vnSize = static_cast<unsigned int>(m_pModel->m_Normals.size());
+
+    const bool vt = (!m_pModel->m_TextureCoord.empty());
+    const bool vn = (!m_pModel->m_Normals.empty());
+    int iPos = 0;
+    while (m_DataIt != m_DataItEnd) {
+        int iStep = 1;
+
+        if (IsLineEnd(*m_DataIt)) {
+            break;
+        }
+
+        if (*m_DataIt == '/') {
+            if (type == aiPrimitiveType_POINT) {
+                ASSIMP_LOG_ERROR("Obj: Separator unexpected in point statement");
+            }
+            iPos++;
+        } else if (IsSpaceOrNewLine(*m_DataIt)) {
+            iPos = 0;
+        } else {
+            //OBJ USES 1 Base ARRAYS!!!!
+            const int iVal(::atoi(&(*m_DataIt)));
+
+            // increment iStep position based off of the sign and # of digits
+            int tmp = iVal;
+            if (iVal < 0) {
+                ++iStep;
+            }
+            while ((tmp = tmp / 10) != 0) {
+                ++iStep;
+            }
+
+            if (iPos == 1 && !vt && vn)
+                iPos = 2; // skip texture coords for normals if there are no tex coords
+
+            if (iVal > 0) {
+                // Store parsed index
+                if (0 == iPos) {
+                    face->m_vertices.push_back(iVal - 1);
+                } else if (1 == iPos) {
+                    face->m_texturCoords.push_back(iVal - 1);
+                } else if (2 == iPos) {
+                    face->m_normals.push_back(iVal - 1);
+                    hasNormal = true;
+                } else {
+                    reportErrorTokenInFace();
+                }
+            } else if (iVal < 0) {
+                // Store relatively index
+                if (0 == iPos) {
+                    face->m_vertices.push_back(vSize + iVal);
+                } else if (1 == iPos) {
+                    face->m_texturCoords.push_back(vtSize + iVal);
+                } else if (2 == iPos) {
+                    face->m_normals.push_back(vnSize + iVal);
+                    hasNormal = true;
+                } else {
+                    reportErrorTokenInFace();
+                }
+            } else {
+                //On error, std::atoi will return 0 which is not a valid value
+                delete face;
+                throw DeadlyImportError("OBJ: Invalid face indice");
+            }
+        }
+        m_DataIt += iStep;
+    }
+
+    if (face->m_vertices.empty()) {
+        ASSIMP_LOG_ERROR("Obj: Ignoring empty face");
+        // skip line and clean up
+        m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
+        delete face;
+        return;
+    }
+
+    // Set active material, if one set
+    if (nullptr != m_pModel->m_pCurrentMaterial) {
+        face->m_pMaterial = m_pModel->m_pCurrentMaterial;
+    } else {
+        face->m_pMaterial = m_pModel->m_pDefaultMaterial;
+    }
+
+    // Create a default object, if nothing is there
+    if (nullptr == m_pModel->m_pCurrent) {
+        createObject(DefaultObjName);
+    }
+
+    // Assign face to mesh
+    if (nullptr == m_pModel->m_pCurrentMesh) {
+        createMesh(DefaultObjName);
+    }
+
+    // Store the face
+    m_pModel->m_pCurrentMesh->m_Faces.push_back(face);
+    m_pModel->m_pCurrentMesh->m_uiNumIndices += (unsigned int)face->m_vertices.size();
+    m_pModel->m_pCurrentMesh->m_uiUVCoordinates[0] += (unsigned int)face->m_texturCoords.size();
+    if (!m_pModel->m_pCurrentMesh->m_hasNormals && hasNormal) {
+        m_pModel->m_pCurrentMesh->m_hasNormals = true;
+    }
+    // Skip the rest of the line
+    m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
+}
+
+void ObjFileParser::getMaterialDesc() {
+    // Get next data for material data
+    m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
+    if (m_DataIt == m_DataItEnd) {
+        return;
+    }
+
+    char *pStart = &(*m_DataIt);
+    while (m_DataIt != m_DataItEnd && !IsLineEnd(*m_DataIt)) {
+        ++m_DataIt;
+    }
+
+    // In some cases we should ignore this 'usemtl' command, this variable helps us to do so
+    bool skip = false;
+
+    // Get name
+    std::string strName(pStart, &(*m_DataIt));
+    strName = trim_whitespaces(strName);
+    if (strName.empty())
+        skip = true;
+
+    // If the current mesh has the same material, we simply ignore that 'usemtl' command
+    // There is no need to create another object or even mesh here
+    if (m_pModel->m_pCurrentMaterial && m_pModel->m_pCurrentMaterial->MaterialName == aiString(strName)) {
+        skip = true;
+    }
+
+    if (!skip) {
+        // Search for material
+        std::map<std::string, ObjFile::Material *>::iterator it = m_pModel->m_MaterialMap.find(strName);
+        if (it == m_pModel->m_MaterialMap.end()) {
+            // Not found, so we don't know anything about the material except for its name.
+            // This may be the case if the material library is missing. We don't want to lose all
+            // materials if that happens, so create a new named material instead of discarding it
+            // completely.
+            ASSIMP_LOG_ERROR("OBJ: failed to locate material ", strName, ", creating new material");
+            m_pModel->m_pCurrentMaterial = new ObjFile::Material();
+            m_pModel->m_pCurrentMaterial->MaterialName.Set(strName);
+            m_pModel->m_MaterialLib.push_back(strName);
+            m_pModel->m_MaterialMap[strName] = m_pModel->m_pCurrentMaterial;
+        } else {
+            // Found, using detected material
+            m_pModel->m_pCurrentMaterial = (*it).second;
+        }
+
+        if (needsNewMesh(strName)) {
+            createMesh(strName);
+        }
+
+        m_pModel->m_pCurrentMesh->m_uiMaterialIndex = getMaterialIndex(strName);
+    }
+
+    // Skip rest of line
+    m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
+}
+
+// -------------------------------------------------------------------
+//  Get a comment, values will be skipped
+void ObjFileParser::getComment() {
+    m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
+}
+
+// -------------------------------------------------------------------
+//  Get material library from file.
+void ObjFileParser::getMaterialLib() {
+    // Translate tuple
+    m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
+    if (m_DataIt == m_DataItEnd) {
+        return;
+    }
+
+    char *pStart = &(*m_DataIt);
+    while (m_DataIt != m_DataItEnd && !IsLineEnd(*m_DataIt)) {
+        ++m_DataIt;
+    }
+
+    // Check for existence
+    const std::string strMatName(pStart, &(*m_DataIt));
+    std::string absName;
+
+    // Check if directive is valid.
+    if (0 == strMatName.length()) {
+        ASSIMP_LOG_WARN("OBJ: no name for material library specified.");
+        return;
+    }
+
+    if (m_pIO->StackSize() > 0) {
+        std::string path = m_pIO->CurrentDirectory();
+        if ('/' != *path.rbegin()) {
+            path += '/';
+        }
+        absName += path;
+        absName += strMatName;
+    } else {
+        absName = strMatName;
+    }
+
+    IOStream *pFile = m_pIO->Open(absName);
+    if (nullptr == pFile) {
+        ASSIMP_LOG_ERROR("OBJ: Unable to locate material file ", strMatName);
+        std::string strMatFallbackName = m_originalObjFileName.substr(0, m_originalObjFileName.length() - 3) + "mtl";
+        ASSIMP_LOG_INFO("OBJ: Opening fallback material file ", strMatFallbackName);
+        pFile = m_pIO->Open(strMatFallbackName);
+        if (!pFile) {
+            ASSIMP_LOG_ERROR("OBJ: Unable to locate fallback material file ", strMatFallbackName);
+            m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
+            return;
+        }
+    }
+
+    // Import material library data from file.
+    // Some exporters (e.g. Silo) will happily write out empty
+    // material files if the model doesn't use any materials, so we
+    // allow that.
+    std::vector<char> buffer;
+    BaseImporter::TextFileToBuffer(pFile, buffer, BaseImporter::ALLOW_EMPTY);
+    m_pIO->Close(pFile);
+
+    // Importing the material library
+    ObjFileMtlImporter mtlImporter(buffer, strMatName, m_pModel.get());
+}
+
+// -------------------------------------------------------------------
+//  Set a new material definition as the current material.
+void ObjFileParser::getNewMaterial() {
+    m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
+    m_DataIt = getNextWord<DataArrayIt>(m_DataIt, m_DataItEnd);
+    if (m_DataIt == m_DataItEnd) {
+        return;
+    }
+
+    char *pStart = &(*m_DataIt);
+    std::string strMat(pStart, *m_DataIt);
+    while (m_DataIt != m_DataItEnd && IsSpaceOrNewLine(*m_DataIt)) {
+        ++m_DataIt;
+    }
+    std::map<std::string, ObjFile::Material *>::iterator it = m_pModel->m_MaterialMap.find(strMat);
+    if (it == m_pModel->m_MaterialMap.end()) {
+        // Show a warning, if material was not found
+        ASSIMP_LOG_WARN("OBJ: Unsupported material requested: ", strMat);
+        m_pModel->m_pCurrentMaterial = m_pModel->m_pDefaultMaterial;
+    } else {
+        // Set new material
+        if (needsNewMesh(strMat)) {
+            createMesh(strMat);
+        }
+        m_pModel->m_pCurrentMesh->m_uiMaterialIndex = getMaterialIndex(strMat);
+    }
+
+    m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
+}
+
+// -------------------------------------------------------------------
+int ObjFileParser::getMaterialIndex(const std::string &strMaterialName) {
+    int mat_index = -1;
+    if (strMaterialName.empty()) {
+        return mat_index;
+    }
+    for (size_t index = 0; index < m_pModel->m_MaterialLib.size(); ++index) {
+        if (strMaterialName == m_pModel->m_MaterialLib[index]) {
+            mat_index = (int)index;
+            break;
+        }
+    }
+    return mat_index;
+}
+
+// -------------------------------------------------------------------
+//  Getter for a group name.
+void ObjFileParser::getGroupName() {
+    std::string groupName;
+
+    // here we skip 'g ' from line
+    m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
+    m_DataIt = getName<DataArrayIt>(m_DataIt, m_DataItEnd, groupName);
+    if (isEndOfBuffer(m_DataIt, m_DataItEnd)) {
+        return;
+    }
+
+    // Change active group, if necessary
+    if (m_pModel->m_strActiveGroup != groupName) {
+        // Search for already existing entry
+        ObjFile::Model::ConstGroupMapIt it = m_pModel->m_Groups.find(groupName);
+
+        // We are mapping groups into the object structure
+        createObject(groupName);
+
+        // New group name, creating a new entry
+        if (it == m_pModel->m_Groups.end()) {
+            std::vector<unsigned int> *pFaceIDArray = new std::vector<unsigned int>;
+            m_pModel->m_Groups[groupName] = pFaceIDArray;
+            m_pModel->m_pGroupFaceIDs = (pFaceIDArray);
+        } else {
+            m_pModel->m_pGroupFaceIDs = (*it).second;
+        }
+        m_pModel->m_strActiveGroup = groupName;
+    }
+    m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
+}
+
+// -------------------------------------------------------------------
+//  Not supported
+void ObjFileParser::getGroupNumber() {
+    // Not used
+
+    m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
+}
+
+// -------------------------------------------------------------------
+//  Not supported
+void ObjFileParser::getGroupNumberAndResolution() {
+    // Not used
+
+    m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
+}
+
+// -------------------------------------------------------------------
+//  Stores values for a new object instance, name will be used to
+//  identify it.
+void ObjFileParser::getObjectName() {
+    m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
+    if (m_DataIt == m_DataItEnd) {
+        return;
+    }
+    char *pStart = &(*m_DataIt);
+    while (m_DataIt != m_DataItEnd && !IsSpaceOrNewLine(*m_DataIt)) {
+        ++m_DataIt;
+    }
+
+    std::string strObjectName(pStart, &(*m_DataIt));
+    if (!strObjectName.empty()) {
+        // Reset current object
+        m_pModel->m_pCurrent = nullptr;
+
+        // Search for actual object
+        for (std::vector<ObjFile::Object *>::const_iterator it = m_pModel->m_Objects.begin();
+                it != m_pModel->m_Objects.end();
+                ++it) {
+            if ((*it)->m_strObjName == strObjectName) {
+                m_pModel->m_pCurrent = *it;
+                break;
+            }
+        }
+
+        // Allocate a new object, if current one was not found before
+        if (nullptr == m_pModel->m_pCurrent) {
+            createObject(strObjectName);
+        }
+    }
+    m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
+}
+// -------------------------------------------------------------------
+//  Creates a new object instance
+void ObjFileParser::createObject(const std::string &objName) {
+    ai_assert(nullptr != m_pModel);
+
+    m_pModel->m_pCurrent = new ObjFile::Object;
+    m_pModel->m_pCurrent->m_strObjName = objName;
+    m_pModel->m_Objects.push_back(m_pModel->m_pCurrent);
+
+    createMesh(objName);
+
+    if (m_pModel->m_pCurrentMaterial) {
+        m_pModel->m_pCurrentMesh->m_uiMaterialIndex =
+                getMaterialIndex(m_pModel->m_pCurrentMaterial->MaterialName.data);
+        m_pModel->m_pCurrentMesh->m_pMaterial = m_pModel->m_pCurrentMaterial;
+    }
+}
+// -------------------------------------------------------------------
+//  Creates a new mesh
+void ObjFileParser::createMesh(const std::string &meshName) {
+    ai_assert(nullptr != m_pModel);
+
+    m_pModel->m_pCurrentMesh = new ObjFile::Mesh(meshName);
+    m_pModel->m_Meshes.push_back(m_pModel->m_pCurrentMesh);
+    unsigned int meshId = static_cast<unsigned int>(m_pModel->m_Meshes.size() - 1);
+    if (nullptr != m_pModel->m_pCurrent) {
+        m_pModel->m_pCurrent->m_Meshes.push_back(meshId);
+    } else {
+        ASSIMP_LOG_ERROR("OBJ: No object detected to attach a new mesh instance.");
+    }
+}
+
+// -------------------------------------------------------------------
+//  Returns true, if a new mesh must be created.
+bool ObjFileParser::needsNewMesh(const std::string &materialName) {
+    // If no mesh data yet
+    if (m_pModel->m_pCurrentMesh == nullptr) {
+        return true;
+    }
+    bool newMat = false;
+    int matIdx = getMaterialIndex(materialName);
+    int curMatIdx = m_pModel->m_pCurrentMesh->m_uiMaterialIndex;
+    if (curMatIdx != int(ObjFile::Mesh::NoMaterial) && curMatIdx != matIdx
+            // no need create a new mesh if no faces in current
+            // lets say 'usemtl' goes straight after 'g'
+            && !m_pModel->m_pCurrentMesh->m_Faces.empty()) {
+        // New material -> only one material per mesh, so we need to create a new
+        // material
+        newMat = true;
+    }
+    return newMat;
+}
+
+// -------------------------------------------------------------------
+//  Shows an error in parsing process.
+void ObjFileParser::reportErrorTokenInFace() {
+    m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
+    ASSIMP_LOG_ERROR("OBJ: Not supported token in face description detected");
+}
+
+// -------------------------------------------------------------------
+
+} // Namespace Assimp
+
+#endif // !! ASSIMP_BUILD_NO_OBJ_IMPORTER

+ 165 - 0
Engine/lib/assimp/code/AssetLib/Obj/ObjFileParser.h

@@ -0,0 +1,165 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2020, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+#ifndef OBJ_FILEPARSER_H_INC
+#define OBJ_FILEPARSER_H_INC
+
+#include <assimp/IOStreamBuffer.h>
+#include <assimp/material.h>
+#include <assimp/mesh.h>
+#include <assimp/vector2.h>
+#include <assimp/vector3.h>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace Assimp {
+
+namespace ObjFile {
+struct Model;
+struct Object;
+struct Material;
+struct Point3;
+struct Point2;
+} // namespace ObjFile
+
+class ObjFileImporter;
+class IOSystem;
+class ProgressHandler;
+
+/// \class  ObjFileParser
+/// \brief  Parser for a obj waveform file
+class ASSIMP_API ObjFileParser {
+public:
+    static const size_t Buffersize = 4096;
+    typedef std::vector<char> DataArray;
+    typedef std::vector<char>::iterator DataArrayIt;
+    typedef std::vector<char>::const_iterator ConstDataArrayIt;
+
+    /// @brief  The default constructor.
+    ObjFileParser();
+    /// @brief  Constructor with data array.
+    ObjFileParser(IOStreamBuffer<char> &streamBuffer, const std::string &modelName, IOSystem *io, ProgressHandler *progress, const std::string &originalObjFileName);
+    /// @brief  Destructor
+    ~ObjFileParser();
+    /// @brief  If you want to load in-core data.
+    void setBuffer(std::vector<char> &buffer);
+    /// @brief  Model getter.
+    ObjFile::Model *GetModel() const;
+
+    ObjFileParser(const ObjFileParser&) = delete;
+    ObjFileParser &operator=(const ObjFileParser& ) = delete;
+
+protected:
+    /// Parse the loaded file
+    void parseFile(IOStreamBuffer<char> &streamBuffer);
+    /// Method to copy the new delimited word in the current line.
+    void copyNextWord(char *pBuffer, size_t length);
+    /// Method to copy the new line.
+    //    void copyNextLine(char *pBuffer, size_t length);
+    /// Get the number of components in a line.
+    size_t getNumComponentsInDataDefinition();
+    /// Stores the vector
+    size_t getTexCoordVector(std::vector<aiVector3D> &point3d_array);
+    /// Stores the following 3d vector.
+    void getVector3(std::vector<aiVector3D> &point3d_array);
+    /// Stores the following homogeneous vector as a 3D vector
+    void getHomogeneousVector3(std::vector<aiVector3D> &point3d_array);
+    /// Stores the following two 3d vectors on the line.
+    void getTwoVectors3(std::vector<aiVector3D> &point3d_array_a, std::vector<aiVector3D> &point3d_array_b);
+    /// Stores the following 3d vector.
+    void getVector2(std::vector<aiVector2D> &point2d_array);
+    /// Stores the following face.
+    void getFace(aiPrimitiveType type);
+    /// Reads the material description.
+    void getMaterialDesc();
+    /// Gets a comment.
+    void getComment();
+    /// Gets a a material library.
+    void getMaterialLib();
+    /// Creates a new material.
+    void getNewMaterial();
+    /// Gets the group name from file.
+    void getGroupName();
+    /// Gets the group number from file.
+    void getGroupNumber();
+    /// Gets the group number and resolution from file.
+    void getGroupNumberAndResolution();
+    /// Returns the index of the material. Is -1 if not material was found.
+    int getMaterialIndex(const std::string &strMaterialName);
+    /// Parse object name
+    void getObjectName();
+    /// Creates a new object.
+    void createObject(const std::string &strObjectName);
+    /// Creates a new mesh.
+    void createMesh(const std::string &meshName);
+    /// Returns true, if a new mesh instance must be created.
+    bool needsNewMesh(const std::string &rMaterialName);
+    /// Error report in token
+    void reportErrorTokenInFace();
+
+private:
+    // Copy and assignment constructor should be private
+    // because the class contains pointer to allocated memory
+
+    /// Default material name
+    static constexpr char DEFAULT_MATERIAL[] = AI_DEFAULT_MATERIAL_NAME;
+    //! Iterator to current position in buffer
+    DataArrayIt m_DataIt;
+    //! Iterator to end position of buffer
+    DataArrayIt m_DataItEnd;
+    //! Pointer to model instance
+    std::unique_ptr<ObjFile::Model> m_pModel;
+    //! Current line (for debugging)
+    unsigned int m_uiLine;
+    //! Helper buffer
+    char m_buffer[Buffersize];
+    /// Pointer to IO system instance.
+    IOSystem *m_pIO;
+    //! Pointer to progress handler
+    ProgressHandler *m_progress;
+    /// Path to the current model, name of the obj file where the buffer comes from
+    const std::string m_originalObjFileName;
+};
+
+} // Namespace Assimp
+
+#endif

+ 284 - 0
Engine/lib/assimp/code/AssetLib/Obj/ObjTools.h

@@ -0,0 +1,284 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2021, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file   ObjTools.h
+ *  @brief  Some helpful templates for text parsing
+ */
+#ifndef OBJ_TOOLS_H_INC
+#define OBJ_TOOLS_H_INC
+
+#include <assimp/ParsingUtils.h>
+#include <assimp/fast_atof.h>
+#include <vector>
+
+namespace Assimp {
+
+/** 
+ *  @brief  Returns true, if the last entry of the buffer is reached.
+ *  @param[in] it   Iterator of current position.
+ *  @param[in] end  Iterator with end of buffer.
+ *  @return true, if the end of the buffer is reached.
+ */
+template <class char_t>
+inline bool isEndOfBuffer(char_t it, char_t end) {
+    if (it == end) {
+        return true;
+    }
+    --end;
+
+    return (it == end);
+}
+
+/** 
+ *  @brief  Returns next word separated by a space
+ *  @param[in] pBuffer  Pointer to data buffer
+ *  @param[in] pEnd     Pointer to end of buffer
+ *  @return Pointer to next space
+ */
+template <class Char_T>
+inline Char_T getNextWord(Char_T pBuffer, Char_T pEnd) {
+    while (!isEndOfBuffer(pBuffer, pEnd)) {
+        if (!IsSpaceOrNewLine(*pBuffer) || IsLineEnd(*pBuffer)) {
+            break;
+        }
+        ++pBuffer;
+    }
+
+    return pBuffer;
+}
+
+/** 
+ *  @brief  Returns pointer a next token
+ *  @param[in] pBuffer  Pointer to data buffer
+ *  @param[in] pEnd     Pointer to end of buffer
+ *  @return Pointer to next token
+ */
+template <class Char_T>
+inline Char_T getNextToken(Char_T pBuffer, Char_T pEnd) {
+    while (!isEndOfBuffer(pBuffer, pEnd)) {
+        if (IsSpaceOrNewLine(*pBuffer)) {
+            break;
+        }
+        ++pBuffer;
+    }
+    return getNextWord(pBuffer, pEnd);
+}
+
+/** 
+ *  @brief  Skips a line
+ *  @param[in]  it      Iterator set to current position
+ *  @param[in]  end     Iterator set to end of scratch buffer for readout
+ *  @param[out] uiLine  Current line number in format
+ *  @return Current-iterator with new position
+ */
+template <class char_t>
+inline char_t skipLine(char_t it, char_t end, unsigned int &uiLine) {
+    while (!isEndOfBuffer(it, end) && !IsLineEnd(*it)) {
+        ++it;
+    }
+
+    if (it != end) {
+        ++it;
+        ++uiLine;
+    }
+    // fix .. from time to time there are spaces at the beginning of a material line
+    while (it != end && (*it == '\t' || *it == ' ')) {
+        ++it;
+    }
+
+    return it;
+}
+
+/** 
+ *  @brief  Get a name from the current line. Preserve space in the middle,
+ *    but trim it at the end.
+ *  @param[in]  it      set to current position
+ *  @param[in]  end     set to end of scratch buffer for readout
+ *  @param[out] name    Separated name
+ *  @return Current-iterator with new position
+ */
+template <class char_t>
+inline char_t getName(char_t it, char_t end, std::string &name) {
+    name = "";
+    if (isEndOfBuffer(it, end)) {
+        return end;
+    }
+
+    char *pStart = &(*it);
+    while (!isEndOfBuffer(it, end) && !IsLineEnd(*it)) {
+        ++it;
+    }
+
+    while (IsSpace(*it)) {
+        --it;
+    }
+    // Get name
+    // if there is no name, and the previous char is a separator, come back to start
+    while (&(*it) < pStart) {
+        ++it;
+    }
+    std::string strName(pStart, &(*it));
+    if (!strName.empty()) {
+        name = strName;
+    } 
+    
+
+    return it;
+}
+
+/** 
+ *  @brief  Get a name from the current line. Do not preserve space
+ *    in the middle, but trim it at the end.
+ *  @param  it      set to current position
+ *  @param  end     set to end of scratch buffer for readout
+ *  @param  name    Separated name
+ *  @return Current-iterator with new position
+ */
+template <class char_t>
+inline char_t getNameNoSpace(char_t it, char_t end, std::string &name) {
+    name = "";
+    if (isEndOfBuffer(it, end)) {
+        return end;
+    }
+
+    char *pStart = &(*it);
+    while (!isEndOfBuffer(it, end) && !IsLineEnd(*it) && !IsSpaceOrNewLine(*it)) {
+        ++it;
+    }
+
+    while (isEndOfBuffer(it, end) || IsLineEnd(*it) || IsSpaceOrNewLine(*it)) {
+        --it;
+    }
+    ++it;
+
+    // Get name
+    // if there is no name, and the previous char is a separator, come back to start
+    while (&(*it) < pStart) {
+        ++it;
+    }
+    std::string strName(pStart, &(*it));
+    if (!strName.empty()) {
+        name = strName;
+    }
+        
+    return it;
+}
+
+/** 
+ *  @brief  Get next word from given line
+ *  @param[in] it      set to current position
+ *  @param[in] end     set to end of scratch buffer for readout
+ *  @param[in] pBuffer Buffer for next word
+ *  @param[in] length  Buffer length
+ *  @return Current-iterator with new position
+ */
+template <class char_t>
+inline char_t CopyNextWord(char_t it, char_t end, char *pBuffer, size_t length) {
+    size_t index = 0;
+    it = getNextWord<char_t>(it, end);
+    while (!IsSpaceOrNewLine(*it) && !isEndOfBuffer(it, end)) {
+        pBuffer[index] = *it;
+        ++index;
+        if (index == length - 1) {
+            break;
+        }
+        ++it;
+    }
+    pBuffer[index] = '\0';
+    return it;
+}
+
+/** 
+ *  @brief  Get next float from given line
+ *  @param[in]  it      set to current position
+ *  @param[in]  end     set to end of scratch buffer for readout
+ *  @param[out] value   Separated float value.
+ *  @return Current-iterator with new position
+ */
+template <class char_t>
+inline char_t getFloat(char_t it, char_t end, ai_real &value) {
+    static const size_t BUFFERSIZE = 1024;
+    char buffer[BUFFERSIZE];
+    it = CopyNextWord<char_t>(it, end, buffer, BUFFERSIZE);
+    value = (ai_real)fast_atof(buffer);
+
+    return it;
+}
+
+/**
+ *  @brief  Will remove white-spaces for a string.
+ *  @param[in] str  The string to clean
+ *  @return The trimmed string.
+ */
+template <class string_type>
+inline string_type trim_whitespaces(string_type str) {
+    while (!str.empty() && IsSpace(str[0])) {
+        str.erase(0);
+    }
+    while (!str.empty() && IsSpace(str[str.length() - 1])) {
+        str.erase(str.length() - 1);
+    }
+    return str;
+}
+
+/**
+ *  @brief  Checks for a line-end.
+ *  @param[in] it   Current iterator in string.
+ *  @param[in] end  End of the string.
+ *  @return The trimmed string.
+ */
+template <class T>
+bool hasLineEnd(T it, T end) {
+    bool hasLineEnd = false;
+    while (!isEndOfBuffer(it, end)) {
+        ++it;
+        if (IsLineEnd(it)) {
+            hasLineEnd = true;
+            break;
+        }
+    }
+
+    return hasLineEnd;
+}
+
+} // Namespace Assimp
+
+#endif // OBJ_TOOLS_H_INC