553 lines
15 KiB
C++
553 lines
15 KiB
C++
#include "src/Resource/ModelLoader.h"
|
|
|
|
#include "src/FileSystem/FileSystem.h"
|
|
#include "src/Core/Logger.h"
|
|
|
|
#include "src/Core/String.h"
|
|
|
|
#include "src/Resource/SkinnedModel.h"
|
|
#include "src/Resource/Model.h"
|
|
#include "src/Resource/SkeletalAnimation.h"
|
|
|
|
namespace Nuake
|
|
{
|
|
ModelLoader::ModelLoader() {}
|
|
ModelLoader::~ModelLoader() {}
|
|
|
|
Ref<Model> ModelLoader::LoadModel(const std::string& path, bool absolute)
|
|
{
|
|
m_Meshes.clear();
|
|
Ref<Model> model = CreateRef<Model>(path);
|
|
|
|
Assimp::Importer importer;
|
|
importer.SetPropertyFloat("PP_GSN_MAX_SMOOTHING_ANGLE", 90);
|
|
|
|
auto importFlags =
|
|
aiProcess_Triangulate |
|
|
aiProcess_GenSmoothNormals |
|
|
aiProcess_FixInfacingNormals |
|
|
aiProcess_CalcTangentSpace;
|
|
|
|
modelDir = absolute ? path + "/../" : FileSystem::Root + path + "/../";
|
|
const std::string filePath = absolute ? path : FileSystem::Root + path;
|
|
const aiScene* scene = importer.ReadFile(filePath, importFlags);
|
|
if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode)
|
|
{
|
|
std::string assimpErrorMsg = std::string(importer.GetErrorString());
|
|
std::string logMsg = "[Failed to load model] - " + assimpErrorMsg;
|
|
Logger::Log(logMsg, "model", WARNING);
|
|
|
|
return model;
|
|
}
|
|
|
|
ProcessNode(scene->mRootNode, scene);
|
|
|
|
for (const auto& mesh : m_Meshes)
|
|
{
|
|
model->AddMesh(mesh);
|
|
}
|
|
|
|
importer.FreeScene();
|
|
|
|
return model;
|
|
}
|
|
|
|
|
|
std::unordered_map<std::string, uint32_t> bonesNameIDMap;
|
|
Ref<SkinnedModel> ModelLoader::LoadSkinnedModel(const std::string& path, bool absolute)
|
|
{
|
|
bonesNameIDMap = std::unordered_map<std::string, uint32_t>();
|
|
m_BoneMap = std::unordered_map<std::string, Bone>();
|
|
|
|
m_SkinnedMeshes.clear();
|
|
Ref<SkinnedModel> model = CreateRef<SkinnedModel>(path);
|
|
|
|
Assimp::Importer importer;
|
|
importer.SetPropertyFloat("PP_GSN_MAX_SMOOTHING_ANGLE", 90);
|
|
|
|
auto importFlags =
|
|
aiProcess_Triangulate |
|
|
aiProcess_GenSmoothNormals |
|
|
aiProcess_FixInfacingNormals |
|
|
aiProcess_CalcTangentSpace |
|
|
aiProcess_OptimizeGraph;
|
|
|
|
modelDir = absolute ? path + "/../" : FileSystem::Root + path + "/../";
|
|
const std::string filePath = absolute ? path : FileSystem::Root + path;
|
|
const aiScene* scene = importer.ReadFile(filePath, importFlags);
|
|
if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode)
|
|
{
|
|
std::string assimpErrorMsg = std::string(importer.GetErrorString());
|
|
std::string logMsg = "Failed to load model: " + assimpErrorMsg;
|
|
Logger::Log(logMsg, "model", WARNING);
|
|
|
|
return model;
|
|
}
|
|
|
|
ProcessSkinnedNode(scene->mRootNode, scene);
|
|
if (scene->HasAnimations())
|
|
{
|
|
std::vector<Ref<SkeletalAnimation>> animations = std::vector<Ref<SkeletalAnimation>>();
|
|
|
|
// Parse animations
|
|
for (uint32_t i = 0; i < scene->mNumAnimations; i++)
|
|
{
|
|
aiAnimation* aiAnim = scene->mAnimations[i];
|
|
|
|
const std::string animationName = aiAnim->mName.data;
|
|
const float animationDuration = static_cast<float>(aiAnim->mDuration);
|
|
const float animationTicksPerSecond = static_cast<float>(aiAnim->mTicksPerSecond);
|
|
auto animation = CreateRef<SkeletalAnimation>(animationName, animationDuration, animationTicksPerSecond);
|
|
|
|
// Here we iterate over every channel(each channel represents a bone) and fill the SkeletalAnimation
|
|
// object with every keyframe. We essentially just convert every assimp vector, quat, string to our own types.
|
|
for (uint32_t j = 0; j < aiAnim->mNumChannels; j++)
|
|
{
|
|
aiNodeAnim* animChannel = aiAnim->mChannels[j];
|
|
const std::string channelName = animChannel->mNodeName.data; // this should be a bone name
|
|
|
|
// Create the track
|
|
BoneTransformTrack& track = animation->GetTrack(channelName);
|
|
|
|
// Create every keyframe in the current channel
|
|
// Position
|
|
for (uint32_t p = 0; p < animChannel->mNumPositionKeys; p++)
|
|
{
|
|
aiVectorKey positionKey = animChannel->mPositionKeys[p];
|
|
const float keyTime = static_cast<float>(positionKey.mTime);
|
|
const aiVector3D assimpKeyValue = positionKey.mValue;
|
|
const Vector3 keyValue = Vector3(assimpKeyValue.x, assimpKeyValue.y, assimpKeyValue.z);
|
|
|
|
track.PushPositionKeyframe(keyTime, keyValue);
|
|
}
|
|
|
|
// Rotation
|
|
for (uint32_t r = 0; r < animChannel->mNumRotationKeys; r++)
|
|
{
|
|
aiQuatKey rotationKey = animChannel->mRotationKeys[r];
|
|
const float keyTime = static_cast<float>(rotationKey.mTime);
|
|
const aiQuaterniont assimpKeyValue = rotationKey.mValue;
|
|
const Quat keyValue = Quat(assimpKeyValue.w, assimpKeyValue.x, assimpKeyValue.y, assimpKeyValue.z);
|
|
|
|
track.PushRotationKeyframe(keyTime, keyValue);
|
|
}
|
|
|
|
// Scaling
|
|
for (uint32_t s = 0; s < animChannel->mNumScalingKeys; s++)
|
|
{
|
|
aiVectorKey scaleKey = animChannel->mScalingKeys[s];
|
|
const float keyTime = static_cast<float>(scaleKey.mTime);
|
|
const aiVector3D assimpKeyValue = scaleKey.mValue;
|
|
const Vector3 keyValue = Vector3(assimpKeyValue.x, assimpKeyValue.y, assimpKeyValue.z);
|
|
|
|
track.PushScaleKeyframe(keyTime, keyValue);
|
|
}
|
|
}
|
|
|
|
animations.push_back(animation);
|
|
}
|
|
|
|
SkeletonNode rootSkeletonNode;
|
|
ProcessAnimationNode(rootSkeletonNode, scene->mRootNode);
|
|
model->SetSkeletonRootNode(rootSkeletonNode);
|
|
model->SetAnimations(std::move(animations));
|
|
}
|
|
|
|
for (const auto& mesh : m_SkinnedMeshes)
|
|
{
|
|
model->AddMesh(mesh);
|
|
}
|
|
|
|
return model;
|
|
}
|
|
|
|
void ModelLoader::ProcessNode(aiNode* node, const aiScene* scene)
|
|
{
|
|
for (uint32_t i = 0; i < node->mNumMeshes; i++)
|
|
{
|
|
aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
|
|
m_Meshes.push_back(ProcessMesh(mesh, scene));
|
|
}
|
|
|
|
for (uint32_t i = 0; i < node->mNumChildren; i++)
|
|
{
|
|
ProcessNode(node->mChildren[i], scene);
|
|
}
|
|
}
|
|
|
|
void ModelLoader::ProcessSkinnedNode(aiNode* node, const aiScene* scene)
|
|
{
|
|
for (uint32_t i = 0; i < node->mNumMeshes; i++)
|
|
{
|
|
aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
|
|
m_SkinnedMeshes.push_back(ProcessSkinnedMesh(mesh, scene));
|
|
}
|
|
|
|
for (uint32_t i = 0; i < node->mNumChildren; i++)
|
|
{
|
|
ProcessSkinnedNode(node->mChildren[i], scene);
|
|
}
|
|
}
|
|
|
|
Ref<SkinnedMesh> ModelLoader::ProcessSkinnedMesh(aiMesh* node, const aiScene* scene)
|
|
{
|
|
auto vertices = ProcessSkinnedVertices(node);
|
|
auto indices = ProcessIndices(node);
|
|
auto material = ProcessMaterials(scene, node);
|
|
auto bones = std::vector<Bone>();
|
|
|
|
if (node->HasBones())
|
|
{
|
|
uint32_t boneCounter = 0;
|
|
for (uint32_t i = 0; i < node->mNumBones; i++)
|
|
{
|
|
aiBone* bone = node->mBones[i];
|
|
const std::string& boneName = bone->mName.C_Str();
|
|
|
|
int32_t boneId = -1;
|
|
if (m_BoneMap.find(boneName) == m_BoneMap.end())
|
|
{
|
|
boneId = boneCounter;
|
|
boneCounter++;
|
|
|
|
Bone newBone = Bone(boneName, boneId);
|
|
newBone.Offset = ConvertMatrixToGLMFormat(bone->mOffsetMatrix);
|
|
bones.push_back(newBone);
|
|
|
|
bonesNameIDMap[boneName] = boneId;
|
|
m_BoneMap[boneName] = newBone;
|
|
}
|
|
else
|
|
{
|
|
boneId = m_BoneMap[boneName].Id;
|
|
}
|
|
|
|
assert(boneId != -1);
|
|
|
|
const uint32_t numWeight = bone->mNumWeights;
|
|
for (uint32_t j = 0; j < numWeight; j++)
|
|
{
|
|
aiVertexWeight vertexWeight = bone->mWeights[j];
|
|
const uint32_t vertexWeightVertexId = vertexWeight.mVertexId;
|
|
|
|
SetVertexBoneData(vertices[vertexWeightVertexId], boneId, vertexWeight.mWeight);
|
|
|
|
//BoneVertexWeight boneVertexWeight
|
|
//{
|
|
// vertexWeightVertexId,
|
|
// vertexWeight.mWeight
|
|
//};
|
|
//
|
|
//Bone& newBone = bones[boneId];
|
|
//newBone.VertexWeights.push_back(std::move(boneVertexWeight));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Logger::Log("Using skinned mesh imported while the model has no bones!", "model loader", WARNING);
|
|
}
|
|
|
|
// Fill in the bones in the vertices
|
|
Ref<SkinnedMesh> mesh = CreateRef<SkinnedMesh>();
|
|
mesh->AddSurface(vertices, indices, bones);
|
|
mesh->SetMaterial(material);
|
|
return mesh;
|
|
}
|
|
|
|
Ref<Mesh> ModelLoader::ProcessMesh(aiMesh* node, const aiScene* scene)
|
|
{
|
|
auto vertices = ProcessVertices(node);
|
|
auto indices = ProcessIndices(node);
|
|
auto material = ProcessMaterials(scene, node);
|
|
|
|
Ref<Mesh> mesh = CreateRef<Mesh>();
|
|
mesh->AddSurface(vertices, indices);
|
|
mesh->SetMaterial(material);
|
|
|
|
return mesh;
|
|
}
|
|
|
|
std::vector<Vertex> ModelLoader::ProcessVertices(aiMesh* mesh)
|
|
{
|
|
auto vertices = std::vector<Vertex>();
|
|
for (uint32_t i = 0; i < mesh->mNumVertices; i++)
|
|
{
|
|
Vertex vertex {};
|
|
|
|
Vector3 current;
|
|
|
|
// Position
|
|
current.x = mesh->mVertices[i].x;
|
|
current.y = mesh->mVertices[i].y;
|
|
current.z = mesh->mVertices[i].z;
|
|
vertex.position = current;
|
|
|
|
// Normals
|
|
current.x = mesh->mNormals[i].x;
|
|
current.y = mesh->mNormals[i].y;
|
|
current.z = mesh->mNormals[i].z;
|
|
vertex.normal = current;
|
|
|
|
// Tangents
|
|
if (mesh->mTangents)
|
|
{
|
|
current.x = mesh->mTangents[i].x;
|
|
current.y = mesh->mTangents[i].z;
|
|
current.z = mesh->mTangents[i].y;
|
|
vertex.tangent = current;
|
|
}
|
|
|
|
if (mesh->mBitangents)
|
|
{
|
|
current.x = mesh->mBitangents[i].x;
|
|
current.y = mesh->mBitangents[i].z;
|
|
current.z = mesh->mBitangents[i].y;
|
|
vertex.bitangent = current;
|
|
}
|
|
|
|
vertex.uv = glm::vec2(0.0f, 0.0f);
|
|
|
|
// Does it contain UVs?
|
|
if (mesh->mTextureCoords[0])
|
|
{
|
|
float u = mesh->mTextureCoords[0][i].x;
|
|
float v = mesh->mTextureCoords[0][i].y;
|
|
vertex.uv = Vector2(u, v);
|
|
}
|
|
|
|
vertices.push_back(vertex);
|
|
}
|
|
|
|
return vertices;
|
|
}
|
|
|
|
std::vector<SkinnedVertex> ModelLoader::ProcessSkinnedVertices(aiMesh* mesh)
|
|
{
|
|
auto vertices = std::vector<SkinnedVertex>();
|
|
for (uint32_t i = 0; i < mesh->mNumVertices; i++)
|
|
{
|
|
SkinnedVertex vertex;
|
|
for (int w = 0; w < MAX_BONE_INFLUENCE; w++)
|
|
{
|
|
vertex.boneIDs[w] = -1;
|
|
vertex.weights[w] = 0.0f;
|
|
}
|
|
|
|
Vector3 current;
|
|
|
|
// Position
|
|
current.x = mesh->mVertices[i].x;
|
|
current.y = mesh->mVertices[i].y;
|
|
current.z = mesh->mVertices[i].z;
|
|
vertex.position = current;
|
|
|
|
// Normals
|
|
current.x = mesh->mNormals[i].x;
|
|
current.y = mesh->mNormals[i].y;
|
|
current.z = mesh->mNormals[i].z;
|
|
vertex.normal = current;
|
|
|
|
// Tangents
|
|
if (mesh->mTangents)
|
|
{
|
|
current.x = mesh->mTangents[i].x;
|
|
current.y = mesh->mTangents[i].y;
|
|
current.z = mesh->mTangents[i].z;
|
|
vertex.tangent = current;
|
|
}
|
|
|
|
if (mesh->mBitangents)
|
|
{
|
|
current.x = mesh->mBitangents[i].x;
|
|
current.y = mesh->mBitangents[i].y;
|
|
current.z = mesh->mBitangents[i].z;
|
|
vertex.bitangent = current;
|
|
}
|
|
|
|
vertex.uv = glm::vec2(0.0f, 0.0f);
|
|
|
|
// Does it contain UVs?
|
|
if (mesh->mTextureCoords[0])
|
|
{
|
|
float u = mesh->mTextureCoords[0][i].x;
|
|
float v = mesh->mTextureCoords[0][i].y;
|
|
vertex.uv = Vector2(u, v);
|
|
}
|
|
|
|
vertices.push_back(vertex);
|
|
}
|
|
|
|
return vertices;
|
|
}
|
|
|
|
void ModelLoader::SetVertexBoneData(SkinnedVertex& vertex, int boneID, float weight)
|
|
{
|
|
for (int i = 0; i < MAX_BONE_INFLUENCE; ++i)
|
|
{
|
|
if (vertex.boneIDs[i] == boneID) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (weight == 0.0f)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < MAX_BONE_INFLUENCE; ++i)
|
|
{
|
|
if (vertex.boneIDs[i] < 0)
|
|
{
|
|
vertex.weights[i] = weight;
|
|
vertex.boneIDs[i] = boneID;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ModelLoader::ProcessAnimationNode(SkeletonNode& dest, const aiNode* src)
|
|
{
|
|
assert(src);
|
|
|
|
dest.Name = src->mName.data;
|
|
dest.Transform = ConvertMatrixToGLMFormat(src->mTransformation);
|
|
dest.ChildrenCount = src->mNumChildren;
|
|
dest.Id = bonesNameIDMap[dest.Name];
|
|
dest.Offset = m_BoneMap[dest.Name].Offset;
|
|
|
|
for (uint32_t i = 0; i < dest.ChildrenCount; i++)
|
|
{
|
|
SkeletonNode newNode;
|
|
ProcessAnimationNode(newNode, src->mChildren[i]);
|
|
dest.Children.push_back(std::move(newNode));
|
|
}
|
|
}
|
|
|
|
void ModelLoader::ProcessSkeleton(SkeletonNode& des, const aiNode* src)
|
|
{
|
|
// Create a Bone object for this node
|
|
SkeletonNode bone;
|
|
bone.Name = src->mName.C_Str();
|
|
bone.Transform = ConvertMatrixToGLMFormat(src->mTransformation);
|
|
bone.Id = bonesNameIDMap[bone.Name];
|
|
|
|
des.ChildrenCount++;
|
|
des.Children.push_back(bone);
|
|
|
|
// Recursively process child nodes (bones)
|
|
for (uint32_t i = 0; i < src->mNumChildren; i++)
|
|
{
|
|
ProcessSkeleton(bone, src->mChildren[i]);
|
|
}
|
|
}
|
|
|
|
std::vector<uint32_t> ModelLoader::ProcessIndices(aiMesh* mesh)
|
|
{
|
|
auto indices = std::vector<uint32_t>();
|
|
for (uint32_t i = 0; i < mesh->mNumFaces; i++)
|
|
{
|
|
aiFace face = mesh->mFaces[i];
|
|
for (uint32_t j = 0; j < face.mNumIndices; j++)
|
|
{
|
|
indices.push_back(face.mIndices[j]);
|
|
}
|
|
}
|
|
|
|
return indices;
|
|
}
|
|
|
|
Ref<Material> ModelLoader::ProcessMaterials(const aiScene* scene, aiMesh* mesh)
|
|
{
|
|
if (mesh->mMaterialIndex < 0)
|
|
return nullptr;
|
|
|
|
aiMaterial* materialNode = scene->mMaterials[mesh->mMaterialIndex];
|
|
Ref<Material> material = CreateRef<Material>();
|
|
|
|
aiString materialName;
|
|
materialNode->Get(AI_MATKEY_NAME, materialName);
|
|
material->SetName(std::string(materialName.C_Str()));
|
|
|
|
aiString str;
|
|
if (materialNode->GetTextureCount(aiTextureType_DIFFUSE) > 0)
|
|
{
|
|
materialNode->GetTexture(aiTextureType_DIFFUSE, 0, &str);
|
|
Ref<Texture> albedoTexture = ProcessTextures(scene, str.C_Str());
|
|
material->SetAlbedo(albedoTexture);
|
|
}
|
|
|
|
if (materialNode->GetTextureCount(aiTextureType_NORMALS) > 0)
|
|
{
|
|
materialNode->GetTexture(aiTextureType_NORMALS, 0, &str);
|
|
Ref<Texture> albedoTexture = ProcessTextures(scene, str.C_Str());
|
|
material->SetNormal(albedoTexture);
|
|
}
|
|
|
|
if (materialNode->GetTextureCount(aiTextureType_METALNESS) > 0)
|
|
{
|
|
materialNode->GetTexture(aiTextureType_METALNESS, 0, &str);
|
|
Ref<Texture> albedoTexture = ProcessTextures(scene, str.C_Str());
|
|
material->SetMetalness(albedoTexture);
|
|
}
|
|
|
|
if (materialNode->GetTextureCount(aiTextureType_AMBIENT_OCCLUSION) > 0)
|
|
{
|
|
materialNode->GetTexture(aiTextureType_AMBIENT_OCCLUSION, 0, &str);
|
|
Ref<Texture> albedoTexture = ProcessTextures(scene, str.C_Str());
|
|
material->SetAO(albedoTexture);
|
|
}
|
|
|
|
if (materialNode->GetTextureCount(aiTextureType_DIFFUSE_ROUGHNESS) > 0)
|
|
{
|
|
materialNode->GetTexture(aiTextureType_DIFFUSE_ROUGHNESS, 0, &str);
|
|
Ref<Texture> albedoTexture = ProcessTextures(scene, str.C_Str());
|
|
material->SetRoughness(albedoTexture);
|
|
}
|
|
|
|
if (materialNode->GetTextureCount(aiTextureType_DISPLACEMENT) > 0)
|
|
{
|
|
materialNode->GetTexture(aiTextureType_DISPLACEMENT, 0, &str);
|
|
Ref<Texture> albedoTexture = ProcessTextures(scene, str.C_Str());
|
|
material->SetDisplacement(albedoTexture);
|
|
}
|
|
|
|
return material;
|
|
}
|
|
|
|
Ref<Texture> ModelLoader::ProcessTextures(const aiScene* scene, const std::string& path)
|
|
{
|
|
// Load embedded textures, a texture is embedded is the path
|
|
// starts with a '*'.
|
|
if (String::BeginsWith(path, "*"))
|
|
{
|
|
uint32_t textureIndex = std::atoi(String::Split(path, '*')[1].c_str());
|
|
const aiTexture* aitexture = scene->GetEmbeddedTexture(path.c_str());
|
|
|
|
Ref<Texture> nuakeTexture;
|
|
if (aitexture->mHeight == 0)
|
|
{
|
|
// We are using a compression like jpeg, where width is the size of the buffer in bytes.
|
|
nuakeTexture = CreateRef<Texture>((unsigned char*)aitexture->pcData, static_cast<int>(aitexture->mWidth));
|
|
}
|
|
else
|
|
{
|
|
Vector2 textureSize = Vector2(aitexture->mWidth, aitexture->mHeight);
|
|
nuakeTexture = CreateRef<Texture>((unsigned char*)aitexture->pcData, static_cast<int>(textureSize.x));
|
|
}
|
|
|
|
return nuakeTexture;
|
|
}
|
|
|
|
std::string texturePath = modelDir + path;
|
|
if (!FileSystem::FileExists(texturePath, true))
|
|
{
|
|
std::string textureNotFoundmsg = "Texture file couldn't be found: " + texturePath;
|
|
Logger::Log(textureNotFoundmsg, "model", Nuake::LOG_TYPE::WARNING);
|
|
|
|
texturePath = "Resources/Textures/Default.png";
|
|
}
|
|
|
|
return TextureManager::Get()->GetTexture(texturePath);
|
|
}
|
|
} |