Moved MD5[Anim|Mesh]Parser loading code to Loader

Similary to OBJParser


Former-commit-id: 243b05f2fbc3899089ef05a29672979d3bbca695
This commit is contained in:
Lynix 2014-07-17 20:15:29 +02:00
parent 4ccf021376
commit fba2e5ae01
7 changed files with 451 additions and 368 deletions

View File

@ -27,7 +27,7 @@ NzOBJParser::~NzOBJParser()
const NzString* NzOBJParser::GetMaterials() const const NzString* NzOBJParser::GetMaterials() const
{ {
return &m_materials[0]; return m_materials.data();
} }
unsigned int NzOBJParser::GetMaterialCount() const unsigned int NzOBJParser::GetMaterialCount() const
@ -37,7 +37,7 @@ unsigned int NzOBJParser::GetMaterialCount() const
const NzOBJParser::Mesh* NzOBJParser::GetMeshes() const const NzOBJParser::Mesh* NzOBJParser::GetMeshes() const
{ {
return &m_meshes[0]; return m_meshes.data();
} }
unsigned int NzOBJParser::GetMeshCount() const unsigned int NzOBJParser::GetMeshCount() const
@ -52,7 +52,7 @@ const NzString& NzOBJParser::GetMtlLib() const
const NzVector3f* NzOBJParser::GetNormals() const const NzVector3f* NzOBJParser::GetNormals() const
{ {
return &m_normals[0]; return m_normals.data();
} }
unsigned int NzOBJParser::GetNormalCount() const unsigned int NzOBJParser::GetNormalCount() const
@ -62,7 +62,7 @@ unsigned int NzOBJParser::GetNormalCount() const
const NzVector4f* NzOBJParser::GetPositions() const const NzVector4f* NzOBJParser::GetPositions() const
{ {
return &m_positions[0]; return m_positions.data();
} }
unsigned int NzOBJParser::GetPositionCount() const unsigned int NzOBJParser::GetPositionCount() const
@ -72,7 +72,7 @@ unsigned int NzOBJParser::GetPositionCount() const
const NzVector3f* NzOBJParser::GetTexCoords() const const NzVector3f* NzOBJParser::GetTexCoords() const
{ {
return &m_texCoords[0]; return m_texCoords.data();
} }
unsigned int NzOBJParser::GetTexCoordCount() const unsigned int NzOBJParser::GetTexCoordCount() const
@ -265,8 +265,8 @@ bool NzOBJParser::Parse()
break; break;
} }
case 's':
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
case 's':
if (m_currentLine.GetSize() <= 2 || m_currentLine[1] == ' ') if (m_currentLine.GetSize() <= 2 || m_currentLine[1] == ' ')
{ {
NzString param = m_currentLine.SubString(2); NzString param = m_currentLine.SubString(2);
@ -275,8 +275,8 @@ bool NzOBJParser::Parse()
} }
else else
UnrecognizedLine(); UnrecognizedLine();
#endif
break; break;
#endif
case 'u': case 'u':
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING

View File

@ -15,14 +15,62 @@ namespace
nzTernary Check(NzInputStream& stream, const NzAnimationParams& parameters) nzTernary Check(NzInputStream& stream, const NzAnimationParams& parameters)
{ {
NzMD5AnimParser parser(stream, parameters); NzMD5AnimParser parser(stream);
return parser.Check(); return parser.Check();
} }
bool Load(NzAnimation* animation, NzInputStream& stream, const NzAnimationParams& parameters) bool Load(NzAnimation* animation, NzInputStream& stream, const NzAnimationParams& parameters)
{ {
NzMD5AnimParser parser(stream, parameters); NzMD5AnimParser parser(stream);
return parser.Parse(animation);
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);
}
}
} }
} }

View File

@ -13,9 +13,8 @@
#include <limits> #include <limits>
#include <Nazara/Utility/Debug.hpp> #include <Nazara/Utility/Debug.hpp>
NzMD5AnimParser::NzMD5AnimParser(NzInputStream& stream, const NzAnimationParams& parameters) : NzMD5AnimParser::NzMD5AnimParser(NzInputStream& stream) :
m_stream(stream), m_stream(stream),
m_parameters(parameters),
m_keepLastLine(false), m_keepLastLine(false),
m_frameIndex(0), m_frameIndex(0),
m_frameRate(0), m_frameRate(0),
@ -47,7 +46,37 @@ nzTernary NzMD5AnimParser::Check()
return nzTernary_False; 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)) while (Advance(false))
{ {
@ -204,47 +233,6 @@ bool NzMD5AnimParser::Parse(NzAnimation* animation)
m_frameRate = 24; 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; return true;
} }
@ -340,7 +328,7 @@ bool NzMD5AnimParser::ParseBounds()
return false; return false;
} }
m_frames[i].aabb.Set(min, max); m_frames[i].bounds.Set(min, max);
} }
if (!Advance()) if (!Advance())

