From fba2e5ae012caa9f554e5204f1bb8e2104bc4e85 Mon Sep 17 00:00:00 2001 From: Lynix Date: Thu, 17 Jul 2014 20:15:29 +0200 Subject: [PATCH] Moved MD5[Anim|Mesh]Parser loading code to Loader Similary to OBJParser Former-commit-id: 243b05f2fbc3899089ef05a29672979d3bbca695 --- src/Nazara/Graphics/Loaders/OBJ/OBJParser.cpp | 14 +- src/Nazara/Utility/Loaders/MD5Anim/Loader.cpp | 54 +++- src/Nazara/Utility/Loaders/MD5Anim/Parser.cpp | 78 ++--- src/Nazara/Utility/Loaders/MD5Anim/Parser.hpp | 37 ++- src/Nazara/Utility/Loaders/MD5Mesh/Loader.cpp | 287 ++++++++++++++++- src/Nazara/Utility/Loaders/MD5Mesh/Parser.cpp | 296 ++---------------- src/Nazara/Utility/Loaders/MD5Mesh/Parser.hpp | 53 ++-- 7 files changed, 451 insertions(+), 368 deletions(-) diff --git a/src/Nazara/Graphics/Loaders/OBJ/OBJParser.cpp b/src/Nazara/Graphics/Loaders/OBJ/OBJParser.cpp index 933deeca6..a527008f8 100644 --- a/src/Nazara/Graphics/Loaders/OBJ/OBJParser.cpp +++ b/src/Nazara/Graphics/Loaders/OBJ/OBJParser.cpp @@ -27,7 +27,7 @@ NzOBJParser::~NzOBJParser() const NzString* NzOBJParser::GetMaterials() const { - return &m_materials[0]; + return m_materials.data(); } unsigned int NzOBJParser::GetMaterialCount() const @@ -37,7 +37,7 @@ unsigned int NzOBJParser::GetMaterialCount() const const NzOBJParser::Mesh* NzOBJParser::GetMeshes() const { - return &m_meshes[0]; + return m_meshes.data(); } unsigned int NzOBJParser::GetMeshCount() const @@ -52,7 +52,7 @@ const NzString& NzOBJParser::GetMtlLib() const const NzVector3f* NzOBJParser::GetNormals() const { - return &m_normals[0]; + return m_normals.data(); } unsigned int NzOBJParser::GetNormalCount() const @@ -62,7 +62,7 @@ unsigned int NzOBJParser::GetNormalCount() const const NzVector4f* NzOBJParser::GetPositions() const { - return &m_positions[0]; + return m_positions.data(); } unsigned int NzOBJParser::GetPositionCount() const @@ -72,7 +72,7 @@ unsigned int NzOBJParser::GetPositionCount() const const NzVector3f* NzOBJParser::GetTexCoords() const { - return &m_texCoords[0]; + return m_texCoords.data(); } unsigned int NzOBJParser::GetTexCoordCount() const @@ -265,8 +265,8 @@ bool NzOBJParser::Parse() break; } + #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING case 's': - #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING if (m_currentLine.GetSize() <= 2 || m_currentLine[1] == ' ') { NzString param = m_currentLine.SubString(2); @@ -275,8 +275,8 @@ bool NzOBJParser::Parse() } else UnrecognizedLine(); - #endif break; + #endif case 'u': #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING diff --git a/src/Nazara/Utility/Loaders/MD5Anim/Loader.cpp b/src/Nazara/Utility/Loaders/MD5Anim/Loader.cpp index 8f9a34783..6558029d1 100644 --- a/src/Nazara/Utility/Loaders/MD5Anim/Loader.cpp +++ b/src/Nazara/Utility/Loaders/MD5Anim/Loader.cpp @@ -15,14 +15,62 @@ namespace nzTernary Check(NzInputStream& stream, const NzAnimationParams& parameters) { - NzMD5AnimParser parser(stream, parameters); + NzMD5AnimParser parser(stream); return parser.Check(); } bool Load(NzAnimation* animation, NzInputStream& stream, const NzAnimationParams& parameters) { - NzMD5AnimParser parser(stream, parameters); - return parser.Parse(animation); + NzMD5AnimParser parser(stream); + + if (!parser.Parse()) + { + NazaraError("MD5Anim parser failed"); + return false; + } + + const NzMD5AnimParser::Frame* frames = parser.GetFrames(); + unsigned int frameCount = parser.GetFrameCount(); + unsigned int frameRate = parser.GetFrameRate(); + const NzMD5AnimParser::Joint* joints = parser.GetJoints(); + unsigned int jointCount = parser.GetJointCount(); + + // À ce stade, nous sommes censés avoir assez d'informations pour créer l'animation + animation->CreateSkeletal(frameCount, jointCount); + + NzSequence sequence; + sequence.firstFrame = 0; + sequence.frameCount = frameCount; + sequence.frameRate = frameRate; + sequence.name = stream.GetPath().SubStringFrom(NAZARA_DIRECTORY_SEPARATOR, -1, true); + + animation->AddSequence(sequence); + + NzSequenceJoint* sequenceJoints = animation->GetSequenceJoints(); + + // Pour que le squelette soit correctement aligné, il faut appliquer un quaternion "de correction" aux joints à la base du squelette + NzQuaternionf rotationQuat = NzEulerAnglesf(-90.f, 90.f, 0.f); + for (unsigned int i = 0; i < jointCount; ++i) + { + int parent = joints[i].parent; + for (unsigned int j = 0; j < frameCount; ++j) + { + NzSequenceJoint& sequenceJoint = sequenceJoints[j*jointCount + i]; + + if (parent >= 0) + { + sequenceJoint.position = frames[j].joints[i].pos; + sequenceJoint.rotation = frames[j].joints[i].orient; + } + else + { + sequenceJoint.position = rotationQuat * frames[j].joints[i].pos; + sequenceJoint.rotation = rotationQuat * frames[j].joints[i].orient; + } + + sequenceJoint.scale.Set(1.f); + } + } } } diff --git a/src/Nazara/Utility/Loaders/MD5Anim/Parser.cpp b/src/Nazara/Utility/Loaders/MD5Anim/Parser.cpp index 8d5399596..4b035a5e7 100644 --- a/src/Nazara/Utility/Loaders/MD5Anim/Parser.cpp +++ b/src/Nazara/Utility/Loaders/MD5Anim/Parser.cpp @@ -13,9 +13,8 @@ #include #include -NzMD5AnimParser::NzMD5AnimParser(NzInputStream& stream, const NzAnimationParams& parameters) : +NzMD5AnimParser::NzMD5AnimParser(NzInputStream& stream) : m_stream(stream), -m_parameters(parameters), m_keepLastLine(false), m_frameIndex(0), m_frameRate(0), @@ -47,7 +46,37 @@ nzTernary NzMD5AnimParser::Check() return nzTernary_False; } -bool NzMD5AnimParser::Parse(NzAnimation* animation) +unsigned int NzMD5AnimParser::GetAnimatedComponentCount() const +{ + return m_animatedComponents.size(); +} + +const NzMD5AnimParser::Frame* NzMD5AnimParser::GetFrames() const +{ + return m_frames.data(); +} + +unsigned int NzMD5AnimParser::GetFrameCount() const +{ + return m_frames.size(); +} + +unsigned int NzMD5AnimParser::GetFrameRate() const +{ + return m_frameRate; +} + +const NzMD5AnimParser::Joint* NzMD5AnimParser::GetJoints() const +{ + return m_joints.data(); +} + +unsigned int NzMD5AnimParser::GetJointCount() const +{ + return m_joints.size(); +} + +bool NzMD5AnimParser::Parse() { while (Advance(false)) { @@ -204,47 +233,6 @@ bool NzMD5AnimParser::Parse(NzAnimation* animation) m_frameRate = 24; } - // À ce stade, nous sommes censés avoir assez d'informations pour créer l'animation - if (!animation->CreateSkeletal(frameCount, jointCount)) - { - NazaraError("Failed to create animation"); - return false; - } - - NzSequence sequence; - sequence.firstFrame = 0; - sequence.frameCount = m_frames.size(); - sequence.frameRate = m_frameRate; - sequence.name = m_stream.GetPath().SubStringFrom(NAZARA_DIRECTORY_SEPARATOR, -1, true); - if (!animation->AddSequence(sequence)) - NazaraWarning("Failed to add sequence"); - - NzSequenceJoint* sequenceJoints = animation->GetSequenceJoints(); - - // Pour que le squelette soit correctement aligné, il faut appliquer un quaternion "de correction" aux joints à la base du squelette - NzQuaternionf rotationQuat = NzEulerAnglesf(-90.f, 90.f, 0.f); - for (unsigned int i = 0; i < jointCount; ++i) - { - int parent = m_joints[i].parent; - for (unsigned int j = 0; j < frameCount; ++j) - { - NzSequenceJoint& sequenceJoint = sequenceJoints[j*jointCount + i]; - - if (parent >= 0) - { - sequenceJoint.position = m_frames[j].joints[i].pos; - sequenceJoint.rotation = m_frames[j].joints[i].orient; - } - else - { - sequenceJoint.position = rotationQuat * m_frames[j].joints[i].pos; - sequenceJoint.rotation = rotationQuat * m_frames[j].joints[i].orient; - } - - sequenceJoint.scale.Set(1.f); - } - } - return true; } @@ -340,7 +328,7 @@ bool NzMD5AnimParser::ParseBounds() return false; } - m_frames[i].aabb.Set(min, max); + m_frames[i].bounds.Set(min, max); } if (!Advance()) diff --git a/src/Nazara/Utility/Loaders/MD5Anim/Parser.hpp b/src/Nazara/Utility/Loaders/MD5Anim/Parser.hpp index 1fc097f59..e3b9e19c8 100644 --- a/src/Nazara/Utility/Loaders/MD5Anim/Parser.hpp +++ b/src/Nazara/Utility/Loaders/MD5Anim/Parser.hpp @@ -18,23 +18,16 @@ class NzMD5AnimParser { public: - NzMD5AnimParser(NzInputStream& stream, const NzAnimationParams& parameters); - ~NzMD5AnimParser(); + struct FrameJoint + { + NzQuaternionf orient; + NzVector3f pos; + }; - nzTernary Check(); - bool Parse(NzAnimation* animation); - - private: struct Frame { - struct Joint - { - NzQuaternionf orient; - NzVector3f pos; - }; - - std::vector joints; - NzBoxf aabb; + std::vector joints; + NzBoxf bounds; }; struct Joint @@ -47,6 +40,21 @@ class NzMD5AnimParser unsigned int index; }; + NzMD5AnimParser(NzInputStream& stream); + ~NzMD5AnimParser(); + + nzTernary Check(); + + unsigned int GetAnimatedComponentCount() const; + const Frame* GetFrames() const; + unsigned int GetFrameCount() const; + unsigned int GetFrameRate() const; + const Joint* GetJoints() const; + unsigned int GetJointCount() const; + + bool Parse(); + + private: bool Advance(bool required = true); void Error(const NzString& message); bool ParseBaseframe(); @@ -61,7 +69,6 @@ class NzMD5AnimParser std::vector m_joints; NzInputStream& m_stream; NzString m_currentLine; - const NzAnimationParams& m_parameters; bool m_keepLastLine; unsigned int m_frameIndex; unsigned int m_frameRate; diff --git a/src/Nazara/Utility/Loaders/MD5Mesh/Loader.cpp b/src/Nazara/Utility/Loaders/MD5Mesh/Loader.cpp index 9973fbc84..ae3e22977 100644 --- a/src/Nazara/Utility/Loaders/MD5Mesh/Loader.cpp +++ b/src/Nazara/Utility/Loaders/MD5Mesh/Loader.cpp @@ -4,6 +4,11 @@ #include #include +#include +#include +#include +#include +#include #include namespace @@ -15,14 +20,290 @@ namespace nzTernary Check(NzInputStream& stream, const NzMeshParams& parameters) { - NzMD5MeshParser parser(stream, parameters); + NazaraUnused(parameters); + + NzMD5MeshParser parser(stream); return parser.Check(); } bool Load(NzMesh* mesh, NzInputStream& stream, const NzMeshParams& parameters) { - NzMD5MeshParser parser(stream, parameters); - return parser.Parse(mesh); + NzMD5MeshParser parser(stream); + if (!parser.Parse()) + { + NazaraError("MD5Mesh parser failed"); + return false; + } + + // Pour que le squelette soit correctement aligné, il faut appliquer un quaternion "de correction" aux joints à la base du squelette + NzQuaternionf rotationQuat = NzEulerAnglesf(-90.f, 180.f, 0.f); + NzString baseDir = stream.GetDirectory(); + + // Le hellknight de Doom 3 fait ~120 unités, et il est dit qu'il fait trois mètres + // Nous réduisons donc la taille générale des fichiers MD5 de 1/40 + NzVector3f scale(parameters.scale/40.f); + + const NzMD5MeshParser::Joint* joints = parser.GetJoints(); + const NzMD5MeshParser::Mesh* meshes = parser.GetMeshes(); + unsigned int jointCount = parser.GetJointCount(); + unsigned int meshCount = parser.GetMeshCount(); + + if (parameters.animated) + { + mesh->CreateSkeletal(jointCount); + + NzSkeleton* skeleton = mesh->GetSkeleton(); + for (unsigned int i = 0; i < jointCount; ++i) + { + NzJoint* joint = skeleton->GetJoint(i); + + int parent = joints[i].parent; + if (parent >= 0) + joint->SetParent(skeleton->GetJoint(parent)); + + joint->SetName(joints[i].name); + + NzMatrix4f bindMatrix; + + if (parent >= 0) + bindMatrix.MakeTransform(joints[i].bindPos, joints[i].bindOrient); + else + bindMatrix.MakeTransform(rotationQuat * joints[i].bindPos, rotationQuat * joints[i].bindOrient); + + joint->SetInverseBindMatrix(bindMatrix.InverseAffine()); + } + + mesh->SetMaterialCount(meshCount); + for (unsigned int i = 0; i < meshCount; ++i) + { + const NzMD5MeshParser::Mesh& md5Mesh = meshes[i]; + + unsigned int indexCount = md5Mesh.triangles.size()*3; + unsigned int vertexCount = md5Mesh.vertices.size(); + + bool largeIndices = (vertexCount > std::numeric_limits::max()); + + std::unique_ptr indexBuffer(new NzIndexBuffer(largeIndices, indexCount, parameters.storage)); + indexBuffer->SetPersistent(false); + + std::unique_ptr vertexBuffer(new NzVertexBuffer(NzVertexDeclaration::Get(nzVertexLayout_XYZ_Normal_UV_Tangent_Skinning), vertexCount, parameters.storage, nzBufferUsage_Static)); + vertexBuffer->SetPersistent(false); + + // Index buffer + NzIndexMapper indexMapper(indexBuffer.get(), nzBufferAccess_DiscardAndWrite); + + // Le format définit un set de triangles nous permettant de retrouver facilement les indices + // Cependant les sommets des triangles ne sont pas spécifiés dans le même ordre que ceux du moteur + // (On parle ici de winding) + unsigned int index = 0; + for (const NzMD5MeshParser::Triangle& triangle : md5Mesh.triangles) + { + // On les respécifie dans le bon ordre (inversion du winding) + indexMapper.Set(index++, triangle.x); + indexMapper.Set(index++, triangle.z); + indexMapper.Set(index++, triangle.y); + } + + indexMapper.Unmap(); + + // Vertex buffer + struct Weight + { + float bias; + unsigned int jointIndex; + }; + + std::vector tempWeights; + + NzBufferMapper vertexMapper(vertexBuffer.get(), nzBufferAccess_WriteOnly); + NzSkeletalMeshVertex* vertices = static_cast(vertexMapper.GetPointer()); + for (const NzMD5MeshParser::Vertex& vertex : md5Mesh.vertices) + { + // Skinning MD5 (Formule d'Id Tech) + NzVector3f finalPos(NzVector3f::Zero()); + + // On stocke tous les poids dans le tableau temporaire en même temps qu'on calcule la position finale du sommet. + tempWeights.resize(vertex.weightCount); + for (unsigned int j = 0; j < vertex.weightCount; ++j) + { + const NzMD5MeshParser::Weight& weight = md5Mesh.weights[vertex.startWeight + j]; + const NzMD5MeshParser::Joint& joint = joints[weight.joint]; + + finalPos += (joint.bindPos + joint.bindOrient*weight.pos) * weight.bias; + + // Avant d'ajouter les poids, il faut s'assurer qu'il n'y en ait pas plus que le maximum supporté + // et dans le cas contraire, garder les poids les plus importants et les renormaliser + tempWeights[j] = {weight.bias, weight.joint}; + } + + // Avons nous plus de poids que le moteur ne peut en supporter ? + unsigned int weightCount = vertex.weightCount; + if (weightCount > NAZARA_UTILITY_SKINNING_MAX_WEIGHTS) + { + // Pour augmenter la qualité du skinning tout en ne gardant que X poids, on ne garde que les poids + // les plus importants, ayant le plus d'impact sur le sommet final + std::sort(tempWeights.begin(), tempWeights.end(), [] (const Weight& a, const Weight& b) -> bool { + return a.bias > b.bias; + }); + + // Sans oublier bien sûr de renormaliser les poids (que leur somme soit 1) + float weightSum = 0.f; + for (unsigned int j = 0; j < NAZARA_UTILITY_SKINNING_MAX_WEIGHTS; ++j) + weightSum += tempWeights[j].bias; + + for (unsigned int j = 0; j < NAZARA_UTILITY_SKINNING_MAX_WEIGHTS; ++j) + tempWeights[j].bias /= weightSum; + + weightCount = NAZARA_UTILITY_SKINNING_MAX_WEIGHTS; + } + + vertices->weightCount = weightCount; + for (unsigned int j = 0; j < NAZARA_UTILITY_SKINNING_MAX_WEIGHTS; ++j) + { + if (j < weightCount) + { + // On donne une valeur aux poids présents + vertices->weights[j] = tempWeights[j].bias; + vertices->jointIndexes[j] = tempWeights[j].jointIndex; + } + else + { + // Et un poids de 0 sur le joint 0 pour les autres (nécessaire pour le GPU Skinning) + // La raison est que le GPU ne tiendra pas compte du nombre de poids pour des raisons de performances. + vertices->weights[j] = 0.f; + vertices->jointIndexes[j] = 0; + } + } + + vertices->position = finalPos; + + // Le format MD5 spécifie ses UV avec l'origine en bas à gauche, contrairement au moteur + // dont l'origine est en haut à gauche, nous inversons donc la valeur en Y. + vertices->uv.Set(vertex.uv.x, 1.f - vertex.uv.y); + vertices++; + } + + vertexMapper.Unmap(); + + // Material + mesh->SetMaterial(i, baseDir + md5Mesh.shader); + + // Submesh + std::unique_ptr subMesh(new NzSkeletalMesh(mesh)); + subMesh->Create(vertexBuffer.get()); + vertexBuffer.release(); + + if (parameters.optimizeIndexBuffers) + indexBuffer->Optimize(); + + subMesh->SetIndexBuffer(indexBuffer.get()); + indexBuffer.release(); + + subMesh->GenerateNormalsAndTangents(); + subMesh->SetMaterialIndex(i); + subMesh->SetPrimitiveMode(nzPrimitiveMode_TriangleList); + + mesh->AddSubMesh(subMesh.get()); + subMesh.release(); + + // Animation + // Il est peut-être éventuellement possible que la probabilité que l'animation ait le même nom soit non-nulle. + NzString path = stream.GetPath(); + if (!path.IsEmpty()) + { + path.Replace(".md5mesh", ".md5anim", -8, NzString::CaseInsensitive); + if (NzFile::Exists(path)) + mesh->SetAnimation(path); + } + } + } + else + { + if (!mesh->CreateStatic()) // Ne devrait jamais échouer + { + NazaraInternalError("Failed to create mesh"); + return false; + } + + mesh->SetMaterialCount(meshCount); + for (unsigned int i = 0; i < meshCount; ++i) + { + const NzMD5MeshParser::Mesh& md5Mesh = meshes[i]; + unsigned int indexCount = md5Mesh.triangles.size()*3; + unsigned int vertexCount = md5Mesh.vertices.size(); + + // Index buffer + bool largeIndices = (vertexCount > std::numeric_limits::max()); + + std::unique_ptr indexBuffer(new NzIndexBuffer(largeIndices, indexCount, parameters.storage)); + indexBuffer->SetPersistent(false); + + NzIndexMapper indexMapper(indexBuffer.get(), nzBufferAccess_DiscardAndWrite); + NzIndexIterator index = indexMapper.begin(); + + for (const NzMD5MeshParser::Triangle& triangle : md5Mesh.triangles) + { + // On les respécifie dans le bon ordre + *index++ = triangle.x; + *index++ = triangle.z; + *index++ = triangle.y; + } + indexMapper.Unmap(); + + // Vertex buffer + std::unique_ptr vertexBuffer(new NzVertexBuffer(NzVertexDeclaration::Get(nzVertexLayout_XYZ_Normal_UV_Tangent), vertexCount, parameters.storage)); + NzBufferMapper vertexMapper(vertexBuffer.get(), nzBufferAccess_WriteOnly); + + NzMeshVertex* vertex = reinterpret_cast(vertexMapper.GetPointer()); + for (const NzMD5MeshParser::Vertex& md5Vertex : md5Mesh.vertices) + { + // Skinning MD5 (Formule d'Id Tech) + NzVector3f finalPos(NzVector3f::Zero()); + for (unsigned int j = 0; j < md5Vertex.weightCount; ++j) + { + const NzMD5MeshParser::Weight& weight = md5Mesh.weights[md5Vertex.startWeight + j]; + const NzMD5MeshParser::Joint& joint = joints[weight.joint]; + + finalPos += (joint.bindPos + joint.bindOrient*weight.pos) * weight.bias; + } + + // On retourne le modèle dans le bon sens + vertex->position = scale * (rotationQuat * finalPos); + vertex->uv.Set(md5Vertex.uv.x, 1.f - md5Vertex.uv.y); + vertex++; + } + + vertexMapper.Unmap(); + + // Submesh + std::unique_ptr subMesh(new NzStaticMesh(mesh)); + subMesh->Create(vertexBuffer.get()); + + vertexBuffer->SetPersistent(false); + vertexBuffer.release(); + + if (parameters.optimizeIndexBuffers) + indexBuffer->Optimize(); + + subMesh->SetIndexBuffer(indexBuffer.get()); + indexBuffer.release(); + + // Material + mesh->SetMaterial(i, baseDir + md5Mesh.shader); + + subMesh->GenerateAABB(); + subMesh->GenerateNormalsAndTangents(); + subMesh->SetMaterialIndex(i); + + if (parameters.center) + subMesh->Center(); + + mesh->AddSubMesh(subMesh.get()); + subMesh.release(); + } + } + + return true; } } diff --git a/src/Nazara/Utility/Loaders/MD5Mesh/Parser.cpp b/src/Nazara/Utility/Loaders/MD5Mesh/Parser.cpp index 03e244a0c..c9747371f 100644 --- a/src/Nazara/Utility/Loaders/MD5Mesh/Parser.cpp +++ b/src/Nazara/Utility/Loaders/MD5Mesh/Parser.cpp @@ -19,9 +19,8 @@ #include #include -NzMD5MeshParser::NzMD5MeshParser(NzInputStream& stream, const NzMeshParams& parameters) : +NzMD5MeshParser::NzMD5MeshParser(NzInputStream& stream) : m_stream(stream), -m_parameters(parameters), m_keepLastLine(false), m_lineCount(0), m_meshIndex(0), @@ -52,7 +51,27 @@ nzTernary NzMD5MeshParser::Check() return nzTernary_False; } -bool NzMD5MeshParser::Parse(NzMesh* mesh) +const NzMD5MeshParser::Joint* NzMD5MeshParser::GetJoints() const +{ + return m_joints.data(); +} + +unsigned int NzMD5MeshParser::GetJointCount() const +{ + return m_joints.size(); +} + +const NzMD5MeshParser::Mesh* NzMD5MeshParser::GetMeshes() const +{ + return m_meshes.data(); +} + +unsigned int NzMD5MeshParser::GetMeshCount() const +{ + return m_meshes.size(); +} + +bool NzMD5MeshParser::Parse() { while (Advance(false)) { @@ -151,271 +170,6 @@ bool NzMD5MeshParser::Parse(NzMesh* mesh) } } - // Pour que le squelette soit correctement aligné, il faut appliquer un quaternion "de correction" aux joints à la base du squelette - NzQuaternionf rotationQuat = NzEulerAnglesf(-90.f, 180.f, 0.f); - NzString baseDir = m_stream.GetDirectory(); - - // Le hellknight de Doom 3 fait ~120 unités, et il est dit qu'il fait trois mètres - // Nous réduisons donc la taille générale des fichiers MD5 de 1/40 - NzVector3f scale(m_parameters.scale/40.f); - - if (m_parameters.animated) - { - if (!mesh->CreateSkeletal(m_joints.size())) // Ne devrait jamais échouer - { - NazaraInternalError("Failed to create mesh"); - return false; - } - - NzSkeleton* skeleton = mesh->GetSkeleton(); - for (unsigned int i = 0; i < m_joints.size(); ++i) - { - NzJoint* joint = skeleton->GetJoint(i); - - int parent = m_joints[i].parent; - if (parent >= 0) - joint->SetParent(skeleton->GetJoint(parent)); - - joint->SetName(m_joints[i].name); - - NzMatrix4f bindMatrix; - - if (parent >= 0) - bindMatrix.MakeTransform(m_joints[i].bindPos, m_joints[i].bindOrient); - else - bindMatrix.MakeTransform(rotationQuat * m_joints[i].bindPos, rotationQuat * m_joints[i].bindOrient); - - joint->SetInverseBindMatrix(bindMatrix.InverseAffine()); - } - - mesh->SetMaterialCount(m_meshes.size()); - for (unsigned int i = 0; i < m_meshes.size(); ++i) - { - const Mesh& md5Mesh = m_meshes[i]; - - unsigned int indexCount = md5Mesh.triangles.size()*3; - unsigned int vertexCount = md5Mesh.vertices.size(); - - bool largeIndices = (vertexCount > std::numeric_limits::max()); - - std::unique_ptr indexBuffer(new NzIndexBuffer(largeIndices, indexCount, m_parameters.storage)); - indexBuffer->SetPersistent(false); - - std::unique_ptr vertexBuffer(new NzVertexBuffer(NzVertexDeclaration::Get(nzVertexLayout_XYZ_Normal_UV_Tangent_Skinning), vertexCount, m_parameters.storage, nzBufferUsage_Static)); - vertexBuffer->SetPersistent(false); - - // Index buffer - NzIndexMapper indexMapper(indexBuffer.get(), nzBufferAccess_DiscardAndWrite); - - unsigned int index = 0; - for (const Mesh::Triangle& triangle : md5Mesh.triangles) - { - // On les respécifie dans le bon ordre - indexMapper.Set(index++, triangle.x); - indexMapper.Set(index++, triangle.z); - indexMapper.Set(index++, triangle.y); - } - - indexMapper.Unmap(); - - // Vertex buffer - struct Weight - { - float bias; - unsigned int jointIndex; - }; - - std::vector tempWeights(NAZARA_UTILITY_SKINNING_MAX_WEIGHTS); - - NzBufferMapper vertexMapper(vertexBuffer.get(), nzBufferAccess_WriteOnly); - NzSkeletalMeshVertex* vertices = static_cast(vertexMapper.GetPointer()); - for (const Mesh::Vertex& vertex : md5Mesh.vertices) - { - // Skinning MD5 (Formule d'Id Tech) - NzVector3f finalPos(NzVector3f::Zero()); - - tempWeights.resize(vertex.weightCount); - for (unsigned int j = 0; j < vertex.weightCount; ++j) - { - const Mesh::Weight& weight = md5Mesh.weights[vertex.startWeight + j]; - const Joint& joint = m_joints[weight.joint]; - - finalPos += (joint.bindPos + joint.bindOrient*weight.pos) * weight.bias; - - // Avant d'ajouter les poids, il faut s'assurer qu'il n'y en ait pas plus que le maximum supporté - // et dans le cas contraire, garder les poids les plus importants et les renormaliser - tempWeights[j] = {weight.bias, weight.joint}; - } - - unsigned int weightCount = vertex.weightCount; - if (weightCount > NAZARA_UTILITY_SKINNING_MAX_WEIGHTS) - { - // Pour augmenter la qualité du skinning tout en ne gardant que X poids, on ne garde que les poids - // les plus importants, ayant le plus d'impact sur le sommet final - std::sort(tempWeights.begin(), tempWeights.end(), [] (const Weight& a, const Weight& b) -> bool { - return a.bias > b.bias; - }); - - // Sans oublier bien sûr de renormaliser les poids (que leur somme soit 1) - float weightSum = 0.f; - for (unsigned int j = 0; j < NAZARA_UTILITY_SKINNING_MAX_WEIGHTS; ++j) - weightSum += tempWeights[j].bias; - - for (unsigned int j = 0; j < NAZARA_UTILITY_SKINNING_MAX_WEIGHTS; ++j) - tempWeights[j].bias /= weightSum; - - weightCount = NAZARA_UTILITY_SKINNING_MAX_WEIGHTS; - } - - vertices->weightCount = weightCount; - for (unsigned int j = 0; j < NAZARA_UTILITY_SKINNING_MAX_WEIGHTS; ++j) - { - // On donne une valeur aux poids présents, et 0 pour les autres (nécessaire pour le GPU Skinning) - if (j < weightCount) - { - vertices->weights[j] = tempWeights[j].bias; - vertices->jointIndexes[j] = tempWeights[j].jointIndex; - } - else - { - vertices->weights[j] = 0.f; - vertices->jointIndexes[j] = 0; - } - } - - vertices->position = finalPos; - vertices->uv.Set(vertex.uv.x, 1.f-vertex.uv.y); - vertices++; - } - - vertexMapper.Unmap(); - - // Material - mesh->SetMaterial(i, baseDir + md5Mesh.shader); - - // Submesh - std::unique_ptr subMesh(new NzSkeletalMesh(mesh)); - if (!subMesh->Create(vertexBuffer.get())) - { - NazaraError("Failed to create skeletal mesh"); - continue; - } - vertexBuffer.release(); - - if (m_parameters.optimizeIndexBuffers) - indexBuffer->Optimize(); - - subMesh->SetIndexBuffer(indexBuffer.get()); - indexBuffer.release(); - - subMesh->GenerateNormalsAndTangents(); - subMesh->SetMaterialIndex(i); - subMesh->SetPrimitiveMode(nzPrimitiveMode_TriangleList); - - mesh->AddSubMesh(subMesh.get()); - subMesh.release(); - - // Animation - // Il est peut-être éventuellement possible que la probabilité que l'animation ait le même nom soit non-nulle. - NzString path = m_stream.GetPath(); - if (!path.IsEmpty()) - { - path.Replace(".md5mesh", ".md5anim", -8, NzString::CaseInsensitive); - if (NzFile::Exists(path)) - mesh->SetAnimation(path); - } - } - } - else - { - if (!mesh->CreateStatic()) // Ne devrait jamais échouer - { - NazaraInternalError("Failed to create mesh"); - return false; - } - - mesh->SetMaterialCount(m_meshes.size()); - for (unsigned int i = 0; i < m_meshes.size(); ++i) - { - const Mesh& md5Mesh = m_meshes[i]; - unsigned int indexCount = md5Mesh.triangles.size()*3; - unsigned int vertexCount = md5Mesh.vertices.size(); - - // Index buffer - bool largeIndices = (vertexCount > std::numeric_limits::max()); - - std::unique_ptr indexBuffer(new NzIndexBuffer(largeIndices, indexCount, m_parameters.storage)); - indexBuffer->SetPersistent(false); - - NzIndexMapper indexMapper(indexBuffer.get(), nzBufferAccess_DiscardAndWrite); - NzIndexIterator index = indexMapper.begin(); - - for (const Mesh::Triangle& triangle : md5Mesh.triangles) - { - // On les respécifie dans le bon ordre - *index++ = triangle.x; - *index++ = triangle.z; - *index++ = triangle.y; - } - indexMapper.Unmap(); - - // Vertex buffer - std::unique_ptr vertexBuffer(new NzVertexBuffer(NzVertexDeclaration::Get(nzVertexLayout_XYZ_Normal_UV_Tangent), vertexCount, m_parameters.storage)); - NzBufferMapper vertexMapper(vertexBuffer.get(), nzBufferAccess_WriteOnly); - - NzMeshVertex* vertex = reinterpret_cast(vertexMapper.GetPointer()); - for (const Mesh::Vertex& md5Vertex : md5Mesh.vertices) - { - // Skinning MD5 (Formule d'Id Tech) - NzVector3f finalPos(NzVector3f::Zero()); - for (unsigned int j = 0; j < md5Vertex.weightCount; ++j) - { - const Mesh::Weight& weight = md5Mesh.weights[md5Vertex.startWeight + j]; - const Joint& joint = m_joints[weight.joint]; - - finalPos += (joint.bindPos + joint.bindOrient*weight.pos) * weight.bias; - } - - // On retourne le modèle dans le bon sens - vertex->position = scale * (rotationQuat * finalPos); - vertex->uv.Set(md5Vertex.uv.x, 1.f - md5Vertex.uv.y); - vertex++; - } - - vertexMapper.Unmap(); - - // Submesh - std::unique_ptr subMesh(new NzStaticMesh(mesh)); - if (!subMesh->Create(vertexBuffer.get())) - { - NazaraError("Failed to create static submesh"); - continue; - } - - vertexBuffer->SetPersistent(false); - vertexBuffer.release(); - - if (m_parameters.optimizeIndexBuffers) - indexBuffer->Optimize(); - - subMesh->SetIndexBuffer(indexBuffer.get()); - indexBuffer.release(); - - // Material - mesh->SetMaterial(i, baseDir + md5Mesh.shader); - - subMesh->GenerateAABB(); - subMesh->GenerateNormalsAndTangents(); - subMesh->SetMaterialIndex(i); - - if (m_parameters.center) - subMesh->Center(); - - mesh->AddSubMesh(subMesh.get()); - subMesh.release(); - } - } - return true; } @@ -555,7 +309,7 @@ bool NzMD5MeshParser::ParseMesh() if (!Advance()) return false; - Mesh::Triangle& triangle = m_meshes[m_meshIndex].triangles[i]; + Triangle& triangle = m_meshes[m_meshIndex].triangles[i]; unsigned int index; if (std::sscanf(&m_currentLine[0], "tri %u %u %u %u", &index, &triangle.x, &triangle.y, &triangle.z) != 4) { @@ -578,7 +332,7 @@ bool NzMD5MeshParser::ParseMesh() if (!Advance()) return false; - Mesh::Vertex& vertex = m_meshes[m_meshIndex].vertices[i]; + Vertex& vertex = m_meshes[m_meshIndex].vertices[i]; unsigned int index; if (std::sscanf(&m_currentLine[0], "vert %u ( %f %f ) %u %u", &index, &vertex.uv.x, &vertex.uv.y, &vertex.startWeight, &vertex.weightCount) != 5) { @@ -601,7 +355,7 @@ bool NzMD5MeshParser::ParseMesh() if (!Advance()) return false; - Mesh::Weight& weight = m_meshes[m_meshIndex].weights[i]; + Weight& weight = m_meshes[m_meshIndex].weights[i]; unsigned int index; if (std::sscanf(&m_currentLine[0], "weight %u %u %f ( %f %f %f )", &index, &weight.joint, &weight.bias, &weight.pos.x, &weight.pos.y, &weight.pos.z) != 6) diff --git a/src/Nazara/Utility/Loaders/MD5Mesh/Parser.hpp b/src/Nazara/Utility/Loaders/MD5Mesh/Parser.hpp index 18f0dbc39..cb63af487 100644 --- a/src/Nazara/Utility/Loaders/MD5Mesh/Parser.hpp +++ b/src/Nazara/Utility/Loaders/MD5Mesh/Parser.hpp @@ -18,13 +18,6 @@ class NzMD5MeshParser { public: - NzMD5MeshParser(NzInputStream& stream, const NzMeshParams& parameters); - ~NzMD5MeshParser(); - - nzTernary Check(); - bool Parse(NzMesh* mesh); - - private: struct Joint { NzQuaternionf bindOrient; @@ -33,30 +26,43 @@ class NzMD5MeshParser int parent; }; + typedef NzVector3ui Triangle; + + struct Vertex + { + NzVector2f uv; + unsigned int startWeight; + unsigned int weightCount; + }; + + struct Weight + { + NzVector3f pos; + float bias; + unsigned int joint; + }; + struct Mesh { - typedef NzVector3ui Triangle; - - struct Vertex - { - NzVector2f uv; - unsigned int startWeight; - unsigned int weightCount; - }; - - struct Weight - { - NzVector3f pos; - float bias; - unsigned int joint; - }; - std::vector triangles; std::vector vertices; std::vector weights; NzString shader; }; + NzMD5MeshParser(NzInputStream& stream); + ~NzMD5MeshParser(); + + nzTernary Check(); + + const Joint* GetJoints() const; + unsigned int GetJointCount() const; + const Mesh* GetMeshes() const; + unsigned int GetMeshCount() const; + + bool Parse(); + + private: bool Advance(bool required = true); void Error(const NzString& message); bool ParseJoints(); @@ -68,7 +74,6 @@ class NzMD5MeshParser std::vector m_meshes; NzInputStream& m_stream; NzString m_currentLine; - const NzMeshParams& m_parameters; bool m_keepLastLine; unsigned int m_lineCount; unsigned int m_meshIndex;