From 16eb70dd59572b62da8f6c08b6b69bdc7bdcff4d Mon Sep 17 00:00:00 2001 From: sippeangelo Date: Tue, 24 Nov 2015 10:55:54 +0100 Subject: [PATCH] Added Model and related classes --- include/Engine/PrecompiledHeader.h | 2 +- include/Engine/Rendering/.gitkeep | 0 include/Engine/Rendering/BaseTexture.h | 20 ++ include/Engine/Rendering/Model.h | 29 +++ include/Engine/Rendering/PNG.h | 21 ++ include/Engine/Rendering/RawModel.h | 77 ++++++ include/Engine/Rendering/Skeleton.h | 95 ++++++++ include/Engine/Rendering/Texture.h | 21 ++ src/Engine/Rendering/.gitkeep | 0 src/Engine/Rendering/Model.cpp | 64 +++++ src/Engine/Rendering/PNG.cpp | 112 +++++++++ src/Engine/Rendering/RawModel.cpp | 320 +++++++++++++++++++++++++ src/Engine/Rendering/Skeleton.cpp | 148 ++++++++++++ src/Engine/Rendering/Texture.cpp | 50 ++++ 14 files changed, 958 insertions(+), 1 deletion(-) delete mode 100755 include/Engine/Rendering/.gitkeep create mode 100644 include/Engine/Rendering/BaseTexture.h create mode 100644 include/Engine/Rendering/Model.h create mode 100644 include/Engine/Rendering/PNG.h create mode 100644 include/Engine/Rendering/RawModel.h create mode 100644 include/Engine/Rendering/Skeleton.h create mode 100644 include/Engine/Rendering/Texture.h delete mode 100755 src/Engine/Rendering/.gitkeep create mode 100644 src/Engine/Rendering/Model.cpp create mode 100644 src/Engine/Rendering/PNG.cpp create mode 100644 src/Engine/Rendering/RawModel.cpp create mode 100644 src/Engine/Rendering/Skeleton.cpp create mode 100644 src/Engine/Rendering/Texture.cpp diff --git a/include/Engine/PrecompiledHeader.h b/include/Engine/PrecompiledHeader.h index 0ac917d2..dd89e386 100644 --- a/include/Engine/PrecompiledHeader.h +++ b/include/Engine/PrecompiledHeader.h @@ -10,7 +10,7 @@ #define NOMINMAX #include #include -#include "Core/Util/GLError.h" +#include "Rendering/Util/GLError.h" // GLM #define GLM_FORCE_RADIANS diff --git a/include/Engine/Rendering/.gitkeep b/include/Engine/Rendering/.gitkeep deleted file mode 100755 index e69de29b..00000000 diff --git a/include/Engine/Rendering/BaseTexture.h b/include/Engine/Rendering/BaseTexture.h new file mode 100644 index 00000000..b915fd55 --- /dev/null +++ b/include/Engine/Rendering/BaseTexture.h @@ -0,0 +1,20 @@ +#ifndef BaseTexture_h__ +#define BaseTexture_h__ + +#include +#include +#include + +#include "Core/ResourceManager.h" +#include "Rendering/PNG.h" + +class BaseTexture : public Resource +{ + friend class ResourceManager; + +public: + unsigned int Width = 0; + unsigned int Height = 0; +}; + +#endif diff --git a/include/Engine/Rendering/Model.h b/include/Engine/Rendering/Model.h new file mode 100644 index 00000000..a1fcb94e --- /dev/null +++ b/include/Engine/Rendering/Model.h @@ -0,0 +1,29 @@ +#ifndef Model_h__ +#define Model_h__ + +#include "Rendering/RawModel.h" + +class Model : public RawModel +{ + friend class ResourceManager; + +private: + Model(std::string fileName); + +public: + ~Model(); + + GLuint VAO; + GLuint ElementBuffer; + +private: + GLuint VertexBuffer; + GLuint DiffuseVertexColorBuffer; + GLuint SpecularVertexColorBuffer; + GLuint NormalBuffer; + GLuint TangentNormalsBuffer; + GLuint BiTangentNormalsBuffer; + GLuint TextureCoordBuffer; +}; + +#endif diff --git a/include/Engine/Rendering/PNG.h b/include/Engine/Rendering/PNG.h new file mode 100644 index 00000000..d12350d9 --- /dev/null +++ b/include/Engine/Rendering/PNG.h @@ -0,0 +1,21 @@ +#ifndef PNG_h_ +#define PNG_h_ + +#include + +#include + +#include "Image.h" + +class PNG : public Image +{ +public: + PNG(std::string path); + ~PNG(); + +private: + static void pngErrorFunction(png_structp png_ptr, png_const_charp error_msg); + static void pngWarningFunction(png_structp png_ptr, png_const_charp warning_msg); +}; + +#endif diff --git a/include/Engine/Rendering/RawModel.h b/include/Engine/Rendering/RawModel.h new file mode 100644 index 00000000..caf8d86f --- /dev/null +++ b/include/Engine/Rendering/RawModel.h @@ -0,0 +1,77 @@ +#ifndef RawModel_h__ +#define RawModel_h__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "Core/ResourceManager.h" +#include "Rendering/Texture.h" +#include "Rendering/Skeleton.h" + +class RawModel : public Resource +{ + friend class ResourceManager; + +protected: + RawModel(std::string fileName); + +public: + ~RawModel(); + + struct Vertex + { + glm::vec3 Position; + glm::vec3 Normal; + glm::vec3 Tangent; + glm::vec3 BiTangent; + glm::vec2 TextureCoords; + glm::vec4 DiffuseVertexColor; + glm::vec4 SpecularVertexColor; + glm::vec4 BoneIndices1; + glm::vec4 BoneIndices2; + glm::vec4 BoneWeights1; + glm::vec4 BoneWeights2; + }; + + struct MaterialGroup + { + float Shininess; + std::shared_ptr Texture; + std::shared_ptr NormalMap; + std::shared_ptr SpecularMap; + unsigned int StartIndex; + unsigned int EndIndex; + }; + + std::vector TextureGroups; + + std::vector m_Vertices; + std::vector m_Indices; + Skeleton* m_Skeleton = nullptr; + glm::mat4 m_Matrix; + +private: + std::vector BoneIndices; + std::vector BoneWeights; + std::vector Normals; + std::vector DiffuseVertexColor; + std::vector SpecularVertexColor; + std::vector TangentNormals; + std::vector BiTangentNormals; + std::vector TextureCoords; + + void CreateSkeleton(std::vector> &boneInfo, std::map &boneNameMapping, aiNode* node, int parentID); +}; + +#endif diff --git a/include/Engine/Rendering/Skeleton.h b/include/Engine/Rendering/Skeleton.h new file mode 100644 index 00000000..a0ee7234 --- /dev/null +++ b/include/Engine/Rendering/Skeleton.h @@ -0,0 +1,95 @@ +#ifndef Skeleton_h__ +#define Skeleton_h__ + +#include + +//struct Bone +//{ +// Bone(std::string name, glm::mat4 offsetMatrix) +// : Name(name) +// , OffsetMatrix(offsetMatrix) +// { } +// +// ~Bone() +// { +// for (auto kv : Children) { +// delete kv.second; +// } +// } +// +// std::string Name; +// glm::mat4 OffsetMatrix; +// glm::mat4 LocalMatrix; +// +// std::map Children; +//}; + +class Skeleton +{ +public: + struct Bone + { + Bone(int id, Bone* parent, std::string name, glm::mat4 offsetMatrix) + : ID(id) + , Parent(parent) + , Name(name) + , OffsetMatrix(offsetMatrix) + { } + + int ID; + std::string Name; + glm::mat4 OffsetMatrix; + + Bone* Parent; + std::vector Children; + }; + + struct Animation + { + struct Keyframe + { + struct BoneProperty + { + int ID; + glm::vec3 Position; + glm::quat Rotation; + glm::vec3 Scale = glm::vec3(1); + }; + + int Index = 0; + double Time = 0.0; + std::map BoneProperties; + }; + + std::string Name; + double Duration; + std::vector Keyframes; + }; + + Skeleton() { } + ~Skeleton(); + + Bone* RootBone; + + std::map Bones; + + // Attach a new bone to the skeleton + // Returns: New bone index + int CreateBone(int ID, int parentID, std::string name, glm::mat4 offsetMatrix); + + int GetBoneID(std::string name); + + const Animation* GetAnimation(std::string name); + std::vector GetFrameBones(const Animation& animation, double time, bool noRootMotion = false); + void AccumulateBoneTransforms(bool noRootMotion, const Animation::Keyframe& currentFrame, const Animation::Keyframe& nextFrame, float progress, std::map& boneMatrices, const Bone* bone, glm::mat4 parentMatrix); + void PrintSkeleton(); + void PrintSkeleton(const Bone* parent, int depthCount); + std::map Animations; + +private: + std::map m_BonesByName; + + int GetKeyframe(const Animation& animation, double time); +}; + +#endif diff --git a/include/Engine/Rendering/Texture.h b/include/Engine/Rendering/Texture.h new file mode 100644 index 00000000..06edbb7f --- /dev/null +++ b/include/Engine/Rendering/Texture.h @@ -0,0 +1,21 @@ +#ifndef Texture_h__ +#define Texture_h__ + +#include "Rendering/BaseTexture.h" + +class Texture : public BaseTexture +{ + friend class ResourceManager; + +private: + Texture(std::string path); + +public: + ~Texture(); + + void Bind(GLenum textureUnit = GL_TEXTURE0); + + GLuint m_Texture = 0; +}; + +#endif diff --git a/src/Engine/Rendering/.gitkeep b/src/Engine/Rendering/.gitkeep deleted file mode 100755 index e69de29b..00000000 diff --git a/src/Engine/Rendering/Model.cpp b/src/Engine/Rendering/Model.cpp new file mode 100644 index 00000000..78743fc0 --- /dev/null +++ b/src/Engine/Rendering/Model.cpp @@ -0,0 +1,64 @@ +#include "PrecompiledHeader.h" +#include "Rendering/Model.h" + +Model::Model(std::string fileName) + : RawModel(fileName) +{ + // Generate GL buffers + GLuint buffer; + glGenBuffers(1, &buffer); + glBindBuffer(GL_ARRAY_BUFFER, buffer); + glBufferData(GL_ARRAY_BUFFER, m_Vertices.size() * sizeof(Vertex), &m_Vertices[0], GL_STATIC_DRAW); + + glGenBuffers(1, &ElementBuffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ElementBuffer); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_Indices.size() * sizeof(unsigned int), &m_Indices[0], GL_STATIC_DRAW); + + glGenVertexArrays(1, &VAO); + glBindVertexArray(VAO); + GLERROR("GLEW: BufferFail4"); + + glBindBuffer(GL_ARRAY_BUFFER, buffer); + std::vector structSizes = { 3, 3, 3, 3, 2, 4, 4, 4, 4, 4, 4 }; + int stride = 0; + for (int size : structSizes) { + stride += size; + } + stride *= sizeof(GLfloat); + int offset = 0; + { + int element = 0; + glVertexAttribPointer(element, structSizes[element], GL_FLOAT, GL_FALSE, stride, (GLvoid*)(sizeof(GLfloat) * offset)); element++; + glVertexAttribPointer(element, structSizes[element], GL_FLOAT, GL_FALSE, stride, (GLvoid*)(sizeof(GLfloat) * (offset += structSizes[element - 1]))); element++; + glVertexAttribPointer(element, structSizes[element], GL_FLOAT, GL_FALSE, stride, (GLvoid*)(sizeof(GLfloat) * (offset += structSizes[element - 1]))); element++; + glVertexAttribPointer(element, structSizes[element], GL_FLOAT, GL_FALSE, stride, (GLvoid*)(sizeof(GLfloat) * (offset += structSizes[element - 1]))); element++; + glVertexAttribPointer(element, structSizes[element], GL_FLOAT, GL_FALSE, stride, (GLvoid*)(sizeof(GLfloat) * (offset += structSizes[element - 1]))); element++; + glVertexAttribPointer(element, structSizes[element], GL_FLOAT, GL_FALSE, stride, (GLvoid*)(sizeof(GLfloat) * (offset += structSizes[element - 1]))); element++; + glVertexAttribPointer(element, structSizes[element], GL_FLOAT, GL_FALSE, stride, (GLvoid*)(sizeof(GLfloat) * (offset += structSizes[element - 1]))); element++; + glVertexAttribPointer(element, structSizes[element], GL_FLOAT, GL_FALSE, stride, (GLvoid*)(sizeof(GLfloat) * (offset += structSizes[element - 1]))); element++; + glVertexAttribPointer(element, structSizes[element], GL_FLOAT, GL_FALSE, stride, (GLvoid*)(sizeof(GLfloat) * (offset += structSizes[element - 1]))); element++; + glVertexAttribPointer(element, structSizes[element], GL_FLOAT, GL_FALSE, stride, (GLvoid*)(sizeof(GLfloat) * (offset += structSizes[element - 1]))); element++; + glVertexAttribPointer(element, structSizes[element], GL_FLOAT, GL_FALSE, stride, (GLvoid*)(sizeof(GLfloat) * (offset += structSizes[element - 1]))); element++; + } + GLERROR("GLEW: BufferFail5"); + + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glEnableVertexAttribArray(2); + glEnableVertexAttribArray(3); + glEnableVertexAttribArray(4); + glEnableVertexAttribArray(5); + glEnableVertexAttribArray(6); + glEnableVertexAttribArray(7); + glEnableVertexAttribArray(8); + glEnableVertexAttribArray(9); + glEnableVertexAttribArray(10); + GLERROR("GLEW: BufferFail5"); + + //CreateBuffers(); +} + +Model::~Model() +{ + +} diff --git a/src/Engine/Rendering/PNG.cpp b/src/Engine/Rendering/PNG.cpp new file mode 100644 index 00000000..deb28fd1 --- /dev/null +++ b/src/Engine/Rendering/PNG.cpp @@ -0,0 +1,112 @@ +#include "PrecompiledHeader.h" +#include "Rendering/PNG.h" + +PNG::PNG(std::string path) +{ + FILE* file = fopen(path.c_str(), "rb"); + if (!file) { + LOG_ERROR("Failed to open texture file \"%s\": %s", path.c_str(), const_cast(strerror(errno))); + return; + } + + png_byte header[8]; + fread(header, 1, 8, file); + bool isPNG = !png_sig_cmp(header, 0, 8); + if (!isPNG) { + LOG_ERROR("Failed to load texture file \"%s\": File isn't PNG", path.c_str()); + fclose(file); + return; + } + + // Initialize libpng + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, (png_error_ptr)&PNG::pngErrorFunction, (png_error_ptr)&PNG::pngErrorFunction); + if (!png_ptr) { + LOG_ERROR("libpng: Failed to initialze png_struct"); + png_destroy_read_struct(&png_ptr, nullptr, nullptr); + fclose(file); + return; + } + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + LOG_ERROR("libpng: Failed to initialze png_info"); + png_destroy_read_struct(&png_ptr, nullptr, nullptr); + fclose(file); + return; + } + png_infop info_end_ptr = png_create_info_struct(png_ptr); + if (!info_end_ptr) { + LOG_ERROR("libpng: Failed to initialze second png_info"); + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + fclose(file); + return; + } + png_init_io(png_ptr, file); + + // We already read the first 8 bytes of the header + png_set_sig_bytes(png_ptr, 8); + // Read all the info up to the image data + png_read_info(png_ptr, info_ptr); + + // Get info + int bit_depth, color_type; + unsigned int width, height; + png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL); + if (bit_depth != 8) { + LOG_ERROR("libpng: Unsupported bit depth \"%i\" of image \"%s\", must be 8", bit_depth, path.c_str()); + return; + } + switch (color_type) { + case PNG_COLOR_TYPE_RGB: + case PNG_COLOR_TYPE_RGBA: + Format = Image::ImageFormat::RGBA; + break; + default: + LOG_ERROR("libpng: Unsupported color format \"%i\" of image \"%s\"", color_type, path.c_str()); + return; + } + + // Convert RGB to RGBA, since DirectX rather treat them all the same way + if (color_type == PNG_COLOR_TYPE_RGB) { + LOG_DEBUG("Converting RGB to RGBA for \"%s\"", path.c_str()); + png_set_add_alpha(png_ptr, 0xff, PNG_FILLER_AFTER); + png_read_update_info(png_ptr, info_ptr); + } + + unsigned int row_bytes = png_get_rowbytes(png_ptr, info_ptr); + this->Data = new unsigned char[height * row_bytes]; + png_bytep* row_pointers = new png_bytep[height]; + + // Point each row to the continuous data array + for (int i = 0; i < height; ++i) { + // Invert Y for OpenGL + row_pointers[height - 1 - i] = this->Data + i * row_bytes; + } + + // Read in the data + png_read_image(png_ptr, row_pointers); + delete[] row_pointers; + + this->Width = width; + this->Height = height; + + png_destroy_read_struct(&png_ptr, &info_ptr, &info_end_ptr); + fclose(file); +} + +PNG::~PNG() +{ + if (this->Data != nullptr) { + delete[] this->Data; + this->Data = nullptr; + } +} + +void PNG::pngErrorFunction(png_structp png_ptr, png_const_charp error_msg) +{ + LOG_WARNING("%s", error_msg); +} + +void PNG::pngWarningFunction(png_structp png_ptr, png_const_charp warning_msg) +{ + LOG_WARNING("%s", warning_msg); +} diff --git a/src/Engine/Rendering/RawModel.cpp b/src/Engine/Rendering/RawModel.cpp new file mode 100644 index 00000000..0c7db76b --- /dev/null +++ b/src/Engine/Rendering/RawModel.cpp @@ -0,0 +1,320 @@ +#include "PrecompiledHeader.h" +#include "Rendering/RawModel.h" + +RawModel::RawModel(std::string fileName) +{ + Assimp::Importer importer; + const aiScene* scene = importer.ReadFile(fileName, aiProcess_CalcTangentSpace | aiProcess_Triangulate); + + if (scene == nullptr) { + LOG_ERROR("Failed to load model \"%s\"", fileName.c_str()); + LOG_ERROR("Assimp error: %s", importer.GetErrorString()); + return; + } + + auto m = scene->mRootNode->mTransformation; + m_Matrix = glm::mat4( + m.a1, m.a2, m.a3, m.a4, + m.b1, m.b2, m.b3, m.b4, + m.c1, m.c2, m.c3, m.c4, + m.d1, m.d2, m.d3, m.d4 + ); + m_Matrix = glm::transpose(m_Matrix); + + auto meshes = scene->mMeshes; + + // Pre-count vertices + int numVertices = 0; + int numIndices = 0; + for (int i = 0; i < scene->mNumMeshes; ++i) { + numVertices += meshes[i]->mNumVertices; + + // Faces + for (int j = 0; j < meshes[i]->mNumFaces; ++j) { + auto face = meshes[i]->mFaces[j]; + numIndices += face.mNumIndices; + } + } + LOG_DEBUG("Vertex count %i", numVertices); + LOG_DEBUG("Index count %i", numIndices); + + LOG_DEBUG("Model has %i embedded textures", scene->mNumTextures); + + std::vector> boneInfo; + std::map boneNameMapping; + + for (int i = 0; i < scene->mNumMeshes; ++i) { + auto mesh = meshes[i]; + auto material = scene->mMaterials[mesh->mMaterialIndex]; + unsigned int indexOffset = m_Vertices.size(); + + // Vertices, normals and texture coordinates + for (int vertexIndex = 0; vertexIndex < mesh->mNumVertices; ++vertexIndex) { + Vertex desc; + + // Position + auto position = mesh->mVertices[vertexIndex]; + desc.Position = glm::vec3(position.x, position.y, position.z); + + // Normal + auto normal = mesh->mNormals[vertexIndex]; + desc.Normal = glm::vec3(normal.x, normal.y, normal.z); + + //if (mesh->HasTangentsAndBitangents()) { + // // Tangent + // auto tangent = mesh->mTangents[vertexIndex]; + // desc.Tangent = glm::vec3(tangent.x, tangent.y, tangent.z); + + // // Bi-tangent + // auto bitangent = mesh->mBitangents[vertexIndex]; + // desc.BiTangent = glm::vec3(bitangent.x, bitangent.y, bitangent.z); + //} + + // UV + if (mesh->HasTextureCoords(0)) { + auto uv = mesh->mTextureCoords[0][vertexIndex]; + desc.TextureCoords = glm::vec2(uv.x, uv.y); + } + + // Material diffuse color + aiColor4D diffuse; + material->Get(AI_MATKEY_COLOR_DIFFUSE, diffuse); + desc.DiffuseVertexColor = glm::vec4(diffuse.r, diffuse.g, diffuse.b, diffuse.a); + // Material specular color + aiColor4D specular; + material->Get(AI_MATKEY_COLOR_SPECULAR, specular); + desc.SpecularVertexColor = glm::vec4(specular.r, specular.g, specular.b, specular.a); + + m_Vertices.push_back(desc); + } + + // Faces + for (int j = 0; j < mesh->mNumFaces; ++j) { + auto face = mesh->mFaces[j]; + for (int k = 0; k < face.mNumIndices; ++k) { + unsigned int index = face.mIndices[k]; + m_Indices.push_back(indexOffset + index); + } + } + + // Calculate normal mapping tangents + for (int i = 0; i < m_Indices.size(); i += 3) { + Vertex& v0 = m_Vertices[m_Indices[i]]; + Vertex& v1 = m_Vertices[m_Indices[i + 1]]; + Vertex& v2 = m_Vertices[m_Indices[i + 2]]; + + glm::vec3 edge1 = v1.Position - v0.Position; + glm::vec3 edge2 = v2.Position - v0.Position; + + float deltaU1 = v1.TextureCoords.x - v0.TextureCoords.x; + float deltaV1 = v1.TextureCoords.y - v0.TextureCoords.y; + float deltaU2 = v2.TextureCoords.x - v0.TextureCoords.x; + float deltaV2 = v2.TextureCoords.y - v0.TextureCoords.y; + + float f = 1.0f / (deltaU1 * deltaV2 - deltaU2 * deltaV1); + + glm::vec3 tangent; + tangent.x = f * (deltaV2 * edge1.x - deltaV1 * edge2.x); + tangent.y = f * (deltaV2 * edge1.y - deltaV1 * edge2.y); + tangent.z = f * (deltaV2 * edge1.z - deltaV1 * edge2.z); + + v0.Tangent += tangent; + v1.Tangent += tangent; + v2.Tangent += tangent; + } + for (auto& vertex : m_Vertices) { + vertex.Tangent = glm::normalize(vertex.Tangent); + vertex.BiTangent = glm::normalize(glm::cross(vertex.Tangent, glm::normalize(vertex.Normal))); + } + + // Material info + MaterialGroup matGroup; + matGroup.StartIndex = indexOffset; + matGroup.EndIndex = m_Indices.size() - 1; + // Material shininess + material->Get(AI_MATKEY_SHININESS, matGroup.Shininess); + LOG_DEBUG("Shininess: %f", matGroup.Shininess); + // Diffuse texture + LOG_DEBUG("%i diffuse textures found", material->GetTextureCount(aiTextureType_DIFFUSE)); + if (material->GetTextureCount(aiTextureType_DIFFUSE)) { + aiString path; + aiTextureMapping mapping; + material->GetTexture(aiTextureType_DIFFUSE, 0, &path, &mapping); + std::string absolutePath = (boost::filesystem::path(fileName).branch_path() / path.C_Str()).string(); + LOG_DEBUG("Diffuse texture: %s", absolutePath.c_str()); + matGroup.Texture = std::shared_ptr(ResourceManager::Load(absolutePath)); + } + // Normal map + LOG_DEBUG("%i normal maps found", material->GetTextureCount(aiTextureType_HEIGHT)); + if (material->GetTextureCount(aiTextureType_HEIGHT)) { + aiString path; + aiTextureMapping mapping; + material->GetTexture(aiTextureType_HEIGHT, 0, &path, &mapping); + std::string absolutePath = (boost::filesystem::path(fileName).branch_path() / path.C_Str()).string(); + LOG_DEBUG("Normal map: %s", absolutePath.c_str()); + matGroup.NormalMap = std::shared_ptr(ResourceManager::Load(absolutePath)); + } + // Specular map + LOG_DEBUG("%i specular maps found", material->GetTextureCount(aiTextureType_SPECULAR)); + if (material->GetTextureCount(aiTextureType_SPECULAR)) { + aiString path; + aiTextureMapping mapping; + material->GetTexture(aiTextureType_SPECULAR, 0, &path, &mapping); + std::string absolutePath = (boost::filesystem::path(fileName).branch_path() / path.C_Str()).string(); + LOG_DEBUG("Specular map: %s", absolutePath.c_str()); + matGroup.SpecularMap = std::shared_ptr(ResourceManager::Load(absolutePath)); + } + TextureGroups.push_back(matGroup); + + // Bones + std::map>> vertexWeights; + for (int j = 0; j < mesh->mNumBones; ++j) { + auto bone = mesh->mBones[j]; + std::string boneName = bone->mName.C_Str(); + + auto mat = bone->mOffsetMatrix; + glm::mat4 glmMat(mat.a1, mat.b1, mat.c1, mat.d1, + mat.a2, mat.b2, mat.c2, mat.d2, + mat.a3, mat.b3, mat.c3, mat.d3, + mat.a4, mat.b4, mat.c4, mat.d4); + + int boneIndex; + if (boneNameMapping.find(boneName) != boneNameMapping.end()) { + boneIndex = boneNameMapping[boneName]; + } else { + boneIndex = boneInfo.size(); + boneInfo.push_back(std::make_tuple(boneName, glmMat)); + boneNameMapping[boneName] = boneIndex; + } + + for (int k = 0; k < bone->mNumWeights; ++k) { + auto weight = bone->mWeights[k]; + unsigned int offsetVertexId = weight.mVertexId + indexOffset; + vertexWeights[offsetVertexId].push_back(std::make_tuple(boneIndex, weight.mWeight)); + } + } + for (auto &pair : vertexWeights) { + auto weights = pair.second; + Vertex& desc = m_Vertices[pair.first]; + + const int maxWeights = 8; + if (weights.size() > maxWeights) { + LOG_WARNING("Vertex weights (%i) greater than max weights per vertex (%i)", weights.size(), maxWeights); + } + for (int weightIndex = 0; weightIndex < weights.size() && weightIndex < maxWeights && weightIndex < 4; ++weightIndex) { + std::tie(desc.BoneIndices1[weightIndex], desc.BoneWeights1[weightIndex]) = weights[weightIndex]; + } + for (int weightIndex = 4; weightIndex < weights.size() && weightIndex < maxWeights && weightIndex < 8; ++weightIndex) { + std::tie(desc.BoneIndices2[weightIndex - 4], desc.BoneWeights2[weightIndex - 4]) = weights[weightIndex]; + } + } + + //break; + } + + // Traverse the node tree and build a skeleton + if (!boneInfo.empty()) { + m_Skeleton = new Skeleton(); + CreateSkeleton(boneInfo, boneNameMapping, scene->mRootNode, -1); + int numBones = m_Skeleton->Bones.size(); + LOG_DEBUG("Bone count: %i", numBones); + if (numBones > 0) { + m_Skeleton->PrintSkeleton(); + } + } + + // Animations + LOG_DEBUG("Animation count: %i", scene->mNumAnimations); + for (int i = 0; i < scene->mNumAnimations; ++i) { + auto animation = scene->mAnimations[i]; + std::string animationName = animation->mName.C_Str(); + LOG_DEBUG("Animation: %s", animationName.c_str()); + LOG_DEBUG("Duration: %f", animation->mDuration); + LOG_DEBUG("Ticks per second: %f", animation->mTicksPerSecond); + + Skeleton::Animation skelAnim; + skelAnim.Name = animationName; + skelAnim.Duration = animation->mDuration / animation->mTicksPerSecond; + + std::map frameTimes; + std::map> frameBoneProperties; + // For each animation channel (bone) + for (int channelIndex = 0; channelIndex < animation->mNumChannels; ++channelIndex) { + auto channel = animation->mChannels[channelIndex]; + std::string boneName = channel->mNodeName.C_Str(); + int boneID = m_Skeleton->GetBoneID(boneName); + if (boneID == -1) { + LOG_ERROR("Animation referenced a bone that doesn't exist: %s", boneName.c_str()); + continue; + } + + // If you don't have the same amount of keyframes for every transformation type you're dumb. + if (channel->mNumPositionKeys != channel->mNumRotationKeys || channel->mNumPositionKeys != channel->mNumScalingKeys) { + LOG_ERROR("Hey, animation! You're dumb!", animationName.c_str()); + continue; + } + + for (int keyframe = 0; keyframe < channel->mNumPositionKeys; ++keyframe) { + auto posKey = channel->mPositionKeys[keyframe]; + auto rotKey = channel->mRotationKeys[keyframe]; + auto scaleKey = channel->mScalingKeys[keyframe]; + + frameTimes[keyframe] = posKey.mTime; + + auto &property = frameBoneProperties[keyframe][boneID]; + property.ID = keyframe; + property.Position = glm::vec3(posKey.mValue.x, posKey.mValue.y, posKey.mValue.z); + property.Rotation = glm::quat(rotKey.mValue.w, rotKey.mValue.x, rotKey.mValue.y, rotKey.mValue.z); + property.Scale = glm::vec3(scaleKey.mValue.x, scaleKey.mValue.y, scaleKey.mValue.z); + } + } + + // Create keyframes from bone properties + for (auto &kv : frameBoneProperties) { + int keyframe = kv.first; + Skeleton::Animation::Keyframe animationFrame; + animationFrame.Index = keyframe; + animationFrame.Time = frameTimes[keyframe] / animation->mTicksPerSecond; + // HACK: For some reason Blender likes to create a first frame that doesn't start at time 0 + if (keyframe == 0) { + animationFrame.Time = 0; + } + for (auto &kv2 : kv.second) { + int boneID = kv2.first; + auto &property = kv2.second; + animationFrame.BoneProperties[boneID] = property; + } + skelAnim.Keyframes.push_back(animationFrame); + } + + m_Skeleton->Animations[animationName] = skelAnim; + } +} + +RawModel::~RawModel() +{ + if (m_Skeleton) { + delete m_Skeleton; + } +} + +void RawModel::CreateSkeleton(std::vector> &boneInfo, std::map &boneNameMapping, aiNode* node, int parentID) +{ + std::string nodeName = node->mName.C_Str(); + + // Find the bone by name in the bone info list + if (boneNameMapping.find(nodeName) == boneNameMapping.end()) { + LOG_DEBUG("Node \"%s\" was not a bone", nodeName.c_str()); + } else { + glm::mat4 offsetMatrix; + int ID = boneNameMapping[nodeName]; + std::tie(std::ignore, offsetMatrix) = boneInfo[ID]; + m_Skeleton->CreateBone(ID, parentID, nodeName, offsetMatrix); + parentID = ID; + } + + for (int childIndex = 0; childIndex < node->mNumChildren; ++childIndex) { + aiNode* child = node->mChildren[childIndex]; + CreateSkeleton(boneInfo, boneNameMapping, child, parentID); + } +} diff --git a/src/Engine/Rendering/Skeleton.cpp b/src/Engine/Rendering/Skeleton.cpp new file mode 100644 index 00000000..dd61643f --- /dev/null +++ b/src/Engine/Rendering/Skeleton.cpp @@ -0,0 +1,148 @@ +#include "PrecompiledHeader.h" +#include "Rendering/Skeleton.h" + +int Skeleton::CreateBone(int ID, int parentID, std::string name, glm::mat4 offsetMatrix) +{ + if (m_BonesByName.find(name) != m_BonesByName.end()) { + return m_BonesByName.at(name)->ID; + } else { + Bone* bone; + + if (parentID == -1) { + bone = new Bone(ID, nullptr, name, offsetMatrix); + RootBone = bone; + } else { + Bone* parent = Bones[parentID]; + bone = new Bone(ID, parent, name, offsetMatrix); + parent->Children.push_back(bone); + } + + Bones[ID] = bone; + m_BonesByName[name] = bone; + return ID; + } +} + +Skeleton::~Skeleton() +{ + for (auto &kv : Bones) { + delete kv.second; + } +} + +const Skeleton::Animation* Skeleton::GetAnimation(std::string name) +{ + auto it = Animations.find(name); + if (it != Animations.end()) { + return const_cast(&it->second); + } else { + return nullptr; + } +} + +std::vector Skeleton::GetFrameBones(const Animation& animation, double time, bool noRootMotion /*= false*/) +{ + // HACK: Animation wrap-around + while (time < 0) { + time += animation.Duration; + } + while (time > animation.Duration) { + time -= animation.Duration; + } + + int currentKeyframeIndex = GetKeyframe(animation, time); + + const Animation::Keyframe& currentFrame = animation.Keyframes[currentKeyframeIndex]; + const Animation::Keyframe& nextFrame = animation.Keyframes[(currentKeyframeIndex + 1) % animation.Keyframes.size()]; + float alpha = (time - currentFrame.Time) / (nextFrame.Time - currentFrame.Time); + + //auto animationFrame = Animations[""].Keyframes[frame]; + std::map frameBones; + AccumulateBoneTransforms(noRootMotion, currentFrame, nextFrame, alpha, frameBones, RootBone, glm::mat4(1)); + + std::vector finalMatrices; + for (auto &kv : frameBones) { + finalMatrices.push_back(kv.second); + } + return finalMatrices; +} + +void Skeleton::AccumulateBoneTransforms(bool noRootMotion, const Animation::Keyframe ¤tFrame, const Animation::Keyframe &nextFrame, float progress, std::map &boneMatrices, const Bone* bone, glm::mat4 parentMatrix) +{ + glm::mat4 boneMatrix; + + if (currentFrame.BoneProperties.find(bone->ID) != currentFrame.BoneProperties.end() || nextFrame.BoneProperties.find(bone->ID) != nextFrame.BoneProperties.end()) { + Animation::Keyframe::BoneProperty currentBoneProperty = currentFrame.BoneProperties.at(bone->ID); + Animation::Keyframe::BoneProperty nextBoneProperty = nextFrame.BoneProperties.at(bone->ID); + + glm::vec3 positionInterp = currentBoneProperty.Position * (1.f - progress) + nextBoneProperty.Position * progress; + glm::quat rotationInterp = glm::slerp(currentBoneProperty.Rotation, nextBoneProperty.Rotation, progress); + glm::vec3 scaleInterp = currentBoneProperty.Scale * (1.f - progress) + nextBoneProperty.Scale * progress; + + // Flag for no root motion + if (bone == RootBone && noRootMotion) { + positionInterp.x = 0; + positionInterp.z = 0; + } + + boneMatrix = parentMatrix * (glm::translate(positionInterp) * glm::toMat4(rotationInterp) * glm::scale(scaleInterp)); + boneMatrices[bone->ID] = boneMatrix * bone->OffsetMatrix; + } else { + boneMatrix = parentMatrix * bone->Parent->OffsetMatrix; // * glm::inverse(bone->OffsetMatrix); + boneMatrices[bone->ID] = boneMatrix; // * bone->OffsetMatrix; + } + + for (auto &child : bone->Children) { + std::string name = child->Name; + AccumulateBoneTransforms(noRootMotion, currentFrame, nextFrame, progress, boneMatrices, child, boneMatrix); + } +} + +int Skeleton::GetBoneID(std::string name) +{ + if (m_BonesByName.find(name) == m_BonesByName.end()) { + return -1; + } else { + return m_BonesByName.at(name)->ID; + } +} + +void Skeleton::PrintSkeleton() +{ + if (LOG_LEVEL < LOG_LEVEL_DEBUG) { + return; + } + PrintSkeleton(RootBone, 0); +} + +void Skeleton::PrintSkeleton(const Bone* bone, int depthCount) +{ + std::stringstream ss; + ss << std::string(depthCount, ' '); + ss << bone->ID << ": " << bone->Name; + std::cout << ss.str() << std::endl; + + depthCount++; + + for (auto &child : bone->Children) { + PrintSkeleton(child, depthCount); + } +} + +int Skeleton::GetKeyframe(const Animation& animation, double time) +{ + if (time < 0) { + time = 0; + } + if (time >= animation.Duration) { + return animation.Keyframes.size() - 1; + } + + for (int keyframe = 0; keyframe < animation.Keyframes.size(); ++keyframe) { + if (animation.Keyframes[keyframe].Time > time) { + return (keyframe - 1) % animation.Keyframes.size(); + } + } + + return 0; +} diff --git a/src/Engine/Rendering/Texture.cpp b/src/Engine/Rendering/Texture.cpp new file mode 100644 index 00000000..df2ca63d --- /dev/null +++ b/src/Engine/Rendering/Texture.cpp @@ -0,0 +1,50 @@ +#include "PrecompiledHeader.h" +#include "Rendering/Texture.h" + +Texture::Texture(std::string path) +{ + PNG image(path); + + if (image.Width == 0 && image.Height == 0 || image.Format == Image::ImageFormat::Unknown) { + image = PNG("Textures/Core/ErrorTexture.png"); + if (image.Width == 0 && image.Height == 0 || image.Format == Image::ImageFormat::Unknown) { + LOG_ERROR("Couldn't even load the error texture. This is a dark day indeed."); + return; + } + } + + this->Width = image.Width; + this->Height = image.Height; + + GLint format; + switch (image.Format) { + case Image::ImageFormat::RGB: + format = GL_RGB; + break; + case Image::ImageFormat::RGBA: + format = GL_RGBA; + break; + } + + // Construct the OpenGL texture + glGenTextures(1, &m_Texture); + glBindTexture(GL_TEXTURE_2D, m_Texture); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexImage2D(GL_TEXTURE_2D, 0, format, image.Width, image.Height, 0, format, GL_UNSIGNED_BYTE, image.Data); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + GLERROR("Texture load"); +} + +Texture::~Texture() +{ + glDeleteTextures(1, &m_Texture); +} + +void Texture::Bind(GLenum textureUnit /* = GL_TEXTURE0 */) +{ + glActiveTexture(textureUnit); + glBindTexture(GL_TEXTURE_2D, m_Texture); +}