View File

@ -18,23 +18,16 @@
class NzMD5AnimParser class NzMD5AnimParser
{ {
public: public:
NzMD5AnimParser(NzInputStream& stream, const NzAnimationParams& parameters); struct FrameJoint
~NzMD5AnimParser();
nzTernary Check();
bool Parse(NzAnimation* animation);
private:
struct Frame
{
struct Joint
{ {
NzQuaternionf orient; NzQuaternionf orient;
NzVector3f pos; NzVector3f pos;
}; };
std::vector<Joint> joints; struct Frame
NzBoxf aabb; {
std::vector<FrameJoint> joints;
NzBoxf bounds;
}; };
struct Joint struct Joint
@ -47,6 +40,21 @@ class NzMD5AnimParser
unsigned int index; 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); bool Advance(bool required = true);
void Error(const NzString& message); void Error(const NzString& message);
bool ParseBaseframe(); bool ParseBaseframe();
@ -61,7 +69,6 @@ class NzMD5AnimParser
std::vector<Joint> m_joints; std::vector<Joint> m_joints;
NzInputStream& m_stream; NzInputStream& m_stream;
NzString m_currentLine; NzString m_currentLine;
const NzAnimationParams& m_parameters;
bool m_keepLastLine; bool m_keepLastLine;
unsigned int m_frameIndex; unsigned int m_frameIndex;
unsigned int m_frameRate; unsigned int m_frameRate;

View File

@ -4,6 +4,11 @@
#include <Nazara/Utility/Loaders/MD5Mesh.hpp> #include <Nazara/Utility/Loaders/MD5Mesh.hpp>
#include <Nazara/Utility/Loaders/MD5Mesh/Parser.hpp> #include <Nazara/Utility/Loaders/MD5Mesh/Parser.hpp>
#include <Nazara/Utility/IndexIterator.hpp>
#include <Nazara/Utility/IndexMapper.hpp>
#include <Nazara/Utility/SkeletalMesh.hpp>
#include <Nazara/Utility/StaticMesh.hpp>
#include <memory>
#include <Nazara/Utility/Debug.hpp> #include <Nazara/Utility/Debug.hpp>
namespace namespace
@ -15,14 +20,290 @@ namespace
nzTernary Check(NzInputStream& stream, const NzMeshParams& parameters) nzTernary Check(NzInputStream& stream, const NzMeshParams& parameters)
{ {
NzMD5MeshParser parser(stream, parameters); NazaraUnused(parameters);
NzMD5MeshParser parser(stream);
return parser.Check(); return parser.Check();
} }
bool Load(NzMesh* mesh, NzInputStream& stream, const NzMeshParams& parameters) bool Load(NzMesh* mesh, NzInputStream& stream, const NzMeshParams& parameters)
{ {
NzMD5MeshParser parser(stream, parameters); NzMD5MeshParser parser(stream);
return parser.Parse(mesh); 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<nzUInt16>::max());
std::unique_ptr<NzIndexBuffer> indexBuffer(new NzIndexBuffer(largeIndices, indexCount, parameters.storage));
indexBuffer->SetPersistent(false);
std::unique_ptr<NzVertexBuffer> 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<Weight> tempWeights;
NzBufferMapper<NzVertexBuffer> vertexMapper(vertexBuffer.get(), nzBufferAccess_WriteOnly);
NzSkeletalMeshVertex* vertices = static_cast<NzSkeletalMeshVertex*>(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<NzSkeletalMesh> 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<nzUInt16>::max());
std::unique_ptr<NzIndexBuffer> 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<NzVertexBuffer> vertexBuffer(new NzVertexBuffer(NzVertexDeclaration::Get(nzVertexLayout_XYZ_Normal_UV_Tangent), vertexCount, parameters.storage));
NzBufferMapper<NzVertexBuffer> vertexMapper(vertexBuffer.get(), nzBufferAccess_WriteOnly);
NzMeshVertex* vertex = reinterpret_cast<NzMeshVertex*>(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<NzStaticMesh> 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;
} }
} }

View File

