/** @file Implementation of the MD3 importer class */ #include "MD3Loader.h" #include "MaterialSystem.h" #include "../include/IOStream.h" #include "../include/IOSystem.h" #include "../include/aiMesh.h" #include "../include/aiScene.h" #include "../include/aiAssert.h" #include using namespace Assimp; // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer MD3Importer::MD3Importer() { } // ------------------------------------------------------------------------------------------------ // Destructor, private as well MD3Importer::~MD3Importer() { } // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. bool MD3Importer::CanRead( const std::string& pFile, IOSystem* pIOHandler) const { // simple check of file extension is enough for the moment std::string::size_type pos = pFile.find_last_of('.'); // no file extension - can't read if( pos == std::string::npos) return false; std::string extension = pFile.substr( pos); // not brilliant but working ;-) if( extension == ".md3" || extension == ".MD3" || extension == ".mD3" || extension == ".Md3") return true; return false; } // ------------------------------------------------------------------------------------------------ // Imports the given file into the given scene structure. void MD3Importer::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) { boost::scoped_ptr file( pIOHandler->Open( pFile)); // Check whether we can read from the file if( file.get() == NULL) { throw new ImportErrorException( "Failed to open md3 file " + pFile + "."); } // check whether the md3 file is large enough to contain // at least the file header size_t fileSize = file->FileSize(); if( fileSize < sizeof(MD3::Header)) { throw new ImportErrorException( ".md3 File is too small."); } // allocate storage and copy the contents of the file to a memory buffer this->mBuffer = new unsigned char[fileSize]; file->Read( (void*)mBuffer, 1, fileSize); this->m_pcHeader = (const MD3::Header*)this->mBuffer; // check magic number if (this->m_pcHeader->IDENT != AI_MD3_MAGIC_NUMBER_BE && this->m_pcHeader->IDENT != AI_MD3_MAGIC_NUMBER_LE) { throw new ImportErrorException( "Invalid md3 file: Magic bytes not found"); } // check file format version if (this->m_pcHeader->VERSION > 15) { throw new ImportErrorException( "Unsupported md3 file version"); } // check some values whether they are valid if (0 == this->m_pcHeader->NUM_FRAMES) { throw new ImportErrorException( "Invalid md3 file: NUM_FRAMES is 0"); } if (0 == this->m_pcHeader->NUM_SURFACES) { throw new ImportErrorException( "Invalid md3 file: NUM_SURFACES is 0"); } if (this->m_pcHeader->OFS_EOF > (int32_t)fileSize) { throw new ImportErrorException( "Invalid md3 file: File is too small"); } // now navigate to the list of surfaces const MD3::Surface* pcSurfaces = (const MD3::Surface*) (this->mBuffer + this->m_pcHeader->OFS_SURFACES); // allocate output storage pScene->mNumMeshes = this->m_pcHeader->NUM_SURFACES; pScene->mMeshes = new aiMesh*[pScene->mNumMeshes]; pScene->mNumMaterials = this->m_pcHeader->NUM_SURFACES; pScene->mMaterials = new aiMaterial*[pScene->mNumMeshes]; unsigned int iNum = this->m_pcHeader->NUM_SURFACES; unsigned int iNumMaterials = 0; unsigned int iDefaultMatIndex = 0xFFFFFFFF; while (iNum-- > 0) { // navigate to the vertex list of the surface const MD3::Vertex* pcVertices = (const MD3::Vertex*) (((unsigned char*)pcSurfaces) + pcSurfaces->OFS_XYZNORMAL); // navigate to the triangle list of the surface const MD3::Triangle* pcTriangles = (const MD3::Triangle*) (((unsigned char*)pcSurfaces) + pcSurfaces->OFS_TRIANGLES); // navigate to the texture coordinate list of the surface const MD3::TexCoord* pcUVs = (const MD3::TexCoord*) (((unsigned char*)pcSurfaces) + pcSurfaces->OFS_ST); // navigate to the shader list of the surface const MD3::Shader* pcShaders = (const MD3::Shader*) (((unsigned char*)pcSurfaces) + pcSurfaces->OFS_SHADERS); // if the submesh is empty ignore it if (0 == pcSurfaces->NUM_VERTICES || 0 == pcSurfaces->NUM_TRIANGLES) { pcSurfaces = (const MD3::Surface*)(((unsigned char*)pcSurfaces) + pcSurfaces->OFS_END); pScene->mNumMeshes--; continue; } // allocate the output mesh pScene->mMeshes[iNum] = new aiMesh(); aiMesh* pcMesh = pScene->mMeshes[iNum]; pcMesh->mNumVertices = pcSurfaces->NUM_VERTICES; pcMesh->mNumBones = 0; pcMesh->mColors[0] = pcMesh->mColors[1] = pcMesh->mColors[2] = pcMesh->mColors[3] = NULL; pcMesh->mNumFaces = pcSurfaces->NUM_TRIANGLES; pcMesh->mFaces = new aiFace[pcSurfaces->NUM_TRIANGLES]; pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices]; pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices]; pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices]; pcMesh->mTextureCoords[1] = pcMesh->mTextureCoords[2] = pcMesh->mTextureCoords[3] = NULL; pcMesh->mNumUVComponents[0] = 2; // fill in all vertices and normals // fill in all texture coordinates for (unsigned int i = 0; i < (unsigned int)pcSurfaces->NUM_VERTICES;++i) { pcMesh->mVertices[i].x = pcVertices->X; pcMesh->mVertices[i].y = pcVertices->Y; pcMesh->mVertices[i].z = -1.0f*pcVertices->Z; // convert the normal vector to uncompressed float3 format LatLngNormalToVec3(pcVertices->NORMAL,(float*)&pcMesh->mNormals[i]); // read texture coordinates pcMesh->mTextureCoords[0][i].x = pcUVs->U; pcMesh->mTextureCoords[0][i].y = 1.0f - pcUVs->V; pcVertices++; pcUVs++; } // fill in all triangles for (unsigned int i = 0; i < (unsigned int)pcSurfaces->NUM_TRIANGLES;++i) { pcMesh->mFaces[i].mIndices = new unsigned int[3]; pcMesh->mFaces[i].mNumIndices = 3; pcMesh->mFaces[i].mIndices[0] = pcTriangles->INDEXES[0]; pcMesh->mFaces[i].mIndices[1] = pcTriangles->INDEXES[1]; pcMesh->mFaces[i].mIndices[2] = pcTriangles->INDEXES[2]; pcTriangles++; } // get the first shader (= texture?) assigned to the surface if (0 != pcSurfaces->NUM_SHADER) { // make a relative path. // if the MD3's internal path itself and the given path are using // the same directory remove it const char* szEndDir1 = strrchr((const char*)this->m_pcHeader->NAME,'\\'); if (!szEndDir1)szEndDir1 = strrchr((const char*)this->m_pcHeader->NAME,'/'); const char* szEndDir2 = strrchr((const char*)pcShaders->NAME,'\\'); if (!szEndDir2)szEndDir2 = strrchr((const char*)pcShaders->NAME,'/'); if (szEndDir1 && szEndDir2) { // both of them are valid const unsigned int iLen1 = (unsigned int)(szEndDir1 - (const char*)this->m_pcHeader->NAME); const unsigned int iLen2 = std::min (iLen1, (unsigned int)(szEndDir2 - (const char*)pcShaders->NAME) ); bool bSuccess = true; for (unsigned int a = 0; a < iLen2;++a) { char sz = tolower ( pcShaders->NAME[a] ); char sz2 = tolower ( this->m_pcHeader->NAME[a] ); if (sz != sz2) { bSuccess = false; break; } } if (bSuccess) { // use the file name only szEndDir2++; } else { // use the full path szEndDir2 = (const char*)pcShaders->NAME; } } // now try to find out whether we have this shader already bool bHave = false; for (unsigned int p = 0; p < iNumMaterials;++p) { if (iDefaultMatIndex == p)continue; aiString szOut; if(AI_SUCCESS == aiGetMaterialString ( (aiMaterial*)pScene->mMaterials[p], AI_MATKEY_TEXBLEND_DIFFUSE(0),&szOut)) { if (0 == ASSIMP_stricmp(szOut.data,szEndDir2)) { // equal. reuse this material (texture) bHave = true; pcMesh->mMaterialIndex = p; break; } } } if (!bHave) { MaterialHelper* pcHelper = new MaterialHelper(); aiString szString; const size_t iLen = strlen(szEndDir2); memcpy(szString.data,szEndDir2,iLen+1); szString.length = iLen-1; pcHelper->AddProperty(&szString,AI_MATKEY_TEXTURE_DIFFUSE(0)); int iMode = (int)aiShadingMode_Gouraud; pcHelper->AddProperty(&iMode, 1, AI_MATKEY_SHADING_MODEL); aiColor3D clr; clr.b = clr.g = clr.r = 1.0f; pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_DIFFUSE); pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_SPECULAR); clr.b = clr.g = clr.r = 0.05f; pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_AMBIENT); pScene->mMaterials[iNumMaterials] = (aiMaterial*)pcHelper; pcMesh->mMaterialIndex = iNumMaterials++; } } else { if (0xFFFFFFFF != iDefaultMatIndex) { pcMesh->mMaterialIndex = iDefaultMatIndex; } else { MaterialHelper* pcHelper = new MaterialHelper(); // fill in a default material int iMode = (int)aiShadingMode_Gouraud; pcHelper->AddProperty(&iMode, 1, AI_MATKEY_SHADING_MODEL); aiColor3D clr; clr.b = clr.g = clr.r = 0.6f; pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_DIFFUSE); pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_SPECULAR); clr.b = clr.g = clr.r = 0.05f; pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_AMBIENT); pScene->mMaterials[iNumMaterials] = (aiMaterial*)pcHelper; pcMesh->mMaterialIndex = iNumMaterials++; } } pcSurfaces = (const MD3::Surface*)(((unsigned char*)pcSurfaces) + pcSurfaces->OFS_END); } if (0 == pScene->mNumMeshes) { // cleanup before returning delete pScene; throw new ImportErrorException( "Invalid md3 file: File contains no valid mesh"); } pScene->mNumMaterials = iNumMaterials; // now we need to generate an empty node graph pScene->mRootNode = new aiNode(); pScene->mRootNode->mNumChildren = pScene->mNumMeshes; pScene->mRootNode->mChildren = new aiNode*[pScene->mNumMeshes]; for (unsigned int i = 0; i < pScene->mNumMeshes;++i) { pScene->mRootNode->mChildren[i] = new aiNode(); pScene->mRootNode->mChildren[i]->mParent = pScene->mRootNode; pScene->mRootNode->mChildren[i]->mNumMeshes = 1; pScene->mRootNode->mChildren[i]->mMeshes = new unsigned int[1]; pScene->mRootNode->mChildren[i]->mMeshes[0] = i; } delete[] this->mBuffer; return; }