#ifndef ASSIMP_BUILD_NO_MD5_IMPORTER
#include "RemoveComments.h"
#include "MD5Loader.h"
#include "StringComparison.h"
#include "fast_atof.h"
#include "SkeletonMeshBuilder.h"
#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/IOSystem.hpp>
#include <assimp/DefaultLogger.hpp>
#include <assimp/importerdesc.h>
#include <memory>
using namespace Assimp;
#define AI_MD5_WEIGHT_EPSILON 1e-5f
static const aiImporterDesc desc = {
"Doom 3 / MD5 Mesh Importer",
"",
"",
"",
aiImporterFlags_SupportBinaryFlavour,
0,
0,
0,
0,
"md5mesh md5camera md5anim"
};
MD5Importer::MD5Importer()
: mIOHandler()
, mBuffer()
, fileSize()
, iLineNumber()
, pScene()
, pIOHandler()
, bHadMD5Mesh()
, bHadMD5Anim()
, bHadMD5Camera()
, configNoAutoLoad (false)
{}
MD5Importer::~MD5Importer()
{}
bool MD5Importer::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
{
const std::string extension = GetExtension(pFile);
if (extension == "md5anim" || extension == "md5mesh" || extension == "md5camera")
return true;
else if (!extension.length() || checkSig) {
if (!pIOHandler) {
return true;
}
const char* tokens[] = {"MD5Version"};
return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1);
}
return false;
}
const aiImporterDesc* MD5Importer::GetInfo () const
{
return &desc;
}
void MD5Importer::SetupProperties(const Importer* pImp)
{
configNoAutoLoad = (0 != pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MD5_NO_ANIM_AUTOLOAD,0));
}
void MD5Importer::InternReadFile( const std::string& pFile,
aiScene* _pScene, IOSystem* _pIOHandler)
{
pIOHandler = _pIOHandler;
pScene = _pScene;
bHadMD5Mesh = bHadMD5Anim = bHadMD5Camera = false;
const std::string::size_type pos = pFile.find_last_of('.');
mFile = (std::string::npos == pos ? pFile : pFile.substr(0,pos+1));
const std::string extension = GetExtension(pFile);
try {
if (extension == "md5camera") {
LoadMD5CameraFile();
}
else if (configNoAutoLoad || extension == "md5anim") {
if (extension.length() == 0) {
throw DeadlyImportError("Failure, need file extension to determine MD5 part type");
}
if (extension == "md5anim") {
LoadMD5AnimFile();
}
else if (extension == "md5mesh") {
LoadMD5MeshFile();
}
}
else {
LoadMD5MeshFile();
LoadMD5AnimFile();
}
}
catch ( ... ) { UnloadFileFromMemory();
throw;
}
if (!bHadMD5Mesh && !bHadMD5Anim && !bHadMD5Camera) {
throw DeadlyImportError("Failed to read valid contents out of this MD5* file");
}
pScene->mRootNode->mTransformation = aiMatrix4x4(1.f,0.f,0.f,0.f,
0.f,0.f,1.f,0.f,0.f,-1.f,0.f,0.f,0.f,0.f,0.f,1.f);
if (!bHadMD5Mesh) {
pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
}
UnloadFileFromMemory();
}
void MD5Importer::LoadFileIntoMemory (IOStream* file)
{
UnloadFileFromMemory();
ai_assert(NULL != file);
fileSize = (unsigned int)file->FileSize();
ai_assert(fileSize);
mBuffer = new char[fileSize+1];
file->Read( (void*)mBuffer, 1, fileSize);
iLineNumber = 1;
mBuffer[fileSize] = '\0';
CommentRemover::RemoveLineComments("//",mBuffer,' ');
}
void MD5Importer::UnloadFileFromMemory ()
{
delete[] mBuffer;
mBuffer = NULL;
fileSize = 0;
}
void MD5Importer::MakeDataUnique (MD5::MeshDesc& meshSrc)
{
std::vector<bool> abHad(meshSrc.mVertices.size(),false);
const unsigned int iNewNum = static_cast<unsigned int>(meshSrc.mFaces.size()*3);
unsigned int iNewIndex = static_cast<unsigned int>(meshSrc.mVertices.size());
meshSrc.mVertices.resize(iNewNum);
const float fWeightsPerVert = meshSrc.mWeights.size() / (float)iNewIndex;
const unsigned int guess = (unsigned int)(fWeightsPerVert*iNewNum);
meshSrc.mWeights.reserve(guess + (guess >> 3));
for (FaceList::const_iterator iter = meshSrc.mFaces.begin(),iterEnd = meshSrc.mFaces.end();iter != iterEnd;++iter){
const aiFace& face = *iter;
for (unsigned int i = 0; i < 3;++i) {
if (face.mIndices[0] >= meshSrc.mVertices.size()) {
throw DeadlyImportError("MD5MESH: Invalid vertex index");
}
if (abHad[face.mIndices[i]]) {
meshSrc.mVertices[iNewIndex] = meshSrc.mVertices[face.mIndices[i]];
face.mIndices[i] = iNewIndex++;
}
else abHad[face.mIndices[i]] = true;
}
std::swap(face.mIndices[0],face.mIndices[2]);
}
}
void MD5Importer::AttachChilds_Mesh(int iParentID,aiNode* piParent, BoneList& bones)
{
ai_assert(NULL != piParent && !piParent->mNumChildren);
for (int i = 0; i < (int)bones.size();++i) {
if (iParentID != i && bones[i].mParentIndex == iParentID) {
++piParent->mNumChildren;
}
}
if (piParent->mNumChildren) {
piParent->mChildren = new aiNode*[piParent->mNumChildren];
for (int i = 0; i < (int)bones.size();++i) {
if (iParentID != i && bones[i].mParentIndex == iParentID) {
aiNode* pc;
*piParent->mChildren++ = pc = new aiNode();
pc->mName = aiString(bones[i].mName);
pc->mParent = piParent;
aiQuaternion quat;
MD5::ConvertQuaternion ( bones[i].mRotationQuat, quat );
bones[i].mTransform = aiMatrix4x4 ( quat.GetMatrix());
bones[i].mTransform.a4 = bones[i].mPositionXYZ.x;
bones[i].mTransform.b4 = bones[i].mPositionXYZ.y;
bones[i].mTransform.c4 = bones[i].mPositionXYZ.z;
pc->mTransformation = bones[i].mInvTransform = bones[i].mTransform;
bones[i].mInvTransform.Inverse();
if (-1 != iParentID) {
pc->mTransformation = bones[iParentID].mInvTransform * pc->mTransformation;
}
AttachChilds_Mesh( i, pc, bones);
}
}
piParent->mChildren -= piParent->mNumChildren;
}
}
void MD5Importer::AttachChilds_Anim(int iParentID,aiNode* piParent, AnimBoneList& bones,const aiNodeAnim** node_anims)
{
ai_assert(NULL != piParent && !piParent->mNumChildren);
for (int i = 0; i < (int)bones.size();++i) {
if (iParentID != i && bones[i].mParentIndex == iParentID) {
++piParent->mNumChildren;
}
}
if (piParent->mNumChildren) {
piParent->mChildren = new aiNode*[piParent->mNumChildren];
for (int i = 0; i < (int)bones.size();++i) {
if (iParentID != i && bones[i].mParentIndex == iParentID)
{
aiNode* pc;
*piParent->mChildren++ = pc = new aiNode();
pc->mName = aiString(bones[i].mName);
pc->mParent = piParent;
const aiNodeAnim** cur = node_anims;
while ((**cur).mNodeName != pc->mName)++cur;
aiMatrix4x4::Translation((**cur).mPositionKeys[0].mValue,pc->mTransformation);
pc->mTransformation = pc->mTransformation * aiMatrix4x4((**cur).mRotationKeys[0].mValue.GetMatrix()) ;
AttachChilds_Anim( i, pc, bones,node_anims);
}
}
piParent->mChildren -= piParent->mNumChildren;
}
}
void MD5Importer::LoadMD5MeshFile ()
{
std::string pFile = mFile + "md5mesh";
std::unique_ptr<IOStream> file( pIOHandler->Open( pFile, "rb"));
if( file.get() == NULL || !file->FileSize()) {
DefaultLogger::get()->warn("Failed to access MD5MESH file: " + pFile);
return;
}
bHadMD5Mesh = true;
LoadFileIntoMemory(file.get());
MD5::MD5Parser parser(mBuffer,fileSize);
MD5::MD5MeshParser meshParser(parser.mSections);
pScene->mRootNode = new aiNode("<MD5_Root>");
pScene->mRootNode->mNumChildren = 2;
pScene->mRootNode->mChildren = new aiNode*[2];
aiNode* pcNode = pScene->mRootNode->mChildren[1] = new aiNode();
pcNode->mName.Set("<MD5_Hierarchy>");
pcNode->mParent = pScene->mRootNode;
AttachChilds_Mesh(-1,pcNode,meshParser.mJoints);
pcNode = pScene->mRootNode->mChildren[0] = new aiNode();
pcNode->mName.Set("<MD5_Mesh>");
pcNode->mParent = pScene->mRootNode;
#if 0#else
for (std::vector<MD5::MeshDesc>::const_iterator it = meshParser.mMeshes.begin(),end = meshParser.mMeshes.end(); it != end;++it) {
if (!(*it).mFaces.empty() && !(*it).mVertices.empty())
++pScene->mNumMaterials;
}
pScene->mNumMeshes = pScene->mNumMaterials;
pScene->mMeshes = new aiMesh*[pScene->mNumMeshes];
pScene->mMaterials = new aiMaterial*[pScene->mNumMeshes];
pcNode->mNumMeshes = pScene->mNumMeshes;
pcNode->mMeshes = new unsigned int[pcNode->mNumMeshes];
for (unsigned int m = 0; m < pcNode->mNumMeshes;++m)
pcNode->mMeshes[m] = m;
unsigned int n = 0;
for (std::vector<MD5::MeshDesc>::iterator it = meshParser.mMeshes.begin(),end = meshParser.mMeshes.end(); it != end;++it) {
MD5::MeshDesc& meshSrc = *it;
if (meshSrc.mFaces.empty() || meshSrc.mVertices.empty())
continue;
aiMesh* mesh = pScene->mMeshes[n] = new aiMesh();
mesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
MakeDataUnique(meshSrc);
mesh->mNumVertices = (unsigned int) meshSrc.mVertices.size();
mesh->mVertices = new aiVector3D[mesh->mNumVertices];
mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices];
mesh->mNumUVComponents[0] = 2;
aiVector3D* pv = mesh->mTextureCoords[0];
for (MD5::VertexList::const_iterator iter = meshSrc.mVertices.begin();iter != meshSrc.mVertices.end();++iter,++pv) {
pv->x = (*iter).mUV.x;
pv->y = 1.0f-(*iter).mUV.y; pv->z = 0.0f;
}
unsigned int* piCount = new unsigned int[meshParser.mJoints.size()];
::memset(piCount,0,sizeof(unsigned int)*meshParser.mJoints.size());
for (MD5::VertexList::const_iterator iter = meshSrc.mVertices.begin();iter != meshSrc.mVertices.end();++iter,++pv) {
for (unsigned int jub = (*iter).mFirstWeight, w = jub; w < jub + (*iter).mNumWeights;++w)
{
MD5::WeightDesc& desc = meshSrc.mWeights[w];
if (!(desc.mWeight < AI_MD5_WEIGHT_EPSILON && desc.mWeight >= -AI_MD5_WEIGHT_EPSILON ))
++piCount[desc.mBone];
}
}
for (unsigned int p = 0; p < meshParser.mJoints.size();++p)
if (piCount[p])mesh->mNumBones++;
if (mesh->mNumBones) {
mesh->mBones = new aiBone*[mesh->mNumBones];
for (unsigned int q = 0,h = 0; q < meshParser.mJoints.size();++q)
{
if (!piCount[q])continue;
aiBone* p = mesh->mBones[h] = new aiBone();
p->mNumWeights = piCount[q];
p->mWeights = new aiVertexWeight[p->mNumWeights];
p->mName = aiString(meshParser.mJoints[q].mName);
p->mOffsetMatrix = meshParser.mJoints[q].mInvTransform;
MD5::BoneDesc& boneSrc = meshParser.mJoints[q];
boneSrc.mMap = h++;
MD5::ConvertQuaternion( boneSrc.mRotationQuat, boneSrc.mRotationQuatConverted );
}
pv = mesh->mVertices;
for (MD5::VertexList::const_iterator iter = meshSrc.mVertices.begin();iter != meshSrc.mVertices.end();++iter,++pv) {
*pv = aiVector3D();
ai_real fSum = 0.0;
for (unsigned int jub = (*iter).mFirstWeight, w = jub; w < jub + (*iter).mNumWeights;++w)
fSum += meshSrc.mWeights[w].mWeight;
if (!fSum) {
DefaultLogger::get()->error("MD5MESH: The sum of all vertex bone weights is 0");
continue;
}
for (unsigned int jub = (*iter).mFirstWeight, w = jub; w < jub + (*iter).mNumWeights;++w) {
if (w >= meshSrc.mWeights.size())
throw DeadlyImportError("MD5MESH: Invalid weight index");
MD5::WeightDesc& desc = meshSrc.mWeights[w];
if ( desc.mWeight < AI_MD5_WEIGHT_EPSILON && desc.mWeight >= -AI_MD5_WEIGHT_EPSILON) {
continue;
}
const ai_real fNewWeight = desc.mWeight / fSum;
MD5::BoneDesc& boneSrc = meshParser.mJoints[desc.mBone];
const aiVector3D v = boneSrc.mRotationQuatConverted.Rotate (desc.vOffsetPosition);
*pv += ((boneSrc.mPositionXYZ+v)* (ai_real)desc.mWeight);
aiBone* bone = mesh->mBones[boneSrc.mMap];
*bone->mWeights++ = aiVertexWeight((unsigned int)(pv-mesh->mVertices),fNewWeight);
}
}
for (unsigned int p = 0; p < mesh->mNumBones;++p) {
mesh->mBones[p]->mWeights -= mesh->mBones[p]->mNumWeights;
}
}
delete[] piCount;
mesh->mNumFaces = (unsigned int)meshSrc.mFaces.size();
mesh->mFaces = new aiFace[mesh->mNumFaces];
for (unsigned int c = 0; c < mesh->mNumFaces;++c) {
mesh->mFaces[c].mNumIndices = 3;
mesh->mFaces[c].mIndices = meshSrc.mFaces[c].mIndices;
meshSrc.mFaces[c].mIndices = NULL;
}
aiMaterial* mat = new aiMaterial();
pScene->mMaterials[n] = mat;
if (meshSrc.mShader.length && !strchr(meshSrc.mShader.data,'.')) {
aiString temp(meshSrc.mShader);
temp.Append("_local.tga");
mat->AddProperty(&temp,AI_MATKEY_TEXTURE_NORMALS(0));
temp = aiString(meshSrc.mShader);
temp.Append("_s.tga");
mat->AddProperty(&temp,AI_MATKEY_TEXTURE_SPECULAR(0));
temp = aiString(meshSrc.mShader);
temp.Append("_d.tga");
mat->AddProperty(&temp,AI_MATKEY_TEXTURE_DIFFUSE(0));
temp = aiString(meshSrc.mShader);
temp.Append("_h.tga");
mat->AddProperty(&temp,AI_MATKEY_TEXTURE_HEIGHT(0));
mat->AddProperty(&meshSrc.mShader,AI_MATKEY_NAME);
}
else mat->AddProperty(&meshSrc.mShader,AI_MATKEY_TEXTURE_DIFFUSE(0));
mesh->mMaterialIndex = n++;
}
#endif
}
void MD5Importer::LoadMD5AnimFile ()
{
std::string pFile = mFile + "md5anim";
std::unique_ptr<IOStream> file( pIOHandler->Open( pFile, "rb"));
if( !file.get() || !file->FileSize()) {
DefaultLogger::get()->warn("Failed to read MD5ANIM file: " + pFile);
return;
}
LoadFileIntoMemory(file.get());
MD5::MD5Parser parser(mBuffer,fileSize);
MD5::MD5AnimParser animParser(parser.mSections);
if (animParser.mAnimatedBones.empty() || animParser.mFrames.empty() ||
animParser.mBaseFrames.size() != animParser.mAnimatedBones.size()) {
DefaultLogger::get()->error("MD5ANIM: No frames or animated bones loaded");
}
else {
bHadMD5Anim = true;
pScene->mAnimations = new aiAnimation*[pScene->mNumAnimations = 1];
aiAnimation* anim = pScene->mAnimations[0] = new aiAnimation();
anim->mNumChannels = (unsigned int)animParser.mAnimatedBones.size();
anim->mChannels = new aiNodeAnim*[anim->mNumChannels];
for (unsigned int i = 0; i < anim->mNumChannels;++i) {
aiNodeAnim* node = anim->mChannels[i] = new aiNodeAnim();
node->mNodeName = aiString( animParser.mAnimatedBones[i].mName );
node->mPositionKeys = new aiVectorKey[animParser.mFrames.size()];
node->mRotationKeys = new aiQuatKey[animParser.mFrames.size()];
}
anim->mTicksPerSecond = animParser.fFrameRate;
for (FrameList::const_iterator iter = animParser.mFrames.begin(), iterEnd = animParser.mFrames.end();iter != iterEnd;++iter){
double dTime = (double)(*iter).iIndex;
aiNodeAnim** pcAnimNode = anim->mChannels;
if (!(*iter).mValues.empty() || iter == animParser.mFrames.begin())
{
MD5::BaseFrameDesc* pcBaseFrame = &animParser.mBaseFrames[0];
for (AnimBoneList::const_iterator iter2 = animParser.mAnimatedBones.begin(); iter2 != animParser.mAnimatedBones.end();++iter2,
++pcAnimNode,++pcBaseFrame)
{
if((*iter2).iFirstKeyIndex >= (*iter).mValues.size()) {
if ((*iter2).iFlags != 0) {
throw DeadlyImportError("MD5: Keyframe index is out of range");
}
continue;
}
const float* fpCur = &(*iter).mValues[(*iter2).iFirstKeyIndex];
aiNodeAnim* pcCurAnimBone = *pcAnimNode;
aiVectorKey* vKey = &pcCurAnimBone->mPositionKeys[pcCurAnimBone->mNumPositionKeys++];
aiQuatKey* qKey = &pcCurAnimBone->mRotationKeys [pcCurAnimBone->mNumRotationKeys++];
aiVector3D vTemp;
for (unsigned int i = 0; i < 3; ++i) {
if ((*iter2).iFlags & (1u << i)) {
vKey->mValue[i] = *fpCur++;
}
else vKey->mValue[i] = pcBaseFrame->vPositionXYZ[i];
}
for (unsigned int i = 0; i < 3; ++i) {
if ((*iter2).iFlags & (8u << i)) {
vTemp[i] = *fpCur++;
}
else vTemp[i] = pcBaseFrame->vRotationQuat[i];
}
MD5::ConvertQuaternion(vTemp, qKey->mValue);
qKey->mTime = vKey->mTime = dTime;
}
}
anim->mDuration = std::max(dTime,anim->mDuration);
}
if (!pScene->mRootNode) {
pScene->mRootNode = new aiNode();
pScene->mRootNode->mName.Set("<MD5_Hierarchy>");
AttachChilds_Anim(-1,pScene->mRootNode,animParser.mAnimatedBones,(const aiNodeAnim**)anim->mChannels);
if (pScene->mRootNode->mNumChildren) {
SkeletonMeshBuilder skeleton_maker(pScene,pScene->mRootNode->mChildren[0]);
}
}
}
}
void MD5Importer::LoadMD5CameraFile ()
{
std::string pFile = mFile + "md5camera";
std::unique_ptr<IOStream> file( pIOHandler->Open( pFile, "rb"));
if( !file.get() || !file->FileSize()) {
throw DeadlyImportError("Failed to read MD5CAMERA file: " + pFile);
}
bHadMD5Camera = true;
LoadFileIntoMemory(file.get());
MD5::MD5Parser parser(mBuffer,fileSize);
MD5::MD5CameraParser cameraParser(parser.mSections);
if (cameraParser.frames.empty()) {
throw DeadlyImportError("MD5CAMERA: No frames parsed");
}
std::vector<unsigned int>& cuts = cameraParser.cuts;
std::vector<MD5::CameraAnimFrameDesc>& frames = cameraParser.frames;
aiNode* root = pScene->mRootNode = new aiNode("<MD5CameraRoot>");
root->mChildren = new aiNode*[root->mNumChildren = 1];
root->mChildren[0] = new aiNode("<MD5Camera>");
root->mChildren[0]->mParent = root;
pScene->mCameras = new aiCamera*[pScene->mNumCameras = 1];
aiCamera* cam = pScene->mCameras[0] = new aiCamera();
cam->mName = "<MD5Camera>";
cam->mHorizontalFOV = AI_DEG_TO_RAD( frames.front().fFOV );
if (!cuts.size()) {
cuts.push_back(0);
cuts.push_back(static_cast<unsigned int>(frames.size()-1));
}
else {
cuts.insert(cuts.begin(),0);
if (cuts.back() < frames.size()-1)
cuts.push_back(static_cast<unsigned int>(frames.size()-1));
}
pScene->mNumAnimations = static_cast<unsigned int>(cuts.size()-1);
aiAnimation** tmp = pScene->mAnimations = new aiAnimation*[pScene->mNumAnimations];
for (std::vector<unsigned int>::const_iterator it = cuts.begin(); it != cuts.end()-1; ++it) {
aiAnimation* anim = *tmp++ = new aiAnimation();
anim->mName.length = ::ai_snprintf(anim->mName.data, MAXLEN, "anim%u_from_%u_to_%u",(unsigned int)(it-cuts.begin()),(*it),*(it+1));
anim->mTicksPerSecond = cameraParser.fFrameRate;
anim->mChannels = new aiNodeAnim*[anim->mNumChannels = 1];
aiNodeAnim* nd = anim->mChannels[0] = new aiNodeAnim();
nd->mNodeName.Set("<MD5Camera>");
nd->mNumPositionKeys = nd->mNumRotationKeys = *(it+1) - (*it);
nd->mPositionKeys = new aiVectorKey[nd->mNumPositionKeys];
nd->mRotationKeys = new aiQuatKey [nd->mNumRotationKeys];
for (unsigned int i = 0; i < nd->mNumPositionKeys; ++i) {
nd->mPositionKeys[i].mValue = frames[*it+i].vPositionXYZ;
MD5::ConvertQuaternion(frames[*it+i].vRotationQuat,nd->mRotationKeys[i].mValue);
nd->mRotationKeys[i].mTime = nd->mPositionKeys[i].mTime = *it+i;
}
}
}
#endif