@ -19,9 +19,8 @@
#include <memory> #include <memory>
#include <Nazara/Utility/Debug.hpp> #include <Nazara/Utility/Debug.hpp>
NzMD5MeshParser::NzMD5MeshParser(NzInputStream& stream, const NzMeshParams& parameters) : NzMD5MeshParser::NzMD5MeshParser(NzInputStream& stream) :
m_stream(stream), m_stream(stream),
m_parameters(parameters),
m_keepLastLine(false), m_keepLastLine(false),
m_lineCount(0), m_lineCount(0),
m_meshIndex(0), m_meshIndex(0),
@ -52,7 +51,27 @@ nzTernary NzMD5MeshParser::Check()
return nzTernary_False; 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)) 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<nzUInt16>::max());
std::unique_ptr<NzIndexBuffer> indexBuffer(new NzIndexBuffer(largeIndices, indexCount, m_parameters.storage));
indexBuffer->SetPersistent(false);
std::unique_ptr<NzVertexBuffer> 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<Weight> tempWeights(NAZARA_UTILITY_SKINNING_MAX_WEIGHTS);
NzBufferMapper<NzVertexBuffer> vertexMapper(vertexBuffer.get(), nzBufferAccess_WriteOnly);
NzSkeletalMeshVertex* vertices = static_cast<NzSkeletalMeshVertex*>(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<NzSkeletalMesh> 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<nzUInt16>::max());
std::unique_ptr<NzIndexBuffer> 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<NzVertexBuffer> vertexBuffer(new NzVertexBuffer(NzVertexDeclaration::Get(nzVertexLayout_XYZ_Normal_UV_Tangent), vertexCount, m_parameters.storage));
NzBufferMapper<NzVertexBuffer> vertexMapper(vertexBuffer.get(), nzBufferAccess_WriteOnly);
NzMeshVertex* vertex = reinterpret_cast<NzMeshVertex*>(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<NzStaticMesh> 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; return true;
} }
@ -555,7 +309,7 @@ bool NzMD5MeshParser::ParseMesh()
if (!Advance()) if (!Advance())
return false; return false;
Mesh::Triangle& triangle = m_meshes[m_meshIndex].triangles[i]; Triangle& triangle = m_meshes[m_meshIndex].triangles[i];
unsigned int index; unsigned int index;
if (std::sscanf(&m_currentLine[0], "tri %u %u %u %u", &index, &triangle.x, &triangle.y, &triangle.z) != 4) 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()) if (!Advance())
return false; return false;
Mesh::Vertex& vertex = m_meshes[m_meshIndex].vertices[i]; Vertex& vertex = m_meshes[m_meshIndex].vertices[i];
unsigned int index; 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) 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()) if (!Advance())
return false; return false;
Mesh::Weight& weight = m_meshes[m_meshIndex].weights[i]; Weight& weight = m_meshes[m_meshIndex].weights[i];
unsigned int index; unsigned int index;
if (std::sscanf(&m_currentLine[0], "weight %u %u %f ( %f %f %f )", &index, &weight.joint, &weight.bias, 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) &weight.pos.x, &weight.pos.y, &weight.pos.z) != 6)

View File

@ -18,13 +18,6 @@
class NzMD5MeshParser class NzMD5MeshParser
{ {
public: public:
NzMD5MeshParser(NzInputStream& stream, const NzMeshParams& parameters);
~NzMD5MeshParser();
nzTernary Check();
bool Parse(NzMesh* mesh);
private:
struct Joint struct Joint
{ {
NzQuaternionf bindOrient; NzQuaternionf bindOrient;
@ -33,8 +26,6 @@ class NzMD5MeshParser
int parent; int parent;
}; };
struct Mesh
{
typedef NzVector3ui Triangle; typedef NzVector3ui Triangle;
struct Vertex struct Vertex
@ -51,12 +42,27 @@ class NzMD5MeshParser
unsigned int joint; unsigned int joint;
}; };
struct Mesh
{
std::vector<Triangle> triangles; std::vector<Triangle> triangles;
std::vector<Vertex> vertices; std::vector<Vertex> vertices;
std::vector<Weight> weights; std::vector<Weight> weights;
NzString shader; 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); bool Advance(bool required = true);
void Error(const NzString& message); void Error(const NzString& message);
bool ParseJoints(); bool ParseJoints();
@ -68,7 +74,6 @@ class NzMD5MeshParser
std::vector<Mesh> m_meshes; std::vector<Mesh> m_meshes;
NzInputStream& m_stream; NzInputStream& m_stream;
NzString m_currentLine; NzString m_currentLine;
const NzMeshParams& m_parameters;
bool m_keepLastLine; bool m_keepLastLine;
unsigned int m_lineCount; unsigned int m_lineCount;
unsigned int m_meshIndex; unsigned int m_meshIndex;