| /* |
| --------------------------------------------------------------------------- |
| Open Asset Import Library (assimp) |
| --------------------------------------------------------------------------- |
| |
| Copyright (c) 2006-2017, assimp team |
| |
| |
| All rights reserved. |
| |
| Redistribution and use of this software in source and binary forms, |
| with or without modification, are permitted provided that the following |
| conditions are met: |
| |
| * Redistributions of source code must retain the above |
| copyright notice, this list of conditions and the |
| following disclaimer. |
| |
| * Redistributions in binary form must reproduce the above |
| copyright notice, this list of conditions and the |
| following disclaimer in the documentation and/or other |
| materials provided with the distribution. |
| |
| * Neither the name of the assimp team, nor the names of its |
| contributors may be used to endorse or promote products |
| derived from this software without specific prior |
| written permission of the assimp team. |
| |
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| --------------------------------------------------------------------------- |
| */ |
| |
| /** @file MDLLoader.cpp |
| * @brief Implementation of the main parts of the MDL importer class |
| * *TODO* Cleanup and further testing of some parts necessary |
| */ |
| |
| // internal headers |
| |
| #ifndef ASSIMP_BUILD_NO_MDL_IMPORTER |
| |
| #include "MDLLoader.h" |
| #include "Macros.h" |
| #include "qnan.h" |
| #include "MDLDefaultColorMap.h" |
| #include "MD2FileData.h" |
| #include "StringUtils.h" |
| #include <assimp/Importer.hpp> |
| #include <assimp/IOSystem.hpp> |
| #include <assimp/scene.h> |
| #include <assimp/DefaultLogger.hpp> |
| #include <assimp/importerdesc.h> |
| |
| #include <memory> |
| |
| using namespace Assimp; |
| |
| static const aiImporterDesc desc = { |
| "Quake Mesh / 3D GameStudio Mesh Importer", |
| "", |
| "", |
| "", |
| aiImporterFlags_SupportBinaryFlavour, |
| 0, |
| 0, |
| 7, |
| 0, |
| "mdl" |
| }; |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Ugly stuff ... nevermind |
| #define _AI_MDL7_ACCESS(_data, _index, _limit, _type) \ |
| (*((const _type*)(((const char*)_data) + _index * _limit))) |
| |
| #define _AI_MDL7_ACCESS_PTR(_data, _index, _limit, _type) \ |
| ((BE_NCONST _type*)(((const char*)_data) + _index * _limit)) |
| |
| #define _AI_MDL7_ACCESS_VERT(_data, _index, _limit) \ |
| _AI_MDL7_ACCESS(_data,_index,_limit,MDL::Vertex_MDL7) |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Constructor to be privately used by Importer |
| MDLImporter::MDLImporter() |
| : configFrameID(), |
| mBuffer(), |
| iGSFileVersion(), |
| pIOHandler(), |
| pScene(), |
| iFileSize() |
| {} |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Destructor, private as well |
| MDLImporter::~MDLImporter() |
| {} |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Returns whether the class can handle the format of the given file. |
| bool MDLImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const |
| { |
| const std::string extension = GetExtension(pFile); |
| |
| // if check for extension is not enough, check for the magic tokens |
| if (extension == "mdl" || !extension.length() || checkSig) { |
| uint32_t tokens[8]; |
| tokens[0] = AI_MDL_MAGIC_NUMBER_LE_HL2a; |
| tokens[1] = AI_MDL_MAGIC_NUMBER_LE_HL2b; |
| tokens[2] = AI_MDL_MAGIC_NUMBER_LE_GS7; |
| tokens[3] = AI_MDL_MAGIC_NUMBER_LE_GS5b; |
| tokens[4] = AI_MDL_MAGIC_NUMBER_LE_GS5a; |
| tokens[5] = AI_MDL_MAGIC_NUMBER_LE_GS4; |
| tokens[6] = AI_MDL_MAGIC_NUMBER_LE_GS3; |
| tokens[7] = AI_MDL_MAGIC_NUMBER_LE; |
| return CheckMagicToken(pIOHandler,pFile,tokens,8,0); |
| } |
| return false; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Setup configuration properties |
| void MDLImporter::SetupProperties(const Importer* pImp) |
| { |
| configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MDL_KEYFRAME,-1); |
| |
| // The |
| // AI_CONFIG_IMPORT_MDL_KEYFRAME option overrides the |
| // AI_CONFIG_IMPORT_GLOBAL_KEYFRAME option. |
| if(static_cast<unsigned int>(-1) == configFrameID) { |
| configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_GLOBAL_KEYFRAME,0); |
| } |
| |
| // AI_CONFIG_IMPORT_MDL_COLORMAP - pallette file |
| configPalette = pImp->GetPropertyString(AI_CONFIG_IMPORT_MDL_COLORMAP,"colormap.lmp"); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Get a list of all supported extensions |
| const aiImporterDesc* MDLImporter::GetInfo () const |
| { |
| return &desc; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Imports the given file into the given scene structure. |
| void MDLImporter::InternReadFile( const std::string& pFile, |
| aiScene* _pScene, IOSystem* _pIOHandler) |
| { |
| pScene = _pScene; |
| pIOHandler = _pIOHandler; |
| std::unique_ptr<IOStream> file( pIOHandler->Open( pFile)); |
| |
| // Check whether we can read from the file |
| if( file.get() == NULL) { |
| throw DeadlyImportError( "Failed to open MDL file " + pFile + "."); |
| } |
| |
| // This should work for all other types of MDL files, too ... |
| // the quake header is one of the smallest, afaik |
| iFileSize = (unsigned int)file->FileSize(); |
| if( iFileSize < sizeof(MDL::Header)) { |
| throw DeadlyImportError( "MDL File is too small."); |
| } |
| |
| // Allocate storage and copy the contents of the file to a memory buffer |
| mBuffer =new unsigned char[iFileSize+1]; |
| file->Read( (void*)mBuffer, 1, iFileSize); |
| |
| // Append a binary zero to the end of the buffer. |
| // this is just for safety that string parsing routines |
| // find the end of the buffer ... |
| mBuffer[iFileSize] = '\0'; |
| const uint32_t iMagicWord = *((uint32_t*)mBuffer); |
| |
| // Determine the file subtype and call the appropriate member function |
| |
| // Original Quake1 format |
| if (AI_MDL_MAGIC_NUMBER_BE == iMagicWord || AI_MDL_MAGIC_NUMBER_LE == iMagicWord) { |
| DefaultLogger::get()->debug("MDL subtype: Quake 1, magic word is IDPO"); |
| iGSFileVersion = 0; |
| InternReadFile_Quake1(); |
| } |
| // GameStudio A<old> MDL2 format - used by some test models that come with 3DGS |
| else if (AI_MDL_MAGIC_NUMBER_BE_GS3 == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_GS3 == iMagicWord) { |
| DefaultLogger::get()->debug("MDL subtype: 3D GameStudio A2, magic word is MDL2"); |
| iGSFileVersion = 2; |
| InternReadFile_Quake1(); |
| } |
| // GameStudio A4 MDL3 format |
| else if (AI_MDL_MAGIC_NUMBER_BE_GS4 == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_GS4 == iMagicWord) { |
| DefaultLogger::get()->debug("MDL subtype: 3D GameStudio A4, magic word is MDL3"); |
| iGSFileVersion = 3; |
| InternReadFile_3DGS_MDL345(); |
| } |
| // GameStudio A5+ MDL4 format |
| else if (AI_MDL_MAGIC_NUMBER_BE_GS5a == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_GS5a == iMagicWord) { |
| DefaultLogger::get()->debug("MDL subtype: 3D GameStudio A4, magic word is MDL4"); |
| iGSFileVersion = 4; |
| InternReadFile_3DGS_MDL345(); |
| } |
| // GameStudio A5+ MDL5 format |
| else if (AI_MDL_MAGIC_NUMBER_BE_GS5b == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_GS5b == iMagicWord) { |
| DefaultLogger::get()->debug("MDL subtype: 3D GameStudio A5, magic word is MDL5"); |
| iGSFileVersion = 5; |
| InternReadFile_3DGS_MDL345(); |
| } |
| // GameStudio A7 MDL7 format |
| else if (AI_MDL_MAGIC_NUMBER_BE_GS7 == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_GS7 == iMagicWord) { |
| DefaultLogger::get()->debug("MDL subtype: 3D GameStudio A7, magic word is MDL7"); |
| iGSFileVersion = 7; |
| InternReadFile_3DGS_MDL7(); |
| } |
| // IDST/IDSQ Format (CS:S/HL^2, etc ...) |
| else if (AI_MDL_MAGIC_NUMBER_BE_HL2a == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_HL2a == iMagicWord || |
| AI_MDL_MAGIC_NUMBER_BE_HL2b == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_HL2b == iMagicWord) |
| { |
| DefaultLogger::get()->debug("MDL subtype: Source(tm) Engine, magic word is IDST/IDSQ"); |
| iGSFileVersion = 0; |
| InternReadFile_HL2(); |
| } |
| else { |
| // print the magic word to the log file |
| throw DeadlyImportError( "Unknown MDL subformat " + pFile + |
| ". Magic word (" + std::string((char*)&iMagicWord,4) + ") is not known"); |
| } |
| |
| // Now rotate the whole scene 90 degrees around the x axis to convert to internal coordinate system |
| 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); |
| |
| // delete the file buffer and cleanup |
| delete [] mBuffer; |
| mBuffer= nullptr; |
| AI_DEBUG_INVALIDATE_PTR(pIOHandler); |
| AI_DEBUG_INVALIDATE_PTR(pScene); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Check whether we're still inside the valid file range |
| void MDLImporter::SizeCheck(const void* szPos) |
| { |
| if (!szPos || (const unsigned char*)szPos > this->mBuffer + this->iFileSize) |
| { |
| throw DeadlyImportError("Invalid MDL file. The file is too small " |
| "or contains invalid data."); |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Just for debgging purposes |
| void MDLImporter::SizeCheck(const void* szPos, const char* szFile, unsigned int iLine) |
| { |
| ai_assert(NULL != szFile); |
| if (!szPos || (const unsigned char*)szPos > mBuffer + iFileSize) |
| { |
| // remove a directory if there is one |
| const char* szFilePtr = ::strrchr(szFile,'\\'); |
| if (!szFilePtr) { |
| if(!(szFilePtr = ::strrchr(szFile,'/'))) |
| szFilePtr = szFile; |
| } |
| if (szFilePtr)++szFilePtr; |
| |
| char szBuffer[1024]; |
| ::sprintf(szBuffer,"Invalid MDL file. The file is too small " |
| "or contains invalid data (File: %s Line: %u)",szFilePtr,iLine); |
| |
| throw DeadlyImportError(szBuffer); |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Validate a quake file header |
| void MDLImporter::ValidateHeader_Quake1(const MDL::Header* pcHeader) |
| { |
| // some values may not be NULL |
| if (!pcHeader->num_frames) |
| throw DeadlyImportError( "[Quake 1 MDL] There are no frames in the file"); |
| |
| if (!pcHeader->num_verts) |
| throw DeadlyImportError( "[Quake 1 MDL] There are no vertices in the file"); |
| |
| if (!pcHeader->num_tris) |
| throw DeadlyImportError( "[Quake 1 MDL] There are no triangles in the file"); |
| |
| // check whether the maxima are exceeded ...however, this applies for Quake 1 MDLs only |
| if (!this->iGSFileVersion) |
| { |
| if (pcHeader->num_verts > AI_MDL_MAX_VERTS) |
| DefaultLogger::get()->warn("Quake 1 MDL model has more than AI_MDL_MAX_VERTS vertices"); |
| |
| if (pcHeader->num_tris > AI_MDL_MAX_TRIANGLES) |
| DefaultLogger::get()->warn("Quake 1 MDL model has more than AI_MDL_MAX_TRIANGLES triangles"); |
| |
| if (pcHeader->num_frames > AI_MDL_MAX_FRAMES) |
| DefaultLogger::get()->warn("Quake 1 MDL model has more than AI_MDL_MAX_FRAMES frames"); |
| |
| // (this does not apply for 3DGS MDLs) |
| if (!this->iGSFileVersion && pcHeader->version != AI_MDL_VERSION) |
| DefaultLogger::get()->warn("Quake 1 MDL model has an unknown version: AI_MDL_VERSION (=6) is " |
| "the expected file format version"); |
| if(pcHeader->num_skins && (!pcHeader->skinwidth || !pcHeader->skinheight)) |
| DefaultLogger::get()->warn("Skin width or height are 0"); |
| } |
| } |
| |
| #ifdef AI_BUILD_BIG_ENDIAN |
| // ------------------------------------------------------------------------------------------------ |
| void FlipQuakeHeader(BE_NCONST MDL::Header* pcHeader) |
| { |
| AI_SWAP4( pcHeader->ident); |
| AI_SWAP4( pcHeader->version); |
| AI_SWAP4( pcHeader->boundingradius); |
| AI_SWAP4( pcHeader->flags); |
| AI_SWAP4( pcHeader->num_frames); |
| AI_SWAP4( pcHeader->num_skins); |
| AI_SWAP4( pcHeader->num_tris); |
| AI_SWAP4( pcHeader->num_verts); |
| for (unsigned int i = 0; i < 3;++i) |
| { |
| AI_SWAP4( pcHeader->scale[i]); |
| AI_SWAP4( pcHeader->translate[i]); |
| } |
| AI_SWAP4( pcHeader->size); |
| AI_SWAP4( pcHeader->skinheight); |
| AI_SWAP4( pcHeader->skinwidth); |
| AI_SWAP4( pcHeader->synctype); |
| } |
| #endif |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Read a Quake 1 file |
| void MDLImporter::InternReadFile_Quake1( ) |
| { |
| ai_assert(NULL != pScene); |
| BE_NCONST MDL::Header *pcHeader = (BE_NCONST MDL::Header*)this->mBuffer; |
| |
| #ifdef AI_BUILD_BIG_ENDIAN |
| FlipQuakeHeader(pcHeader); |
| #endif |
| |
| ValidateHeader_Quake1(pcHeader); |
| |
| // current cursor position in the file |
| const unsigned char* szCurrent = (const unsigned char*)(pcHeader+1); |
| |
| // need to read all textures |
| for (unsigned int i = 0; i < (unsigned int)pcHeader->num_skins;++i) |
| { |
| union{BE_NCONST MDL::Skin* pcSkin;BE_NCONST MDL::GroupSkin* pcGroupSkin;}; |
| if (szCurrent + sizeof(MDL::Skin) > this->mBuffer + this->iFileSize) { |
| throw DeadlyImportError("[Quake 1 MDL] Unexpected EOF"); |
| } |
| pcSkin = (BE_NCONST MDL::Skin*)szCurrent; |
| |
| AI_SWAP4( pcSkin->group ); |
| |
| // Quake 1 groupskins |
| if (1 == pcSkin->group) |
| { |
| AI_SWAP4( pcGroupSkin->nb ); |
| |
| // need to skip multiple images |
| const unsigned int iNumImages = (unsigned int)pcGroupSkin->nb; |
| szCurrent += sizeof(uint32_t) * 2; |
| |
| if (0 != iNumImages) |
| { |
| if (!i) { |
| // however, create only one output image (the first) |
| this->CreateTextureARGB8_3DGS_MDL3(szCurrent + iNumImages * sizeof(float)); |
| } |
| // go to the end of the skin section / the beginning of the next skin |
| szCurrent += pcHeader->skinheight * pcHeader->skinwidth + |
| sizeof(float) * iNumImages; |
| } |
| } |
| // 3DGS has a few files that are using other 3DGS like texture formats here |
| else |
| { |
| szCurrent += sizeof(uint32_t); |
| unsigned int iSkip = i ? UINT_MAX : 0; |
| CreateTexture_3DGS_MDL4(szCurrent,pcSkin->group,&iSkip); |
| szCurrent += iSkip; |
| } |
| } |
| // get a pointer to the texture coordinates |
| BE_NCONST MDL::TexCoord* pcTexCoords = (BE_NCONST MDL::TexCoord*)szCurrent; |
| szCurrent += sizeof(MDL::TexCoord) * pcHeader->num_verts; |
| |
| // get a pointer to the triangles |
| BE_NCONST MDL::Triangle* pcTriangles = (BE_NCONST MDL::Triangle*)szCurrent; |
| szCurrent += sizeof(MDL::Triangle) * pcHeader->num_tris; |
| VALIDATE_FILE_SIZE(szCurrent); |
| |
| // now get a pointer to the first frame in the file |
| BE_NCONST MDL::Frame* pcFrames = (BE_NCONST MDL::Frame*)szCurrent; |
| BE_NCONST MDL::SimpleFrame* pcFirstFrame; |
| |
| if (0 == pcFrames->type) |
| { |
| // get address of single frame |
| pcFirstFrame = &pcFrames->frame; |
| } |
| else |
| { |
| // get the first frame in the group |
| BE_NCONST MDL::GroupFrame* pcFrames2 = (BE_NCONST MDL::GroupFrame*)pcFrames; |
| pcFirstFrame = (BE_NCONST MDL::SimpleFrame*)(&pcFrames2->time + pcFrames->type); |
| } |
| BE_NCONST MDL::Vertex* pcVertices = (BE_NCONST MDL::Vertex*) ((pcFirstFrame->name) + sizeof(pcFirstFrame->name)); |
| VALIDATE_FILE_SIZE((const unsigned char*)(pcVertices + pcHeader->num_verts)); |
| |
| #ifdef AI_BUILD_BIG_ENDIAN |
| for (int i = 0; i<pcHeader->num_verts;++i) |
| { |
| AI_SWAP4( pcTexCoords[i].onseam ); |
| AI_SWAP4( pcTexCoords[i].s ); |
| AI_SWAP4( pcTexCoords[i].t ); |
| } |
| |
| for (int i = 0; i<pcHeader->num_tris;++i) |
| { |
| AI_SWAP4( pcTriangles[i].facesfront); |
| AI_SWAP4( pcTriangles[i].vertex[0]); |
| AI_SWAP4( pcTriangles[i].vertex[1]); |
| AI_SWAP4( pcTriangles[i].vertex[2]); |
| } |
| #endif |
| |
| // setup materials |
| SetupMaterialProperties_3DGS_MDL5_Quake1(); |
| |
| // allocate enough storage to hold all vertices and triangles |
| aiMesh* pcMesh = new aiMesh(); |
| |
| pcMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; |
| pcMesh->mNumVertices = pcHeader->num_tris * 3; |
| pcMesh->mNumFaces = pcHeader->num_tris; |
| pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices]; |
| pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices]; |
| pcMesh->mFaces = new aiFace[pcMesh->mNumFaces]; |
| pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices]; |
| pcMesh->mNumUVComponents[0] = 2; |
| |
| // there won't be more than one mesh inside the file |
| pScene->mRootNode = new aiNode(); |
| pScene->mRootNode->mNumMeshes = 1; |
| pScene->mRootNode->mMeshes = new unsigned int[1]; |
| pScene->mRootNode->mMeshes[0] = 0; |
| pScene->mNumMeshes = 1; |
| pScene->mMeshes = new aiMesh*[1]; |
| pScene->mMeshes[0] = pcMesh; |
| |
| // now iterate through all triangles |
| unsigned int iCurrent = 0; |
| for (unsigned int i = 0; i < (unsigned int) pcHeader->num_tris;++i) |
| { |
| pcMesh->mFaces[i].mIndices = new unsigned int[3]; |
| pcMesh->mFaces[i].mNumIndices = 3; |
| |
| unsigned int iTemp = iCurrent; |
| for (unsigned int c = 0; c < 3;++c,++iCurrent) |
| { |
| pcMesh->mFaces[i].mIndices[c] = iCurrent; |
| |
| // read vertices |
| unsigned int iIndex = pcTriangles->vertex[c]; |
| if (iIndex >= (unsigned int)pcHeader->num_verts) |
| { |
| iIndex = pcHeader->num_verts-1; |
| DefaultLogger::get()->warn("Index overflow in Q1-MDL vertex list."); |
| } |
| |
| aiVector3D& vec = pcMesh->mVertices[iCurrent]; |
| vec.x = (float)pcVertices[iIndex].v[0] * pcHeader->scale[0]; |
| vec.x += pcHeader->translate[0]; |
| |
| vec.y = (float)pcVertices[iIndex].v[1] * pcHeader->scale[1]; |
| vec.y += pcHeader->translate[1]; |
| //vec.y *= -1.0f; |
| |
| vec.z = (float)pcVertices[iIndex].v[2] * pcHeader->scale[2]; |
| vec.z += pcHeader->translate[2]; |
| |
| // read the normal vector from the precalculated normal table |
| MD2::LookupNormalIndex(pcVertices[iIndex].normalIndex,pcMesh->mNormals[iCurrent]); |
| //pcMesh->mNormals[iCurrent].y *= -1.0f; |
| |
| // read texture coordinates |
| float s = (float)pcTexCoords[iIndex].s; |
| float t = (float)pcTexCoords[iIndex].t; |
| |
| // translate texture coordinates |
| if (0 == pcTriangles->facesfront && 0 != pcTexCoords[iIndex].onseam) { |
| s += pcHeader->skinwidth * 0.5f; |
| } |
| |
| // Scale s and t to range from 0.0 to 1.0 |
| pcMesh->mTextureCoords[0][iCurrent].x = (s + 0.5f) / pcHeader->skinwidth; |
| pcMesh->mTextureCoords[0][iCurrent].y = 1.0f-(t + 0.5f) / pcHeader->skinheight; |
| |
| } |
| pcMesh->mFaces[i].mIndices[0] = iTemp+2; |
| pcMesh->mFaces[i].mIndices[1] = iTemp+1; |
| pcMesh->mFaces[i].mIndices[2] = iTemp+0; |
| pcTriangles++; |
| } |
| return; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Setup material properties for Quake and older GameStudio files |
| void MDLImporter::SetupMaterialProperties_3DGS_MDL5_Quake1( ) |
| { |
| const MDL::Header* const pcHeader = (const MDL::Header*)this->mBuffer; |
| |
| // allocate ONE material |
| pScene->mMaterials = new aiMaterial*[1]; |
| pScene->mMaterials[0] = new aiMaterial(); |
| pScene->mNumMaterials = 1; |
| |
| // setup the material's properties |
| const int iMode = (int)aiShadingMode_Gouraud; |
| aiMaterial* const pcHelper = (aiMaterial*)pScene->mMaterials[0]; |
| pcHelper->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL); |
| |
| aiColor4D clr; |
| if (0 != pcHeader->num_skins && pScene->mNumTextures) { |
| // can we replace the texture with a single color? |
| clr = this->ReplaceTextureWithColor(pScene->mTextures[0]); |
| if (is_not_qnan(clr.r)) { |
| delete pScene->mTextures[0]; |
| delete[] pScene->mTextures; |
| |
| pScene->mTextures = NULL; |
| pScene->mNumTextures = 0; |
| } |
| else { |
| clr.b = clr.a = clr.g = clr.r = 1.0f; |
| aiString szString; |
| ::memcpy(szString.data,AI_MAKE_EMBEDDED_TEXNAME(0),3); |
| szString.length = 2; |
| pcHelper->AddProperty(&szString,AI_MATKEY_TEXTURE_DIFFUSE(0)); |
| } |
| } |
| |
| pcHelper->AddProperty<aiColor4D>(&clr, 1,AI_MATKEY_COLOR_DIFFUSE); |
| pcHelper->AddProperty<aiColor4D>(&clr, 1,AI_MATKEY_COLOR_SPECULAR); |
| |
| clr.r *= 0.05f;clr.g *= 0.05f; |
| clr.b *= 0.05f;clr.a = 1.0f; |
| pcHelper->AddProperty<aiColor4D>(&clr, 1,AI_MATKEY_COLOR_AMBIENT); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Read a MDL 3,4,5 file |
| void MDLImporter::InternReadFile_3DGS_MDL345( ) |
| { |
| ai_assert(NULL != pScene); |
| |
| // the header of MDL 3/4/5 is nearly identical to the original Quake1 header |
| BE_NCONST MDL::Header *pcHeader = (BE_NCONST MDL::Header*)this->mBuffer; |
| #ifdef AI_BUILD_BIG_ENDIAN |
| FlipQuakeHeader(pcHeader); |
| #endif |
| ValidateHeader_Quake1(pcHeader); |
| |
| // current cursor position in the file |
| const unsigned char* szCurrent = (const unsigned char*)(pcHeader+1); |
| const unsigned char* szEnd = mBuffer + iFileSize; |
| |
| // need to read all textures |
| for (unsigned int i = 0; i < (unsigned int)pcHeader->num_skins;++i) { |
| if (szCurrent >= szEnd) { |
| throw DeadlyImportError( "Texture data past end of file."); |
| } |
| BE_NCONST MDL::Skin* pcSkin; |
| pcSkin = (BE_NCONST MDL::Skin*)szCurrent; |
| AI_SWAP4( pcSkin->group); |
| // create one output image |
| unsigned int iSkip = i ? UINT_MAX : 0; |
| if (5 <= iGSFileVersion) |
| { |
| // MDL5 format could contain MIPmaps |
| CreateTexture_3DGS_MDL5((unsigned char*)pcSkin + sizeof(uint32_t), |
| pcSkin->group,&iSkip); |
| } |
| else { |
| CreateTexture_3DGS_MDL4((unsigned char*)pcSkin + sizeof(uint32_t), |
| pcSkin->group,&iSkip); |
| } |
| // need to skip one image |
| szCurrent += iSkip + sizeof(uint32_t); |
| |
| } |
| // get a pointer to the texture coordinates |
| BE_NCONST MDL::TexCoord_MDL3* pcTexCoords = (BE_NCONST MDL::TexCoord_MDL3*)szCurrent; |
| szCurrent += sizeof(MDL::TexCoord_MDL3) * pcHeader->synctype; |
| |
| // NOTE: for MDLn formats "synctype" corresponds to the number of UV coords |
| |
| // get a pointer to the triangles |
| BE_NCONST MDL::Triangle_MDL3* pcTriangles = (BE_NCONST MDL::Triangle_MDL3*)szCurrent; |
| szCurrent += sizeof(MDL::Triangle_MDL3) * pcHeader->num_tris; |
| |
| #ifdef AI_BUILD_BIG_ENDIAN |
| |
| for (int i = 0; i<pcHeader->synctype;++i) { |
| AI_SWAP2( pcTexCoords[i].u ); |
| AI_SWAP2( pcTexCoords[i].v ); |
| } |
| |
| for (int i = 0; i<pcHeader->num_tris;++i) { |
| AI_SWAP2( pcTriangles[i].index_xyz[0]); |
| AI_SWAP2( pcTriangles[i].index_xyz[1]); |
| AI_SWAP2( pcTriangles[i].index_xyz[2]); |
| AI_SWAP2( pcTriangles[i].index_uv[0]); |
| AI_SWAP2( pcTriangles[i].index_uv[1]); |
| AI_SWAP2( pcTriangles[i].index_uv[2]); |
| } |
| |
| #endif |
| |
| VALIDATE_FILE_SIZE(szCurrent); |
| |
| // setup materials |
| SetupMaterialProperties_3DGS_MDL5_Quake1(); |
| |
| // allocate enough storage to hold all vertices and triangles |
| aiMesh* pcMesh = new aiMesh(); |
| pcMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; |
| |
| pcMesh->mNumVertices = pcHeader->num_tris * 3; |
| pcMesh->mNumFaces = pcHeader->num_tris; |
| pcMesh->mFaces = new aiFace[pcMesh->mNumFaces]; |
| |
| // there won't be more than one mesh inside the file |
| pScene->mRootNode = new aiNode(); |
| pScene->mRootNode->mNumMeshes = 1; |
| pScene->mRootNode->mMeshes = new unsigned int[1]; |
| pScene->mRootNode->mMeshes[0] = 0; |
| pScene->mNumMeshes = 1; |
| pScene->mMeshes = new aiMesh*[1]; |
| pScene->mMeshes[0] = pcMesh; |
| |
| // allocate output storage |
| pcMesh->mNumVertices = (unsigned int)pcHeader->num_tris*3; |
| pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices]; |
| pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices]; |
| |
| if (pcHeader->synctype) { |
| pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices]; |
| pcMesh->mNumUVComponents[0] = 2; |
| } |
| |
| // now get a pointer to the first frame in the file |
| BE_NCONST MDL::Frame* pcFrames = (BE_NCONST MDL::Frame*)szCurrent; |
| AI_SWAP4(pcFrames->type); |
| |
| // byte packed vertices |
| // FIXME: these two snippets below are almost identical ... join them? |
| ///////////////////////////////////////////////////////////////////////////////////// |
| if (0 == pcFrames->type || 3 >= this->iGSFileVersion) { |
| |
| const MDL::SimpleFrame* pcFirstFrame = (const MDL::SimpleFrame*)(szCurrent + sizeof(uint32_t)); |
| const MDL::Vertex* pcVertices = (const MDL::Vertex*) ((pcFirstFrame->name) + sizeof(pcFirstFrame->name)); |
| |
| VALIDATE_FILE_SIZE(pcVertices + pcHeader->num_verts); |
| |
| // now iterate through all triangles |
| unsigned int iCurrent = 0; |
| for (unsigned int i = 0; i < (unsigned int) pcHeader->num_tris;++i) { |
| pcMesh->mFaces[i].mIndices = new unsigned int[3]; |
| pcMesh->mFaces[i].mNumIndices = 3; |
| |
| unsigned int iTemp = iCurrent; |
| for (unsigned int c = 0; c < 3;++c,++iCurrent) { |
| // read vertices |
| unsigned int iIndex = pcTriangles->index_xyz[c]; |
| if (iIndex >= (unsigned int)pcHeader->num_verts) { |
| iIndex = pcHeader->num_verts-1; |
| DefaultLogger::get()->warn("Index overflow in MDLn vertex list"); |
| } |
| |
| aiVector3D& vec = pcMesh->mVertices[iCurrent]; |
| vec.x = (float)pcVertices[iIndex].v[0] * pcHeader->scale[0]; |
| vec.x += pcHeader->translate[0]; |
| |
| vec.y = (float)pcVertices[iIndex].v[1] * pcHeader->scale[1]; |
| vec.y += pcHeader->translate[1]; |
| // vec.y *= -1.0f; |
| |
| vec.z = (float)pcVertices[iIndex].v[2] * pcHeader->scale[2]; |
| vec.z += pcHeader->translate[2]; |
| |
| // read the normal vector from the precalculated normal table |
| MD2::LookupNormalIndex(pcVertices[iIndex].normalIndex,pcMesh->mNormals[iCurrent]); |
| // pcMesh->mNormals[iCurrent].y *= -1.0f; |
| |
| // read texture coordinates |
| if (pcHeader->synctype) { |
| ImportUVCoordinate_3DGS_MDL345(pcMesh->mTextureCoords[0][iCurrent], |
| pcTexCoords,pcTriangles->index_uv[c]); |
| } |
| } |
| pcMesh->mFaces[i].mIndices[0] = iTemp+2; |
| pcMesh->mFaces[i].mIndices[1] = iTemp+1; |
| pcMesh->mFaces[i].mIndices[2] = iTemp+0; |
| pcTriangles++; |
| } |
| |
| } |
| // short packed vertices |
| ///////////////////////////////////////////////////////////////////////////////////// |
| else { |
| // now get a pointer to the first frame in the file |
| const MDL::SimpleFrame_MDLn_SP* pcFirstFrame = (const MDL::SimpleFrame_MDLn_SP*) (szCurrent + sizeof(uint32_t)); |
| |
| // get a pointer to the vertices |
| const MDL::Vertex_MDL4* pcVertices = (const MDL::Vertex_MDL4*) ((pcFirstFrame->name) + |
| sizeof(pcFirstFrame->name)); |
| |
| VALIDATE_FILE_SIZE(pcVertices + pcHeader->num_verts); |
| |
| // now iterate through all triangles |
| unsigned int iCurrent = 0; |
| for (unsigned int i = 0; i < (unsigned int) pcHeader->num_tris;++i) { |
| pcMesh->mFaces[i].mIndices = new unsigned int[3]; |
| pcMesh->mFaces[i].mNumIndices = 3; |
| |
| unsigned int iTemp = iCurrent; |
| for (unsigned int c = 0; c < 3;++c,++iCurrent) { |
| // read vertices |
| unsigned int iIndex = pcTriangles->index_xyz[c]; |
| if (iIndex >= (unsigned int)pcHeader->num_verts) { |
| iIndex = pcHeader->num_verts-1; |
| DefaultLogger::get()->warn("Index overflow in MDLn vertex list"); |
| } |
| |
| aiVector3D& vec = pcMesh->mVertices[iCurrent]; |
| vec.x = (float)pcVertices[iIndex].v[0] * pcHeader->scale[0]; |
| vec.x += pcHeader->translate[0]; |
| |
| vec.y = (float)pcVertices[iIndex].v[1] * pcHeader->scale[1]; |
| vec.y += pcHeader->translate[1]; |
| // vec.y *= -1.0f; |
| |
| vec.z = (float)pcVertices[iIndex].v[2] * pcHeader->scale[2]; |
| vec.z += pcHeader->translate[2]; |
| |
| // read the normal vector from the precalculated normal table |
| MD2::LookupNormalIndex(pcVertices[iIndex].normalIndex,pcMesh->mNormals[iCurrent]); |
| // pcMesh->mNormals[iCurrent].y *= -1.0f; |
| |
| // read texture coordinates |
| if (pcHeader->synctype) { |
| ImportUVCoordinate_3DGS_MDL345(pcMesh->mTextureCoords[0][iCurrent], |
| pcTexCoords,pcTriangles->index_uv[c]); |
| } |
| } |
| pcMesh->mFaces[i].mIndices[0] = iTemp+2; |
| pcMesh->mFaces[i].mIndices[1] = iTemp+1; |
| pcMesh->mFaces[i].mIndices[2] = iTemp+0; |
| pcTriangles++; |
| } |
| } |
| |
| // For MDL5 we will need to build valid texture coordinates |
| // basing upon the file loaded (only support one file as skin) |
| if (0x5 == iGSFileVersion) |
| CalculateUVCoordinates_MDL5(); |
| return; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Get a single UV coordinate for Quake and older GameStudio files |
| void MDLImporter::ImportUVCoordinate_3DGS_MDL345( |
| aiVector3D& vOut, |
| const MDL::TexCoord_MDL3* pcSrc, |
| unsigned int iIndex) |
| { |
| ai_assert(NULL != pcSrc); |
| const MDL::Header* const pcHeader = (const MDL::Header*)this->mBuffer; |
| |
| // validate UV indices |
| if (iIndex >= (unsigned int) pcHeader->synctype) { |
| iIndex = pcHeader->synctype-1; |
| DefaultLogger::get()->warn("Index overflow in MDLn UV coord list"); |
| } |
| |
| float s = (float)pcSrc[iIndex].u; |
| float t = (float)pcSrc[iIndex].v; |
| |
| // Scale s and t to range from 0.0 to 1.0 |
| if (0x5 != iGSFileVersion) { |
| s = (s + 0.5f) / pcHeader->skinwidth; |
| t = 1.0f-(t + 0.5f) / pcHeader->skinheight; |
| } |
| |
| vOut.x = s; |
| vOut.y = t; |
| vOut.z = 0.0f; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Compute UV coordinates for a MDL5 file |
| void MDLImporter::CalculateUVCoordinates_MDL5() |
| { |
| const MDL::Header* const pcHeader = (const MDL::Header*)this->mBuffer; |
| if (pcHeader->num_skins && this->pScene->mNumTextures) { |
| const aiTexture* pcTex = this->pScene->mTextures[0]; |
| |
| // if the file is loaded in DDS format: get the size of the |
| // texture from the header of the DDS file |
| // skip three DWORDs and read first height, then the width |
| unsigned int iWidth, iHeight; |
| if (!pcTex->mHeight) { |
| const uint32_t* piPtr = (uint32_t*)pcTex->pcData; |
| |
| piPtr += 3; |
| iHeight = (unsigned int)*piPtr++; |
| iWidth = (unsigned int)*piPtr; |
| if (!iHeight || !iWidth) |
| { |
| DefaultLogger::get()->warn("Either the width or the height of the " |
| "embedded DDS texture is zero. Unable to compute final texture " |
| "coordinates. The texture coordinates remain in their original " |
| "0-x/0-y (x,y = texture size) range."); |
| iWidth = 1; |
| iHeight = 1; |
| } |
| } |
| else { |
| iWidth = pcTex->mWidth; |
| iHeight = pcTex->mHeight; |
| } |
| |
| if (1 != iWidth || 1 != iHeight) { |
| const float fWidth = (float)iWidth; |
| const float fHeight = (float)iHeight; |
| aiMesh* pcMesh = this->pScene->mMeshes[0]; |
| for (unsigned int i = 0; i < pcMesh->mNumVertices;++i) |
| { |
| pcMesh->mTextureCoords[0][i].x /= fWidth; |
| pcMesh->mTextureCoords[0][i].y /= fHeight; |
| pcMesh->mTextureCoords[0][i].y = 1.0f - pcMesh->mTextureCoords[0][i].y; // DX to OGL |
| } |
| } |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Validate the header of a MDL7 file |
| void MDLImporter::ValidateHeader_3DGS_MDL7(const MDL::Header_MDL7* pcHeader) |
| { |
| ai_assert(NULL != pcHeader); |
| |
| // There are some fixed sizes ... |
| if (sizeof(MDL::ColorValue_MDL7) != pcHeader->colorvalue_stc_size) { |
| throw DeadlyImportError( |
| "[3DGS MDL7] sizeof(MDL::ColorValue_MDL7) != pcHeader->colorvalue_stc_size"); |
| } |
| if (sizeof(MDL::TexCoord_MDL7) != pcHeader->skinpoint_stc_size) { |
| throw DeadlyImportError( |
| "[3DGS MDL7] sizeof(MDL::TexCoord_MDL7) != pcHeader->skinpoint_stc_size"); |
| } |
| if (sizeof(MDL::Skin_MDL7) != pcHeader->skin_stc_size) { |
| throw DeadlyImportError( |
| "sizeof(MDL::Skin_MDL7) != pcHeader->skin_stc_size"); |
| } |
| |
| // if there are no groups ... how should we load such a file? |
| if(!pcHeader->groups_num) { |
| throw DeadlyImportError( "[3DGS MDL7] No frames found"); |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // resolve bone animation matrices |
| void MDLImporter::CalcAbsBoneMatrices_3DGS_MDL7(MDL::IntBone_MDL7** apcOutBones) |
| { |
| const MDL::Header_MDL7 *pcHeader = (const MDL::Header_MDL7*)this->mBuffer; |
| const MDL::Bone_MDL7* pcBones = (const MDL::Bone_MDL7*)(pcHeader+1); |
| ai_assert(NULL != apcOutBones); |
| |
| // first find the bone that has NO parent, calculate the |
| // animation matrix for it, then go on and search for the next parent |
| // index (0) and so on until we can't find a new node. |
| uint16_t iParent = 0xffff; |
| uint32_t iIterations = 0; |
| while (iIterations++ < pcHeader->bones_num) { |
| for (uint32_t iBone = 0; iBone < pcHeader->bones_num;++iBone) { |
| BE_NCONST MDL::Bone_MDL7* pcBone = _AI_MDL7_ACCESS_PTR(pcBones,iBone, |
| pcHeader->bone_stc_size,MDL::Bone_MDL7); |
| |
| AI_SWAP2(pcBone->parent_index); |
| AI_SWAP4(pcBone->x); |
| AI_SWAP4(pcBone->y); |
| AI_SWAP4(pcBone->z); |
| |
| if (iParent == pcBone->parent_index) { |
| // MDL7 readme |
| //////////////////////////////////////////////////////////////// |
| /* |
| The animation matrix is then calculated the following way: |
| |
| vector3 bPos = <absolute bone position> |
| matrix44 laM; // local animation matrix |
| sphrvector key_rotate = <bone rotation> |
| |
| matrix44 m1,m2; |
| create_trans_matrix(m1, -bPos.x, -bPos.y, -bPos.z); |
| create_trans_matrix(m2, -bPos.x, -bPos.y, -bPos.z); |
| |
| create_rotation_matrix(laM,key_rotate); |
| |
| laM = sm1 * laM; |
| laM = laM * sm2; |
| */ |
| ///////////////////////////////////////////////////////////////// |
| |
| MDL::IntBone_MDL7* const pcOutBone = apcOutBones[iBone]; |
| |
| // store the parent index of the bone |
| pcOutBone->iParent = pcBone->parent_index; |
| if (0xffff != iParent) { |
| const MDL::IntBone_MDL7* pcParentBone = apcOutBones[iParent]; |
| pcOutBone->mOffsetMatrix.a4 = -pcParentBone->vPosition.x; |
| pcOutBone->mOffsetMatrix.b4 = -pcParentBone->vPosition.y; |
| pcOutBone->mOffsetMatrix.c4 = -pcParentBone->vPosition.z; |
| } |
| pcOutBone->vPosition.x = pcBone->x; |
| pcOutBone->vPosition.y = pcBone->y; |
| pcOutBone->vPosition.z = pcBone->z; |
| pcOutBone->mOffsetMatrix.a4 -= pcBone->x; |
| pcOutBone->mOffsetMatrix.b4 -= pcBone->y; |
| pcOutBone->mOffsetMatrix.c4 -= pcBone->z; |
| |
| if (AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_NOT_THERE == pcHeader->bone_stc_size) { |
| // no real name for our poor bone is specified :-( |
| pcOutBone->mName.length = ai_snprintf(pcOutBone->mName.data, MAXLEN, |
| "UnnamedBone_%i",iBone); |
| } |
| else { |
| // Make sure we won't run over the buffer's end if there is no |
| // terminal 0 character (however the documentation says there |
| // should be one) |
| uint32_t iMaxLen = pcHeader->bone_stc_size-16; |
| for (uint32_t qq = 0; qq < iMaxLen;++qq) { |
| if (!pcBone->name[qq]) { |
| iMaxLen = qq; |
| break; |
| } |
| } |
| |
| // store the name of the bone |
| pcOutBone->mName.length = (size_t)iMaxLen; |
| ::memcpy(pcOutBone->mName.data,pcBone->name,pcOutBone->mName.length); |
| pcOutBone->mName.data[pcOutBone->mName.length] = '\0'; |
| } |
| } |
| } |
| ++iParent; |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // read bones from a MDL7 file |
| MDL::IntBone_MDL7** MDLImporter::LoadBones_3DGS_MDL7() |
| { |
| const MDL::Header_MDL7 *pcHeader = (const MDL::Header_MDL7*)this->mBuffer; |
| if (pcHeader->bones_num) { |
| // validate the size of the bone data structure in the file |
| if (AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_20_CHARS != pcHeader->bone_stc_size && |
| AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_32_CHARS != pcHeader->bone_stc_size && |
| AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_NOT_THERE != pcHeader->bone_stc_size) |
| { |
| DefaultLogger::get()->warn("Unknown size of bone data structure"); |
| return NULL; |
| } |
| |
| MDL::IntBone_MDL7** apcBonesOut = new MDL::IntBone_MDL7*[pcHeader->bones_num]; |
| for (uint32_t crank = 0; crank < pcHeader->bones_num;++crank) |
| apcBonesOut[crank] = new MDL::IntBone_MDL7(); |
| |
| // and calculate absolute bone offset matrices ... |
| CalcAbsBoneMatrices_3DGS_MDL7(apcBonesOut); |
| return apcBonesOut; |
| } |
| return NULL; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // read faces from a MDL7 file |
| void MDLImporter::ReadFaces_3DGS_MDL7(const MDL::IntGroupInfo_MDL7& groupInfo, |
| MDL::IntGroupData_MDL7& groupData) |
| { |
| const MDL::Header_MDL7 *pcHeader = (const MDL::Header_MDL7*)this->mBuffer; |
| MDL::Triangle_MDL7* pcGroupTris = groupInfo.pcGroupTris; |
| |
| // iterate through all triangles and build valid display lists |
| unsigned int iOutIndex = 0; |
| for (unsigned int iTriangle = 0; iTriangle < (unsigned int)groupInfo.pcGroup->numtris; ++iTriangle) { |
| AI_SWAP2(pcGroupTris->v_index[0]); |
| AI_SWAP2(pcGroupTris->v_index[1]); |
| AI_SWAP2(pcGroupTris->v_index[2]); |
| |
| // iterate through all indices of the current triangle |
| for (unsigned int c = 0; c < 3;++c,++iOutIndex) { |
| |
| // validate the vertex index |
| unsigned int iIndex = pcGroupTris->v_index[c]; |
| if(iIndex > (unsigned int)groupInfo.pcGroup->numverts) { |
| // (we might need to read this section a second time - to process frame vertices correctly) |
| pcGroupTris->v_index[c] = iIndex = groupInfo.pcGroup->numverts-1; |
| DefaultLogger::get()->warn("Index overflow in MDL7 vertex list"); |
| } |
| |
| // write the output face index |
| groupData.pcFaces[iTriangle].mIndices[2-c] = iOutIndex; |
| |
| aiVector3D& vPosition = groupData.vPositions[ iOutIndex ]; |
| vPosition.x = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts,iIndex, pcHeader->mainvertex_stc_size) .x; |
| vPosition.y = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts,iIndex,pcHeader->mainvertex_stc_size) .y; |
| vPosition.z = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts,iIndex,pcHeader->mainvertex_stc_size) .z; |
| |
| // if we have bones, save the index |
| if (!groupData.aiBones.empty()) { |
| groupData.aiBones[iOutIndex] = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts, |
| iIndex,pcHeader->mainvertex_stc_size).vertindex; |
| } |
| |
| // now read the normal vector |
| if (AI_MDL7_FRAMEVERTEX030305_STCSIZE <= pcHeader->mainvertex_stc_size) { |
| // read the full normal vector |
| aiVector3D& vNormal = groupData.vNormals[ iOutIndex ]; |
| vNormal.x = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts,iIndex,pcHeader->mainvertex_stc_size) .norm[0]; |
| AI_SWAP4(vNormal.x); |
| vNormal.y = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts,iIndex,pcHeader->mainvertex_stc_size) .norm[1]; |
| AI_SWAP4(vNormal.y); |
| vNormal.z = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts,iIndex,pcHeader->mainvertex_stc_size) .norm[2]; |
| AI_SWAP4(vNormal.z); |
| } |
| else if (AI_MDL7_FRAMEVERTEX120503_STCSIZE <= pcHeader->mainvertex_stc_size) { |
| // read the normal vector from Quake2's smart table |
| aiVector3D& vNormal = groupData.vNormals[ iOutIndex ]; |
| MD2::LookupNormalIndex(_AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts,iIndex, |
| pcHeader->mainvertex_stc_size) .norm162index,vNormal); |
| } |
| // validate and process the first uv coordinate set |
| if (pcHeader->triangle_stc_size >= AI_MDL7_TRIANGLE_STD_SIZE_ONE_UV) { |
| |
| if (groupInfo.pcGroup->num_stpts) { |
| AI_SWAP2(pcGroupTris->skinsets[0].st_index[0]); |
| AI_SWAP2(pcGroupTris->skinsets[0].st_index[1]); |
| AI_SWAP2(pcGroupTris->skinsets[0].st_index[2]); |
| |
| iIndex = pcGroupTris->skinsets[0].st_index[c]; |
| if(iIndex > (unsigned int)groupInfo.pcGroup->num_stpts) { |
| iIndex = groupInfo.pcGroup->num_stpts-1; |
| DefaultLogger::get()->warn("Index overflow in MDL7 UV coordinate list (#1)"); |
| } |
| |
| float u = groupInfo.pcGroupUVs[iIndex].u; |
| float v = 1.0f-groupInfo.pcGroupUVs[iIndex].v; // DX to OGL |
| |
| groupData.vTextureCoords1[iOutIndex].x = u; |
| groupData.vTextureCoords1[iOutIndex].y = v; |
| } |
| // assign the material index, but only if it is existing |
| if (pcHeader->triangle_stc_size >= AI_MDL7_TRIANGLE_STD_SIZE_ONE_UV_WITH_MATINDEX){ |
| AI_SWAP4(pcGroupTris->skinsets[0].material); |
| groupData.pcFaces[iTriangle].iMatIndex[0] = pcGroupTris->skinsets[0].material; |
| } |
| } |
| // validate and process the second uv coordinate set |
| if (pcHeader->triangle_stc_size >= AI_MDL7_TRIANGLE_STD_SIZE_TWO_UV) { |
| |
| if (groupInfo.pcGroup->num_stpts) { |
| AI_SWAP2(pcGroupTris->skinsets[1].st_index[0]); |
| AI_SWAP2(pcGroupTris->skinsets[1].st_index[1]); |
| AI_SWAP2(pcGroupTris->skinsets[1].st_index[2]); |
| AI_SWAP4(pcGroupTris->skinsets[1].material); |
| |
| iIndex = pcGroupTris->skinsets[1].st_index[c]; |
| if(iIndex > (unsigned int)groupInfo.pcGroup->num_stpts) { |
| iIndex = groupInfo.pcGroup->num_stpts-1; |
| DefaultLogger::get()->warn("Index overflow in MDL7 UV coordinate list (#2)"); |
| } |
| |
| float u = groupInfo.pcGroupUVs[ iIndex ].u; |
| float v = 1.0f-groupInfo.pcGroupUVs[ iIndex ].v; |
| |
| groupData.vTextureCoords2[ iOutIndex ].x = u; |
| groupData.vTextureCoords2[ iOutIndex ].y = v; // DX to OGL |
| |
| // check whether we do really need the second texture |
| // coordinate set ... wastes memory and loading time |
| if (0 != iIndex && (u != groupData.vTextureCoords1[ iOutIndex ].x || |
| v != groupData.vTextureCoords1[ iOutIndex ].y ) ) |
| groupData.bNeed2UV = true; |
| |
| // if the material differs, we need a second skin, too |
| if (pcGroupTris->skinsets[ 1 ].material != pcGroupTris->skinsets[ 0 ].material) |
| groupData.bNeed2UV = true; |
| } |
| // assign the material index |
| groupData.pcFaces[ iTriangle ].iMatIndex[ 1 ] = pcGroupTris->skinsets[ 1 ].material; |
| } |
| } |
| // get the next triangle in the list |
| pcGroupTris = (MDL::Triangle_MDL7*)((const char*)pcGroupTris + pcHeader->triangle_stc_size); |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // handle frames in a MDL7 file |
| bool MDLImporter::ProcessFrames_3DGS_MDL7(const MDL::IntGroupInfo_MDL7& groupInfo, |
| MDL::IntGroupData_MDL7& groupData, |
| MDL::IntSharedData_MDL7& shared, |
| const unsigned char* szCurrent, |
| const unsigned char** szCurrentOut) |
| { |
| ai_assert( nullptr != szCurrent ); |
| ai_assert( nullptr != szCurrentOut); |
| |
| const MDL::Header_MDL7 *pcHeader = (const MDL::Header_MDL7*)mBuffer; |
| |
| // if we have no bones we can simply skip all frames, |
| // otherwise we'll need to process them. |
| // FIX: If we need another frame than the first we must apply frame vertex replacements ... |
| for(unsigned int iFrame = 0; iFrame < (unsigned int)groupInfo.pcGroup->numframes;++iFrame) { |
| MDL::IntFrameInfo_MDL7 frame ((BE_NCONST MDL::Frame_MDL7*)szCurrent,iFrame); |
| |
| AI_SWAP4(frame.pcFrame->vertices_count); |
| AI_SWAP4(frame.pcFrame->transmatrix_count); |
| |
| const unsigned int iAdd = pcHeader->frame_stc_size + |
| frame.pcFrame->vertices_count * pcHeader->framevertex_stc_size + |
| frame.pcFrame->transmatrix_count * pcHeader->bonetrans_stc_size; |
| |
| if (((const char*)szCurrent - (const char*)pcHeader) + iAdd > (unsigned int)pcHeader->data_size) { |
| DefaultLogger::get()->warn("Index overflow in frame area. " |
| "Ignoring all frames and all further mesh groups, too."); |
| |
| // don't parse more groups if we can't even read one |
| // FIXME: sometimes this seems to occur even for valid files ... |
| *szCurrentOut = szCurrent; |
| return false; |
| } |
| // our output frame? |
| if (configFrameID == iFrame) { |
| BE_NCONST MDL::Vertex_MDL7* pcFrameVertices = (BE_NCONST MDL::Vertex_MDL7*)(szCurrent+pcHeader->frame_stc_size); |
| |
| for (unsigned int qq = 0; qq < frame.pcFrame->vertices_count;++qq) { |
| // I assume this are simple replacements for normal vertices, the bone index serving |
| // as the index of the vertex to be replaced. |
| uint16_t iIndex = _AI_MDL7_ACCESS(pcFrameVertices,qq,pcHeader->framevertex_stc_size,MDL::Vertex_MDL7).vertindex; |
| AI_SWAP2(iIndex); |
| if (iIndex >= groupInfo.pcGroup->numverts) { |
| DefaultLogger::get()->warn("Invalid vertex index in frame vertex section"); |
| continue; |
| } |
| |
| aiVector3D vPosition,vNormal; |
| |
| vPosition.x = _AI_MDL7_ACCESS_VERT(pcFrameVertices,qq,pcHeader->framevertex_stc_size) .x; |
| AI_SWAP4(vPosition.x); |
| vPosition.y = _AI_MDL7_ACCESS_VERT(pcFrameVertices,qq,pcHeader->framevertex_stc_size) .y; |
| AI_SWAP4(vPosition.y); |
| vPosition.z = _AI_MDL7_ACCESS_VERT(pcFrameVertices,qq,pcHeader->framevertex_stc_size) .z; |
| AI_SWAP4(vPosition.z); |
| |
| // now read the normal vector |
| if (AI_MDL7_FRAMEVERTEX030305_STCSIZE <= pcHeader->mainvertex_stc_size) { |
| // read the full normal vector |
| vNormal.x = _AI_MDL7_ACCESS_VERT(pcFrameVertices,qq,pcHeader->framevertex_stc_size) .norm[0]; |
| AI_SWAP4(vNormal.x); |
| vNormal.y = _AI_MDL7_ACCESS_VERT(pcFrameVertices,qq,pcHeader->framevertex_stc_size) .norm[1]; |
| AI_SWAP4(vNormal.y); |
| vNormal.z = _AI_MDL7_ACCESS_VERT(pcFrameVertices,qq,pcHeader->framevertex_stc_size) .norm[2]; |
| AI_SWAP4(vNormal.z); |
| } |
| else if (AI_MDL7_FRAMEVERTEX120503_STCSIZE <= pcHeader->mainvertex_stc_size) { |
| // read the normal vector from Quake2's smart table |
| MD2::LookupNormalIndex(_AI_MDL7_ACCESS_VERT(pcFrameVertices,qq, |
| pcHeader->framevertex_stc_size) .norm162index,vNormal); |
| } |
| |
| // FIXME: O(n^2) at the moment ... |
| BE_NCONST MDL::Triangle_MDL7* pcGroupTris = groupInfo.pcGroupTris; |
| unsigned int iOutIndex = 0; |
| for (unsigned int iTriangle = 0; iTriangle < (unsigned int)groupInfo.pcGroup->numtris; ++iTriangle) { |
| // iterate through all indices of the current triangle |
| for (unsigned int c = 0; c < 3;++c,++iOutIndex) { |
| // replace the vertex with the new data |
| const unsigned int iCurIndex = pcGroupTris->v_index[c]; |
| if (iCurIndex == iIndex) { |
| groupData.vPositions[iOutIndex] = vPosition; |
| groupData.vNormals[iOutIndex] = vNormal; |
| } |
| } |
| // get the next triangle in the list |
| pcGroupTris = (BE_NCONST MDL::Triangle_MDL7*)((const char*) |
| pcGroupTris + pcHeader->triangle_stc_size); |
| } |
| } |
| } |
| // parse bone trafo matrix keys (only if there are bones ...) |
| if (shared.apcOutBones) { |
| ParseBoneTrafoKeys_3DGS_MDL7(groupInfo,frame,shared); |
| } |
| szCurrent += iAdd; |
| } |
| *szCurrentOut = szCurrent; |
| return true; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Sort faces by material, handle multiple UVs correctly |
| void MDLImporter::SortByMaterials_3DGS_MDL7( |
| const MDL::IntGroupInfo_MDL7& groupInfo, |
| MDL::IntGroupData_MDL7& groupData, |
| MDL::IntSplitGroupData_MDL7& splitGroupData) |
| { |
| const unsigned int iNumMaterials = (unsigned int)splitGroupData.shared.pcMats.size(); |
| if (!groupData.bNeed2UV) { |
| // if we don't need a second set of texture coordinates there is no reason to keep it in memory ... |
| groupData.vTextureCoords2.clear(); |
| |
| // allocate the array |
| splitGroupData.aiSplit = new std::vector<unsigned int>*[iNumMaterials]; |
| |
| for (unsigned int m = 0; m < iNumMaterials;++m) |
| splitGroupData.aiSplit[m] = new std::vector<unsigned int>(); |
| |
| // iterate through all faces and sort by material |
| for (unsigned int iFace = 0; iFace < (unsigned int)groupInfo.pcGroup->numtris;++iFace) { |
| // check range |
| if (groupData.pcFaces[iFace].iMatIndex[0] >= iNumMaterials) { |
| // use the last material instead |
| splitGroupData.aiSplit[iNumMaterials-1]->push_back(iFace); |
| |
| // sometimes MED writes -1, but normally only if there is only |
| // one skin assigned. No warning in this case |
| if(0xFFFFFFFF != groupData.pcFaces[iFace].iMatIndex[0]) |
| DefaultLogger::get()->warn("Index overflow in MDL7 material list [#0]"); |
| } |
| else splitGroupData.aiSplit[groupData.pcFaces[iFace]. |
| iMatIndex[0]]->push_back(iFace); |
| } |
| } |
| else |
| { |
| // we need to build combined materials for each combination of |
| std::vector<MDL::IntMaterial_MDL7> avMats; |
| avMats.reserve(iNumMaterials*2); |
| |
| // fixme: why on the heap? |
| std::vector<std::vector<unsigned int>* > aiTempSplit(iNumMaterials*2); |
| for (unsigned int m = 0; m < iNumMaterials;++m) |
| aiTempSplit[m] = new std::vector<unsigned int>(); |
| |
| // iterate through all faces and sort by material |
| for (unsigned int iFace = 0; iFace < (unsigned int)groupInfo.pcGroup->numtris;++iFace) { |
| // check range |
| unsigned int iMatIndex = groupData.pcFaces[iFace].iMatIndex[0]; |
| if (iMatIndex >= iNumMaterials) { |
| // sometimes MED writes -1, but normally only if there is only |
| // one skin assigned. No warning in this case |
| if(UINT_MAX != iMatIndex) |
| DefaultLogger::get()->warn("Index overflow in MDL7 material list [#1]"); |
| iMatIndex = iNumMaterials-1; |
| } |
| unsigned int iMatIndex2 = groupData.pcFaces[iFace].iMatIndex[1]; |
| |
| unsigned int iNum = iMatIndex; |
| if (UINT_MAX != iMatIndex2 && iMatIndex != iMatIndex2) { |
| if (iMatIndex2 >= iNumMaterials) { |
| // sometimes MED writes -1, but normally only if there is only |
| // one skin assigned. No warning in this case |
| DefaultLogger::get()->warn("Index overflow in MDL7 material list [#2]"); |
| iMatIndex2 = iNumMaterials-1; |
| } |
| |
| // do a slow search in the list ... |
| iNum = 0; |
| bool bFound = false; |
| for (std::vector<MDL::IntMaterial_MDL7>::iterator i = avMats.begin();i != avMats.end();++i,++iNum){ |
| if ((*i).iOldMatIndices[0] == iMatIndex && (*i).iOldMatIndices[1] == iMatIndex2) { |
| // reuse this material |
| bFound = true; |
| break; |
| } |
| } |
| if (!bFound) { |
| // build a new material ... |
| MDL::IntMaterial_MDL7 sHelper; |
| sHelper.pcMat = new aiMaterial(); |
| sHelper.iOldMatIndices[0] = iMatIndex; |
| sHelper.iOldMatIndices[1] = iMatIndex2; |
| JoinSkins_3DGS_MDL7(splitGroupData.shared.pcMats[iMatIndex], |
| splitGroupData.shared.pcMats[iMatIndex2],sHelper.pcMat); |
| |
| // and add it to the list |
| avMats.push_back(sHelper); |
| iNum = (unsigned int)avMats.size()-1; |
| } |
| // adjust the size of the file array |
| if (iNum == aiTempSplit.size()) { |
| aiTempSplit.push_back(new std::vector<unsigned int>()); |
| } |
| } |
| aiTempSplit[iNum]->push_back(iFace); |
| } |
| |
| // now add the newly created materials to the old list |
| if (0 == groupInfo.iIndex) { |
| splitGroupData.shared.pcMats.resize(avMats.size()); |
| for (unsigned int o = 0; o < avMats.size();++o) |
| splitGroupData.shared.pcMats[o] = avMats[o].pcMat; |
| } |
| else { |
| // This might result in redundant materials ... |
| splitGroupData.shared.pcMats.resize(iNumMaterials + avMats.size()); |
| for (unsigned int o = iNumMaterials; o < avMats.size();++o) |
| splitGroupData.shared.pcMats[o] = avMats[o].pcMat; |
| } |
| |
| // and build the final face-to-material array |
| splitGroupData.aiSplit = new std::vector<unsigned int>*[aiTempSplit.size()]; |
| for (unsigned int m = 0; m < iNumMaterials;++m) |
| splitGroupData.aiSplit[m] = aiTempSplit[m]; |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Read a MDL7 file |
| void MDLImporter::InternReadFile_3DGS_MDL7( ) |
| { |
| ai_assert(NULL != pScene); |
| |
| MDL::IntSharedData_MDL7 sharedData; |
| |
| // current cursor position in the file |
| BE_NCONST MDL::Header_MDL7 *pcHeader = (BE_NCONST MDL::Header_MDL7*)this->mBuffer; |
| const unsigned char* szCurrent = (const unsigned char*)(pcHeader+1); |
| |
| AI_SWAP4(pcHeader->version); |
| AI_SWAP4(pcHeader->bones_num); |
| AI_SWAP4(pcHeader->groups_num); |
| AI_SWAP4(pcHeader->data_size); |
| AI_SWAP4(pcHeader->entlump_size); |
| AI_SWAP4(pcHeader->medlump_size); |
| AI_SWAP2(pcHeader->bone_stc_size); |
| AI_SWAP2(pcHeader->skin_stc_size); |
| AI_SWAP2(pcHeader->colorvalue_stc_size); |
| AI_SWAP2(pcHeader->material_stc_size); |
| AI_SWAP2(pcHeader->skinpoint_stc_size); |
| AI_SWAP2(pcHeader->triangle_stc_size); |
| AI_SWAP2(pcHeader->mainvertex_stc_size); |
| AI_SWAP2(pcHeader->framevertex_stc_size); |
| AI_SWAP2(pcHeader->bonetrans_stc_size); |
| AI_SWAP2(pcHeader->frame_stc_size); |
| |
| // validate the header of the file. There are some structure |
| // sizes that are expected by the loader to be constant |
| this->ValidateHeader_3DGS_MDL7(pcHeader); |
| |
| // load all bones (they are shared by all groups, so |
| // we'll need to add them to all groups/meshes later) |
| // apcBonesOut is a list of all bones or NULL if they could not been loaded |
| szCurrent += pcHeader->bones_num * pcHeader->bone_stc_size; |
| sharedData.apcOutBones = this->LoadBones_3DGS_MDL7(); |
| |
| // vector to held all created meshes |
| std::vector<aiMesh*>* avOutList; |
| |
| // 3 meshes per group - that should be OK for most models |
| avOutList = new std::vector<aiMesh*>[pcHeader->groups_num]; |
| for (uint32_t i = 0; i < pcHeader->groups_num;++i) |
| avOutList[i].reserve(3); |
| |
| // buffer to held the names of all groups in the file |
| const size_t buffersize( AI_MDL7_MAX_GROUPNAMESIZE*pcHeader->groups_num ); |
| char* aszGroupNameBuffer = new char[ buffersize ]; |
| |
| // read all groups |
| for (unsigned int iGroup = 0; iGroup < (unsigned int)pcHeader->groups_num;++iGroup) { |
| MDL::IntGroupInfo_MDL7 groupInfo((BE_NCONST MDL::Group_MDL7*)szCurrent,iGroup); |
| szCurrent = (const unsigned char*)(groupInfo.pcGroup+1); |
| |
| VALIDATE_FILE_SIZE(szCurrent); |
| |
| AI_SWAP4(groupInfo.pcGroup->groupdata_size); |
| AI_SWAP4(groupInfo.pcGroup->numskins); |
| AI_SWAP4(groupInfo.pcGroup->num_stpts); |
| AI_SWAP4(groupInfo.pcGroup->numtris); |
| AI_SWAP4(groupInfo.pcGroup->numverts); |
| AI_SWAP4(groupInfo.pcGroup->numframes); |
| |
| if (1 != groupInfo.pcGroup->typ) { |
| // Not a triangle-based mesh |
| DefaultLogger::get()->warn("[3DGS MDL7] Not a triangle mesh group. Continuing happily"); |
| } |
| |
| // store the name of the group |
| const unsigned int ofs = iGroup*AI_MDL7_MAX_GROUPNAMESIZE; |
| ::memcpy(&aszGroupNameBuffer[ofs], |
| groupInfo.pcGroup->name,AI_MDL7_MAX_GROUPNAMESIZE); |
| |
| // make sure '\0' is at the end |
| aszGroupNameBuffer[ofs+AI_MDL7_MAX_GROUPNAMESIZE-1] = '\0'; |
| |
| // read all skins |
| sharedData.pcMats.reserve(sharedData.pcMats.size() + groupInfo.pcGroup->numskins); |
| sharedData.abNeedMaterials.resize(sharedData.abNeedMaterials.size() + |
| groupInfo.pcGroup->numskins,false); |
| |
| for (unsigned int iSkin = 0; iSkin < (unsigned int)groupInfo.pcGroup->numskins;++iSkin) { |
| ParseSkinLump_3DGS_MDL7(szCurrent,&szCurrent,sharedData.pcMats); |
| } |
| // if we have absolutely no skin loaded we need to generate a default material |
| if (sharedData.pcMats.empty()) { |
| const int iMode = (int)aiShadingMode_Gouraud; |
| sharedData.pcMats.push_back(new aiMaterial()); |
| aiMaterial* pcHelper = (aiMaterial*)sharedData.pcMats[0]; |
| pcHelper->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL); |
| |
| aiColor3D clr; |
| clr.b = clr.g = clr.r = 0.6f; |
| pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_DIFFUSE); |
| pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_SPECULAR); |
| |
| clr.b = clr.g = clr.r = 0.05f; |
| pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_AMBIENT); |
| |
| aiString szName; |
| szName.Set(AI_DEFAULT_MATERIAL_NAME); |
| pcHelper->AddProperty(&szName,AI_MATKEY_NAME); |
| |
| sharedData.abNeedMaterials.resize(1,false); |
| } |
| |
| // now get a pointer to all texture coords in the group |
| groupInfo.pcGroupUVs = (BE_NCONST MDL::TexCoord_MDL7*)szCurrent; |
| for(int i = 0; i < groupInfo.pcGroup->num_stpts; ++i){ |
| AI_SWAP4(groupInfo.pcGroupUVs[i].u); |
| AI_SWAP4(groupInfo.pcGroupUVs[i].v); |
| } |
| szCurrent += pcHeader->skinpoint_stc_size * groupInfo.pcGroup->num_stpts; |
| |
| // now get a pointer to all triangle in the group |
| groupInfo.pcGroupTris = (Triangle_MDL7*)szCurrent; |
| szCurrent += pcHeader->triangle_stc_size * groupInfo.pcGroup->numtris; |
| |
| // now get a pointer to all vertices in the group |
| groupInfo.pcGroupVerts = (BE_NCONST MDL::Vertex_MDL7*)szCurrent; |
| for(int i = 0; i < groupInfo.pcGroup->numverts; ++i){ |
| AI_SWAP4(groupInfo.pcGroupVerts[i].x); |
| AI_SWAP4(groupInfo.pcGroupVerts[i].y); |
| AI_SWAP4(groupInfo.pcGroupVerts[i].z); |
| |
| AI_SWAP2(groupInfo.pcGroupVerts[i].vertindex); |
| //We can not swap the normal information now as we don't know which of the two kinds it is |
| } |
| szCurrent += pcHeader->mainvertex_stc_size * groupInfo.pcGroup->numverts; |
| VALIDATE_FILE_SIZE(szCurrent); |
| |
| MDL::IntSplitGroupData_MDL7 splitGroupData(sharedData,avOutList[iGroup]); |
| MDL::IntGroupData_MDL7 groupData; |
| if (groupInfo.pcGroup->numtris && groupInfo.pcGroup->numverts) |
| { |
| // build output vectors |
| const unsigned int iNumVertices = groupInfo.pcGroup->numtris*3; |
| groupData.vPositions.resize(iNumVertices); |
| groupData.vNormals.resize(iNumVertices); |
| |
| if (sharedData.apcOutBones)groupData.aiBones.resize(iNumVertices,UINT_MAX); |
| |
| // it is also possible that there are 0 UV coordinate sets |
| if (groupInfo.pcGroup->num_stpts){ |
| groupData.vTextureCoords1.resize(iNumVertices,aiVector3D()); |
| |
| // check whether the triangle data structure is large enough |
| // to contain a second UV coodinate set |
| if (pcHeader->triangle_stc_size >= AI_MDL7_TRIANGLE_STD_SIZE_TWO_UV) { |
| groupData.vTextureCoords2.resize(iNumVertices,aiVector3D()); |
| groupData.bNeed2UV = true; |
| } |
| } |
| groupData.pcFaces = new MDL::IntFace_MDL7[groupInfo.pcGroup->numtris]; |
| |
| // read all faces into the preallocated arrays |
| ReadFaces_3DGS_MDL7(groupInfo, groupData); |
| |
| // sort by materials |
| SortByMaterials_3DGS_MDL7(groupInfo, groupData, |
| splitGroupData); |
| |
| for (unsigned int qq = 0; qq < sharedData.pcMats.size();++qq) { |
| if (!splitGroupData.aiSplit[qq]->empty()) |
| sharedData.abNeedMaterials[qq] = true; |
| } |
| } |
| else DefaultLogger::get()->warn("[3DGS MDL7] Mesh group consists of 0 " |
| "vertices or faces. It will be skipped."); |
| |
| // process all frames and generate output meshes |
| ProcessFrames_3DGS_MDL7(groupInfo,groupData, sharedData,szCurrent,&szCurrent); |
| GenerateOutputMeshes_3DGS_MDL7(groupData,splitGroupData); |
| } |
| |
| // generate a nodegraph and subnodes for each group |
| pScene->mRootNode = new aiNode(); |
| |
| // now we need to build a final mesh list |
| for (uint32_t i = 0; i < pcHeader->groups_num;++i) |
| pScene->mNumMeshes += (unsigned int)avOutList[i].size(); |
| |
| pScene->mMeshes = new aiMesh*[pScene->mNumMeshes]; { |
| unsigned int p = 0,q = 0; |
| for (uint32_t i = 0; i < pcHeader->groups_num;++i) { |
| for (unsigned int a = 0; a < avOutList[i].size();++a) { |
| pScene->mMeshes[p++] = avOutList[i][a]; |
| } |
| if (!avOutList[i].empty())++pScene->mRootNode->mNumChildren; |
| } |
| // we will later need an extra node to serve as parent for all bones |
| if (sharedData.apcOutBones)++pScene->mRootNode->mNumChildren; |
| this->pScene->mRootNode->mChildren = new aiNode*[pScene->mRootNode->mNumChildren]; |
| p = 0; |
| for (uint32_t i = 0; i < pcHeader->groups_num;++i) { |
| if (avOutList[i].empty())continue; |
| |
| aiNode* const pcNode = pScene->mRootNode->mChildren[p] = new aiNode(); |
| pcNode->mNumMeshes = (unsigned int)avOutList[i].size(); |
| pcNode->mMeshes = new unsigned int[pcNode->mNumMeshes]; |
| pcNode->mParent = this->pScene->mRootNode; |
| for (unsigned int a = 0; a < pcNode->mNumMeshes;++a) |
| pcNode->mMeshes[a] = q + a; |
| q += (unsigned int)avOutList[i].size(); |
| |
| // setup the name of the node |
| char* const szBuffer = &aszGroupNameBuffer[i*AI_MDL7_MAX_GROUPNAMESIZE]; |
| if ('\0' == *szBuffer) { |
| const size_t maxSize(buffersize - (i*AI_MDL7_MAX_GROUPNAMESIZE)); |
| pcNode->mName.length = ai_snprintf(szBuffer, maxSize, "Group_%u", p); |
| } else { |
| pcNode->mName.length = ::strlen(szBuffer); |
| } |
| ::strncpy(pcNode->mName.data,szBuffer,MAXLEN-1); |
| ++p; |
| } |
| } |
| |
| // if there is only one root node with a single child we can optimize it a bit ... |
| if (1 == pScene->mRootNode->mNumChildren && !sharedData.apcOutBones) { |
| aiNode* pcOldRoot = this->pScene->mRootNode; |
| pScene->mRootNode = pcOldRoot->mChildren[0]; |
| pcOldRoot->mChildren[0] = NULL; |
| delete pcOldRoot; |
| pScene->mRootNode->mParent = NULL; |
| } |
| else pScene->mRootNode->mName.Set("<mesh_root>"); |
| |
| delete[] avOutList; |
| delete[] aszGroupNameBuffer; |
| AI_DEBUG_INVALIDATE_PTR(avOutList); |
| AI_DEBUG_INVALIDATE_PTR(aszGroupNameBuffer); |
| |
| // build a final material list. |
| CopyMaterials_3DGS_MDL7(sharedData); |
| HandleMaterialReferences_3DGS_MDL7(); |
| |
| // generate output bone animations and add all bones to the scenegraph |
| if (sharedData.apcOutBones) { |
| // this step adds empty dummy bones to the nodegraph |
| // insert another dummy node to avoid name conflicts |
| aiNode* const pc = pScene->mRootNode->mChildren[pScene->mRootNode->mNumChildren-1] = new aiNode(); |
| |
| pc->mName.Set("<skeleton_root>"); |
| |
| // add bones to the nodegraph |
| AddBonesToNodeGraph_3DGS_MDL7((const Assimp::MDL::IntBone_MDL7 **) |
| sharedData.apcOutBones,pc,0xffff); |
| |
| // this steps build a valid output animation |
| BuildOutputAnims_3DGS_MDL7((const Assimp::MDL::IntBone_MDL7 **) |
| sharedData.apcOutBones); |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Copy materials |
| void MDLImporter::CopyMaterials_3DGS_MDL7(MDL::IntSharedData_MDL7 &shared) |
| { |
| pScene->mNumMaterials = (unsigned int)shared.pcMats.size(); |
| pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials]; |
| for (unsigned int i = 0; i < pScene->mNumMaterials;++i) |
| pScene->mMaterials[i] = shared.pcMats[i]; |
| } |
| |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Process material references |
| void MDLImporter::HandleMaterialReferences_3DGS_MDL7() |
| { |
| // search for referrer materials |
| for (unsigned int i = 0; i < pScene->mNumMaterials;++i) { |
| int iIndex = 0; |
| if (AI_SUCCESS == aiGetMaterialInteger(pScene->mMaterials[i],AI_MDL7_REFERRER_MATERIAL, &iIndex) ) { |
| for (unsigned int a = 0; a < pScene->mNumMeshes;++a) { |
| aiMesh* const pcMesh = pScene->mMeshes[a]; |
| if (i == pcMesh->mMaterialIndex) { |
| pcMesh->mMaterialIndex = iIndex; |
| } |
| } |
| // collapse the rest of the array |
| delete pScene->mMaterials[i]; |
| for (unsigned int pp = i; pp < pScene->mNumMaterials-1;++pp) { |
| |
| pScene->mMaterials[pp] = pScene->mMaterials[pp+1]; |
| for (unsigned int a = 0; a < pScene->mNumMeshes;++a) { |
| aiMesh* const pcMesh = pScene->mMeshes[a]; |
| if (pcMesh->mMaterialIndex > i)--pcMesh->mMaterialIndex; |
| } |
| } |
| --pScene->mNumMaterials; |
| } |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Read bone transformation keys |
| void MDLImporter::ParseBoneTrafoKeys_3DGS_MDL7( |
| const MDL::IntGroupInfo_MDL7& groupInfo, |
| IntFrameInfo_MDL7& frame, |
| MDL::IntSharedData_MDL7& shared) |
| { |
| const MDL::Header_MDL7* const pcHeader = (const MDL::Header_MDL7*)this->mBuffer; |
| |
| // only the first group contains bone animation keys |
| if (frame.pcFrame->transmatrix_count) { |
| if (!groupInfo.iIndex) { |
| // skip all frames vertices. We can't support them |
| const MDL::BoneTransform_MDL7* pcBoneTransforms = (const MDL::BoneTransform_MDL7*) |
| (((const char*)frame.pcFrame) + pcHeader->frame_stc_size + |
| frame.pcFrame->vertices_count * pcHeader->framevertex_stc_size); |
| |
| // read all transformation matrices |
| for (unsigned int iTrafo = 0; iTrafo < frame.pcFrame->transmatrix_count;++iTrafo) { |
| if(pcBoneTransforms->bone_index >= pcHeader->bones_num) { |
| DefaultLogger::get()->warn("Index overflow in frame area. " |
| "Unable to parse this bone transformation"); |
| } |
| else { |
| AddAnimationBoneTrafoKey_3DGS_MDL7(frame.iIndex, |
| pcBoneTransforms,shared.apcOutBones); |
| } |
| pcBoneTransforms = (const MDL::BoneTransform_MDL7*)( |
| (const char*)pcBoneTransforms + pcHeader->bonetrans_stc_size); |
| } |
| } |
| else { |
| DefaultLogger::get()->warn("Ignoring animation keyframes in groups != 0"); |
| } |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Attach bones to the output nodegraph |
| void MDLImporter::AddBonesToNodeGraph_3DGS_MDL7(const MDL::IntBone_MDL7** apcBones, |
| aiNode* pcParent,uint16_t iParentIndex) |
| { |
| ai_assert(NULL != apcBones && NULL != pcParent); |
| |
| // get a pointer to the header ... |
| const MDL::Header_MDL7* const pcHeader = (const MDL::Header_MDL7*)this->mBuffer; |
| |
| const MDL::IntBone_MDL7** apcBones2 = apcBones; |
| for (uint32_t i = 0; i < pcHeader->bones_num;++i) { |
| |
| const MDL::IntBone_MDL7* const pcBone = *apcBones2++; |
| if (pcBone->iParent == iParentIndex) { |
| ++pcParent->mNumChildren; |
| } |
| } |
| pcParent->mChildren = new aiNode*[pcParent->mNumChildren]; |
| unsigned int qq = 0; |
| for (uint32_t i = 0; i < pcHeader->bones_num;++i) { |
| |
| const MDL::IntBone_MDL7* const pcBone = *apcBones++; |
| if (pcBone->iParent != iParentIndex)continue; |
| |
| aiNode* pcNode = pcParent->mChildren[qq++] = new aiNode(); |
| pcNode->mName = aiString( pcBone->mName ); |
| |
| AddBonesToNodeGraph_3DGS_MDL7(apcBones,pcNode,(uint16_t)i); |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Build output animations |
| void MDLImporter::BuildOutputAnims_3DGS_MDL7( |
| const MDL::IntBone_MDL7** apcBonesOut) |
| { |
| ai_assert(NULL != apcBonesOut); |
| const MDL::Header_MDL7* const pcHeader = (const MDL::Header_MDL7*)mBuffer; |
| |
| // one animation ... |
| aiAnimation* pcAnim = new aiAnimation(); |
| for (uint32_t i = 0; i < pcHeader->bones_num;++i) { |
| if (!apcBonesOut[i]->pkeyPositions.empty()) { |
| |
| // get the last frame ... (needn't be equal to pcHeader->frames_num) |
| for (size_t qq = 0; qq < apcBonesOut[i]->pkeyPositions.size();++qq) { |
| pcAnim->mDuration = std::max(pcAnim->mDuration, (double) |
| apcBonesOut[i]->pkeyPositions[qq].mTime); |
| } |
| ++pcAnim->mNumChannels; |
| } |
| } |
| if (pcAnim->mDuration) { |
| pcAnim->mChannels = new aiNodeAnim*[pcAnim->mNumChannels]; |
| |
| unsigned int iCnt = 0; |
| for (uint32_t i = 0; i < pcHeader->bones_num;++i) { |
| if (!apcBonesOut[i]->pkeyPositions.empty()) { |
| const MDL::IntBone_MDL7* const intBone = apcBonesOut[i]; |
| |
| aiNodeAnim* const pcNodeAnim = pcAnim->mChannels[iCnt++] = new aiNodeAnim(); |
| pcNodeAnim->mNodeName = aiString( intBone->mName ); |
| |
| // allocate enough storage for all keys |
| pcNodeAnim->mNumPositionKeys = (unsigned int)intBone->pkeyPositions.size(); |
| pcNodeAnim->mNumScalingKeys = (unsigned int)intBone->pkeyPositions.size(); |
| pcNodeAnim->mNumRotationKeys = (unsigned int)intBone->pkeyPositions.size(); |
| |
| pcNodeAnim->mPositionKeys = new aiVectorKey[pcNodeAnim->mNumPositionKeys]; |
| pcNodeAnim->mScalingKeys = new aiVectorKey[pcNodeAnim->mNumPositionKeys]; |
| pcNodeAnim->mRotationKeys = new aiQuatKey[pcNodeAnim->mNumPositionKeys]; |
| |
| // copy all keys |
| for (unsigned int qq = 0; qq < pcNodeAnim->mNumPositionKeys;++qq) { |
| pcNodeAnim->mPositionKeys[qq] = intBone->pkeyPositions[qq]; |
| pcNodeAnim->mScalingKeys[qq] = intBone->pkeyScalings[qq]; |
| pcNodeAnim->mRotationKeys[qq] = intBone->pkeyRotations[qq]; |
| } |
| } |
| } |
| |
| // store the output animation |
| pScene->mNumAnimations = 1; |
| pScene->mAnimations = new aiAnimation*[1]; |
| pScene->mAnimations[0] = pcAnim; |
| } |
| else delete pcAnim; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| void MDLImporter::AddAnimationBoneTrafoKey_3DGS_MDL7(unsigned int iTrafo, |
| const MDL::BoneTransform_MDL7* pcBoneTransforms, |
| MDL::IntBone_MDL7** apcBonesOut) |
| { |
| ai_assert(NULL != pcBoneTransforms); |
| ai_assert(NULL != apcBonesOut); |
| |
| // first .. get the transformation matrix |
| aiMatrix4x4 mTransform; |
| mTransform.a1 = pcBoneTransforms->m[0]; |
| mTransform.b1 = pcBoneTransforms->m[1]; |
| mTransform.c1 = pcBoneTransforms->m[2]; |
| mTransform.d1 = pcBoneTransforms->m[3]; |
| |
| mTransform.a2 = pcBoneTransforms->m[4]; |
| mTransform.b2 = pcBoneTransforms->m[5]; |
| mTransform.c2 = pcBoneTransforms->m[6]; |
| mTransform.d2 = pcBoneTransforms->m[7]; |
| |
| mTransform.a3 = pcBoneTransforms->m[8]; |
| mTransform.b3 = pcBoneTransforms->m[9]; |
| mTransform.c3 = pcBoneTransforms->m[10]; |
| mTransform.d3 = pcBoneTransforms->m[11]; |
| |
| // now decompose the transformation matrix into separate |
| // scaling, rotation and translation |
| aiVectorKey vScaling,vPosition; |
| aiQuatKey qRotation; |
| |
| // FIXME: Decompose will assert in debug builds if the matrix is invalid ... |
| mTransform.Decompose(vScaling.mValue,qRotation.mValue,vPosition.mValue); |
| |
| // now generate keys |
| vScaling.mTime = qRotation.mTime = vPosition.mTime = (double)iTrafo; |
| |
| // add the keys to the bone |
| MDL::IntBone_MDL7* const pcBoneOut = apcBonesOut[pcBoneTransforms->bone_index]; |
| pcBoneOut->pkeyPositions.push_back ( vPosition ); |
| pcBoneOut->pkeyScalings.push_back ( vScaling ); |
| pcBoneOut->pkeyRotations.push_back ( qRotation ); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Construct output meshes |
| void MDLImporter::GenerateOutputMeshes_3DGS_MDL7( |
| MDL::IntGroupData_MDL7& groupData, |
| MDL::IntSplitGroupData_MDL7& splitGroupData) |
| { |
| const MDL::IntSharedData_MDL7& shared = splitGroupData.shared; |
| |
| // get a pointer to the header ... |
| const MDL::Header_MDL7* const pcHeader = (const MDL::Header_MDL7*)this->mBuffer; |
| const unsigned int iNumOutBones = pcHeader->bones_num; |
| |
| for (std::vector<aiMaterial*>::size_type i = 0; i < shared.pcMats.size();++i) { |
| if (!splitGroupData.aiSplit[i]->empty()) { |
| |
| // allocate the output mesh |
| aiMesh* pcMesh = new aiMesh(); |
| |
| pcMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; |
| pcMesh->mMaterialIndex = (unsigned int)i; |
| |
| // allocate output storage |
| pcMesh->mNumFaces = (unsigned int)splitGroupData.aiSplit[i]->size(); |
| pcMesh->mFaces = new aiFace[pcMesh->mNumFaces]; |
| |
| pcMesh->mNumVertices = pcMesh->mNumFaces*3; |
| pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices]; |
| pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices]; |
| |
| if (!groupData.vTextureCoords1.empty()) { |
| pcMesh->mNumUVComponents[0] = 2; |
| pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices]; |
| if (!groupData.vTextureCoords2.empty()) { |
| pcMesh->mNumUVComponents[1] = 2; |
| pcMesh->mTextureCoords[1] = new aiVector3D[pcMesh->mNumVertices]; |
| } |
| } |
| |
| // iterate through all faces and build an unique set of vertices |
| unsigned int iCurrent = 0; |
| for (unsigned int iFace = 0; iFace < pcMesh->mNumFaces;++iFace) { |
| pcMesh->mFaces[iFace].mNumIndices = 3; |
| pcMesh->mFaces[iFace].mIndices = new unsigned int[3]; |
| |
| unsigned int iSrcFace = splitGroupData.aiSplit[i]->operator[](iFace); |
| const MDL::IntFace_MDL7& oldFace = groupData.pcFaces[iSrcFace]; |
| |
| // iterate through all face indices |
| for (unsigned int c = 0; c < 3;++c) { |
| const uint32_t iIndex = oldFace.mIndices[c]; |
| pcMesh->mVertices[iCurrent] = groupData.vPositions[iIndex]; |
| pcMesh->mNormals[iCurrent] = groupData.vNormals[iIndex]; |
| |
| if (!groupData.vTextureCoords1.empty()) { |
| |
| pcMesh->mTextureCoords[0][iCurrent] = groupData.vTextureCoords1[iIndex]; |
| if (!groupData.vTextureCoords2.empty()) { |
| pcMesh->mTextureCoords[1][iCurrent] = groupData.vTextureCoords2[iIndex]; |
| } |
| } |
| pcMesh->mFaces[iFace].mIndices[c] = iCurrent++; |
| } |
| } |
| |
| // if we have bones in the mesh we'll need to generate |
| // proper vertex weights for them |
| if (!groupData.aiBones.empty()) { |
| std::vector<std::vector<unsigned int> > aaiVWeightList; |
| aaiVWeightList.resize(iNumOutBones); |
| |
| int iCurrent = 0; |
| for (unsigned int iFace = 0; iFace < pcMesh->mNumFaces;++iFace) { |
| unsigned int iSrcFace = splitGroupData.aiSplit[i]->operator[](iFace); |
| const MDL::IntFace_MDL7& oldFace = groupData.pcFaces[iSrcFace]; |
| |
| // iterate through all face indices |
| for (unsigned int c = 0; c < 3;++c) { |
| unsigned int iBone = groupData.aiBones[ oldFace.mIndices[c] ]; |
| if (UINT_MAX != iBone) { |
| if (iBone >= iNumOutBones) { |
| DefaultLogger::get()->error("Bone index overflow. " |
| "The bone index of a vertex exceeds the allowed range. "); |
| iBone = iNumOutBones-1; |
| } |
| aaiVWeightList[ iBone ].push_back ( iCurrent ); |
| } |
| ++iCurrent; |
| } |
| } |
| // now check which bones are required ... |
| for (std::vector<std::vector<unsigned int> >::const_iterator k = aaiVWeightList.begin();k != aaiVWeightList.end();++k) { |
| if (!(*k).empty()) { |
| ++pcMesh->mNumBones; |
| } |
| } |
| pcMesh->mBones = new aiBone*[pcMesh->mNumBones]; |
| iCurrent = 0; |
| for (std::vector<std::vector<unsigned int> >::const_iterator k = aaiVWeightList.begin();k!= aaiVWeightList.end();++k,++iCurrent) |
| { |
| if ((*k).empty()) |
| continue; |
| |
| // seems we'll need this node |
| aiBone* pcBone = pcMesh->mBones[ iCurrent ] = new aiBone(); |
| pcBone->mName = aiString(shared.apcOutBones[ iCurrent ]->mName); |
| pcBone->mOffsetMatrix = shared.apcOutBones[ iCurrent ]->mOffsetMatrix; |
| |
| // setup vertex weights |
| pcBone->mNumWeights = (unsigned int)(*k).size(); |
| pcBone->mWeights = new aiVertexWeight[pcBone->mNumWeights]; |
| |
| for (unsigned int weight = 0; weight < pcBone->mNumWeights;++weight) { |
| pcBone->mWeights[weight].mVertexId = (*k)[weight]; |
| pcBone->mWeights[weight].mWeight = 1.0f; |
| } |
| } |
| } |
| // add the mesh to the list of output meshes |
| splitGroupData.avOutList.push_back(pcMesh); |
| } |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Join to materials |
| void MDLImporter::JoinSkins_3DGS_MDL7( |
| aiMaterial* pcMat1, |
| aiMaterial* pcMat2, |
| aiMaterial* pcMatOut) |
| { |
| ai_assert(NULL != pcMat1 && NULL != pcMat2 && NULL != pcMatOut); |
| |
| // first create a full copy of the first skin property set |
| // and assign it to the output material |
| aiMaterial::CopyPropertyList(pcMatOut,pcMat1); |
| |
| int iVal = 0; |
| pcMatOut->AddProperty<int>(&iVal,1,AI_MATKEY_UVWSRC_DIFFUSE(0)); |
| |
| // then extract the diffuse texture from the second skin, |
| // setup 1 as UV source and we have it |
| aiString sString; |
| if(AI_SUCCESS == aiGetMaterialString ( pcMat2, AI_MATKEY_TEXTURE_DIFFUSE(0),&sString )) { |
| iVal = 1; |
| pcMatOut->AddProperty<int>(&iVal,1,AI_MATKEY_UVWSRC_DIFFUSE(1)); |
| pcMatOut->AddProperty(&sString,AI_MATKEY_TEXTURE_DIFFUSE(1)); |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Read a half-life 2 MDL |
| void MDLImporter::InternReadFile_HL2( ) |
| { |
| //const MDL::Header_HL2* pcHeader = (const MDL::Header_HL2*)this->mBuffer; |
| throw DeadlyImportError("HL2 MDLs are not implemented"); |
| } |
| |
| #endif // !! ASSIMP_BUILD_NO_MDL_IMPORTER |