diff --git a/.gitmodules b/.gitmodules index 410b338..34cf744 100644 --- a/.gitmodules +++ b/.gitmodules @@ -32,3 +32,6 @@ [submodule "thirdParty/imgui"] path = thirdParty/imgui url = https://github.com/ocornut/imgui +[submodule "thirdParty/assimp"] + path = thirdParty/assimp + url = https://github.com/assimp/assimp diff --git a/Future/CMakeLists.txt b/Future/CMakeLists.txt index 1bb47fe..b15043c 100644 --- a/Future/CMakeLists.txt +++ b/Future/CMakeLists.txt @@ -13,6 +13,7 @@ add_subdirectory(${THIRD_PARTY_LIB_DIR}/glfw glfw) add_subdirectory(${THIRD_PARTY_LIB_DIR}/glm glm) add_subdirectory(${THIRD_PARTY_LIB_DIR}/json json) add_subdirectory(${THIRD_PARTY_LIB_DIR}/tinygltf tinygltf) +add_subdirectory(${THIRD_PARTY_LIB_DIR}/assimp assimp) add_library(Future SHARED # GLAD @@ -41,10 +42,11 @@ add_library(Future SHARED src/Rendering/Textures/Texture.hpp src/Rendering/Camera.cpp src/Rendering/Camera.hpp - src/Rendering/Mesh.cpp - src/Rendering/Mesh.hpp - src/Rendering/Model.cpp - src/Rendering/Model.hpp + src/Rendering/Model/Mesh.cpp + src/Rendering/Model/Mesh.hpp + src/Rendering/Model/Model.cpp + src/Rendering/Model/Model.hpp + src/Rendering/Vertex.hpp ) target_include_directories(Future PUBLIC @@ -55,6 +57,7 @@ target_include_directories(Future PUBLIC ${THIRD_PARTY_LIB_DIR}/glm/glm/ ${THIRD_PARTY_LIB_DIR}/json/include ${THIRD_PARTY_LIB_DIR}/tinygltf/ + ${THIRD_PARTY_LIB_DIR}/assimp/include ) target_link_libraries(Future PUBLIC spdlog::spdlog $<$:ws2_32>) @@ -63,6 +66,7 @@ target_link_libraries(Future PUBLIC SDL2::SDL2) target_link_libraries(Future PUBLIC glfw) target_link_libraries(Future PUBLIC glm) target_link_libraries(Future PUBLIC tinygltf) +target_link_libraries(Future PUBLIC assimp) add_custom_command(TARGET Future POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory diff --git a/Future/src/Rendering/Buffers/VBO.hpp b/Future/src/Rendering/Buffers/VBO.hpp index 5132d4d..f6baa66 100644 --- a/Future/src/Rendering/Buffers/VBO.hpp +++ b/Future/src/Rendering/Buffers/VBO.hpp @@ -4,14 +4,7 @@ #include "vec2.hpp" #include "vec3.hpp" - -struct Vertex -{ - glm::vec3 Position; - glm::vec3 Normal; - glm::vec3 color; - glm::vec2 TexCoords; -}; +#include "../Vertex.hpp" namespace Future { diff --git a/Future/src/Rendering/Model.cpp b/Future/src/Rendering/Model.cpp deleted file mode 100644 index bcfdae1..0000000 --- a/Future/src/Rendering/Model.cpp +++ /dev/null @@ -1,347 +0,0 @@ -#include "Model.hpp" - -namespace Future -{ - Model::Model(const char *file) - { - std::string text = GetFileContents(file); - JSON = nlohmann::json::parse(text); - - Model::file = file; - data = getData(); - - traverseNode(0); - } - - void Model::Draw(Shaders& shader, Camera& camera) - { - // Go over all meshes and draw each one - for (unsigned int i = 0; i < meshes.size(); i++) - { - meshes[i].Mesh::Draw(shader, camera, matricesMeshes[i]); - } - } - - void Model::loadMesh(unsigned int indMesh) - { - // Get all accessor indices - unsigned int posAccInd = JSON["meshes"][indMesh]["primitives"][0]["attributes"]["POSITION"]; - unsigned int normalAccInd = JSON["meshes"][indMesh]["primitives"][0]["attributes"]["NORMAL"]; - unsigned int texAccInd = JSON["meshes"][indMesh]["primitives"][0]["attributes"]["TEXCOORD_0"]; - unsigned int indAccInd = JSON["meshes"][indMesh]["primitives"][0]["indices"]; - - // Use accessor indices to get all vertices components - std::vector posVec = getFloats(JSON["accessors"][posAccInd]); - std::vector positions = groupFloatsVec3(posVec); - std::vector normalVec = getFloats(JSON["accessors"][normalAccInd]); - std::vector normals = groupFloatsVec3(normalVec); - std::vector texVec = getFloats(JSON["accessors"][texAccInd]); - std::vector texUVs = groupFloatsVec2(texVec); - - // Combine all the vertex components and also get the indices and textures - std::vector vertices = assembleVertices(positions, normals, texUVs); - std::vector indices = getIndices(JSON["accessors"][indAccInd]); - std::vector textures = getTextures(); - - // Combine the vertices, indices, and textures into a mesh - meshes.push_back(Mesh(vertices, indices, textures)); - } - - void Model::traverseNode(unsigned int nextNode, glm::mat4 matrix) - { - // Current node - nlohmann::json node = JSON["nodes"][nextNode]; - - // Get translation if it exists - glm::vec3 translation = glm::vec3(0.0f, 0.0f, 0.0f); - if (node.find("translation") != node.end()) - { - float transValues[3]; - for (unsigned int i = 0; i < node["translation"].size(); i++) - transValues[i] = (node["translation"][i]); - translation = glm::make_vec3(transValues); - } - // Get quaternion if it exists - glm::quat rotation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f); - if (node.find("rotation") != node.end()) - { - float rotValues[4] = - { - node["rotation"][3], - node["rotation"][0], - node["rotation"][1], - node["rotation"][2] - }; - rotation = glm::make_quat(rotValues); - } - // Get scale if it exists - glm::vec3 scale = glm::vec3(1.0f, 1.0f, 1.0f); - if (node.find("scale") != node.end()) - { - float scaleValues[3]; - for (unsigned int i = 0; i < node["scale"].size(); i++) - scaleValues[i] = (node["scale"][i]); - scale = glm::make_vec3(scaleValues); - } - // Get matrix if it exists - glm::mat4 matNode = glm::mat4(1.0f); - if (node.find("matrix") != node.end()) - { - float matValues[16]; - for (unsigned int i = 0; i < node["matrix"].size(); i++) - matValues[i] = (node["matrix"][i]); - matNode = glm::make_mat4(matValues); - } - - // Initialize matrices - glm::mat4 trans = glm::mat4(1.0f); - glm::mat4 rot = glm::mat4(1.0f); - glm::mat4 sca = glm::mat4(1.0f); - - // Use translation, rotation, and scale to change the initialized matrices - trans = glm::translate(trans, translation); - rot = glm::mat4_cast(rotation); - sca = glm::scale(sca, scale); - - // Multiply all matrices together - glm::mat4 matNextNode = matrix * matNode * trans * rot * sca; - - // Check if the node contains a mesh and if it does load it - if (node.find("mesh") != node.end()) - { - translationsMeshes.push_back(translation); - rotationsMeshes.push_back(rotation); - scalesMeshes.push_back(scale); - matricesMeshes.push_back(matNextNode); - - loadMesh(node["mesh"]); - } - - // Check if the node has children, and if it does, apply this function to them with the matNextNode - if (node.find("children") != node.end()) - { - for (unsigned int i = 0; i < node["children"].size(); i++) - traverseNode(node["children"][i], matNextNode); - } - } - - std::vector Model::getData() - { - // Create a place to store the raw text, and get the uri of the .bin file - std::string bytesText; - std::string uri = JSON["buffers"][0]["uri"]; - - // Store raw text data into bytesText - std::string fileStr = std::string(file); - std::string fileDirectory = fileStr.substr(0, fileStr.find_last_of('/') + 1); - bytesText = GetFileContents((fileDirectory + uri).c_str()); - - // Transform the raw text data into bytes and put them in a vector - std::vector data(bytesText.begin(), bytesText.end()); - return data; - } - - std::vector Model::getFloats(nlohmann::json accessor) - { - std::vector floatVec; - - // Get properties from the accessor - unsigned int buffViewInd = accessor.value("bufferView", 1); - unsigned int count = accessor["count"]; - unsigned int accByteOffset = accessor.value("byteOffset", 0); - std::string type = accessor["type"]; - - // Get properties from the bufferView - nlohmann::json bufferView = JSON["bufferViews"][buffViewInd]; - unsigned int byteOffset = bufferView["byteOffset"]; - - // Interpret the type and store it into numPerVert - unsigned int numPerVert; - if (type == "SCALAR") numPerVert = 1; - else if (type == "VEC2") numPerVert = 2; - else if (type == "VEC3") numPerVert = 3; - else if (type == "VEC4") numPerVert = 4; - else throw std::invalid_argument("Type is invalid (not SCALAR, VEC2, VEC3, or VEC4)"); - - // Go over all the bytes in the data at the correct place using the properties from above - unsigned int beginningOfData = byteOffset + accByteOffset; - unsigned int lengthOfData = count * 4 * numPerVert; - for (unsigned int i = beginningOfData; i < beginningOfData + lengthOfData; i) - { - unsigned char bytes[] = { data[i++], data[i++], data[i++], data[i++] }; - float value; - std::memcpy(&value, bytes, sizeof(float)); - floatVec.push_back(value); - } - - return floatVec; - } - - std::vector Model::getIndices(nlohmann::json accessor) - { - std::vector indices; - - // Get properties from the accessor - unsigned int buffViewInd = accessor.value("bufferView", 0); - unsigned int count = accessor["count"]; - unsigned int accByteOffset = accessor.value("byteOffset", 0); - unsigned int componentType = accessor["componentType"]; - - // Get properties from the bufferView - nlohmann::json bufferView = JSON["bufferViews"][buffViewInd]; - unsigned int byteOffset = bufferView["byteOffset"]; - - // Get indices with regards to their type: unsigned int, unsigned short, or short - unsigned int beginningOfData = byteOffset + accByteOffset; - if (componentType == 5125) - { - for (unsigned int i = beginningOfData; i < byteOffset + accByteOffset + count * 4; i) - { - unsigned char bytes[] = { data[i++], data[i++], data[i++], data[i++] }; - unsigned int value; - std::memcpy(&value, bytes, sizeof(unsigned int)); - indices.push_back((GLuint)value); - } - } - else if (componentType == 5123) - { - for (unsigned int i = beginningOfData; i < byteOffset + accByteOffset + count * 2; i) - { - unsigned char bytes[] = { data[i++], data[i++] }; - unsigned short value; - std::memcpy(&value, bytes, sizeof(unsigned short)); - indices.push_back((GLuint)value); - } - } - else if (componentType == 5122) - { - for (unsigned int i = beginningOfData; i < byteOffset + accByteOffset + count * 2; i) - { - unsigned char bytes[] = { data[i++], data[i++] }; - short value; - std::memcpy(&value, bytes, sizeof(short)); - indices.push_back((GLuint)value); - } - } - - return indices; - } - - std::vector Model::getTextures() - { - std::vector textures; - - std::string fileStr = std::string(file); - std::string fileDirectory = fileStr.substr(0, fileStr.find_last_of('/') + 1); - - // Go over all images - for (unsigned int i = 0; i < JSON["images"].size(); i++) - { - // uri of current texture - std::string texPath = JSON["images"][i]["uri"]; - - // Check if the texture has already been loaded - bool skip = false; - for (unsigned int j = 0; j < loadedTexName.size(); j++) - { - if (loadedTexName[j] == texPath) - { - textures.push_back(loadedTex[j]); - skip = true; - break; - } - } - - // If the texture has been loaded, skip this - if (!skip) - { - // Load diffuse texture - if (texPath.find("baseColor") != std::string::npos) - { - Texture diffuse = Texture((fileDirectory + texPath).c_str(), "diffuse", loadedTex.size()); - textures.push_back(diffuse); - loadedTex.push_back(diffuse); - loadedTexName.push_back(texPath); - } - // Load specular texture - else if (texPath.find("metallicRoughness") != std::string::npos) - { - Texture specular = Texture((fileDirectory + texPath).c_str(), "specular", loadedTex.size()); - textures.push_back(specular); - loadedTex.push_back(specular); - loadedTexName.push_back(texPath); - } - } - } - - return textures; - } - - std::vector Model::assembleVertices - ( - std::vector positions, - std::vector normals, - std::vector texUVs - ) - { - std::vector vertices; - for (int i = 0; i < positions.size(); i++) - { - vertices.push_back - ( - Vertex - { - positions[i], - normals[i], - glm::vec3(1.0f, 1.0f, 1.0f), - texUVs[i] - } - ); - } - return vertices; - } - - std::vector Model::groupFloatsVec2(std::vector floatVec) - { - std::vector vectors; - for (int i = 0; i < floatVec.size(); i) - { - vectors.push_back(glm::vec2(floatVec[i++], floatVec[i++])); - } - return vectors; - } - std::vector Model::groupFloatsVec3(std::vector floatVec) - { - std::vector vectors; - for (int i = 0; i < floatVec.size(); i) - { - vectors.push_back(glm::vec3(floatVec[i++], floatVec[i++], floatVec[i++])); - } - return vectors; - } - std::vector Model::groupFloatsVec4(std::vector floatVec) - { - std::vector vectors; - for (int i = 0; i < floatVec.size(); i) - { - vectors.push_back(glm::vec4(floatVec[i++], floatVec[i++], floatVec[i++], floatVec[i++])); - } - return vectors; - } - - std::string Model::GetFileContents(const char* filename) - { - std::ifstream in(filename, std::ios::binary); - if (in) - { - std::string contents; - in.seekg(0, std::ios::end); - contents.resize(in.tellg()); - in.seekg(0, std::ios::beg); - in.read(&contents[0], contents.size()); - in.close(); - return(contents); - } - throw(errno); - } -} \ No newline at end of file diff --git a/Future/src/Rendering/Model.hpp b/Future/src/Rendering/Model.hpp deleted file mode 100644 index 3233d97..0000000 --- a/Future/src/Rendering/Model.hpp +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef MODEL_HPP -#define MODEL_HPP - -#include <../../../thirdParty/json/include/nlohmann/json.hpp> - -#include "Camera.hpp" -#include "Mesh.hpp" -#include "Shaders.hpp" -#include "Buffers/VBO.hpp" -#include "Textures/Texture.hpp" - -namespace Future -{ - class Model - { - public: - Model(const char* file); - - void Draw(Shaders& shader, Camera& camera); - private: - // Variables for easy access - const char* file; - std::vector data; - nlohmann::json JSON; - - // All the meshes and transformations - std::vector meshes; - std::vector translationsMeshes; - std::vector rotationsMeshes; - std::vector scalesMeshes; - std::vector matricesMeshes; - - // Prevents textures from being loaded twice - std::vector loadedTexName; - std::vector loadedTex; - - // Loads a single mesh by its index - void loadMesh(unsigned int indMesh); - - // Traverses a node recursively, so it essentially traverses all connected nodes - void traverseNode(unsigned int nextNode, glm::mat4 matrix = glm::mat4(1.0f)); - - // Gets the binary data from a file - std::vector getData(); - // Interprets the binary data into floats, indices, and textures - std::vector getFloats(nlohmann::json accessor); - std::vector getIndices(nlohmann::json accessor); - std::vector getTextures(); - - // Assembles all the floats into vertices - std::vector assembleVertices - ( - std::vector positions, - std::vector normals, - std::vector texUVs - ); - - // Helps with the assembly from above by grouping floats - std::vector groupFloatsVec2(std::vector floatVec); - std::vector groupFloatsVec3(std::vector floatVec); - std::vector groupFloatsVec4(std::vector floatVec); - - std::string GetFileContents(const char* filename); - }; -} - -#endif //MODEL_HPP \ No newline at end of file diff --git a/Future/src/Rendering/Mesh.cpp b/Future/src/Rendering/Model/Mesh.cpp similarity index 100% rename from Future/src/Rendering/Mesh.cpp rename to Future/src/Rendering/Model/Mesh.cpp diff --git a/Future/src/Rendering/Mesh.hpp b/Future/src/Rendering/Model/Mesh.hpp similarity index 79% rename from Future/src/Rendering/Mesh.hpp rename to Future/src/Rendering/Model/Mesh.hpp index d282059..9f02937 100644 --- a/Future/src/Rendering/Mesh.hpp +++ b/Future/src/Rendering/Model/Mesh.hpp @@ -2,10 +2,12 @@ #define MESH_HPP #include -#include "Buffers/VAO.hpp" -#include "Buffers/EBO.hpp" -#include "Camera.hpp" -#include "Textures/Texture.hpp" +#include + +#include "../Buffers/VAO.hpp" +#include "../Buffers/EBO.hpp" +#include "../Camera.hpp" +#include "../Textures/Texture.hpp" namespace Future { @@ -20,7 +22,6 @@ namespace Future Mesh(std::vector & vertices, std::vector & indices, std::vector& textures); - // Draws the mesh void Draw ( Shaders& shader, @@ -30,6 +31,8 @@ namespace Future glm::quat rotation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f), glm::vec3 scale = glm::vec3(1.0f, 1.0f, 1.0f) ); + private: + void SetupMesh(); }; } diff --git a/Future/src/Rendering/Model/Model.cpp b/Future/src/Rendering/Model/Model.cpp new file mode 100644 index 0000000..30bec92 --- /dev/null +++ b/Future/src/Rendering/Model/Model.cpp @@ -0,0 +1,121 @@ +#include "Model.hpp" + +namespace Future +{ + Model::Model(const char* file) + { + LoadModel(file); + } + + void Model::Draw(Shaders& shader, Camera& camera) + { + for (unsigned int i = 0; i < meshes.size(); i++) + { + meshes[i].Draw(shader, camera); + } + } + + void Model::LoadModel(const char* file) + { + Assimp::Importer import; + const aiScene* scene = import.ReadFile(file, aiProcess_Triangulate | aiProcess_FlipUVs | aiProcess_CalcTangentSpace); + + if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) + { + return; + } + + directory = std::string(file).substr(0, std::string(file).find_last_of('/')).data(); + ProcessNode(scene->mRootNode, scene); + } + + void Model::ProcessNode(const aiNode* node, const aiScene* scene) + { + for (unsigned int i = 0; i < node->mNumMeshes; i++) + { + aiMesh* mesh = scene->mMeshes[node->mMeshes[i]]; + meshes.push_back(ProcessMesh(mesh, scene)); + } + for (unsigned int i = 0; i < node->mNumChildren; i++) + { + ProcessNode(node->mChildren[i], scene); + } + } + + Mesh Model::ProcessMesh(aiMesh* mesh, const aiScene* scene) + { + std::vector vertices; + std::vector indices; + std::vector textures; + + for (unsigned int i = 0; i < mesh->mNumVertices; i++) + { + Vertex vertex; + glm::vec3 vector; + + vector.x = mesh->mVertices[i].x; + vector.y = mesh->mVertices[i].y; + vector.z = mesh->mVertices[i].z; + vertex.Position = vector; + + if (mesh->HasNormals()) + { + vector.x = mesh->mNormals[i].x; + vector.y = mesh->mNormals[i].y; + vector.z = mesh->mNormals[i].z; + vertex.Normal = vector; + } + + if (mesh->mTextureCoords[0]) + { + glm::vec2 texCoord; + texCoord.x = mesh->mTextureCoords[0][i].x; + texCoord.y = mesh->mTextureCoords[0][i].y; + vertex.TexCoords = texCoord; + } + else + vertex.TexCoords = glm::vec2(0.0f, 0.0f); + + vertices.push_back(vertex); + } + + for (unsigned int i = 0; i < mesh->mNumFaces; i++) + { + aiFace face = mesh->mFaces[i]; + for (unsigned int j = 0; j < face.mNumIndices; j++) + indices.push_back(face.mIndices[j]); + } + + if (mesh->mMaterialIndex >= 0) + { + aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex]; + + std::vector diffuseMaps = LoadMaterialTextures(material, aiTextureType_DIFFUSE, "diffuse"); + textures.insert(textures.end(), diffuseMaps.begin(), diffuseMaps.end()); + + std::vector specularMaps = LoadMaterialTextures(material, aiTextureType_SPECULAR, "specular"); + textures.insert(textures.end(), specularMaps.begin(), specularMaps.end()); + + std::vector normalMaps = LoadMaterialTextures(material, aiTextureType_NORMALS, "normal"); + textures.insert(textures.end(), normalMaps.begin(), normalMaps.end()); + } + + return Mesh(vertices, indices, textures); + } + + std::vector Model::LoadMaterialTextures(aiMaterial* mat, aiTextureType type, char* typeName) + { + std::vector textures; + for (unsigned int i = 0; i < mat->GetTextureCount(type); i++) + { + aiString str; + mat->GetTexture(type, i, &str); + std::string filename = std::string(str.C_Str()); + filename = directory + '/' + filename; + + Texture texture(filename.c_str(), typeName, i); + textures.push_back(texture); + } + return textures; + } +} diff --git a/Future/src/Rendering/Model/Model.hpp b/Future/src/Rendering/Model/Model.hpp new file mode 100644 index 0000000..fac6cd6 --- /dev/null +++ b/Future/src/Rendering/Model/Model.hpp @@ -0,0 +1,36 @@ +#ifndef MODEL_HPP +#define MODEL_HPP + +#include <../../../thirdParty/json/include/nlohmann/json.hpp> + +#include "../Camera.hpp" +#include "Mesh.hpp" +#include "../Shaders.hpp" +#include "../Buffers/VBO.hpp" +#include "../Textures/Texture.hpp" +#include +#include +#include +#include + +namespace Future +{ + class Model + { + public: + Model(const char* file); + + void Draw(Shaders& shader, Camera& camera); + private: + // model data + std::vector meshes; + char* directory; + + void LoadModel(const char* file); + void ProcessNode(const aiNode *node, const aiScene *scene); + Mesh ProcessMesh(aiMesh *mesh, const aiScene *scene); + std::vector LoadMaterialTextures(aiMaterial *mat, aiTextureType type, char* typeName); + }; +} + +#endif //MODEL_HPP \ No newline at end of file diff --git a/Future/src/Rendering/Renderer.hpp b/Future/src/Rendering/Renderer.hpp index 1aa829d..8ecfac7 100644 --- a/Future/src/Rendering/Renderer.hpp +++ b/Future/src/Rendering/Renderer.hpp @@ -13,7 +13,7 @@ #include <../../../thirdParty/glm/glm/gtc/matrix_transform.hpp> #include <../../../thirdParty/glm/glm/gtc/type_ptr.hpp> #include "Shaders.hpp" -#include "Model.hpp" +#include "Model/Model.hpp" #include "Camera.hpp" #include "Shaders.hpp" #include "Buffers/EBO.hpp" @@ -31,6 +31,7 @@ namespace Future ~Renderer(); void Init(); + [[nodiscard]] Camera* GetMainCamera() const { return m_mainCamera; } private: static void PreInitBackend(); static void InitBackend(); diff --git a/Future/src/Rendering/Vertex.hpp b/Future/src/Rendering/Vertex.hpp new file mode 100644 index 0000000..d6df3bd --- /dev/null +++ b/Future/src/Rendering/Vertex.hpp @@ -0,0 +1,14 @@ +#ifndef VERTEX_HPP +#define VERTEX_HPP +#include "vec2.hpp" +#include "vec3.hpp" + +struct Vertex +{ + glm::vec3 Position; + glm::vec3 Normal; + glm::vec3 color; + glm::vec2 TexCoords; +}; + +#endif //VERTEX_HPP diff --git a/Future/src/Window/Window.cpp b/Future/src/Window/Window.cpp index ee70489..4101d38 100644 --- a/Future/src/Window/Window.cpp +++ b/Future/src/Window/Window.cpp @@ -110,6 +110,7 @@ namespace Future SDL_Event Window::Tick() { + SDL_Event e; if (mRunning) { SDL_GL_SwapWindow(sdlWindow); @@ -119,7 +120,6 @@ namespace Future SDL_WarpMouseInWindow(sdlWindow, mWidth / 2, mHeight / 2); } - SDL_Event e; while (SDL_PollEvent(&e)) { //ImGui_ImplSDL2_ProcessEvent(&e); @@ -135,7 +135,7 @@ namespace Future } } } - return e; } + return e; } } diff --git a/thirdParty/assimp b/thirdParty/assimp new file mode 160000 index 0000000..f636252 --- /dev/null +++ b/thirdParty/assimp @@ -0,0 +1 @@ +Subproject commit f63625256c4e6f18fca8e7dc857b47a02320e867