nuclear@0: /* nuclear@0: --------------------------------------------------------------------------- nuclear@0: Open Asset Import Library (assimp) nuclear@0: --------------------------------------------------------------------------- nuclear@0: nuclear@0: Copyright (c) 2006-2012, assimp team nuclear@0: nuclear@0: All rights reserved. nuclear@0: nuclear@0: Redistribution and use of this software in source and binary forms, nuclear@0: with or without modification, are permitted provided that the following nuclear@0: conditions are met: nuclear@0: nuclear@0: * Redistributions of source code must retain the above nuclear@0: copyright notice, this list of conditions and the nuclear@0: following disclaimer. nuclear@0: nuclear@0: * Redistributions in binary form must reproduce the above nuclear@0: copyright notice, this list of conditions and the nuclear@0: following disclaimer in the documentation and/or other nuclear@0: materials provided with the distribution. nuclear@0: nuclear@0: * Neither the name of the assimp team, nor the names of its nuclear@0: contributors may be used to endorse or promote products nuclear@0: derived from this software without specific prior nuclear@0: written permission of the assimp team. nuclear@0: nuclear@0: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS nuclear@0: "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT nuclear@0: LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR nuclear@0: A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT nuclear@0: OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, nuclear@0: SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT nuclear@0: LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, nuclear@0: DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY nuclear@0: THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT nuclear@0: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE nuclear@0: OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. nuclear@0: --------------------------------------------------------------------------- nuclear@0: */ nuclear@0: nuclear@0: #include "AssimpPCH.h" nuclear@0: #ifndef ASSIMP_BUILD_NO_MD2_IMPORTER nuclear@0: nuclear@0: /** @file Implementation of the MD2 importer class */ nuclear@0: #include "MD2Loader.h" nuclear@0: #include "ByteSwap.h" nuclear@0: #include "MD2NormalTable.h" // shouldn't be included by other units nuclear@0: nuclear@0: using namespace Assimp; nuclear@0: using namespace Assimp::MD2; nuclear@0: nuclear@0: nuclear@0: // helper macro to determine the size of an array nuclear@0: #if (!defined ARRAYSIZE) nuclear@0: # define ARRAYSIZE(_array) (int(sizeof(_array) / sizeof(_array[0]))) nuclear@0: #endif nuclear@0: nuclear@0: static const aiImporterDesc desc = { nuclear@0: "Quake II Mesh Importer", nuclear@0: "", nuclear@0: "", nuclear@0: "", nuclear@0: aiImporterFlags_SupportBinaryFlavour, nuclear@0: 0, nuclear@0: 0, nuclear@0: 0, nuclear@0: 0, nuclear@0: "md2" nuclear@0: }; nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Helper function to lookup a normal in Quake 2's precalculated table nuclear@0: void MD2::LookupNormalIndex(uint8_t iNormalIndex,aiVector3D& vOut) nuclear@0: { nuclear@0: // make sure the normal index has a valid value nuclear@0: if (iNormalIndex >= ARRAYSIZE(g_avNormals)) { nuclear@0: DefaultLogger::get()->warn("Index overflow in Quake II normal vector list"); nuclear@0: iNormalIndex = ARRAYSIZE(g_avNormals) - 1; nuclear@0: } nuclear@0: vOut = *((const aiVector3D*)(&g_avNormals[iNormalIndex])); nuclear@0: } nuclear@0: nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Constructor to be privately used by Importer nuclear@0: MD2Importer::MD2Importer() nuclear@0: {} nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Destructor, private as well nuclear@0: MD2Importer::~MD2Importer() nuclear@0: {} nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Returns whether the class can handle the format of the given file. nuclear@0: bool MD2Importer::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const nuclear@0: { nuclear@0: const std::string extension = GetExtension(pFile); nuclear@0: if (extension == "md2") nuclear@0: return true; nuclear@0: nuclear@0: // if check for extension is not enough, check for the magic tokens nuclear@0: if (!extension.length() || checkSig) { nuclear@0: uint32_t tokens[1]; nuclear@0: tokens[0] = AI_MD2_MAGIC_NUMBER_LE; nuclear@0: return CheckMagicToken(pIOHandler,pFile,tokens,1); nuclear@0: } nuclear@0: return false; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Get a list of all extensions supported by this loader nuclear@0: const aiImporterDesc* MD2Importer::GetInfo () const nuclear@0: { nuclear@0: return &desc; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Setup configuration properties nuclear@0: void MD2Importer::SetupProperties(const Importer* pImp) nuclear@0: { nuclear@0: // The nuclear@0: // AI_CONFIG_IMPORT_MD2_KEYFRAME option overrides the nuclear@0: // AI_CONFIG_IMPORT_GLOBAL_KEYFRAME option. nuclear@0: configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MD2_KEYFRAME,-1); nuclear@0: if(static_cast(-1) == configFrameID){ nuclear@0: configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_GLOBAL_KEYFRAME,0); nuclear@0: } nuclear@0: } nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Validate the file header nuclear@0: void MD2Importer::ValidateHeader( ) nuclear@0: { nuclear@0: // check magic number nuclear@0: if (m_pcHeader->magic != AI_MD2_MAGIC_NUMBER_BE && nuclear@0: m_pcHeader->magic != AI_MD2_MAGIC_NUMBER_LE) nuclear@0: { nuclear@0: char szBuffer[5]; nuclear@0: szBuffer[0] = ((char*)&m_pcHeader->magic)[0]; nuclear@0: szBuffer[1] = ((char*)&m_pcHeader->magic)[1]; nuclear@0: szBuffer[2] = ((char*)&m_pcHeader->magic)[2]; nuclear@0: szBuffer[3] = ((char*)&m_pcHeader->magic)[3]; nuclear@0: szBuffer[4] = '\0'; nuclear@0: nuclear@0: throw DeadlyImportError("Invalid MD2 magic word: should be IDP2, the " nuclear@0: "magic word found is " + std::string(szBuffer)); nuclear@0: } nuclear@0: nuclear@0: // check file format version nuclear@0: if (m_pcHeader->version != 8) nuclear@0: DefaultLogger::get()->warn( "Unsupported md2 file version. Continuing happily ..."); nuclear@0: nuclear@0: // check some values whether they are valid nuclear@0: if (0 == m_pcHeader->numFrames) nuclear@0: throw DeadlyImportError( "Invalid md2 file: NUM_FRAMES is 0"); nuclear@0: nuclear@0: if (m_pcHeader->offsetEnd > (uint32_t)fileSize) nuclear@0: throw DeadlyImportError( "Invalid md2 file: File is too small"); nuclear@0: nuclear@0: if (m_pcHeader->offsetSkins + m_pcHeader->numSkins * sizeof (MD2::Skin) >= fileSize || nuclear@0: m_pcHeader->offsetTexCoords + m_pcHeader->numTexCoords * sizeof (MD2::TexCoord) >= fileSize || nuclear@0: m_pcHeader->offsetTriangles + m_pcHeader->numTriangles * sizeof (MD2::Triangle) >= fileSize || nuclear@0: m_pcHeader->offsetFrames + m_pcHeader->numFrames * sizeof (MD2::Frame) >= fileSize || nuclear@0: m_pcHeader->offsetEnd > fileSize) nuclear@0: { nuclear@0: throw DeadlyImportError("Invalid MD2 header: some offsets are outside the file"); nuclear@0: } nuclear@0: nuclear@0: if (m_pcHeader->numSkins > AI_MD2_MAX_SKINS) nuclear@0: DefaultLogger::get()->warn("The model contains more skins than Quake 2 supports"); nuclear@0: if ( m_pcHeader->numFrames > AI_MD2_MAX_FRAMES) nuclear@0: DefaultLogger::get()->warn("The model contains more frames than Quake 2 supports"); nuclear@0: if (m_pcHeader->numVertices > AI_MD2_MAX_VERTS) nuclear@0: DefaultLogger::get()->warn("The model contains more vertices than Quake 2 supports"); nuclear@0: nuclear@0: if (m_pcHeader->numFrames <= configFrameID ) nuclear@0: throw DeadlyImportError("The requested frame is not existing the file"); nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Imports the given file into the given scene structure. nuclear@0: void MD2Importer::InternReadFile( const std::string& pFile, nuclear@0: aiScene* pScene, IOSystem* pIOHandler) nuclear@0: { nuclear@0: boost::scoped_ptr file( pIOHandler->Open( pFile)); nuclear@0: nuclear@0: // Check whether we can read from the file nuclear@0: if( file.get() == NULL) nuclear@0: throw DeadlyImportError( "Failed to open MD2 file " + pFile + ""); nuclear@0: nuclear@0: // check whether the md3 file is large enough to contain nuclear@0: // at least the file header nuclear@0: fileSize = (unsigned int)file->FileSize(); nuclear@0: if( fileSize < sizeof(MD2::Header)) nuclear@0: throw DeadlyImportError( "MD2 File is too small"); nuclear@0: nuclear@0: std::vector mBuffer2(fileSize); nuclear@0: file->Read(&mBuffer2[0], 1, fileSize); nuclear@0: mBuffer = &mBuffer2[0]; nuclear@0: nuclear@0: nuclear@0: m_pcHeader = (BE_NCONST MD2::Header*)mBuffer; nuclear@0: nuclear@0: #ifdef AI_BUILD_BIG_ENDIAN nuclear@0: nuclear@0: ByteSwap::Swap4(&m_pcHeader->frameSize); nuclear@0: ByteSwap::Swap4(&m_pcHeader->magic); nuclear@0: ByteSwap::Swap4(&m_pcHeader->numFrames); nuclear@0: ByteSwap::Swap4(&m_pcHeader->numGlCommands); nuclear@0: ByteSwap::Swap4(&m_pcHeader->numSkins); nuclear@0: ByteSwap::Swap4(&m_pcHeader->numTexCoords); nuclear@0: ByteSwap::Swap4(&m_pcHeader->numTriangles); nuclear@0: ByteSwap::Swap4(&m_pcHeader->numVertices); nuclear@0: ByteSwap::Swap4(&m_pcHeader->offsetEnd); nuclear@0: ByteSwap::Swap4(&m_pcHeader->offsetFrames); nuclear@0: ByteSwap::Swap4(&m_pcHeader->offsetGlCommands); nuclear@0: ByteSwap::Swap4(&m_pcHeader->offsetSkins); nuclear@0: ByteSwap::Swap4(&m_pcHeader->offsetTexCoords); nuclear@0: ByteSwap::Swap4(&m_pcHeader->offsetTriangles); nuclear@0: ByteSwap::Swap4(&m_pcHeader->skinHeight); nuclear@0: ByteSwap::Swap4(&m_pcHeader->skinWidth); nuclear@0: ByteSwap::Swap4(&m_pcHeader->version); nuclear@0: nuclear@0: #endif nuclear@0: nuclear@0: ValidateHeader(); nuclear@0: nuclear@0: // there won't be more than one mesh inside the file nuclear@0: pScene->mNumMaterials = 1; nuclear@0: pScene->mRootNode = new aiNode(); nuclear@0: pScene->mRootNode->mNumMeshes = 1; nuclear@0: pScene->mRootNode->mMeshes = new unsigned int[1]; nuclear@0: pScene->mRootNode->mMeshes[0] = 0; nuclear@0: pScene->mMaterials = new aiMaterial*[1]; nuclear@0: pScene->mMaterials[0] = new aiMaterial(); nuclear@0: pScene->mNumMeshes = 1; nuclear@0: pScene->mMeshes = new aiMesh*[1]; nuclear@0: nuclear@0: aiMesh* pcMesh = pScene->mMeshes[0] = new aiMesh(); nuclear@0: pcMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; nuclear@0: nuclear@0: // navigate to the begin of the frame data nuclear@0: BE_NCONST MD2::Frame* pcFrame = (BE_NCONST MD2::Frame*) ((uint8_t*) nuclear@0: m_pcHeader + m_pcHeader->offsetFrames); nuclear@0: nuclear@0: pcFrame += configFrameID; nuclear@0: nuclear@0: // navigate to the begin of the triangle data nuclear@0: MD2::Triangle* pcTriangles = (MD2::Triangle*) ((uint8_t*) nuclear@0: m_pcHeader + m_pcHeader->offsetTriangles); nuclear@0: nuclear@0: // navigate to the begin of the tex coords data nuclear@0: BE_NCONST MD2::TexCoord* pcTexCoords = (BE_NCONST MD2::TexCoord*) ((uint8_t*) nuclear@0: m_pcHeader + m_pcHeader->offsetTexCoords); nuclear@0: nuclear@0: // navigate to the begin of the vertex data nuclear@0: BE_NCONST MD2::Vertex* pcVerts = (BE_NCONST MD2::Vertex*) (pcFrame->vertices); nuclear@0: nuclear@0: #ifdef AI_BUILD_BIG_ENDIAN nuclear@0: for (uint32_t i = 0; i< m_pcHeader->numTriangles; ++i) nuclear@0: { nuclear@0: for (unsigned int p = 0; p < 3;++p) nuclear@0: { nuclear@0: ByteSwap::Swap2(& pcTriangles[i].textureIndices[p]); nuclear@0: ByteSwap::Swap2(& pcTriangles[i].vertexIndices[p]); nuclear@0: } nuclear@0: } nuclear@0: for (uint32_t i = 0; i < m_pcHeader->offsetTexCoords;++i) nuclear@0: { nuclear@0: ByteSwap::Swap2(& pcTexCoords[i].s); nuclear@0: ByteSwap::Swap2(& pcTexCoords[i].t); nuclear@0: } nuclear@0: ByteSwap::Swap4( & pcFrame->scale[0] ); nuclear@0: ByteSwap::Swap4( & pcFrame->scale[1] ); nuclear@0: ByteSwap::Swap4( & pcFrame->scale[2] ); nuclear@0: ByteSwap::Swap4( & pcFrame->translate[0] ); nuclear@0: ByteSwap::Swap4( & pcFrame->translate[1] ); nuclear@0: ByteSwap::Swap4( & pcFrame->translate[2] ); nuclear@0: #endif nuclear@0: nuclear@0: pcMesh->mNumFaces = m_pcHeader->numTriangles; nuclear@0: pcMesh->mFaces = new aiFace[m_pcHeader->numTriangles]; nuclear@0: nuclear@0: // allocate output storage nuclear@0: pcMesh->mNumVertices = (unsigned int)pcMesh->mNumFaces*3; nuclear@0: pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices]; nuclear@0: pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices]; nuclear@0: nuclear@0: // Not sure whether there are MD2 files without texture coordinates nuclear@0: // NOTE: texture coordinates can be there without a texture, nuclear@0: // but a texture can't be there without a valid UV channel nuclear@0: aiMaterial* pcHelper = (aiMaterial*)pScene->mMaterials[0]; nuclear@0: const int iMode = (int)aiShadingMode_Gouraud; nuclear@0: pcHelper->AddProperty(&iMode, 1, AI_MATKEY_SHADING_MODEL); nuclear@0: nuclear@0: if (m_pcHeader->numTexCoords && m_pcHeader->numSkins) nuclear@0: { nuclear@0: // navigate to the first texture associated with the mesh nuclear@0: const MD2::Skin* pcSkins = (const MD2::Skin*) ((unsigned char*)m_pcHeader + nuclear@0: m_pcHeader->offsetSkins); nuclear@0: nuclear@0: aiColor3D clr; nuclear@0: clr.b = clr.g = clr.r = 1.0f; nuclear@0: pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_DIFFUSE); nuclear@0: pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_SPECULAR); nuclear@0: nuclear@0: clr.b = clr.g = clr.r = 0.05f; nuclear@0: pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_AMBIENT); nuclear@0: nuclear@0: if (pcSkins->name[0]) nuclear@0: { nuclear@0: aiString szString; nuclear@0: const size_t iLen = ::strlen(pcSkins->name); nuclear@0: ::memcpy(szString.data,pcSkins->name,iLen); nuclear@0: szString.data[iLen] = '\0'; nuclear@0: szString.length = iLen; nuclear@0: nuclear@0: pcHelper->AddProperty(&szString,AI_MATKEY_TEXTURE_DIFFUSE(0)); nuclear@0: } nuclear@0: else{ nuclear@0: DefaultLogger::get()->warn("Texture file name has zero length. It will be skipped."); nuclear@0: } nuclear@0: } nuclear@0: else { nuclear@0: // apply a default material nuclear@0: aiColor3D clr; nuclear@0: clr.b = clr.g = clr.r = 0.6f; nuclear@0: pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_DIFFUSE); nuclear@0: pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_SPECULAR); nuclear@0: nuclear@0: clr.b = clr.g = clr.r = 0.05f; nuclear@0: pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_AMBIENT); nuclear@0: nuclear@0: aiString szName; nuclear@0: szName.Set(AI_DEFAULT_MATERIAL_NAME); nuclear@0: pcHelper->AddProperty(&szName,AI_MATKEY_NAME); nuclear@0: nuclear@0: aiString sz; nuclear@0: nuclear@0: // TODO: Try to guess the name of the texture file from the model file name nuclear@0: nuclear@0: sz.Set("$texture_dummy.bmp"); nuclear@0: pcHelper->AddProperty(&sz,AI_MATKEY_TEXTURE_DIFFUSE(0)); nuclear@0: } nuclear@0: nuclear@0: nuclear@0: // now read all triangles of the first frame, apply scaling and translation nuclear@0: unsigned int iCurrent = 0; nuclear@0: nuclear@0: float fDivisorU = 1.0f,fDivisorV = 1.0f; nuclear@0: if (m_pcHeader->numTexCoords) { nuclear@0: // allocate storage for texture coordinates, too nuclear@0: pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices]; nuclear@0: pcMesh->mNumUVComponents[0] = 2; nuclear@0: nuclear@0: // check whether the skin width or height are zero (this would nuclear@0: // cause a division through zero) nuclear@0: if (!m_pcHeader->skinWidth) { nuclear@0: DefaultLogger::get()->error("MD2: No valid skin width given"); nuclear@0: } nuclear@0: else fDivisorU = (float)m_pcHeader->skinWidth; nuclear@0: if (!m_pcHeader->skinHeight){ nuclear@0: DefaultLogger::get()->error("MD2: No valid skin height given"); nuclear@0: } nuclear@0: else fDivisorV = (float)m_pcHeader->skinHeight; nuclear@0: } nuclear@0: nuclear@0: for (unsigned int i = 0; i < (unsigned int)m_pcHeader->numTriangles;++i) { nuclear@0: // Allocate the face nuclear@0: pScene->mMeshes[0]->mFaces[i].mIndices = new unsigned int[3]; nuclear@0: pScene->mMeshes[0]->mFaces[i].mNumIndices = 3; nuclear@0: nuclear@0: // copy texture coordinates nuclear@0: // check whether they are different from the previous value at this index. nuclear@0: // In this case, create a full separate set of vertices/normals/texcoords nuclear@0: for (unsigned int c = 0; c < 3;++c,++iCurrent) { nuclear@0: nuclear@0: // validate vertex indices nuclear@0: register unsigned int iIndex = (unsigned int)pcTriangles[i].vertexIndices[c]; nuclear@0: if (iIndex >= m_pcHeader->numVertices) { nuclear@0: DefaultLogger::get()->error("MD2: Vertex index is outside the allowed range"); nuclear@0: iIndex = m_pcHeader->numVertices-1; nuclear@0: } nuclear@0: nuclear@0: // read x,y, and z component of the vertex nuclear@0: aiVector3D& vec = pcMesh->mVertices[iCurrent]; nuclear@0: nuclear@0: vec.x = (float)pcVerts[iIndex].vertex[0] * pcFrame->scale[0]; nuclear@0: vec.x += pcFrame->translate[0]; nuclear@0: nuclear@0: vec.y = (float)pcVerts[iIndex].vertex[1] * pcFrame->scale[1]; nuclear@0: vec.y += pcFrame->translate[1]; nuclear@0: nuclear@0: vec.z = (float)pcVerts[iIndex].vertex[2] * pcFrame->scale[2]; nuclear@0: vec.z += pcFrame->translate[2]; nuclear@0: nuclear@0: // read the normal vector from the precalculated normal table nuclear@0: aiVector3D& vNormal = pcMesh->mNormals[iCurrent]; nuclear@0: LookupNormalIndex(pcVerts[iIndex].lightNormalIndex,vNormal); nuclear@0: nuclear@0: // flip z and y to become right-handed nuclear@0: std::swap((float&)vNormal.z,(float&)vNormal.y); nuclear@0: std::swap((float&)vec.z,(float&)vec.y); nuclear@0: nuclear@0: if (m_pcHeader->numTexCoords) { nuclear@0: // validate texture coordinates nuclear@0: iIndex = pcTriangles[i].textureIndices[c]; nuclear@0: if (iIndex >= m_pcHeader->numTexCoords) { nuclear@0: DefaultLogger::get()->error("MD2: UV index is outside the allowed range"); nuclear@0: iIndex = m_pcHeader->numTexCoords-1; nuclear@0: } nuclear@0: nuclear@0: aiVector3D& pcOut = pcMesh->mTextureCoords[0][iCurrent]; nuclear@0: nuclear@0: // the texture coordinates are absolute values but we nuclear@0: // need relative values between 0 and 1 nuclear@0: pcOut.x = pcTexCoords[iIndex].s / fDivisorU; nuclear@0: pcOut.y = 1.f-pcTexCoords[iIndex].t / fDivisorV; nuclear@0: } nuclear@0: pScene->mMeshes[0]->mFaces[i].mIndices[c] = iCurrent; nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: #endif // !! ASSIMP_BUILD_NO_MD2_IMPORTER