diff --git a/code/3DSConverter.cpp b/code/3DSConverter.cpp index ad5ce9346..ad9eafcf0 100644 --- a/code/3DSConverter.cpp +++ b/code/3DSConverter.cpp @@ -40,11 +40,15 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** @file Implementation of the 3ds importer class */ + +// internal headers #include "3DSLoader.h" #include "MaterialSystem.h" #include "TextureTransform.h" #include "StringComparison.h" +#include "qnan.h" +// public ASSIMP headers #include "../include/DefaultLogger.h" #include "../include/IOStream.h" #include "../include/IOSystem.h" @@ -52,7 +56,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "../include/aiScene.h" #include "../include/aiAssert.h" -#include using namespace Assimp; @@ -427,10 +430,12 @@ void Dot3DSImporter::ConvertMeshes(aiScene* pcOut) a != (*i).mFaceMaterials.end();++a,++iNum) { // check range - if ((*a) >= this->mScene->mMaterials.size()) + if ((*a) >= this->mScene->mMaterials.size()) { - // use the last material instead - aiSplit[this->mScene->mMaterials.size()-1].push_back(iNum); + DefaultLogger::get()->error("Face material index is out of range"); + + // use the last material instead + aiSplit[this->mScene->mMaterials.size()-1].push_back(iNum); } else aiSplit[*a].push_back(iNum); } @@ -450,15 +455,15 @@ void Dot3DSImporter::ConvertMeshes(aiScene* pcOut) p_pcOut->mColors[0] = (aiColor4D*)new std::string((*i).mName); avOutMeshes.push_back(p_pcOut); - +// (code for keyframe animation. however, this is currently not supported by Assimp) +#if 0 if (bFirst) { p_pcOut->mColors[1] = (aiColor4D*)new aiMatrix4x4(); - *((aiMatrix4x4*)p_pcOut->mColors[1]) = (*i).mMat; bFirst = false; } - +#endif // convert vertices p_pcOut->mNumVertices = (unsigned int)aiSplit[p].size()*3; @@ -519,16 +524,6 @@ void Dot3DSImporter::ConvertMeshes(aiScene* pcOut) // apply texture coordinate scalings TextureTransform::BakeScaleNOffset ( p_pcOut, &this->mScene->mMaterials[ p_pcOut->mMaterialIndex] ); - - // setup bitflags to indicate which texture coordinate - // channels are used - p_pcOut->mNumUVComponents[0] = 2; - if (p_pcOut->HasTextureCoords(1)) - p_pcOut->mNumUVComponents[1] = 2; - if (p_pcOut->HasTextureCoords(2)) - p_pcOut->mNumUVComponents[2] = 2; - if (p_pcOut->HasTextureCoords(3)) - p_pcOut->mNumUVComponents[3] = 2; } } } @@ -579,6 +574,8 @@ void Dot3DSImporter::AddNodeToGraph(aiScene* pcSOut,aiNode* pcOut,Dot3DS::Node* { const unsigned int iIndex = iArray[i]; +// (code for keyframe animation. however, this is currently not supported by Assimp) +#if 0 if (NULL != pcSOut->mMeshes[iIndex]->mColors[1]) { pcOut->mTransformation = *((aiMatrix4x4*) @@ -587,7 +584,7 @@ void Dot3DSImporter::AddNodeToGraph(aiScene* pcSOut,aiNode* pcOut,Dot3DS::Node* delete (aiMatrix4x4*)pcSOut->mMeshes[iIndex]->mColors[1]; pcSOut->mMeshes[iIndex]->mColors[1] = NULL; } - +#endif pcOut->mMeshes[i] = iIndex; } @@ -681,11 +678,17 @@ void Dot3DSImporter::GenerateNodeGraph(aiScene* pcOut) pcNode->mMeshes[0] = i; pcNode->mNumMeshes = 1; - std::string s; - std::stringstream ss(s); - ss << "UNNAMED[" << i << + "]"; - - pcNode->mName.Set(s); + char szBuffer[128]; + int iLen; +#if _MSC_VER >= 1400 + iLen = sprintf_s(szBuffer,"UNNAMED_%i",i); +#else + iLen = sprintf(szBuffer,"UNNAMED_%i",i); +#endif + ai_assert(0 < iLen); + ::memcpy(pcNode->mName.data,szBuffer,iLen); + pcNode->mName.data[iLen] = '\0'; + pcNode->mName.length = iLen; // add the new child to the parent node pcOut->mRootNode->mChildren[i] = pcNode; diff --git a/code/3DSHelper.h b/code/3DSHelper.h index b086bb384..38d287b99 100644 --- a/code/3DSHelper.h +++ b/code/3DSHelper.h @@ -93,10 +93,22 @@ public: //! From AutoDesk 3ds SDK typedef enum { + // translated to gouraud shading with wireframe active Wire = 0, + + // if this material is set, no vertex normals will + // be calculated for the model. Face normals + gouraud Flat = 1, + + // standard gouraud shading Gouraud = 2, + + // phong shading Phong = 3, + + // cooktorrance or anistropic phong shading ... + // the exact meaning is unknown, if you know it + // feel free to tell me ;-) Metal = 4, // required by the ASE loader @@ -127,7 +139,8 @@ public: CHUNK_PERCENTF = 0x0031, // float4 percentage // ************************************************************** - // Unknown and ignored + // Unknown and ignored. Possibly a chunk used by PROJ ( + // Discreet 3DS max Project File)? CHUNK_PRJ = 0xC23D, // Unknown. Possibly a reference to an external .mli file? @@ -387,9 +400,10 @@ struct Material mTwoSided (false) { static int iCnt = 0; - std::stringstream ss; - ss << "$$_UNNAMED_" << iCnt++ << "_$$"; - ss >> mName; + + char szTemp[128]; + sprintf(szTemp,"$$_UNNAMED_%i_$$",iCnt++); + mName = szTemp; } //! Name of the material @@ -442,9 +456,10 @@ struct Mesh Mesh() { static int iCnt = 0; - std::stringstream ss; - ss << "$$_UNNAMED_" << iCnt++ << "_$$"; - ss >> mName; + + char szTemp[128]; + sprintf(szTemp,"$$_UNNAMED_%i_$$",iCnt++); + mName = szTemp; } //! Name of the mesh @@ -481,9 +496,10 @@ struct Node { static int iCnt = 0; - std::stringstream ss; - ss << "$$_UNNAMED_" << iCnt++ << "_$$"; - ss >> mName; + + char szTemp[128]; + sprintf(szTemp,"$$_UNNAMED_%i_$$",iCnt++); + mName = szTemp; mHierarchyPos = 0; mHierarchyIndex = 0; @@ -538,26 +554,6 @@ struct Scene Node* pcRootNode; }; -// --------------------------------------------------------------------------- -inline bool is_qnan(float p_fIn) -{ - // NOTE: Comparison against qnan is generally problematic - // because qnan == qnan is false AFAIK - union FTOINT - { - float fFloat; - int32_t iInt; - } one, two; - one.fFloat = std::numeric_limits::quiet_NaN(); - two.fFloat = p_fIn; - - return (one.iInt == two.iInt); -} -// --------------------------------------------------------------------------- -inline bool is_not_qnan(float p_fIn) -{ - return !is_qnan(p_fIn); -} } // end of namespace Dot3DS } // end of namespace Assimp diff --git a/code/3DSLoader.cpp b/code/3DSLoader.cpp index ffa942adf..38568c8b7 100644 --- a/code/3DSLoader.cpp +++ b/code/3DSLoader.cpp @@ -40,11 +40,15 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** @file Implementation of the 3ds importer class */ + +// internal headers #include "3DSLoader.h" #include "MaterialSystem.h" #include "TextureTransform.h" #include "StringComparison.h" +#include "qnan.h" +// public ASSIMP headers #include "../include/DefaultLogger.h" #include "../include/IOStream.h" #include "../include/IOSystem.h" @@ -52,14 +56,17 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "../include/aiScene.h" #include "../include/aiAssert.h" +// boost headers #include using namespace Assimp; -#define ASSIMP_3DS_WARN_CHUNK_OVERFLOW_MSG \ - "WARNING: Size of chunk data plus size of " \ - "subordinate chunks is larger than the size " \ - "specified in the higher-level chunk header." \ +#if (!defined ASSIMP_3DS_WARN_CHUNK_OVERFLOW_MSG) +# define ASSIMP_3DS_WARN_CHUNK_OVERFLOW_MSG \ + "WARNING: Size of chunk data plus size of " \ + "subordinate chunks is larger than the size " \ + "specified in the top-level chunk header." +#endif // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer @@ -92,22 +99,6 @@ bool Dot3DSImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) co return false; } // ------------------------------------------------------------------------------------------------ -// recursively delete a given node -void DeleteNodeRecursively (aiNode* p_piNode) -{ - if (!p_piNode)return; - - if (p_piNode->mChildren) - { - for (unsigned int i = 0 ; i < p_piNode->mNumChildren;++i) - { - DeleteNodeRecursively(p_piNode->mChildren[i]); - } - } - delete p_piNode; - return; -} -// ------------------------------------------------------------------------------------------------ // Imports the given file into the given scene structure. void Dot3DSImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) @@ -125,7 +116,7 @@ void Dot3DSImporter::InternReadFile( size_t fileSize = file->FileSize(); if( fileSize < 16) { - throw new ImportErrorException( ".3ds File is too small."); + throw new ImportErrorException( "3DS File is too small."); } this->mScene = new Dot3DS::Scene(); @@ -780,8 +771,9 @@ void Dot3DSImporter::ParseFaceChunk(int* piRemaining) case Dot3DSFile::CHUNK_FACEMAT: // at fist an asciiz with the material name - while (*sz++ != '\0') + while (*sz++) { + // make sure we don't run over the end of the chunk if (sz > pcCurNext-1)break; } @@ -800,7 +792,7 @@ void Dot3DSImporter::ParseFaceChunk(int* piRemaining) break; } } - if (iIndex == 0xFFFFFFFF) + if (0xFFFFFFFF == iIndex) { // this material is not known. Ignore this. We will later // assign the default material to all faces using *this* @@ -818,13 +810,14 @@ void Dot3DSImporter::ParseFaceChunk(int* piRemaining) // check range if (iTemp >= mMesh.mFaceMaterials.size()) - { + { + DefaultLogger::get()->error("Invalid face index in face material list"); mMesh.mFaceMaterials[mMesh.mFaceMaterials.size()-1] = iIndex; - } + } else - { + { mMesh.mFaceMaterials[iTemp] = iIndex; - } + } this->mCurrent += sizeof(uint16_t); } @@ -959,8 +952,7 @@ void Dot3DSImporter::ParseMeshChunk(int* piRemaining) } break; -#if (defined _DEBUG) - +#if 0 case Dot3DSFile::CHUNK_TXTINFO: // for debugging purposes. Read two bytes to determine the mapping type diff --git a/code/ASELoader.cpp b/code/ASELoader.cpp index bdb2ce611..80d019623 100644 --- a/code/ASELoader.cpp +++ b/code/ASELoader.cpp @@ -40,13 +40,19 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** @file Implementation of the ASE importer class */ + +// internal headers #include "ASELoader.h" #include "3DSSpatialSort.h" #include "MaterialSystem.h" #include "StringComparison.h" #include "TextureTransform.h" -#include "fast_atof.h" +// utilities +#include "fast_atof.h" +#include "qnan.h" + +// ASSIMP public headers #include "../include/IOStream.h" #include "../include/IOSystem.h" #include "../include/aiMesh.h" diff --git a/code/BaseImporter.h b/code/BaseImporter.h index 48194ef98..1d0be37a4 100644 --- a/code/BaseImporter.h +++ b/code/BaseImporter.h @@ -111,7 +111,7 @@ public: // ------------------------------------------------------------------- /** Imports the given file and returns the imported data. * If the import succeeds, ownership of the data is transferred to - * the caller. If the import failes, NULL is returned. The function + * the caller. If the import fails, NULL is returned. The function * takes care that any partially constructed data is destroyed * beforehand. * @@ -154,7 +154,15 @@ protected: * an error. If it terminates normally, the data in aiScene is * expected to be correct. Override this function to implement the * actual importing. - * + *
+ * The output scene must meet the following conditions:
+ * - at least one mesh must be there
+ * - at least a root node must be there
+ * - at least one material must be there
+ * - there may be no meshes with 0 vertices or faces
+ * This won't be checked (except by the validation step), Assimp will + * crash if one of the conditions is not met! + * * @param pFile Path of the file to be imported. * @param pScene The scene object to hold the imported data. * NULL is not a valid parameter. diff --git a/code/DefaultIOStream.cpp b/code/DefaultIOStream.cpp index ec395e862..dc4057dd7 100644 --- a/code/DefaultIOStream.cpp +++ b/code/DefaultIOStream.cpp @@ -53,7 +53,7 @@ DefaultIOStream::~DefaultIOStream() { if (this->mFile) { - fclose(this->mFile); + ::fclose(this->mFile); } } // --------------------------------------------------------------------------- @@ -61,20 +61,24 @@ size_t DefaultIOStream::Read(void* pvBuffer, size_t pSize, size_t pCount) { + ai_assert(NULL != pvBuffer && 0 != pSize && 0 != pCount); + if (!this->mFile) return 0; - return fread(pvBuffer, pSize, pCount, this->mFile); + return ::fread(pvBuffer, pSize, pCount, this->mFile); } // --------------------------------------------------------------------------- size_t DefaultIOStream::Write(const void* pvBuffer, size_t pSize, size_t pCount) { + ai_assert(NULL != pvBuffer && 0 != pSize && 0 != pCount); + if (!this->mFile)return 0; - fseek(mFile, 0, SEEK_SET); - return fwrite(pvBuffer, pSize, pCount, this->mFile); + ::fseek(mFile, 0, SEEK_SET); + return ::fwrite(pvBuffer, pSize, pCount, this->mFile); } // --------------------------------------------------------------------------- aiReturn DefaultIOStream::Seek(size_t pOffset, @@ -82,7 +86,7 @@ aiReturn DefaultIOStream::Seek(size_t pOffset, { if (!this->mFile)return AI_FAILURE; - return (0 == fseek(this->mFile, (long)pOffset, + return (0 == ::fseek(this->mFile, (long)pOffset, (aiOrigin_CUR == pOrigin ? SEEK_CUR : (aiOrigin_END == pOrigin ? SEEK_END : SEEK_SET))) ? AI_SUCCESS : AI_FAILURE); @@ -92,7 +96,7 @@ size_t DefaultIOStream::Tell() const { if (!this->mFile)return 0; - return ftell(this->mFile); + return ::ftell(this->mFile); } // --------------------------------------------------------------------------- size_t DefaultIOStream::FileSize() const diff --git a/code/DefaultIOSystem.cpp b/code/DefaultIOSystem.cpp index 4feb0705c..4ce18d5c8 100644 --- a/code/DefaultIOSystem.cpp +++ b/code/DefaultIOSystem.cpp @@ -65,11 +65,11 @@ DefaultIOSystem::~DefaultIOSystem() // Tests for the existence of a file at the given path. bool DefaultIOSystem::Exists( const std::string& pFile) const { - FILE* file = fopen( pFile.c_str(), "rb"); + FILE* file = ::fopen( pFile.c_str(), "rb"); if( !file) return false; - fclose( file); + ::fclose( file); return true; } @@ -77,7 +77,7 @@ bool DefaultIOSystem::Exists( const std::string& pFile) const // Open a new file with a given path. IOStream* DefaultIOSystem::Open( const std::string& strFile, const std::string& strMode) { - FILE* file = fopen( strFile.c_str(), strMode.c_str()); + FILE* file = ::fopen( strFile.c_str(), strMode.c_str()); if( NULL == file) return NULL; diff --git a/code/HMPLoader.cpp b/code/HMPLoader.cpp new file mode 100644 index 000000000..e749b5e9b --- /dev/null +++ b/code/HMPLoader.cpp @@ -0,0 +1,449 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (ASSIMP) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2008, ASSIMP Development 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 Development 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 Implementation of the MDL importer class */ + +#include "MaterialSystem.h" +#include "HMPLoader.h" + +#include "../include/DefaultLogger.h" +#include "../include/IOStream.h" +#include "../include/IOSystem.h" +#include "../include/aiMesh.h" +#include "../include/aiScene.h" +#include "../include/aiAssert.h" + +#include + +using namespace Assimp; + +extern float g_avNormals[162][3]; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +HMPImporter::HMPImporter() +{ + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +HMPImporter::~HMPImporter() +{ + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool HMPImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const +{ + // simple check of file extension is enough for the moment + std::string::size_type pos = pFile.find_last_of('.'); + // no file extension - can't read + if( pos == std::string::npos) + return false; + std::string extension = pFile.substr( pos); + + if (extension.length() < 4)return false; + if (extension[0] != '.')return false; + if (extension[1] != 'h' && extension[1] != 'H')return false; + if (extension[2] != 'm' && extension[2] != 'M')return false; + if (extension[3] != 'p' && extension[3] != 'P')return false; + + return true; +} +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void HMPImporter::InternReadFile( + const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) +{ + boost::scoped_ptr file( pIOHandler->Open( pFile)); + + // Check whether we can read from the file + if( file.get() == NULL) + { + throw new ImportErrorException( "Failed to open HMP file " + pFile + "."); + } + + // check whether the ply file is large enough to contain + // at least the file header + size_t fileSize = file->FileSize(); + if( fileSize < 50) + { + throw new ImportErrorException( ".hmp File is too small."); + } + + // allocate storage and copy the contents of the file to a memory buffer + this->pScene = pScene; + this->pIOHandler = pIOHandler; + this->mBuffer = new unsigned char[fileSize+1]; + file->Read( (void*)mBuffer, 1, fileSize); + + this->iFileSize = (unsigned int)fileSize; + + // determine the file subtype and call the appropriate member function + uint32_t iMagic = *((uint32_t*)this->mBuffer); + + try { + + // HMP4 format + if (AI_HMP_MAGIC_NUMBER_LE_4 == iMagic || + AI_HMP_MAGIC_NUMBER_BE_4 == iMagic) + { + DefaultLogger::get()->debug("HMP subtype: 3D GameStudio A4, magic word is HMP4"); + this->InternReadFile_HMP4(); + } + // HMP5 format + else if (AI_HMP_MAGIC_NUMBER_LE_5 == iMagic || + AI_HMP_MAGIC_NUMBER_BE_5 == iMagic) + { + DefaultLogger::get()->debug("HMP subtype: 3D GameStudio A5, magic word is HMP5"); + this->InternReadFile_HMP5(); + } + // HMP7 format + else if (AI_HMP_MAGIC_NUMBER_LE_7 == iMagic || + AI_HMP_MAGIC_NUMBER_BE_7 == iMagic) + { + DefaultLogger::get()->debug("HMP subtype: 3D GameStudio A7, magic word is HMP7"); + this->InternReadFile_HMP7(); + } + else + { + // we're definitely unable to load this file + throw new ImportErrorException( "Unknown HMP subformat " + pFile + + ". Magic word is not known"); + } + + } catch (ImportErrorException* ex) { + delete[] this->mBuffer; + throw ex; + } + + // delete the file buffer + delete[] this->mBuffer; + return; +} + +// ------------------------------------------------------------------------------------------------ +void HMPImporter::InternReadFile_HMP4( ) +{ + throw new ImportErrorException("HMP4 is currently not supported"); +} + +// ------------------------------------------------------------------------------------------------ +void HMPImporter::InternReadFile_HMP5( ) +{ + throw new ImportErrorException("HMP4 is currently not supported"); +} + +// ------------------------------------------------------------------------------------------------ +void HMPImporter::InternReadFile_HMP7( ) +{ + if (120 > this->iFileSize) + { + throw new ImportErrorException("HMP7 file is too small (header size is " + "120 bytes, this file is smaller)"); + } + + // read the file header and skip everything to byte 84 + const HMP::Header_HMP5* pcHeader = (const HMP::Header_HMP5*)this->mBuffer; + const unsigned char* szCurrent = (const unsigned char*)(this->mBuffer+84); + + if (!pcHeader->ftrisize_x || !pcHeader->ftrisize_y || + pcHeader->fnumverts_x < 1.0f || (pcHeader->numverts/pcHeader->fnumverts_x) < 1.0f || + !pcHeader->numframes) + { + throw new ImportErrorException("One or more of the values in the HMP7 " + "file header are invalid."); + } + + // generate an output mesh + this->pScene->mNumMeshes = 1; + this->pScene->mMeshes = new aiMesh*[1]; + aiMesh* pcMesh = this->pScene->mMeshes[0] = new aiMesh(); + + pcMesh->mMaterialIndex = 0; + pcMesh->mVertices = new aiVector3D[pcHeader->numverts]; + pcMesh->mNormals = new aiVector3D[pcHeader->numverts]; + + const unsigned int height = (unsigned int)(pcHeader->numverts / pcHeader->fnumverts_x); + const unsigned int width = (unsigned int)pcHeader->fnumverts_x; + + // we don't need to generate texture coordinates if + // we have no textures in the file ... + if (pcHeader->numskins) + { + pcMesh->mTextureCoords[0] = new aiVector3D[pcHeader->numverts]; + pcMesh->mNumUVComponents[0] = 2; + + // now read the first skin and skip all others + this->ReadFirstSkin(pcHeader->numskins,szCurrent,&szCurrent); + } + else + { + // generate a default material + const int iMode = (int)aiShadingMode_Gouraud; + MaterialHelper* pcHelper = new MaterialHelper(); + pcHelper->AddProperty(&iMode, 1, AI_MATKEY_SHADING_MODEL); + + aiColor3D clr; + clr.b = clr.g = clr.r = 0.7f; + pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_DIFFUSE); + pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_SPECULAR); + + clr.b = clr.g = clr.r = 0.05f; + pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_AMBIENT); + + aiString szName; + szName.Set(AI_DEFAULT_MATERIAL_NAME); + pcHelper->AddProperty(&szName,AI_MATKEY_NAME); + + // add the material to the scene + this->pScene->mNumMaterials = 1; + this->pScene->mMaterials = new aiMaterial*[1]; + this->pScene->mMaterials[0] = pcHelper; + } + + // goto offset 120, I don't know why ... + // (fixme) is this the frame header? I assume yes since it starts + // with 2. + szCurrent += 36; + + this->SizeCheck(szCurrent + sizeof(const HMP::Vertex_HMP7)*height*width); + + // now load all vertices from the file + aiVector3D* pcVertOut = pcMesh->mVertices; + aiVector3D* pcNorOut = pcMesh->mNormals; + const HMP::Vertex_HMP7* src = (const HMP::Vertex_HMP7*) szCurrent; + for (unsigned int y = 0; y < height;++y) + { + for (unsigned int x = 0; x < width;++x) + { + pcVertOut->x = x * pcHeader->ftrisize_x; + pcVertOut->y = y * pcHeader->ftrisize_y; + // FIXME: What exctly is the correct scaling factor to use? + // possibly pcHeader->scale_origin[2] in combination with a + // signed interpretation of src->z? + pcVertOut->z = (((float)src->z / 0xffff)-0.5f) * pcHeader->ftrisize_x * 8.0f; + + pcNorOut->x = ((float)src->normal_x / 0x80 ); // * pcHeader->scale_origin[0]; + pcNorOut->y = ((float)src->normal_y / 0x80 ); // * pcHeader->scale_origin[1]; + pcNorOut->z = 1.0f; + pcNorOut->Normalize(); + + ++pcVertOut;++pcNorOut;++src; + } + } + + // generate texture coordinates if necessary + if (pcHeader->numskins) + { + this->GenerateTextureCoords(width,height); + } + + // now build a list of faces + const unsigned int iNumSquares = (width-1) * (height-1); + pcMesh->mNumFaces = iNumSquares << 1; + pcMesh->mFaces = new aiFace[pcMesh->mNumFaces]; + + pcMesh->mNumVertices = pcMesh->mNumFaces*3; + aiVector3D* pcVertices = new aiVector3D[pcMesh->mNumVertices]; + aiVector3D* pcNormals = new aiVector3D[pcMesh->mNumVertices]; + + aiFace* pcFaceOut(pcMesh->mFaces); + pcVertOut = pcVertices; + pcNorOut = pcNormals; + + aiVector3D* pcUVs = pcMesh->mTextureCoords[0] ? new aiVector3D[pcMesh->mNumVertices] : NULL; + aiVector3D* pcUVOut(pcUVs); + + unsigned int iCurrent = 0; + for (unsigned int y = 0; y < height-1;++y) + { + for (unsigned int x = 0; x < width-1;++x) + { + // first triangle of the square + pcFaceOut->mNumIndices = 3; + pcFaceOut->mIndices = new unsigned int[3]; + + *pcVertOut++ = pcMesh->mVertices[y*width+x]; + *pcVertOut++ = pcMesh->mVertices[y*width+x+1]; + *pcVertOut++ = pcMesh->mVertices[(y+1)*width+x]; + + *pcNorOut++ = pcMesh->mNormals[y*width+x]; + *pcNorOut++ = pcMesh->mNormals[y*width+x+1]; + *pcNorOut++ = pcMesh->mNormals[(y+1)*width+x]; + + if (pcMesh->mTextureCoords[0]) + { + *pcUVOut++ = pcMesh->mTextureCoords[0][y*width+x]; + *pcUVOut++ = pcMesh->mTextureCoords[0][y*width+x+1]; + *pcUVOut++ = pcMesh->mTextureCoords[0][(y+1)*width+x]; + } + + pcFaceOut->mIndices[2] = iCurrent++; + pcFaceOut->mIndices[1] = iCurrent++; + pcFaceOut->mIndices[0] = iCurrent++; + ++pcFaceOut; + + // second triangle of the square + pcFaceOut->mNumIndices = 3; + pcFaceOut->mIndices = new unsigned int[3]; + + *pcVertOut++ = pcMesh->mVertices[(y+1)*width+x]; + *pcVertOut++ = pcMesh->mVertices[y*width+x+1]; + *pcVertOut++ = pcMesh->mVertices[(y+1)*width+x+1]; + + *pcNorOut++ = pcMesh->mNormals[(y+1)*width+x]; + *pcNorOut++ = pcMesh->mNormals[y*width+x+1]; + *pcNorOut++ = pcMesh->mNormals[(y+1)*width+x+1]; + + if (pcMesh->mTextureCoords[0]) + { + *pcUVOut++ = pcMesh->mTextureCoords[0][(y+1)*width+x]; + *pcUVOut++ = pcMesh->mTextureCoords[0][y*width+x+1]; + *pcUVOut++ = pcMesh->mTextureCoords[0][(y+1)*width+x+1]; + } + + pcFaceOut->mIndices[2] = iCurrent++; + pcFaceOut->mIndices[1] = iCurrent++; + pcFaceOut->mIndices[0] = iCurrent++; + ++pcFaceOut; + } + } + delete[] pcMesh->mVertices; + pcMesh->mVertices = pcVertices; + + delete[] pcMesh->mNormals; + pcMesh->mNormals = pcNormals; + + if (pcMesh->mTextureCoords[0]) + { + delete[] pcMesh->mTextureCoords[0]; + pcMesh->mTextureCoords[0] = pcUVs; + } + + // there is no nodegraph in HMP files. Simply assign the one mesh + // (no, not the one ring) to the root node + this->pScene->mRootNode = new aiNode(); + this->pScene->mRootNode->mName.Set("terrain_root"); + this->pScene->mRootNode->mNumMeshes = 1; + this->pScene->mRootNode->mMeshes = new unsigned int[1]; + this->pScene->mRootNode->mMeshes[0] = 0; + + return; +} +// ------------------------------------------------------------------------------------------------ +void HMPImporter::ReadFirstSkin(unsigned int iNumSkins, const unsigned char* szCursor, + const unsigned char** szCursorOut) +{ + ai_assert(0 != iNumSkins && NULL != szCursor); + + // read the type of the skin ... + // sometimes we need to skip 12 bytes here, I don't know why ... + uint32_t iType = *((uint32_t*)szCursor);szCursor += sizeof(uint32_t); + if (0 == iType) + { + DefaultLogger::get()->warn("Skin type is 0. Skipping 12 bytes to " + "the next valid value, which seems to be the real skin type. " + "However, it is not known whether or not this is correct."); + szCursor += sizeof(uint32_t) * 2; + iType = *((uint32_t*)szCursor);szCursor += sizeof(uint32_t); + if (0 == iType) + { + throw new ImportErrorException("Unable to read HMP7 skin chunk"); + } + } + // read width and height + uint32_t iWidth = *((uint32_t*)szCursor);szCursor += sizeof(uint32_t); + uint32_t iHeight = *((uint32_t*)szCursor);szCursor += sizeof(uint32_t); + + // allocate an output material + MaterialHelper* pcMat = new MaterialHelper(); + + // read the skin, this works exactly as for MDL7 + this->ParseSkinLump_3DGS_MDL7(szCursor,&szCursor, + pcMat,iType,iWidth,iHeight); + + // now we need to skip any other skins ... + for (unsigned int i = 1; i< iNumSkins;++i) + { + iType = *((uint32_t*)szCursor);szCursor += sizeof(uint32_t); + iWidth = *((uint32_t*)szCursor);szCursor += sizeof(uint32_t); + iHeight = *((uint32_t*)szCursor);szCursor += sizeof(uint32_t); + + this->SkipSkinLump_3DGS_MDL7(szCursor,&szCursor, + iType,iWidth,iHeight); + + this->SizeCheck(szCursor); + } + + // setup the material ... + this->pScene->mNumMaterials = 1; + this->pScene->mMaterials = new aiMaterial*[1]; + this->pScene->mMaterials[0] = pcMat; + + *szCursorOut = szCursor; + return; +} +// ------------------------------------------------------------------------------------------------ +void HMPImporter::GenerateTextureCoords( + const unsigned int width, const unsigned int height) +{ + ai_assert(NULL != this->pScene->mMeshes && NULL != this->pScene->mMeshes[0] && + NULL != this->pScene->mMeshes[0]->mTextureCoords[0]); + + aiVector3D* uv = this->pScene->mMeshes[0]->mTextureCoords[0]; + + const float fX = (1.0f / height) + (1.0f / height) / (height-1); + const float fY = (1.0f / width) + (1.0f / width) / (width-1); + + for (unsigned int y = 0; y < height;++y) + { + for (unsigned int x = 0; x < width;++x) + { + uv->x = fX*x; + uv->y = 1.0f-fY*y; + ++uv; + } + } + return; +} diff --git a/code/HMPLoader.h b/code/HMPLoader.h new file mode 100644 index 000000000..6b7ae4182 --- /dev/null +++ b/code/HMPLoader.h @@ -0,0 +1,208 @@ +/* +Open Asset Import Library (ASSIMP) +---------------------------------------------------------------------- + +Copyright (c) 2006-2008, ASSIMP Development 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 Development 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 Definition of HMP importer class +//! + +#ifndef AI_HMPLOADER_H_INCLUDED +#define AI_HMPLOADER_H_INCLUDED + +#include "BaseImporter.h" +#include "../include/aiTypes.h" +#include "../include/aiTexture.h" +#include "../include/aiMaterial.h" + +struct aiNode; +#include "MDLLoader.h" + +namespace Assimp +{ +class MaterialHelper; + +#define AI_HMP_MAGIC_NUMBER_BE_4 'HMP4' +#define AI_HMP_MAGIC_NUMBER_LE_4 '4PMH' + +#define AI_HMP_MAGIC_NUMBER_BE_5 'HMP5' +#define AI_HMP_MAGIC_NUMBER_LE_5 '5PMH' + +#define AI_HMP_MAGIC_NUMBER_BE_7 'HMP7' +#define AI_HMP_MAGIC_NUMBER_LE_7 '7PMH' + +namespace HMP +{ + +// --------------------------------------------------------------------------- +/** Data structure for the header of a HMP5 file. + * This is also used by HMP4 and HMP7, but with modifications +*/ +struct Header_HMP5 +{ + int8_t ident[4]; // "HMP5" + int32_t version; + + // ignored + float scale[3]; + float scale_origin[3]; + float boundingradius; + + //! Size of one triangle in x direction + float ftrisize_x; + //! Size of one triangle in y direction + float ftrisize_y; + //! Number of vertices in x direction + float fnumverts_x; + + //! Number of skins in the file + int32_t numskins; + + // can ignore this? + int32_t skinwidth; + int32_t skinheight; + + //!Number of vertices in the file + int32_t numverts; + + // ignored and zero + int32_t numtris; + + //! only one supported ... + int32_t numframes; + + //! Always 0 ... + int32_t num_stverts; + int32_t flags; + float size; +}; + + +// --------------------------------------------------------------------------- +/** Data structure for a terrain vertex in a HMP7 file +*/ +struct Vertex_HMP7 +{ + uint16_t z; + int8_t normal_x,normal_y; +}; + +}; //! namespace HMP + +// --------------------------------------------------------------------------- +/** Used to load 3D GameStudio HMP files (terrains) +*/ +class HMPImporter : public MDLImporter +{ + friend class Importer; + +protected: + /** Constructor to be privately used by Importer */ + HMPImporter(); + + /** Destructor, private as well */ + ~HMPImporter(); + +public: + + // ------------------------------------------------------------------- + /** Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. */ + bool CanRead( const std::string& pFile, IOSystem* pIOHandler) const; + +protected: + + + // ------------------------------------------------------------------- + /** Called by Importer::GetExtensionList() for each loaded importer. + * See BaseImporter::GetExtensionList() for details + */ + void GetExtensionList(std::string& append) + { + append.append("*.hmp"); + } + + // ------------------------------------------------------------------- + /** Imports the given file into the given scene structure. + * See BaseImporter::InternReadFile() for details + */ + void InternReadFile( const std::string& pFile, aiScene* pScene, + IOSystem* pIOHandler); + +protected: + + // ------------------------------------------------------------------- + /** Import a HMP4 file + */ + void InternReadFile_HMP4( ); + + // ------------------------------------------------------------------- + /** Import a HMP5 file + */ + void InternReadFile_HMP5( ); + + // ------------------------------------------------------------------- + /** Import a HMP7 file + */ + void InternReadFile_HMP7( ); + + + // ------------------------------------------------------------------- + /** Generate planar texture coordinates for a terrain + * \param width Width of the terrain, in vertices + * \param height Height of the terrain, in vertices + */ + void GenerateTextureCoords(const unsigned int width, + const unsigned int height); + + // ------------------------------------------------------------------- + /** Read the first skin from the file and skip all others ... + * \param iNumSkins Number of skins in the file + * \param szCursor Position of the first skin (offset 84) + */ + void ReadFirstSkin(unsigned int iNumSkins, const unsigned char* szCursor, + const unsigned char** szCursorOut); + +private: + + +}; +}; // end of namespace Assimp + +#endif // AI_HMPIMPORTER_H_INC \ No newline at end of file diff --git a/code/Importer.cpp b/code/Importer.cpp index c3ed3d6e7..bbdb28e31 100644 --- a/code/Importer.cpp +++ b/code/Importer.cpp @@ -54,6 +54,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "DefaultIOStream.h" #include "DefaultIOSystem.h" +// Importers #if (!defined AI_BUILD_NO_X_IMPORTER) # include "XFileImporter.h" #endif @@ -81,18 +82,47 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #if (!defined AI_BUILD_NO_OBJ_IMPORTER) # include "ObjFileImporter.h" #endif +#if (!defined AI_BUILD_NO_HMP_IMPORTER) +# include "HMPLoader.h" +#endif +#if (!defined AI_BUILD_NO_SMD_IMPORTER) +# include "SMDLoader.h" +#endif -#include "CalcTangentsProcess.h" -#include "JoinVerticesProcess.h" -#include "ConvertToLHProcess.h" -#include "TriangulateProcess.h" -#include "GenFaceNormalsProcess.h" -#include "GenVertexNormalsProcess.h" -#include "KillNormalsProcess.h" -#include "SplitLargeMeshes.h" -#include "PretransformVertices.h" -#include "LimitBoneWeightsProcess.h" -#include "ValidateDataStructure.h" +// PostProcess-Steps +#if (!defined AI_BUILD_NO_CALCTANGENTS_PROCESS) +# include "CalcTangentsProcess.h" +#endif +#if (!defined AI_BUILD_NO_JOINVERTICES_PROCESS) +# include "JoinVerticesProcess.h" +#endif +#if (!defined AI_BUILD_NO_CONVERTTOLH_PROCESS) +# include "ConvertToLHProcess.h" +#endif +#if (!defined AI_BUILD_NO_TRIANGULATE_PROCESS) +# include "TriangulateProcess.h" +#endif +#if (!defined AI_BUILD_NO_GENFACENORMALS_PROCESS) +# include "GenFaceNormalsProcess.h" +#endif +#if (!defined AI_BUILD_NO_GENVERTEXNORMALS_PROCESS) +# include "GenVertexNormalsProcess.h" +#endif +#if (!defined AI_BUILD_NO_KILLNORMALS_PROCESS) +# include "KillNormalsProcess.h" +#endif +#if (!defined AI_BUILD_NO_SPLITLARGEMESHES_PROCESS) +# include "SplitLargeMeshes.h" +#endif +#if (!defined AI_BUILD_NO_PRETRANSFORMVERTICES_PROCESS) +# include "PretransformVertices.h" +#endif +#if (!defined AI_BUILD_NO_LIMITBONEWEIGHTS_PROCESS) +# include "LimitBoneWeightsProcess.h" +#endif +#if (!defined AI_BUILD_NO_VALIDATEDS_PROCESS) +# include "ValidateDataStructure.h" +#endif using namespace Assimp; @@ -105,6 +135,7 @@ Importer::Importer() : { // allocate a default IO handler mIOHandler = new DefaultIOSystem; + mIsDefaultHandler = true; // add an instance of each worker class here #if (!defined AI_BUILD_NO_X_IMPORTER) @@ -134,20 +165,51 @@ Importer::Importer() : #if (!defined AI_BUILD_NO_ASE_IMPORTER) mImporter.push_back( new ASEImporter()); #endif + #if (!defined AI_BUILD_NO_HMP_IMPORTER) + mImporter.push_back( new HMPImporter()); +#endif + #if (!defined AI_BUILD_NO_SMD_IMPORTER) + mImporter.push_back( new SMDImporter()); +#endif - // add an instance of each post processing step here in the order of sequence it is executed + // add an instance of each post processing step here in the order + // of sequence it is executed +#if (!defined AI_BUILD_NO_VALIDATEDS_PROCESS) mPostProcessingSteps.push_back( new ValidateDSProcess()); +#endif +#if (!defined AI_BUILD_NO_TRIANGULATE_PROCESS) mPostProcessingSteps.push_back( new TriangulateProcess()); +#endif +#if (!defined AI_BUILD_NO_PRETRANSFORMVERTICES_PROCESS) mPostProcessingSteps.push_back( new PretransformVertices()); +#endif +#if (!defined AI_BUILD_NO_SPLITLARGEMESHES_PROCESS) mPostProcessingSteps.push_back( new SplitLargeMeshesProcess_Triangle()); +#endif +#if (!defined AI_BUILD_NO_KILLNORMALS_PROCESS) mPostProcessingSteps.push_back( new KillNormalsProcess()); +#endif +#if (!defined AI_BUILD_NO_GENFACENORMALS_PROCESS) mPostProcessingSteps.push_back( new GenFaceNormalsProcess()); +#endif +#if (!defined AI_BUILD_NO_GENVERTEXNORMALS_PROCESS) mPostProcessingSteps.push_back( new GenVertexNormalsProcess()); +#endif +#if (!defined AI_BUILD_NO_CALCTANGENTS_PROCESS) mPostProcessingSteps.push_back( new CalcTangentsProcess()); +#endif +#if (!defined AI_BUILD_NO_JOINVERTICES_PROCESS) mPostProcessingSteps.push_back( new JoinVerticesProcess()); +#endif +#if (!defined AI_BUILD_NO_SPLITLARGEMESHES_PROCESS) mPostProcessingSteps.push_back( new SplitLargeMeshesProcess_Vertex()); +#endif +#if (!defined AI_BUILD_NO_CONVERTTOLH_PROCESS) mPostProcessingSteps.push_back( new ConvertToLHProcess()); +#endif +#if (!defined AI_BUILD_NO_LIMITBONEWEIGHTS_PROCESS) mPostProcessingSteps.push_back( new LimitBoneWeightsProcess()); +#endif } // ------------------------------------------------------------------------------------------------ @@ -170,19 +232,31 @@ Importer::~Importer() // Supplies a custom IO handler to the importer to open and access files. void Importer::SetIOHandler( IOSystem* pIOHandler) { - if (NULL == pIOHandler) + if (!pIOHandler) { delete mIOHandler; mIOHandler = new DefaultIOSystem(); + mIsDefaultHandler = true; } else if (mIOHandler != pIOHandler) { delete mIOHandler; mIOHandler = pIOHandler; + mIsDefaultHandler = false; } return; } // ------------------------------------------------------------------------------------------------ +IOSystem* Importer::GetIOHandler() +{ + return mIOHandler; +} +// ------------------------------------------------------------------------------------------------ +bool Importer::IsDefaultIOHandler() +{ + return mIsDefaultHandler; +} +// ------------------------------------------------------------------------------------------------ // Validate post process step flags bool ValidateFlags(unsigned int pFlags) { diff --git a/code/LimitBoneWeightsProcess.cpp b/code/LimitBoneWeightsProcess.cpp index 964203d63..e37b9ad30 100644 --- a/code/LimitBoneWeightsProcess.cpp +++ b/code/LimitBoneWeightsProcess.cpp @@ -1,20 +1,80 @@ +/* +Open Asset Import Library (ASSIMP) +---------------------------------------------------------------------- + +Copyright (c) 2006-2008, ASSIMP Development 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 Development 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. + +---------------------------------------------------------------------- +*/ + /** Implementation of the LimitBoneWeightsProcess post processing step */ #include #include + #include "LimitBoneWeightsProcess.h" + #include "../include/aiPostProcess.h" #include "../include/aiMesh.h" #include "../include/aiScene.h" +#include "../include/DefaultLogger.h" using namespace Assimp; + +/*static*/ unsigned int LimitBoneWeightsProcess::mMaxWeights = AI_LMW_MAX_WEIGHTS; + +extern "C" { +// ------------------------------------------------------------------------------------------------ +aiReturn aiSetBoneWeightLimit(unsigned int pLimit) +{ + if (0 == pLimit) + { + LimitBoneWeightsProcess::mMaxWeights = 0xFFFFFFFF; + return AI_FAILURE; + } + + LimitBoneWeightsProcess::mMaxWeights = pLimit; + DefaultLogger::get()->debug("aiSetBoneWeightLimit() - bone weight limit was changed"); + return AI_SUCCESS; +} +}; + // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer LimitBoneWeightsProcess::LimitBoneWeightsProcess() { - // TODO: (thom) make this configurable from somewhere? - mMaxWeights = 4; } // ------------------------------------------------------------------------------------------------ diff --git a/code/LimitBoneWeightsProcess.h b/code/LimitBoneWeightsProcess.h index d8b37ac32..653acc34a 100644 --- a/code/LimitBoneWeightsProcess.h +++ b/code/LimitBoneWeightsProcess.h @@ -1,3 +1,43 @@ +/* +Open Asset Import Library (ASSIMP) +---------------------------------------------------------------------- + +Copyright (c) 2006-2008, ASSIMP Development 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 Development 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. + +---------------------------------------------------------------------- +*/ + /** Defines a post processing step to limit the number of bones affecting a single vertex. */ #ifndef AI_LIMITBONEWEIGHTSPROCESS_H_INC #define AI_LIMITBONEWEIGHTSPROCESS_H_INC @@ -9,6 +49,18 @@ struct aiMesh; namespace Assimp { +// NOTE: If you change these limits, don't forget to change the +// corresponding values in all Assimp ports + +// ********************************************************** +// Java: PostProcessStep.java, +// PostProcessStep.DEFAULT_BONE_WEIGHT_LIMIT +// ********************************************************** + +#if (!defined AI_LMW_MAX_WEIGHTS) +# define AI_LMW_MAX_WEIGHTS 0x4 +#endif // !! AI_LMW_MAX_WEIGHTS + // --------------------------------------------------------------------------- /** This post processing step limits the number of bones affecting a vertex * to a certain maximum value. If a vertex is affected by more than that number @@ -58,12 +110,14 @@ protected: float mWeight; ///< Weight of that bone on this vertex Weight() { } Weight( unsigned int pBone, float pWeight) { mBone = pBone; mWeight = pWeight; } + /** Comparision operator to sort bone weights by descending weight */ bool operator < (const Weight& pWeight) const { return mWeight > pWeight.mWeight; } }; +public: /** Maximum number of bones influencing any single vertex. */ - unsigned int mMaxWeights; + static unsigned int mMaxWeights; }; } // end of namespace Assimp diff --git a/code/MD2FileData.h b/code/MD2FileData.h index 907bf528f..67b1796a7 100644 --- a/code/MD2FileData.h +++ b/code/MD2FileData.h @@ -80,34 +80,33 @@ namespace MD2 // --------------------------------------------------------------------------- /** \brief Data structure for the MD2 main header */ -// --------------------------------------------------------------------------- struct Header { - int32_t magic; - int32_t version; - int32_t skinWidth; - int32_t skinHeight; - int32_t frameSize; - int32_t numSkins; - int32_t numVertices; - int32_t numTexCoords; - int32_t numTriangles; - int32_t numGlCommands; - int32_t numFrames; - int32_t offsetSkins; - int32_t offsetTexCoords; - int32_t offsetTriangles; - int32_t offsetFrames; - int32_t offsetGlCommands; - int32_t offsetEnd; + uint32_t magic; + uint32_t version; + uint32_t skinWidth; + uint32_t skinHeight; + uint32_t frameSize; + uint32_t numSkins; + uint32_t numVertices; + uint32_t numTexCoords; + uint32_t numTriangles; + uint32_t numGlCommands; + uint32_t numFrames; + uint32_t offsetSkins; + uint32_t offsetTexCoords; + uint32_t offsetTriangles; + uint32_t offsetFrames; + uint32_t offsetGlCommands; + uint32_t offsetEnd; } PACK_STRUCT; + // --------------------------------------------------------------------------- /** \brief Data structure for a MD2 OpenGl draw command */ -// --------------------------------------------------------------------------- struct GLCommand { float s, t; @@ -117,7 +116,6 @@ struct GLCommand // --------------------------------------------------------------------------- /** \brief Data structure for a MD2 triangle */ -// --------------------------------------------------------------------------- struct Triangle { uint16_t vertexIndices[3]; @@ -127,7 +125,6 @@ struct Triangle // --------------------------------------------------------------------------- /** \brief Data structure for a MD2 vertex */ -// --------------------------------------------------------------------------- struct Vertex { uint8_t vertex[3]; @@ -137,7 +134,6 @@ struct Vertex // --------------------------------------------------------------------------- /** \brief Data structure for a MD2 frame */ -// --------------------------------------------------------------------------- struct Frame { float scale[3]; @@ -149,7 +145,6 @@ struct Frame // --------------------------------------------------------------------------- /** \brief Data structure for a MD2 texture coordinate */ -// --------------------------------------------------------------------------- struct TexCoord { int16_t s; @@ -159,7 +154,6 @@ struct TexCoord // --------------------------------------------------------------------------- /** \brief Data structure for a MD2 skin */ -// --------------------------------------------------------------------------- struct Skin { char name[AI_MD2_MAXQPATH]; /* texture file name */ @@ -171,6 +165,14 @@ struct Skin #endif #undef PACK_STRUCT + +// --------------------------------------------------------------------------- +//! Lookup a normal vector from Quake's normal lookup table +//! \param index Input index (0-161) +//! \param vOut Receives the output normal +void LookupNormalIndex(uint8_t index,aiVector3D& vOut); + + }; }; diff --git a/code/MD2Loader.cpp b/code/MD2Loader.cpp index 2ac190935..a771057cd 100644 --- a/code/MD2Loader.cpp +++ b/code/MD2Loader.cpp @@ -42,40 +42,42 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** @file Implementation of the MD2 importer class */ #include "MD2Loader.h" #include "MaterialSystem.h" - -#include "MD2NormalTable.h" +#include "MD2NormalTable.h" // shouldn't be included by other units #include "../include/IOStream.h" #include "../include/IOSystem.h" #include "../include/aiMesh.h" #include "../include/aiScene.h" #include "../include/aiAssert.h" +#include "../include/DefaultLogger.h" #include using namespace Assimp; +using namespace Assimp::MD2; + + +// helper macro to determine the size of an array +#if (!defined ARRAYSIZE) +# define ARRAYSIZE(_array) (int(sizeof(_array) / sizeof(_array[0]))) +#endif // ------------------------------------------------------------------------------------------------ -inline bool is_qnan(float p_fIn) +// Helper function to lookup a normal in Quake 2's precalculated table +void MD2::LookupNormalIndex(uint8_t iNormalIndex,aiVector3D& vOut) { - // NOTE: Comparison against qnan is generally problematic - // because qnan == qnan is false AFAIK - union FTOINT + // make sure the normal index has a valid value + if (iNormalIndex >= ARRAYSIZE(g_avNormals)) { - float fFloat; - int32_t iInt; - } one, two; - one.fFloat = std::numeric_limits::quiet_NaN(); - two.fFloat = p_fIn; + DefaultLogger::get()->warn("Index overflow in MDL7 normal vector list (the " + " LUT has only 162 entries). "); - return (one.iInt == two.iInt); -} -// ------------------------------------------------------------------------------------------------ -inline bool is_not_qnan(float p_fIn) -{ - return !is_qnan(p_fIn); + iNormalIndex = ARRAYSIZE(g_avNormals) - 1; + } + vOut = *((const aiVector3D*)(&g_avNormals[iNormalIndex])); } + // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer MD2Importer::MD2Importer() @@ -99,12 +101,43 @@ bool MD2Importer::CanRead( const std::string& pFile, IOSystem* pIOHandler) const return false; std::string extension = pFile.substr( pos); - // not brilliant but working ;-) - if( extension == ".md2" || extension == ".MD2" || - extension == ".mD2" || extension == ".Md2") - return true; + if (extension.length() < 4)return false; + if (extension[0] != '.')return false; + if (extension[1] != 'm' && extension[1] != 'M')return false; + if (extension[2] != 'd' && extension[2] != 'D')return false; + if (extension[3] != '2')return false; - return false; + return true; +} +// ------------------------------------------------------------------------------------------------ +// Validate the file header +void MD2Importer::ValidateHeader( ) +{ + /* to be validated: + int32_t offsetSkins; + int32_t offsetTexCoords; + int32_t offsetTriangles; + int32_t offsetFrames; + //int32_t offsetGlCommands; + int32_t offsetEnd; + */ + + if (this->m_pcHeader->offsetSkins + this->m_pcHeader->numSkins * sizeof (MD2::Skin) >= this->fileSize || + this->m_pcHeader->offsetTexCoords + this->m_pcHeader->numTexCoords * sizeof (MD2::TexCoord) >= this->fileSize || + this->m_pcHeader->offsetTriangles + this->m_pcHeader->numTriangles * sizeof (MD2::Triangle) >= this->fileSize || + this->m_pcHeader->offsetFrames + this->m_pcHeader->numFrames * sizeof (MD2::Frame) >= this->fileSize || + this->m_pcHeader->offsetEnd > this->fileSize) + { + throw new ImportErrorException("Invalid MD2 header: some offsets are outside the file"); + delete[] this->mBuffer; + } + + if (this->m_pcHeader->numSkins > AI_MD2_MAX_SKINS) + DefaultLogger::get()->warn("The model contains more skins than Quake 2 supports"); + if ( this->m_pcHeader->numFrames > AI_MD2_MAX_FRAMES) + DefaultLogger::get()->warn("The model contains more frames than Quake 2 supports"); + if (this->m_pcHeader->numVertices > AI_MD2_MAX_VERTS) + DefaultLogger::get()->warn("The model contains more vertices than Quake 2 supports"); } // ------------------------------------------------------------------------------------------------ // Imports the given file into the given scene structure. @@ -121,10 +154,10 @@ void MD2Importer::InternReadFile( // check whether the md3 file is large enough to contain // at least the file header - size_t fileSize = file->FileSize(); + fileSize = (unsigned int)file->FileSize(); if( fileSize < sizeof(MD2::Header)) { - throw new ImportErrorException( ".md2 File is too small."); + throw new ImportErrorException( "md2 File is too small."); } // allocate storage and copy the contents of the file to a memory buffer @@ -137,22 +170,26 @@ void MD2Importer::InternReadFile( if (this->m_pcHeader->magic != AI_MD2_MAGIC_NUMBER_BE && this->m_pcHeader->magic != AI_MD2_MAGIC_NUMBER_LE) { + delete[] this->mBuffer; throw new ImportErrorException( "Invalid md2 file: Magic bytes not found"); } // check file format version if (this->m_pcHeader->version != 8) { - throw new ImportErrorException( "Unsupported md3 file version"); + DefaultLogger::get()->warn( "Unsupported md2 file version. Continuing happily ..."); } + this->ValidateHeader(); // check some values whether they are valid if (0 == this->m_pcHeader->numFrames) { + delete[] this->mBuffer; throw new ImportErrorException( "Invalid md2 file: NUM_FRAMES is 0"); } if (this->m_pcHeader->offsetEnd > (int32_t)fileSize) { + delete[] this->mBuffer; throw new ImportErrorException( "Invalid md2 file: File is too small"); } @@ -166,39 +203,35 @@ void MD2Importer::InternReadFile( pScene->mMaterials[0] = new MaterialHelper(); pScene->mNumMeshes = 1; pScene->mMeshes = new aiMesh*[1]; - pScene->mMeshes[0] = new aiMesh(); + aiMesh* pcMesh = pScene->mMeshes[0] = new aiMesh(); // navigate to the begin of the frame data - const MD2::Frame* pcFrame = (const MD2::Frame*) ((unsigned char*)this->m_pcHeader + - this->m_pcHeader->offsetFrames); + const MD2::Frame* pcFrame = (const MD2::Frame*) ( + (unsigned char*)this->m_pcHeader + this->m_pcHeader->offsetFrames); // navigate to the begin of the triangle data - MD2::Triangle* pcTriangles = (MD2::Triangle*) ((unsigned char*)this->m_pcHeader + - this->m_pcHeader->offsetTriangles); + MD2::Triangle* pcTriangles = (MD2::Triangle*) ( + (unsigned char*)this->m_pcHeader + this->m_pcHeader->offsetTriangles); // navigate to the begin of the tex coords data - const MD2::TexCoord* pcTexCoords = (const MD2::TexCoord*) ((unsigned char*)this->m_pcHeader + - this->m_pcHeader->offsetTexCoords); + const MD2::TexCoord* pcTexCoords = (const MD2::TexCoord*) ( + (unsigned char*)this->m_pcHeader + this->m_pcHeader->offsetTexCoords); // navigate to the begin of the vertex data const MD2::Vertex* pcVerts = (const MD2::Vertex*) (pcFrame->vertices); - pScene->mMeshes[0]->mNumFaces = this->m_pcHeader->numTriangles; - pScene->mMeshes[0]->mFaces = new aiFace[this->m_pcHeader->numTriangles]; + pcMesh->mNumFaces = this->m_pcHeader->numTriangles; + pcMesh->mFaces = new aiFace[this->m_pcHeader->numTriangles]; - // temporary vectors for position/texture coordinates/normals - std::vector vPositions; - std::vector vTexCoords; - std::vector vNormals; - - vPositions.resize(pScene->mMeshes[0]->mNumFaces*3,aiVector3D()); - vTexCoords.resize(pScene->mMeshes[0]->mNumFaces*3,aiVector3D( - std::numeric_limits::quiet_NaN(), - std::numeric_limits::quiet_NaN(),0.0f)); - vNormals.resize(pScene->mMeshes[0]->mNumFaces*3,aiVector3D()); + // allocate output storage + pcMesh->mNumVertices = (unsigned int)pcMesh->mNumFaces*3; + pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices]; + pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices]; // not sure whether there are MD2 files without texture coordinates - if (0 != this->m_pcHeader->numTexCoords && 0 != this->m_pcHeader->numSkins) + // NOTE: texture coordinates can be there without a texture, + // but a texture can't be there without a valid UV channel + if (this->m_pcHeader->numTexCoords && this->m_pcHeader->numSkins) { // navigate to the first texture associated with the mesh const MD2::Skin* pcSkins = (const MD2::Skin*) ((unsigned char*)this->m_pcHeader + @@ -216,12 +249,20 @@ void MD2Importer::InternReadFile( clr.b = clr.g = clr.r = 0.05f; pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_AMBIENT); - aiString szString; - const size_t iLen = strlen(pcSkins->name); - memcpy(szString.data,pcSkins->name,iLen+1); - szString.length = iLen-1; + if (pcSkins->name[0]) + { + aiString szString; + const size_t iLen = ::strlen(pcSkins->name); + ::memcpy(szString.data,pcSkins->name,iLen); + szString.data[iLen] = '\0'; + szString.length = iLen; - pcHelper->AddProperty(&szString,AI_MATKEY_TEXTURE_DIFFUSE(0)); + pcHelper->AddProperty(&szString,AI_MATKEY_TEXTURE_DIFFUSE(0)); + } + else + { + DefaultLogger::get()->warn("Texture file name has zero length. It will be skipped."); + } } else { @@ -237,13 +278,42 @@ void MD2Importer::InternReadFile( clr.b = clr.g = clr.r = 0.05f; pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_AMBIENT); + + aiString szName; + szName.Set(AI_DEFAULT_MATERIAL_NAME); + pcHelper->AddProperty(&szName,AI_MATKEY_NAME); } // now read all triangles of the first frame, apply scaling and translation unsigned int iCurrent = 0; - if (0 != this->m_pcHeader->numTexCoords) + if (this->m_pcHeader->numTexCoords) { + // allocate storage for texture coordinates, too + pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices]; + pcMesh->mNumUVComponents[0] = 2; + + // check whether the skin width or height are zero (this would + // cause a division through zero) + float fDivisorU; + if (!this->m_pcHeader->skinWidth) + { + DefaultLogger::get()->error("Skin width is zero but there are " + "valid absolute texture coordinates. Unable to compute " + "relative texture coordinates ranging from 0 to 1"); + fDivisorU = 1.0f; + } + else fDivisorU = (float)this->m_pcHeader->skinWidth; + + float fDivisorV; + if (!this->m_pcHeader->skinHeight) + { + DefaultLogger::get()->error("Skin height is zero but there are " + "valid absolute texture coordinates. Unable to compute " + "relative texture coordinates ranging from 0 to 1"); + fDivisorV = 1.0f; + } + else fDivisorV = (float)this->m_pcHeader->skinHeight; for (unsigned int i = 0; i < (unsigned int)this->m_pcHeader->numTriangles;++i) { @@ -259,13 +329,16 @@ void MD2Importer::InternReadFile( { // validate vertex indices if (pcTriangles[i].vertexIndices[c] >= this->m_pcHeader->numVertices) + { + DefaultLogger::get()->error("Vertex index is outside the allowed range"); pcTriangles[i].vertexIndices[c] = this->m_pcHeader->numVertices-1; + } // copy face indices unsigned int iIndex = (unsigned int)pcTriangles[i].vertexIndices[c]; // read x,y, and z component of the vertex - aiVector3D& vec = vPositions[iCurrent]; + aiVector3D& vec = pcMesh->mVertices[iCurrent]; vec.x = (float)pcVerts[iIndex].vertex[0] * pcFrame->scale[0]; vec.x += pcFrame->translate[0]; @@ -278,23 +351,28 @@ void MD2Importer::InternReadFile( vec.y += pcFrame->translate[2]; // read the normal vector from the precalculated normal table - vNormals[iCurrent] = *((const aiVector3D*)(&g_avNormals[std::min( - int(pcVerts[iIndex].lightNormalIndex), - int(sizeof(g_avNormals) / sizeof(g_avNormals[0]))-1)])); - - std::swap ( vNormals[iCurrent].y,vNormals[iCurrent].z ); + aiVector3D& vNormal = pcMesh->mNormals[iCurrent]; + LookupNormalIndex(pcVerts[iIndex].lightNormalIndex,vNormal); + std::swap ( vNormal.y,vNormal.z ); // validate texture coordinates if (pcTriangles[iIndex].textureIndices[c] >= this->m_pcHeader->numTexCoords) + { + DefaultLogger::get()->error("UV index is outside the allowed range"); pcTriangles[iIndex].textureIndices[c] = this->m_pcHeader->numTexCoords-1; + } - aiVector3D* pcOut = &vTexCoords[iCurrent]; + aiVector3D& pcOut = pcMesh->mTextureCoords[0][iCurrent]; float u,v; - u = (float)pcTexCoords[pcTriangles[i].textureIndices[c]].s / this->m_pcHeader->skinWidth; - v = (float)pcTexCoords[pcTriangles[i].textureIndices[c]].t / this->m_pcHeader->skinHeight; - pcOut->x = u; - pcOut->y = v; + + // the texture coordinates are absolute values but we + // need relative values between 0 and 1 + u = (float)pcTexCoords[pcTriangles[i].textureIndices[c]].s / fDivisorU; + v = (float)pcTexCoords[pcTriangles[i].textureIndices[c]].t / fDivisorV; + pcOut.x = u; + pcOut.y = 1.0f - v; // FIXME: Is this correct for MD2? } + // FIX: flip the face order for use with OpenGL pScene->mMeshes[0]->mFaces[i].mIndices[0] = iTemp+2; pScene->mMeshes[0]->mFaces[i].mIndices[1] = iTemp+1; pScene->mMeshes[0]->mFaces[i].mIndices[2] = iTemp+0; @@ -316,13 +394,16 @@ void MD2Importer::InternReadFile( { // validate vertex indices if (pcTriangles[i].vertexIndices[c] >= this->m_pcHeader->numVertices) + { + DefaultLogger::get()->error("Vertex index is outside the allowed range"); pcTriangles[i].vertexIndices[c] = this->m_pcHeader->numVertices-1; + } // copy face indices unsigned int iIndex = (unsigned int)pcTriangles[i].vertexIndices[c]; // read x,y, and z component of the vertex - aiVector3D& vec = vPositions[iCurrent]; + aiVector3D& vec = pcMesh->mVertices[iCurrent]; vec.x = (float)pcVerts[iIndex].vertex[0] * pcFrame->scale[0]; vec.x += pcFrame->translate[0]; @@ -335,39 +416,17 @@ void MD2Importer::InternReadFile( vec.y += pcFrame->translate[2]; // read the normal vector from the precalculated normal table - vNormals[iCurrent] = *((const aiVector3D*)(&g_avNormals[std::min( - int(pcVerts[iIndex].lightNormalIndex), - int(sizeof(g_avNormals) / sizeof(g_avNormals[0]))-1)])); - - std::swap ( vNormals[iCurrent].y,vNormals[iCurrent].z ); - - aiVector3D* pcOut = &vTexCoords[iCurrent]; - pcOut->x = (float)pcTexCoords[pcTriangles[i].textureIndices[c]].s / this->m_pcHeader->skinWidth; - pcOut->y = (float)pcTexCoords[pcTriangles[i].textureIndices[c]].t / this->m_pcHeader->skinHeight; + aiVector3D& vNormal = pcMesh->mNormals[iCurrent]; + LookupNormalIndex(pcVerts[iIndex].lightNormalIndex,vNormal); + std::swap ( vNormal.y,vNormal.z ); } + // FIX: flip the face order for use with OpenGL pScene->mMeshes[0]->mFaces[i].mIndices[0] = iTemp+2; pScene->mMeshes[0]->mFaces[i].mIndices[1] = iTemp+1; pScene->mMeshes[0]->mFaces[i].mIndices[2] = iTemp+0; } } - - // allocate output storage - pScene->mMeshes[0]->mNumVertices = (unsigned int)vPositions.size(); - pScene->mMeshes[0]->mVertices = new aiVector3D[vPositions.size()]; - pScene->mMeshes[0]->mNormals = new aiVector3D[vPositions.size()]; - pScene->mMeshes[0]->mTextureCoords[0] = new aiVector3D[vPositions.size()]; - - // memcpy() the data to the c-syle arrays - memcpy(pScene->mMeshes[0]->mVertices, &vPositions[0], - vPositions.size() * sizeof(aiVector3D)); - memcpy(pScene->mMeshes[0]->mNormals, &vNormals[0], - vPositions.size() * sizeof(aiVector3D)); - - if (0 != this->m_pcHeader->numTexCoords) - { - memcpy(pScene->mMeshes[0]->mTextureCoords[0], &vTexCoords[0], - vPositions.size() * sizeof(aiVector3D)); - } - + // delete the file buffer and return + delete[] this->mBuffer; return; } \ No newline at end of file diff --git a/code/MD2Loader.h b/code/MD2Loader.h index f26dbdeb2..ac78ec778 100644 --- a/code/MD2Loader.h +++ b/code/MD2Loader.h @@ -93,6 +93,12 @@ protected: void InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler); + + // ------------------------------------------------------------------- + /** Validate the header of the file + */ + void ValidateHeader(); + protected: /** Header of the MD2 file */ @@ -100,6 +106,9 @@ protected: /** Buffer to hold the loaded file */ const unsigned char* mBuffer; + + /** Size of the file, in bytes */ + unsigned int fileSize; }; } // end of namespace Assimp diff --git a/code/MD3FileData.h b/code/MD3FileData.h index 646bfe9ef..b3305c03c 100644 --- a/code/MD3FileData.h +++ b/code/MD3FileData.h @@ -89,10 +89,10 @@ namespace MD3 struct Header { //! magic number - int32_t IDENT; + uint32_t IDENT; //! file format version - int32_t VERSION; + uint32_t VERSION; //! original name in .pak archive unsigned char NAME[ AI_MD3_MAXQPATH ]; @@ -101,28 +101,28 @@ struct Header int32_t FLAGS; //! number of frames in the file - int32_t NUM_FRAMES; + uint32_t NUM_FRAMES; //! number of tags in the file - int32_t NUM_TAGS; + uint32_t NUM_TAGS; //! number of surfaces in the file - int32_t NUM_SURFACES; + uint32_t NUM_SURFACES; //! number of skins in the file - int32_t NUM_SKINS; + uint32_t NUM_SKINS; //! offset of the first frame - int32_t OFS_FRAMES; + uint32_t OFS_FRAMES; //! offset of the first tag - int32_t OFS_TAGS; + uint32_t OFS_TAGS; //! offset of the first surface - int32_t OFS_SURFACES; + uint32_t OFS_SURFACES; //! end of file - int32_t OFS_EOF; + uint32_t OFS_EOF; } PACK_STRUCT; @@ -162,29 +162,29 @@ struct Surface int32_t FLAGS; //! number of frames in the surface - int32_t NUM_FRAMES; + uint32_t NUM_FRAMES; //! number of shaders in the surface - int32_t NUM_SHADER; + uint32_t NUM_SHADER; //! number of vertices in the surface - int32_t NUM_VERTICES; + uint32_t NUM_VERTICES; //! number of triangles in the surface - int32_t NUM_TRIANGLES; + uint32_t NUM_TRIANGLES; //! offset to the triangle data - int32_t OFS_TRIANGLES; + uint32_t OFS_TRIANGLES; //! offset to the shader data - int32_t OFS_SHADERS; + uint32_t OFS_SHADERS; //! offset to the texture coordinate data - int32_t OFS_ST; + uint32_t OFS_ST; //! offset to the vertex/normal data - int32_t OFS_XYZNORMAL; + uint32_t OFS_XYZNORMAL; //! offset to the end of the Surface object int32_t OFS_END; @@ -200,7 +200,7 @@ struct Shader unsigned char NAME[ AI_MD3_MAXQPATH ]; //! index of the shader - int32_t SHADER_INDEX; + uint32_t SHADER_INDEX; } PACK_STRUCT; @@ -211,7 +211,7 @@ struct Shader struct Triangle { //! triangle indices - int32_t INDEXES[3]; + uint32_t INDEXES[3]; } PACK_STRUCT; @@ -236,7 +236,7 @@ struct Vertex int16_t X,Y,Z; //! encoded normal vector - int16_t NORMAL; + uint16_t NORMAL; } PACK_STRUCT; // reset packing to the original value @@ -254,9 +254,9 @@ struct Vertex * \note This has been taken from q3 source (misc_model.c) */ // --------------------------------------------------------------------------- -inline void LatLngNormalToVec3(int16_t p_iNormal, float* p_afOut) +inline void LatLngNormalToVec3(uint16_t p_iNormal, float* p_afOut) { - float lat = (float)(( p_iNormal >> 8 ) & 0xff); + float lat = (float)(( p_iNormal >> 8u ) & 0xff); float lng = (float)(( p_iNormal & 0xff )); lat *= 3.141926f/128.0f; lng *= 3.141926f/128.0f; diff --git a/code/MD3Loader.cpp b/code/MD3Loader.cpp index 42a38eac3..fa1842061 100644 --- a/code/MD3Loader.cpp +++ b/code/MD3Loader.cpp @@ -49,6 +49,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "../include/aiMesh.h" #include "../include/aiScene.h" #include "../include/aiAssert.h" +#include "../include/DefaultLogger.h" #include @@ -78,12 +79,48 @@ bool MD3Importer::CanRead( const std::string& pFile, IOSystem* pIOHandler) const return false; std::string extension = pFile.substr( pos); - // not brilliant but working ;-) - if( extension == ".md3" || extension == ".MD3" || - extension == ".mD3" || extension == ".Md3") - return true; + if (extension.length() < 4)return false; + if (extension[0] != '.')return false; + if (extension[1] != 'm' && extension[1] != 'M')return false; + if (extension[2] != 'd' && extension[2] != 'D')return false; + if (extension[3] != '3')return false; - return false; + return true; +} +// ------------------------------------------------------------------------------------------------ +void MD3Importer::ValidateHeaderOffsets() +{ + if (this->m_pcHeader->OFS_FRAMES >= this->fileSize || + this->m_pcHeader->OFS_SURFACES >= this->fileSize || + this->m_pcHeader->OFS_EOF > this->fileSize) + { + delete[] this->mBuffer; + throw new ImportErrorException("Invalid MD3 header: some offsets are outside the file"); + } +} +// ------------------------------------------------------------------------------------------------ +void MD3Importer::ValidateSurfaceHeaderOffsets(const MD3::Surface* pcSurf) +{ + // calculate the relative offset of the surface + int32_t ofs = int32_t((const unsigned char*)pcSurf-this->mBuffer); + + if (pcSurf->OFS_TRIANGLES + ofs + pcSurf->NUM_TRIANGLES * sizeof(MD3::Triangle) > this->fileSize || + pcSurf->OFS_SHADERS + ofs + pcSurf->NUM_SHADER * sizeof(MD3::Shader) > this->fileSize || + pcSurf->OFS_ST + ofs + pcSurf->NUM_VERTICES * sizeof(MD3::TexCoord) > this->fileSize || + pcSurf->OFS_XYZNORMAL + ofs + pcSurf->NUM_VERTICES * sizeof(MD3::Vertex) > this->fileSize) + { + delete[] this->mBuffer; + throw new ImportErrorException("Invalid MD3 surface header: some offsets are outside the file"); + } + + if (pcSurf->NUM_TRIANGLES > AI_MD3_MAX_TRIANGLES) + DefaultLogger::get()->warn("The model contains more triangles than Quake 3 supports"); + if (pcSurf->NUM_SHADER > AI_MD3_MAX_SHADERS) + DefaultLogger::get()->warn("The model contains more shaders than Quake 3 supports"); + if (pcSurf->NUM_VERTICES > AI_MD3_MAX_VERTS) + DefaultLogger::get()->warn("The model contains more vertices than Quake 3 supports"); + if (pcSurf->NUM_FRAMES > AI_MD3_MAX_FRAMES) + DefaultLogger::get()->warn("The model contains more frames than Quake 3 supports"); } // ------------------------------------------------------------------------------------------------ // Imports the given file into the given scene structure. @@ -100,7 +137,7 @@ void MD3Importer::InternReadFile( // check whether the md3 file is large enough to contain // at least the file header - size_t fileSize = file->FileSize(); + fileSize = (unsigned int)file->FileSize(); if( fileSize < sizeof(MD3::Header)) { throw new ImportErrorException( ".md3 File is too small."); @@ -116,28 +153,28 @@ void MD3Importer::InternReadFile( if (this->m_pcHeader->IDENT != AI_MD3_MAGIC_NUMBER_BE && this->m_pcHeader->IDENT != AI_MD3_MAGIC_NUMBER_LE) { + delete[] this->mBuffer; throw new ImportErrorException( "Invalid md3 file: Magic bytes not found"); } // check file format version if (this->m_pcHeader->VERSION > 15) { - throw new ImportErrorException( "Unsupported md3 file version"); + DefaultLogger::get()->warn( "Unsupported md3 file version. Continuing happily ..."); } // check some values whether they are valid if (0 == this->m_pcHeader->NUM_FRAMES) { + delete[] this->mBuffer; throw new ImportErrorException( "Invalid md3 file: NUM_FRAMES is 0"); } if (0 == this->m_pcHeader->NUM_SURFACES) { + delete[] this->mBuffer; throw new ImportErrorException( "Invalid md3 file: NUM_SURFACES is 0"); } - if (this->m_pcHeader->OFS_EOF > (int32_t)fileSize) - { - throw new ImportErrorException( "Invalid md3 file: File is too small"); - } + this->ValidateHeaderOffsets(); // now navigate to the list of surfaces const MD3::Surface* pcSurfaces = (const MD3::Surface*) @@ -150,11 +187,19 @@ void MD3Importer::InternReadFile( pScene->mNumMaterials = this->m_pcHeader->NUM_SURFACES; pScene->mMaterials = new aiMaterial*[pScene->mNumMeshes]; + // if an exception is thrown before the meshes are allocated -> + // otherwise the pointer value would be invalid and delete would crash + ::memset(pScene->mMeshes,0,pScene->mNumMeshes*sizeof(aiMesh*)); + ::memset(pScene->mMaterials,0,pScene->mNumMaterials*sizeof(aiMaterial*)); + unsigned int iNum = this->m_pcHeader->NUM_SURFACES; unsigned int iNumMaterials = 0; unsigned int iDefaultMatIndex = 0xFFFFFFFF; while (iNum-- > 0) { + // validate the surface + this->ValidateSurfaceHeaderOffsets(pcSurfaces); + // navigate to the vertex list of the surface const MD3::Vertex* pcVertices = (const MD3::Vertex*) (((unsigned char*)pcSurfaces) + pcSurfaces->OFS_XYZNORMAL); @@ -171,7 +216,6 @@ void MD3Importer::InternReadFile( const MD3::Shader* pcShaders = (const MD3::Shader*) (((unsigned char*)pcSurfaces) + pcSurfaces->OFS_SHADERS); - // if the submesh is empty ignore it if (0 == pcSurfaces->NUM_VERTICES || 0 == pcSurfaces->NUM_TRIANGLES) { @@ -185,14 +229,14 @@ void MD3Importer::InternReadFile( aiMesh* pcMesh = pScene->mMeshes[iNum]; pcMesh->mNumVertices = pcSurfaces->NUM_TRIANGLES*3; - pcMesh->mNumBones = 0; - pcMesh->mColors[0] = pcMesh->mColors[1] = pcMesh->mColors[2] = pcMesh->mColors[3] = NULL; + //pcMesh->mNumBones = 0; + //pcMesh->mColors[0] = pcMesh->mColors[1] = pcMesh->mColors[2] = pcMesh->mColors[3] = NULL; pcMesh->mNumFaces = pcSurfaces->NUM_TRIANGLES; pcMesh->mFaces = new aiFace[pcSurfaces->NUM_TRIANGLES]; pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices]; pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices]; pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices]; - pcMesh->mTextureCoords[1] = pcMesh->mTextureCoords[2] = pcMesh->mTextureCoords[3] = NULL; + //pcMesh->mTextureCoords[1] = pcMesh->mTextureCoords[2] = pcMesh->mTextureCoords[3] = NULL; pcMesh->mNumUVComponents[0] = 2; // fill in all triangles @@ -221,6 +265,7 @@ void MD3Importer::InternReadFile( pcMesh->mTextureCoords[0][iCurrent].x = pcUVs[ pcTriangles->INDEXES[c]].U; pcMesh->mTextureCoords[0][iCurrent].y = 1.0f - pcUVs[ pcTriangles->INDEXES[c]].V; } + // FIX: flip the face ordering for use with OpenGL pcMesh->mFaces[i].mIndices[0] = iTemp+2; pcMesh->mFaces[i].mIndices[1] = iTemp+1; pcMesh->mFaces[i].mIndices[2] = iTemp+0; @@ -233,11 +278,11 @@ void MD3Importer::InternReadFile( // make a relative path. // if the MD3's internal path itself and the given path are using // the same directory remove it - const char* szEndDir1 = strrchr((const char*)this->m_pcHeader->NAME,'\\'); - if (!szEndDir1)szEndDir1 = strrchr((const char*)this->m_pcHeader->NAME,'/'); + const char* szEndDir1 = ::strrchr((const char*)this->m_pcHeader->NAME,'\\'); + if (!szEndDir1)szEndDir1 = ::strrchr((const char*)this->m_pcHeader->NAME,'/'); - const char* szEndDir2 = strrchr((const char*)pcShaders->NAME,'\\'); - if (!szEndDir2)szEndDir2 = strrchr((const char*)pcShaders->NAME,'/'); + const char* szEndDir2 = ::strrchr((const char*)pcShaders->NAME,'\\'); + if (!szEndDir2)szEndDir2 = ::strrchr((const char*)pcShaders->NAME,'/'); if (szEndDir1 && szEndDir2) { @@ -276,7 +321,7 @@ void MD3Importer::InternReadFile( aiString szOut; if(AI_SUCCESS == aiGetMaterialString ( (aiMaterial*)pScene->mMaterials[p], - AI_MATKEY_TEXBLEND_DIFFUSE(0),&szOut)) + AI_MATKEY_TEXTURE_DIFFUSE(0),&szOut)) { if (0 == ASSIMP_stricmp(szOut.data,szEndDir2)) { @@ -294,25 +339,35 @@ void MD3Importer::InternReadFile( if (szEndDir2) { - aiString szString; - const size_t iLen = strlen(szEndDir2); - memcpy(szString.data,szEndDir2,iLen+1); - szString.length = iLen-1; + if (szEndDir2[0]) + { + aiString szString; + const size_t iLen = ::strlen(szEndDir2); + ::memcpy(szString.data,szEndDir2,iLen); + szString.data[iLen] = '\0'; + szString.length = iLen; - pcHelper->AddProperty(&szString,AI_MATKEY_TEXTURE_DIFFUSE(0)); + pcHelper->AddProperty(&szString,AI_MATKEY_TEXTURE_DIFFUSE(0)); + } + else + { + DefaultLogger::get()->warn("Texture file name has zero length. " + "It will be skipped."); + } } int iMode = (int)aiShadingMode_Gouraud; pcHelper->AddProperty(&iMode, 1, AI_MATKEY_SHADING_MODEL); + // add a small ambient color value - Quake 3 seems to have one aiColor3D clr; - clr.b = clr.g = clr.r = 1.0f; - pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_DIFFUSE); - pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_SPECULAR); - clr.b = clr.g = clr.r = 0.05f; pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_AMBIENT); + aiString szName; + szName.Set(AI_DEFAULT_MATERIAL_NAME); + pcHelper->AddProperty(&szName,AI_MATKEY_NAME); + pScene->mMaterials[iNumMaterials] = (aiMaterial*)pcHelper; pcMesh->mMaterialIndex = iNumMaterials++; } @@ -343,31 +398,27 @@ void MD3Importer::InternReadFile( pcMesh->mMaterialIndex = iNumMaterials++; } } + // go to the next surface pcSurfaces = (const MD3::Surface*)(((unsigned char*)pcSurfaces) + pcSurfaces->OFS_END); } if (0 == pScene->mNumMeshes) { // cleanup before returning - delete pScene; + delete[] this->mBuffer; throw new ImportErrorException( "Invalid md3 file: File contains no valid mesh"); } pScene->mNumMaterials = iNumMaterials; // now we need to generate an empty node graph pScene->mRootNode = new aiNode(); - pScene->mRootNode->mNumChildren = pScene->mNumMeshes; - pScene->mRootNode->mChildren = new aiNode*[pScene->mNumMeshes]; + pScene->mRootNode->mNumMeshes = pScene->mNumMeshes; + pScene->mRootNode->mMeshes = new unsigned int[pScene->mNumMeshes]; for (unsigned int i = 0; i < pScene->mNumMeshes;++i) - { - pScene->mRootNode->mChildren[i] = new aiNode(); - pScene->mRootNode->mChildren[i]->mParent = pScene->mRootNode; - pScene->mRootNode->mChildren[i]->mNumMeshes = 1; - pScene->mRootNode->mChildren[i]->mMeshes = new unsigned int[1]; - pScene->mRootNode->mChildren[i]->mMeshes[0] = i; - } + pScene->mRootNode->mMeshes[i] = i; + // delete the file buffer and return delete[] this->mBuffer; return; } \ No newline at end of file diff --git a/code/MD3Loader.h b/code/MD3Loader.h index f34af8d8c..36e69ea93 100644 --- a/code/MD3Loader.h +++ b/code/MD3Loader.h @@ -96,6 +96,13 @@ protected: void InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler); + + // ------------------------------------------------------------------- + /** Validate offsets in the header + */ + void ValidateHeaderOffsets(); + void ValidateSurfaceHeaderOffsets(const MD3::Surface* pcSurfHeader); + protected: /** Header of the MD3 file */ @@ -103,6 +110,9 @@ protected: /** Buffer to hold the loaded file */ const unsigned char* mBuffer; + + /** Size of the file, in bytes */ + unsigned int fileSize; }; } // end of namespace Assimp diff --git a/code/MD4Loader.cpp b/code/MD4Loader.cpp index f806e3e9e..7a353bfac 100644 --- a/code/MD4Loader.cpp +++ b/code/MD4Loader.cpp @@ -109,7 +109,7 @@ void MD4Importer::InternReadFile( size_t fileSize = file->FileSize(); if( fileSize < sizeof(MD4::Header)) { - throw new ImportErrorException( ".mdd File is too small."); + throw new ImportErrorException( ".md4 File is too small."); } return; } \ No newline at end of file diff --git a/code/MDLFileData.h b/code/MDLFileData.h index a5927db7b..2cfe73950 100644 --- a/code/MDLFileData.h +++ b/code/MDLFileData.h @@ -43,8 +43,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //! @file Definition of in-memory structures for the MDL file format. // // The specification has been taken from various sources on the internet. -// http://tfc.duke.free.fr/coding/mdl-specs-en.html - +// - http://tfc.duke.free.fr/coding/mdl-specs-en.html +// - Conitec's MED SDK +// - Many quite long HEX-editor sessions #ifndef AI_MDLFILEHELPER_H_INC #define AI_MDLFILEHELPER_H_INC @@ -67,15 +68,17 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # error Compiler not supported. Never do this again. #endif -namespace Assimp -{ -namespace MDL -{ +namespace Assimp { +namespace MDL { // magic bytes used in Quake 1 MDL meshes #define AI_MDL_MAGIC_NUMBER_BE 'IDPO' #define AI_MDL_MAGIC_NUMBER_LE 'OPDI' +// magic bytes used in GameStudio A MDL meshes +#define AI_MDL_MAGIC_NUMBER_BE_GS3 'MDL2' +#define AI_MDL_MAGIC_NUMBER_LE_GS3 '2LDM' + // magic bytes used in GameStudio A4 MDL meshes #define AI_MDL_MAGIC_NUMBER_BE_GS4 'MDL3' #define AI_MDL_MAGIC_NUMBER_LE_GS4 '3LDM' @@ -96,7 +99,7 @@ namespace MDL // common limitations for Quake1 meshes. The loader does not check them, -// but models should not exceed these limits. +// (however it warns) but models should not exceed these limits. #if (!defined AI_MDL_VERSION) # define AI_MDL_VERSION 6 #endif @@ -113,11 +116,25 @@ namespace MDL # define AI_MDL_MAX_TRIANGLES 2048 #endif +// helper macro that sets a pointer to NULL in debug builds +#if (!defined DEBUG_INVALIDATE_PTR) +# if (defined _DEBUG) +# define DEBUG_INVALIDATE_PTR(x) x = NULL; +# else +# define DEBUG_INVALIDATE_PTR(x) +# endif +#endif + +// material key that is set for dummy materials that are +// just referencing another material +#if (!defined AI_MDL7_REFERRER_MATERIAL) +# define AI_MDL7_REFERRER_MATERIAL "&&&referrer&&&" +#endif + // --------------------------------------------------------------------------- /** \struct Header * \brief Data structure for the MDL main header */ -// --------------------------------------------------------------------------- struct Header { //! magic number: "IDPO" @@ -157,12 +174,13 @@ struct Header int32_t num_frames; //! 0 = synchron, 1 = random . Ignored + //! (MDLn formats: number of texture coordinates) int32_t synctype; //! State flag int32_t flags; - //! ??? + //! Could be the total size of the file (and not a float) float size; } PACK_STRUCT; @@ -171,7 +189,6 @@ struct Header /** \struct Header_MDL7 * \brief Data structure for the MDL 7 main header */ -// --------------------------------------------------------------------------- struct Header_MDL7 { //! magic number: "MDL7" @@ -181,13 +198,13 @@ struct Header_MDL7 int32_t version; //! Number of bones in file - int32_t bones_num; + uint32_t bones_num; //! Number of groups in file - int32_t groups_num; + uint32_t groups_num; //! Size of data in the file - int32_t data_size; + uint32_t data_size; //! Ignored. Used to store entity specific information int32_t entlump_size; @@ -195,45 +212,77 @@ struct Header_MDL7 //! Ignored. Used to store MED related data int32_t medlump_size; - // ------------------------------------------------------- - // Sizes of some file parts - + //! Size of the Bone_MDL7 data structure used in the file uint16_t bone_stc_size; + + //! Size of the Skin_MDL 7 data structure used in the file uint16_t skin_stc_size; + + //! Size of a single color (e.g. in a material) uint16_t colorvalue_stc_size; + + //! Size of the Material_MDL7 data structure used in the file uint16_t material_stc_size; + + //! Size of a texture coordinate set in the file uint16_t skinpoint_stc_size; + + //! Size of a triangle in the file uint16_t triangle_stc_size; + + //! Size of a normal vertex in the file uint16_t mainvertex_stc_size; + + //! Size of a per-frame animated vertex in the file + //! (this is not supported) uint16_t framevertex_stc_size; + + //! Size of a bone animation matrix uint16_t bonetrans_stc_size; + + //! Size of the Frame_MDL7 data structure used in the file uint16_t frame_stc_size; } PACK_STRUCT; - -#define AI_MDL7_MAX_BONENAMESIZE 20 - // --------------------------------------------------------------------------- /** \struct Bone_MDL7 - * \brief Bone in a MDL7 file + * \brief Data structure for a bone in a MDL7 file */ -// --------------------------------------------------------------------------- struct Bone_MDL7 { + //! Index of the parent bone of *this* bone. 0xffff means: + //! "hey, I have no parent, I'm an orphan" uint16_t parent_index; - uint8_t _unused_[2]; // + uint8_t _unused_[2]; + + //! Relative position of the bone (relative to the + //! parent bone) float x,y,z; - char name[AI_MDL7_MAX_BONENAMESIZE]; -}; + //! Optional name of the bone + char name[1 /* DUMMY SIZE */]; +} PACK_STRUCT; -#define AI_MDL7_MAX_GROUPNAMESIZE 16 +#if (!defined AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_20_CHARS) +# define AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_20_CHARS (16 + 20) +#endif + +#if (!defined AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_32_CHARS) +# define AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_32_CHARS (16 + 32) +#endif + +#if (!defined AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_NOT_THERE) +# define AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_NOT_THERE (16) +#endif + +#if (!defined AI_MDL7_MAX_GROUPNAMESIZE) +# define AI_MDL7_MAX_GROUPNAMESIZE 16 +#endif // ! AI_MDL7_MAX_GROUPNAMESIZE // --------------------------------------------------------------------------- /** \struct Group_MDL7 * \brief Group in a MDL7 file */ -// --------------------------------------------------------------------------- struct Group_MDL7 { //! = '1' -> triangle based Mesh @@ -268,14 +317,14 @@ struct Group_MDL7 #define AI_MDL7_SKINTYPE_MATERIAL_ASCDEF 0x20 #define AI_MDL7_SKINTYPE_RGBFLAG 0x80 - -#define AI_MDL7_MAX_BONENAMESIZE 20 +#if (!defined AI_MDL7_MAX_BONENAMESIZE) +# define AI_MDL7_MAX_BONENAMESIZE 20 +#endif // !! AI_MDL7_MAX_BONENAMESIZE // --------------------------------------------------------------------------- /** \struct Deformer_MDL7 * \brief Deformer in a MDL7 file */ -// --------------------------------------------------------------------------- struct Deformer_MDL7 { int8_t deformer_version; // 0 @@ -291,7 +340,6 @@ struct Deformer_MDL7 /** \struct DeformerElement_MDL7 * \brief Deformer element in a MDL7 file */ -// --------------------------------------------------------------------------- struct DeformerElement_MDL7 { //! bei deformer_typ==0 (==bones) element_index == bone index @@ -305,7 +353,6 @@ struct DeformerElement_MDL7 /** \struct DeformerWeight_MDL7 * \brief Deformer weight in a MDL7 file */ -// --------------------------------------------------------------------------- struct DeformerWeight_MDL7 { //! for deformer_typ==0 (==bones) index == vertex index @@ -313,27 +360,14 @@ struct DeformerWeight_MDL7 float weight; } PACK_STRUCT; -// maximum length of texture file name -#define AI_MDL7_MAX_TEXNAMESIZE 0x10 // don't know why this was in the original headers ... -// to be removed in future versions typedef int32_t MD7_MATERIAL_ASCDEFSIZE; // --------------------------------------------------------------------------- -/** \struct Skin_MDL7 - * \brief Skin in a MDL7 file +/** \struct ColorValue_MDL7 + * \brief Data structure for a color value in a MDL7 file */ -// --------------------------------------------------------------------------- -struct Skin_MDL7 -{ - uint8_t typ; - int8_t _unused_[3]; - int32_t width; - int32_t height; - char texture_name[AI_MDL7_MAX_TEXNAMESIZE]; -} PACK_STRUCT; - struct ColorValue_MDL7 { float r,g,b,a; @@ -341,9 +375,8 @@ struct ColorValue_MDL7 // --------------------------------------------------------------------------- /** \struct Material_MDL7 - * \brief Material in a MDL7 file + * \brief Data structure for a Material in a MDL7 file */ -// --------------------------------------------------------------------------- struct Material_MDL7 { //! Diffuse base color of the material @@ -365,9 +398,8 @@ struct Material_MDL7 // --------------------------------------------------------------------------- /** \struct Skin - * \brief Skin data structure #1 + * \brief Skin data structure #1 - used by Quake1, MDL2, MDL3 and MDL4 */ -// --------------------------------------------------------------------------- struct Skin { //! 0 = single (Skin), 1 = group (GroupSkin) @@ -387,12 +419,40 @@ struct Skin uint8_t *data; } PACK_STRUCT; + +// --------------------------------------------------------------------------- +/** \struct Skin + * \brief Skin data structure #2 - used by MDL5, MDL6 and MDL7 + * \see Skin + */ struct Skin_MDL5 { int32_t size, width, height; uint8_t *data; } PACK_STRUCT; +// maximum length of texture file name +#if (!defined AI_MDL7_MAX_TEXNAMESIZE) +# define AI_MDL7_MAX_TEXNAMESIZE 0x10 +#endif + +// --------------------------------------------------------------------------- +/** \struct Skin_MDL7 + * \brief Skin data structure #3 - used by MDL7 and HMP7 + */ +struct Skin_MDL7 +{ + uint8_t typ; + int8_t _unused_[3]; + int32_t width; + int32_t height; + char texture_name[AI_MDL7_MAX_TEXNAMESIZE]; +} PACK_STRUCT; + +// --------------------------------------------------------------------------- +/** \struct RGB565 + * \brief Data structure for a RGB565 pixel in a texture + */ struct RGB565 { uint16_t r : 5; @@ -400,6 +460,10 @@ struct RGB565 uint16_t b : 5; } PACK_STRUCT; +// --------------------------------------------------------------------------- +/** \struct ARGB4 + * \brief Data structure for a ARGB4444 pixel in a texture + */ struct ARGB4 { uint16_t a : 4; @@ -412,7 +476,6 @@ struct ARGB4 /** \struct GroupSkin * \brief Skin data structure #2 (group of pictures) */ -// --------------------------------------------------------------------------- struct GroupSkin { //! 0 = single (Skin), 1 = group (GroupSkin) @@ -430,9 +493,8 @@ struct GroupSkin // --------------------------------------------------------------------------- /** \struct TexCoord - * \brief Texture coordinate data structure + * \brief Texture coordinate data structure used by the Quake1 MDL format */ -// --------------------------------------------------------------------------- struct TexCoord { //! Is the vertex on the noundary between front and back piece? @@ -445,7 +507,10 @@ struct TexCoord int32_t t; } PACK_STRUCT; - +// --------------------------------------------------------------------------- +/** \struct TexCoord_MDL3 + * \brief Data structure for an UV coordinate in the 3DGS MDL3 format + */ struct TexCoord_MDL3 { //! position, horizontally in range 0..skinwidth-1 @@ -455,6 +520,10 @@ struct TexCoord_MDL3 int16_t v; } PACK_STRUCT; +// --------------------------------------------------------------------------- +/** \struct TexCoord_MDL7 + * \brief Data structure for an UV coordinate in the 3DGS MDL7 format + */ struct TexCoord_MDL7 { //! position, horizontally in range 0..1 @@ -464,32 +533,13 @@ struct TexCoord_MDL7 float v; } PACK_STRUCT; - // --------------------------------------------------------------------------- -/** \struct Triangle - * \brief Triangle data structure +/** \struct SkinSet_MDL7 + * \brief Skin set data structure for the 3DGS MDL7 format + * MDL7 references UV coordinates per face via an index list. + * This allows the use of multiple skins per face with just one + * UV coordinate set. */ -// --------------------------------------------------------------------------- -struct Triangle -{ - //! 0 = backface, 1 = frontface - int32_t facesfront; - - //! Vertex indices - int32_t vertex[3]; -} PACK_STRUCT; - - -struct Triangle_MDL3 -{ - //! Index of 3 3D vertices in range 0..numverts - uint16_t index_xyz[3]; - - //! Index of 3 skin vertices in range 0..numskinverts - uint16_t index_uv[3]; -} PACK_STRUCT; - - struct SkinSet_MDL7 { //! Index into the UV coordinate list @@ -499,7 +549,36 @@ struct SkinSet_MDL7 int32_t material; // size 4 } PACK_STRUCT; +// --------------------------------------------------------------------------- +/** \struct Triangle + * \brief Triangle data structure for the Quake1 MDL format + */ +struct Triangle +{ + //! 0 = backface, 1 = frontface + int32_t facesfront; + //! Vertex indices + int32_t vertex[3]; +} PACK_STRUCT; + +// --------------------------------------------------------------------------- +/** \struct Triangle_MDL3 + * \brief Triangle data structure for the 3DGS MDL3 format + */ +struct Triangle_MDL3 +{ + //! Index of 3 3D vertices in range 0..numverts + uint16_t index_xyz[3]; + + //! Index of 3 skin vertices in range 0..numskinverts + uint16_t index_uv[3]; +} PACK_STRUCT; + +// --------------------------------------------------------------------------- +/** \struct Triangle_MDL7 + * \brief Triangle data structure for the 3DGS MDL7 format + */ struct Triangle_MDL7 { //! Vertex indices @@ -509,6 +588,15 @@ struct Triangle_MDL7 SkinSet_MDL7 skinsets[2]; } PACK_STRUCT; +#if (!defined AI_MDL7_TRIANGLE_STD_SIZE_ONE_UV) +# define AI_MDL7_TRIANGLE_STD_SIZE_ONE_UV (6+sizeof(SkinSet_MDL7)-sizeof(uint32_t)) +#endif +#if (!defined AI_MDL7_TRIANGLE_STD_SIZE_ONE_UV_WITH_MATINDEX) +# define AI_MDL7_TRIANGLE_STD_SIZE_ONE_UV_WITH_MATINDEX (6+sizeof(SkinSet_MDL7)) +#endif +#if (!defined AI_MDL7_TRIANGLE_STD_SIZE_TWO_UV) +# define AI_MDL7_TRIANGLE_STD_SIZE_TWO_UV (6+2*sizeof(SkinSet_MDL7)) +#endif // Helper constants for Triangle::facesfront #if (!defined AI_MDL_BACKFACE) @@ -522,7 +610,6 @@ struct Triangle_MDL7 /** \struct Vertex * \brief Vertex data structure */ -// --------------------------------------------------------------------------- struct Vertex { uint8_t v[3]; @@ -530,6 +617,7 @@ struct Vertex } PACK_STRUCT; +// --------------------------------------------------------------------------- struct Vertex_MDL4 { uint16_t v[3]; @@ -544,13 +632,12 @@ struct Vertex_MDL4 /** \struct Vertex_MDL7 * \brief Vertex data structure used in MDL7 files */ -// --------------------------------------------------------------------------- struct Vertex_MDL7 { float x,y,z; uint16_t vertindex; // = bone index union { - uint16_t norm162index; + uint8_t norm162index; float norm[3]; }; } PACK_STRUCT; @@ -560,7 +647,6 @@ struct Vertex_MDL7 /** \struct BoneTransform_MDL7 * \brief bone transformation matrix structure used in MDL7 files */ -// --------------------------------------------------------------------------- struct BoneTransform_MDL7 { //! 4*3 @@ -582,7 +668,6 @@ struct BoneTransform_MDL7 /** \struct Frame_MDL7 * \brief Frame data structure used by MDL7 files */ -// --------------------------------------------------------------------------- struct Frame_MDL7 { char frame_name[AI_MDL7_MAX_FRAMENAMESIZE]; @@ -595,7 +680,6 @@ struct Frame_MDL7 /** \struct SimpleFrame * \brief Data structure for a simple frame */ -// --------------------------------------------------------------------------- struct SimpleFrame { //! Minimum vertex of the bounding box @@ -615,7 +699,6 @@ struct SimpleFrame /** \struct Frame * \brief Model frame data structure */ -// --------------------------------------------------------------------------- struct Frame { //! 0 = simple frame, !0 = group frame @@ -626,6 +709,7 @@ struct Frame } PACK_STRUCT; +// --------------------------------------------------------------------------- struct SimpleFrame_MDLn_SP { //! Minimum vertex of the bounding box @@ -645,7 +729,6 @@ struct SimpleFrame_MDLn_SP /** \struct GroupFrame * \brief Data structure for a group of frames */ -// --------------------------------------------------------------------------- struct GroupFrame { //! 0 = simple frame, !0 = group frame @@ -675,7 +758,6 @@ struct GroupFrame /** \struct IntFace_MDL7 * \brief Internal data structure to temporarily represent a face */ -// --------------------------------------------------------------------------- struct IntFace_MDL7 { // provide a constructor for our own convenience @@ -700,7 +782,6 @@ struct IntFace_MDL7 * which has been created from two single materials along with the * original material indices. */ -// --------------------------------------------------------------------------- struct IntMaterial_MDL7 { // provide a constructor for our own convenience @@ -717,6 +798,188 @@ struct IntMaterial_MDL7 unsigned int iOldMatIndices[2]; }; + +// --------------------------------------------------------------------------- +/** \struct IntBone_MDL7 + * \brief Internal data structure to represent a bone in a MDL7 file with + * all of its animation channels assigned to it. + */ +struct IntBone_MDL7 : aiBone +{ + //! Default constructor + IntBone_MDL7() : iParent (0xffff) + { + pkeyPositions.reserve(30); + pkeyScalings.reserve(30); + pkeyRotations.reserve(30); + } + + //! Parent bone of the bone + uint64_t iParent; + + //! Relative position of the bone + aiVector3D vPosition; + + //! Array of position keys + std::vector pkeyPositions; + + //! Array of scaling keys + std::vector pkeyScalings; + + //! Array of rotation keys + std::vector pkeyRotations; +}; + +// --------------------------------------------------------------------------- +//! Describes a MDL7 frame +struct IntFrameInfo_MDL7 +{ + //! Construction from an existing frame header + IntFrameInfo_MDL7(const MDL::Frame_MDL7* _pcFrame,unsigned int _iIndex) + : pcFrame(_pcFrame), iIndex(_iIndex) + {} + + //! Index of the frame + unsigned int iIndex; + + //! Points to the header of the frame + const MDL::Frame_MDL7* pcFrame; +}; + +// --------------------------------------------------------------------------- +//! Describes a MDL7 mesh group +struct IntGroupInfo_MDL7 +{ + //! Default constructor + IntGroupInfo_MDL7() : + iIndex(0), + pcGroup(NULL), pcGroupUVs(NULL), + pcGroupTris(NULL), pcGroupVerts(NULL) + {} + + //! Construction from an existing group header + IntGroupInfo_MDL7(const MDL::Group_MDL7* _pcGroup,unsigned int _iIndex) + : + pcGroup(_pcGroup),iIndex(_iIndex) + {} + + //! Index of the group + unsigned int iIndex; + + //! Points to the header of the group + const MDL::Group_MDL7* pcGroup; + + //! Points to the beginning of the uv coordinate section + const MDL::TexCoord_MDL7* pcGroupUVs; + + //! Points to the beginning of the triangle section + const MDL::Triangle_MDL7* pcGroupTris; + + //! Points to the beginning of the vertex section + const MDL::Vertex_MDL7* pcGroupVerts; +}; + +// --------------------------------------------------------------------------- +//! Holds the data that belongs to a MDL7 mesh group +struct IntGroupData_MDL7 +{ + IntGroupData_MDL7() + : pcFaces(NULL), bNeed2UV(false) + {} + + //! Array of faces that belong to the group + MDL::IntFace_MDL7* pcFaces; + + //! Array of vertex positions + std::vector vPositions; + + //! Array of vertex normals + std::vector vNormals; + + //! Array of bones indices + std::vector aiBones; + + //! First UV coordinate set + std::vector vTextureCoords1; + + //! Optional second UV coordinate set + std::vector vTextureCoords2; + + //! Specifies whether there are two texture + //! coordinate sets required + bool bNeed2UV; +}; + +// --------------------------------------------------------------------------- +//! Holds data from an MDL7 file that is shared by all mesh groups +struct IntSharedData_MDL7 +{ + //! Default constructor + IntSharedData_MDL7() + { + abNeedMaterials.reserve(10); + } + + //! Destruction: properly delete all allocated resources + ~IntSharedData_MDL7() + { + // kill all bones + if (this->apcOutBones) + { + for (unsigned int m = 0; m < iNum;++m) + delete this->apcOutBones[m]; + delete[] this->apcOutBones; + } + } + + //! Specifies which materials are used + std::vector abNeedMaterials; + + //! List of all materials + std::vector pcMats; + + //! List of all bones + IntBone_MDL7** apcOutBones; + + //! number of bones + unsigned int iNum; +}; + +// --------------------------------------------------------------------------- +//! Contains input data for GenerateOutputMeshes_3DGS_MDL7 +struct IntSplittedGroupData_MDL7 +{ + //! Construction from a given shared data set + IntSplittedGroupData_MDL7(IntSharedData_MDL7& _shared, + std::vector& _avOutList) + + : shared(_shared), avOutList(_avOutList) + { + } + + //! Destruction: properly delete all allocated resources + ~IntSplittedGroupData_MDL7() + { + // kill all face lists + if(this->aiSplit) + { + for (unsigned int m = 0; m < shared.pcMats.size();++m) + delete this->aiSplit[m]; + delete[] this->aiSplit; + } + } + + //! Contains a list of all faces per material + std::vector** aiSplit; + + //! Shared data for all groups of the model + IntSharedData_MDL7& shared; + + //! List of meshes + std::vector& avOutList; +}; + + };}; // end namespaces #endif // !! AI_MDLFILEHELPER_H_INC \ No newline at end of file diff --git a/code/MDLLoader.cpp b/code/MDLLoader.cpp index 4e49715ae..f2ea2df28 100644 --- a/code/MDLLoader.cpp +++ b/code/MDLLoader.cpp @@ -39,19 +39,24 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------------------------- */ -/** @file Implementation of the MDL importer class */ +/** @file Implementation of the main parts of the MDL importer class */ +// internal headers #include "MaterialSystem.h" #include "MDLLoader.h" #include "MDLDefaultColorMap.h" -#include "../include/DefaultLogger.h" +#include "MD2FileData.h" +#include "qnan.h" +// public ASSIMP headers +#include "../include/DefaultLogger.h" #include "../include/IOStream.h" #include "../include/IOSystem.h" #include "../include/aiMesh.h" #include "../include/aiScene.h" #include "../include/aiAssert.h" +// boost headers #include using namespace Assimp; @@ -60,25 +65,21 @@ extern float g_avNormals[162][3]; // ------------------------------------------------------------------------------------------------ -inline bool is_qnan(float p_fIn) -{ - // NOTE: Comparison against qnan is generally problematic - // because qnan == qnan is false AFAIK - union FTOINT - { - float fFloat; - int32_t iInt; - } one, two; - one.fFloat = std::numeric_limits::quiet_NaN(); - two.fFloat = p_fIn; +// macros used by the MDL7 loader + +#if (!defined _AI_MDL7_ACCESS) +# define _AI_MDL7_ACCESS(_data, _index, _limit, _type) \ + (*((const _type*)(((const char*)_data) + _index * _limit))) +#endif +#if (!defined _AI_MDL7_ACCESS_PTR) +# define _AI_MDL7_ACCESS_PTR(_data, _index, _limit, _type) \ + ((const _type*)(((const char*)_data) + _index * _limit)) +#endif +#if (!defined _AI_MDL7_ACCESS_VERT) +# define _AI_MDL7_ACCESS_VERT(_data, _index, _limit) \ + _AI_MDL7_ACCESS(_data,_index,_limit,MDL::Vertex_MDL7) +#endif - return (one.iInt == two.iInt); -} -// ------------------------------------------------------------------------------------------------ -inline bool is_not_qnan(float p_fIn) -{ - return !is_qnan(p_fIn); -} // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer @@ -126,467 +127,262 @@ void MDLImporter::InternReadFile( throw new ImportErrorException( "Failed to open MDL file " + pFile + "."); } - // check whether the ply file is large enough to contain - // at least the file header - size_t fileSize = file->FileSize(); - if( fileSize < sizeof(MDL::Header)) + // this should work for all other types of MDL files, too ... + // the quake header is one of the smallest, afaik + this->iFileSize = (unsigned int)file->FileSize(); + if( this->iFileSize < sizeof(MDL::Header)) { - throw new ImportErrorException( ".mdl File is too small."); + throw new ImportErrorException( "MDL File is too small."); } // allocate storage and copy the contents of the file to a memory buffer this->pScene = pScene; this->pIOHandler = pIOHandler; - this->mBuffer = new unsigned char[fileSize+1]; - file->Read( (void*)mBuffer, 1, fileSize); + this->mBuffer = new unsigned char[this->iFileSize+1]; + file->Read( (void*)mBuffer, 1, this->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 ... + this->mBuffer[this->iFileSize] = '\0'; + uint32_t iMagicWord = *((uint32_t*)this->mBuffer); // determine the file subtype and call the appropriate member function + try { // Original Quake1 format - this->m_pcHeader = (const MDL::Header*)this->mBuffer; - if (AI_MDL_MAGIC_NUMBER_BE == this->m_pcHeader->ident || - AI_MDL_MAGIC_NUMBER_LE == this->m_pcHeader->ident) + if (AI_MDL_MAGIC_NUMBER_BE == iMagicWord || + AI_MDL_MAGIC_NUMBER_LE == iMagicWord) { DefaultLogger::get()->debug("MDL subtype: Quake 1, magic word is IDPO"); + this->iGSFileVersion = 0; + this->InternReadFile_Quake1(); + } + // GameStudio A 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"); + this->iGSFileVersion = 2; this->InternReadFile_Quake1(); } // GameStudio A4 MDL3 format - else if (AI_MDL_MAGIC_NUMBER_BE_GS4 == this->m_pcHeader->ident || - AI_MDL_MAGIC_NUMBER_LE_GS4 == this->m_pcHeader->ident) + 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"); this->iGSFileVersion = 3; - this->InternReadFile_GameStudio(); + this->InternReadFile_3DGS_MDL345(); } // GameStudio A5+ MDL4 format - else if (AI_MDL_MAGIC_NUMBER_BE_GS5a == this->m_pcHeader->ident || - AI_MDL_MAGIC_NUMBER_LE_GS5a == this->m_pcHeader->ident) + 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"); this->iGSFileVersion = 4; - this->InternReadFile_GameStudio(); + this->InternReadFile_3DGS_MDL345(); } // GameStudio A5+ MDL5 format - else if (AI_MDL_MAGIC_NUMBER_BE_GS5b == this->m_pcHeader->ident || - AI_MDL_MAGIC_NUMBER_LE_GS5b == this->m_pcHeader->ident) + 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"); this->iGSFileVersion = 5; - this->InternReadFile_GameStudio(); + this->InternReadFile_3DGS_MDL345(); } - // GameStudio A6+ MDL6 format - else if (AI_MDL_MAGIC_NUMBER_BE_GS6 == this->m_pcHeader->ident || - AI_MDL_MAGIC_NUMBER_LE_GS6 == this->m_pcHeader->ident) + // GameStudio A6+ MDL6 format (not sure whether it is really existing ... ) + else if (AI_MDL_MAGIC_NUMBER_BE_GS6 == iMagicWord || + AI_MDL_MAGIC_NUMBER_LE_GS6 == iMagicWord) { DefaultLogger::get()->debug("MDL subtype: 3D GameStudio A6, magic word is MDL6"); this->iGSFileVersion = 6; - this->InternReadFile_GameStudio(); + this->InternReadFile_3DGS_MDL345(); } // GameStudio A7 MDL7 format - else if (AI_MDL_MAGIC_NUMBER_BE_GS7 == this->m_pcHeader->ident || - AI_MDL_MAGIC_NUMBER_LE_GS7 == this->m_pcHeader->ident) + 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"); this->iGSFileVersion = 7; - this->InternReadFile_GameStudioA7(); + this->InternReadFile_3DGS_MDL7(); } // IDST/IDSQ Format (CS:S/HL˛, etc ...) - else if (AI_MDL_MAGIC_NUMBER_BE_HL2a == this->m_pcHeader->ident || - AI_MDL_MAGIC_NUMBER_LE_HL2a == this->m_pcHeader->ident || - AI_MDL_MAGIC_NUMBER_BE_HL2b == this->m_pcHeader->ident || - AI_MDL_MAGIC_NUMBER_LE_HL2b == this->m_pcHeader->ident) + 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: CS:S\\HL˛, magic word is IDST/IDSQ"); + this->iGSFileVersion = 0; this->InternReadFile_HL2(); } else { + // print the magic word to the logger + char szBuffer[5]; + szBuffer[0] = ((char*)&iMagicWord)[0]; + szBuffer[1] = ((char*)&iMagicWord)[1]; + szBuffer[2] = ((char*)&iMagicWord)[2]; + szBuffer[3] = ((char*)&iMagicWord)[3]; + szBuffer[4] = '\0'; + // we're definitely unable to load this file throw new ImportErrorException( "Unknown MDL subformat " + pFile + - ". Magic word is not known"); + ". Magic word (" + szBuffer + ") is not known"); + } + + } catch (ImportErrorException* ex) { + delete[] this->mBuffer; + throw ex; } // make sure that the normals are facing outwards + // (mainly this applies to MDL7 (due to the FlipNormals option in MED). + // However there are some invalid models in other format, too) for (unsigned int i = 0; i < pScene->mNumMeshes;++i) this->FlipNormals(pScene->mMeshes[i]); - // delete the file buffer + // delete the file buffer and cleanup delete[] this->mBuffer; + DEBUG_INVALIDATE_PTR(this->mBuffer); + DEBUG_INVALIDATE_PTR(this->pIOHandler); + DEBUG_INVALIDATE_PTR(this->pScene); return; } // ------------------------------------------------------------------------------------------------ -void MDLImporter::SearchPalette(const unsigned char** pszColorMap) +void MDLImporter::SizeCheck(const void* szPos) { - // now try to find the color map in the current directory - IOStream* pcStream = this->pIOHandler->Open("colormap.lmp","rb"); - - const unsigned char* szColorMap = (const unsigned char*)::g_aclrDefaultColorMap; - if(pcStream) + if (!szPos || (const unsigned char*)szPos > this->mBuffer + this->iFileSize) { - if (pcStream->FileSize() >= 768) - { - szColorMap = new unsigned char[256*3]; - pcStream->Read(const_cast(szColorMap),256*3,1); - - DefaultLogger::get()->info("Found valid colormap.lmp in directory. " - "It will be used to decode embedded textures in palletized formats."); - } - delete pcStream; - pcStream = NULL; + throw new ImportErrorException("Invalid file. The file is too small " + "or contains invalid data."); } - *pszColorMap = szColorMap; - return; } // ------------------------------------------------------------------------------------------------ -void MDLImporter::FreePalette(const unsigned char* szColorMap) +void MDLImporter::SizeCheck(const void* szPos, const char* szFile, unsigned int iLine) { - if (szColorMap != (const unsigned char*)::g_aclrDefaultColorMap) + if (!szFile)return SizeCheck(szPos); + if (!szPos || (const unsigned char*)szPos > this->mBuffer + this->iFileSize) { - delete[] szColorMap; + // 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]; +#if _MSC_VER >= 1400 + ::sprintf_s(szBuffer, +#else + ::sprintf(szBuffer, +#endif + "Invalid file. The file is too small " + "or contains invalid data (File: %s Line: %i)",szFilePtr,iLine); + + throw new ImportErrorException(szBuffer); } - return; } // ------------------------------------------------------------------------------------------------ -void MDLImporter::CreateTextureARGB8(const unsigned char* szData) +void MDLImporter::ValidateHeader_Quake1(const MDL::Header* pcHeader) { - // allocate a new texture object - aiTexture* pcNew = new aiTexture(); - pcNew->mWidth = this->m_pcHeader->skinwidth; - pcNew->mHeight = this->m_pcHeader->skinheight; - - pcNew->pcData = new aiTexel[pcNew->mWidth * pcNew->mHeight]; - - const unsigned char* szColorMap; - this->SearchPalette(&szColorMap); - - // copy texture data - for (unsigned int i = 0; i < pcNew->mWidth*pcNew->mHeight;++i) + // some values may not be NULL + if (!pcHeader->num_frames) { - const unsigned char val = szData[i]; - const unsigned char* sz = &szColorMap[val*3]; - - pcNew->pcData[i].a = 0xFF; - pcNew->pcData[i].r = *sz++; - pcNew->pcData[i].g = *sz++; - pcNew->pcData[i].b = *sz; + throw new ImportErrorException( "[Quake 1 MDL] There are no frames in the file"); + } + if (!pcHeader->num_verts) + { + throw new ImportErrorException( "[Quake 1 MDL] There are no vertices in the file"); + } + if (!pcHeader->num_tris) + { + throw new ImportErrorException( "[Quake 1 MDL] There are no triangles in the file"); } - this->FreePalette(szColorMap); - - // store the texture - aiTexture** pc = this->pScene->mTextures; - this->pScene->mTextures = new aiTexture*[this->pScene->mNumTextures+1]; - for (unsigned int i = 0; i < this->pScene->mNumTextures;++i) - this->pScene->mTextures[i] = pc[i]; - - this->pScene->mTextures[this->pScene->mNumTextures] = pcNew; - this->pScene->mNumTextures++; - delete[] pc; - return; -} -// ------------------------------------------------------------------------------------------------ -void MDLImporter::CreateTextureARGB8_GS4(const unsigned char* szData, - unsigned int iType, - unsigned int* piSkip) -{ - ai_assert(NULL != piSkip); - - // allocate a new texture object - aiTexture* pcNew = new aiTexture(); - pcNew->mWidth = this->m_pcHeader->skinwidth; - pcNew->mHeight = this->m_pcHeader->skinheight; - - pcNew->pcData = new aiTexel[pcNew->mWidth * pcNew->mHeight]; - - // 8 Bit paletized. Use Q1 default palette. - if (0 == iType) + // check whether the maxima are exceeded ... + if (pcHeader->num_verts > AI_MDL_MAX_VERTS) { - const unsigned char* szColorMap; - this->SearchPalette(&szColorMap); - - // copy texture data - unsigned int i = 0; - for (; i < pcNew->mWidth*pcNew->mHeight;++i) - { - const unsigned char val = szData[i]; - const unsigned char* sz = &szColorMap[val*3]; - - pcNew->pcData[i].a = 0xFF; - pcNew->pcData[i].r = *sz++; - pcNew->pcData[i].g = *sz++; - pcNew->pcData[i].b = *sz; - } - *piSkip = i; - - this->FreePalette(szColorMap); + DefaultLogger::get()->warn("Quake 1 MDL model has more than AI_MDL_MAX_VERTS vertices"); } - // R5G6B5 format - else if (2 == iType) + if (pcHeader->num_tris > AI_MDL_MAX_TRIANGLES) { - // copy texture data - unsigned int i = 0; - for (i = 0; i < pcNew->mWidth*pcNew->mHeight;++i) - { - MDL::RGB565 val = ((MDL::RGB565*)szData)[i]; - - pcNew->pcData[i].a = 0xFF; - pcNew->pcData[i].r = (unsigned char)val.b << 3; - pcNew->pcData[i].g = (unsigned char)val.g << 2; - pcNew->pcData[i].b = (unsigned char)val.r << 3; - } - *piSkip = i * 2; + DefaultLogger::get()->warn("Quake 1 MDL model has more than AI_MDL_MAX_TRIANGLES triangles"); } - // ARGB4 format - else if (3 == iType) + if (pcHeader->num_frames > AI_MDL_MAX_FRAMES) { - // copy texture data - unsigned int i = 0; - for (i = 0; i < pcNew->mWidth*pcNew->mHeight;++i) - { - MDL::ARGB4 val = ((MDL::ARGB4*)szData)[i]; - - pcNew->pcData[i].a = (unsigned char)val.a << 4; - pcNew->pcData[i].r = (unsigned char)val.r << 4; - pcNew->pcData[i].g = (unsigned char)val.g << 4; - pcNew->pcData[i].b = (unsigned char)val.b << 4; - } - *piSkip = i * 2; + DefaultLogger::get()->warn("Quake 1 MDL model has more than AI_MDL_MAX_FRAMES frames"); } - // store the texture - aiTexture** pc = this->pScene->mTextures; - this->pScene->mTextures = new aiTexture*[this->pScene->mNumTextures+1]; - for (unsigned int i = 0; i < this->pScene->mNumTextures;++i) - this->pScene->mTextures[i] = pc[i]; - - this->pScene->mTextures[this->pScene->mNumTextures] = pcNew; - this->pScene->mNumTextures++; - delete[] pc; - return; -} -// ------------------------------------------------------------------------------------------------ -void MDLImporter::ParseTextureColorData(const unsigned char* szData, - unsigned int iType, - unsigned int* piSkip, - aiTexture* pcNew) -{ - // allocate storage for the texture image - pcNew->pcData = new aiTexel[pcNew->mWidth * pcNew->mHeight]; - - // R5G6B5 format (with or without MIPs) - if (2 == iType || 10 == iType) + // (this does not apply for 3DGS MDLs) + if (!this->iGSFileVersion && pcHeader->version != AI_MDL_VERSION) { - // copy texture data - unsigned int i = 0; - for (i = 0; i < pcNew->mWidth*pcNew->mHeight;++i) - { - MDL::RGB565 val = ((MDL::RGB565*)szData)[i]; + DefaultLogger::get()->warn("Quake 1 MDL model has an unknown version: AI_MDL_VERSION (=6) is " + "the expected file format version"); + } - pcNew->pcData[i].a = 0xFF; - pcNew->pcData[i].r = (unsigned char)val.b << 3; - pcNew->pcData[i].g = (unsigned char)val.g << 2; - pcNew->pcData[i].b = (unsigned char)val.r << 3; - } - *piSkip = i * 2; - - // apply MIP maps - if (10 == iType) + if (pcHeader->num_skins) + { + if(!pcHeader->skinwidth || !pcHeader->skinheight) { - *piSkip += ((i >> 2) + (i >> 4) + (i >> 6)) << 1; + DefaultLogger::get()->warn("Skin width or height are 0. Division through " + "zero would occur ..."); } } - // ARGB4 format (with or without MIPs) - else if (3 == iType || 11 == iType) - { - // copy texture data - unsigned int i = 0; - for (i = 0; i < pcNew->mWidth*pcNew->mHeight;++i) - { - MDL::ARGB4 val = ((MDL::ARGB4*)szData)[i]; - - pcNew->pcData[i].a = (unsigned char)val.a << 4; - pcNew->pcData[i].r = (unsigned char)val.r << 4; - pcNew->pcData[i].g = (unsigned char)val.g << 4; - pcNew->pcData[i].b = (unsigned char)val.b << 4; - } - *piSkip = i * 2; - - // apply MIP maps - if (11 == iType) - { - *piSkip += ((i >> 2) + (i >> 4) + (i >> 6)) << 1; - } - } - // RGB8 format (with or without MIPs) - else if (4 == iType || 12 == iType) - { - // copy texture data - unsigned int i = 0; - for (i = 0; i < pcNew->mWidth*pcNew->mHeight;++i) - { - const unsigned char* _szData = &szData[i*3]; - - pcNew->pcData[i].a = 0xFF; - pcNew->pcData[i].b = *_szData++; - pcNew->pcData[i].g = *_szData++; - pcNew->pcData[i].r = *_szData; - } - // apply MIP maps - *piSkip = i * 3; - if (12 == iType) - { - *piSkip += ((i >> 2) + (i >> 4) + (i >> 6)) *3; - } - } - // ARGB8 format (with ir without MIPs) - else if (5 == iType || 13 == iType) - { - // copy texture data - unsigned int i = 0; - for (i = 0; i < pcNew->mWidth*pcNew->mHeight;++i) - { - const unsigned char* _szData = &szData[i*4]; - - pcNew->pcData[i].b = *_szData++; - pcNew->pcData[i].g = *_szData++; - pcNew->pcData[i].r = *_szData++; - pcNew->pcData[i].a = *_szData; - } - // apply MIP maps - *piSkip = i << 2; - if (13 == iType) - { - *piSkip += (i + (i >> 2) + (i >> 4) + (i >> 6)) << 2; - } - } - // palletized 8 bit texture. As for Quake 1 - else if (0 == iType) - { - const unsigned char* szColorMap; - this->SearchPalette(&szColorMap); - - // copy texture data - unsigned int i = 0; - for (; i < pcNew->mWidth*pcNew->mHeight;++i) - { - const unsigned char val = szData[i]; - const unsigned char* sz = &szColorMap[val*3]; - - pcNew->pcData[i].a = 0xFF; - pcNew->pcData[i].r = *sz++; - pcNew->pcData[i].g = *sz++; - pcNew->pcData[i].b = *sz; - } - *piSkip = i; - - this->FreePalette(szColorMap); - } - return; -} -// ------------------------------------------------------------------------------------------------ -void MDLImporter::CreateTextureARGB8_GS5(const unsigned char* szData, - unsigned int iType, - unsigned int* piSkip) -{ - ai_assert(NULL != piSkip); - - // allocate a new texture object - aiTexture* pcNew = new aiTexture(); - - // first read the size of the texture - pcNew->mWidth = *((uint32_t*)szData); - szData += sizeof(uint32_t); - - pcNew->mHeight = *((uint32_t*)szData); - szData += sizeof(uint32_t); - - if (6 == iType) - { - // this is a compressed texture in DDS format - *piSkip = pcNew->mWidth; - - pcNew->mHeight = 0; - pcNew->achFormatHint[0] = 'd'; - pcNew->achFormatHint[1] = 'd'; - pcNew->achFormatHint[2] = 's'; - pcNew->achFormatHint[3] = '\0'; - - pcNew->pcData = (aiTexel*) new unsigned char[pcNew->mWidth]; - memcpy(pcNew->pcData,szData,pcNew->mWidth); - } - else - { - // parse the color data of the texture - this->ParseTextureColorData(szData,iType, - piSkip,pcNew); - } - *piSkip += sizeof(uint32_t) * 2; - - // store the texture - aiTexture** pc = this->pScene->mTextures; - this->pScene->mTextures = new aiTexture*[this->pScene->mNumTextures+1]; - for (unsigned int i = 0; i < this->pScene->mNumTextures;++i) - this->pScene->mTextures[i] = pc[i]; - - this->pScene->mTextures[this->pScene->mNumTextures] = pcNew; - this->pScene->mNumTextures++; - delete[] pc; - return; } // ------------------------------------------------------------------------------------------------ void MDLImporter::InternReadFile_Quake1( ) { ai_assert(NULL != pScene); - if(0 == this->m_pcHeader->num_frames) - { - throw new ImportErrorException( "[Quake 1 MDL] No frames found"); - } - - // allocate enough storage to hold all vertices and triangles - aiMesh* pcMesh = new aiMesh(); + const MDL::Header* pcHeader = (const MDL::Header*)this->mBuffer; + ValidateHeader_Quake1(pcHeader); // current cursor position in the file - const unsigned char* szCurrent = (const unsigned char*)(this->m_pcHeader+1); + const unsigned char* szCurrent = (const unsigned char*)(pcHeader+1); // need to read all textures - for (unsigned int i = 0; i < (unsigned int)this->m_pcHeader->num_skins;++i) + for (unsigned int i = 0; i < (unsigned int)pcHeader->num_skins;++i) { union{const MDL::Skin* pcSkin;const MDL::GroupSkin* pcGroupSkin;}; pcSkin = (const MDL::Skin*)szCurrent; - if (0 == pcSkin->group) - { - // create one output image - this->CreateTextureARGB8((unsigned char*)pcSkin + sizeof(uint32_t)); - // need to skip one image - szCurrent += this->m_pcHeader->skinheight * this->m_pcHeader->skinwidth+ sizeof(uint32_t); - } - else + + // Quake 1 groupskins + if (1 == pcSkin->group) { // need to skip multiple images const unsigned int iNumImages = (unsigned int)pcGroupSkin->nb; szCurrent += sizeof(uint32_t) * 2; - if (0 != iNumImages) + if (0 != iNumImages) { - // however, create only one output image (the first) - this->CreateTextureARGB8(szCurrent + iNumImages * sizeof(float)); - - for (unsigned int a = 0; a < iNumImages;++a) - { - szCurrent += this->m_pcHeader->skinheight * this->m_pcHeader->skinwidth + - sizeof(float); + 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 ? 0xffffffff : 0; + this->CreateTexture_3DGS_MDL4(szCurrent,pcSkin->group,&iSkip); + szCurrent += iSkip; + } } // get a pointer to the texture coordinates const MDL::TexCoord* pcTexCoords = (const MDL::TexCoord*)szCurrent; - szCurrent += sizeof(MDL::TexCoord) * this->m_pcHeader->num_verts; + szCurrent += sizeof(MDL::TexCoord) * pcHeader->num_verts; // get a pointer to the triangles const MDL::Triangle* pcTriangles = (const MDL::Triangle*)szCurrent; - szCurrent += sizeof(MDL::Triangle) * this->m_pcHeader->num_tris; + szCurrent += sizeof(MDL::Triangle) * pcHeader->num_tris; + VALIDATE_FILE_SIZE(szCurrent); // now get a pointer to the first frame in the file const MDL::Frame* pcFrames = (const MDL::Frame*)szCurrent; @@ -606,8 +402,16 @@ void MDLImporter::InternReadFile_Quake1( ) const MDL::Vertex* pcVertices = (const MDL::Vertex*) ((pcFirstFrame->name) + sizeof(pcFirstFrame->name)); - pcMesh->mNumVertices = this->m_pcHeader->num_tris * 3; - pcMesh->mNumFaces = this->m_pcHeader->num_tris; + VALIDATE_FILE_SIZE((const unsigned char*)(pcVertices + pcHeader->num_verts)); + + // setup materials + this->SetupMaterialProperties_3DGS_MDL5_Quake1(); + + // allocate enough storage to hold all vertices and triangles + aiMesh* pcMesh = new aiMesh(); + + 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]; @@ -615,41 +419,17 @@ void MDLImporter::InternReadFile_Quake1( ) pcMesh->mNumUVComponents[0] = 2; // there won't be more than one mesh inside the file - pScene->mNumMaterials = 1; pScene->mRootNode = new aiNode(); pScene->mRootNode->mNumMeshes = 1; pScene->mRootNode->mMeshes = new unsigned int[1]; pScene->mRootNode->mMeshes[0] = 0; - pScene->mMaterials = new aiMaterial*[1]; - pScene->mMaterials[0] = new MaterialHelper(); pScene->mNumMeshes = 1; pScene->mMeshes = new aiMesh*[1]; pScene->mMeshes[0] = pcMesh; - // setup the material properties - const int iMode = (int)aiShadingMode_Gouraud; - MaterialHelper* pcHelper = (MaterialHelper*)pScene->mMaterials[0]; - pcHelper->AddProperty(&iMode, 1, AI_MATKEY_SHADING_MODEL); - - aiColor3D clr; - clr.b = clr.g = clr.r = 1.0f; - pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_DIFFUSE); - pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_SPECULAR); - - clr.b = clr.g = clr.r = 0.05f; - pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_AMBIENT); - - if (0 != this->m_pcHeader->num_skins) - { - aiString szString; - memcpy(szString.data,AI_MAKE_EMBEDDED_TEXNAME(0),3); - szString.length = 2; - pcHelper->AddProperty(&szString,AI_MATKEY_TEXTURE_DIFFUSE(0)); - } - // now iterate through all triangles unsigned int iCurrent = 0; - for (unsigned int i = 0; i < (unsigned int) this->m_pcHeader->num_tris;++i) + 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; @@ -661,33 +441,26 @@ void MDLImporter::InternReadFile_Quake1( ) // read vertices unsigned int iIndex = pcTriangles->vertex[c]; - if (iIndex >= (unsigned int)this->m_pcHeader->num_verts) + if (iIndex >= (unsigned int)pcHeader->num_verts) { - iIndex = this->m_pcHeader->num_verts-1; + 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] * this->m_pcHeader->scale[0]; - vec.x += this->m_pcHeader->translate[0]; + vec.x = (float)pcVertices[iIndex].v[0] * pcHeader->scale[0]; + vec.x += pcHeader->translate[0]; // (flip z and y component) - vec.z = (float)pcVertices[iIndex].v[1] * this->m_pcHeader->scale[1]; - vec.z += this->m_pcHeader->translate[1]; + vec.z = (float)pcVertices[iIndex].v[1] * pcHeader->scale[1]; + vec.z += pcHeader->translate[1]; - vec.y = (float)pcVertices[iIndex].v[2] * this->m_pcHeader->scale[2]; - vec.y += this->m_pcHeader->translate[2]; - - // flip the Z-axis - //pcMesh->mVertices[iBase+c].z *= -1.0f; + vec.y = (float)pcVertices[iIndex].v[2] * pcHeader->scale[2]; + vec.y += pcHeader->translate[2]; // read the normal vector from the precalculated normal table - pcMesh->mNormals[iCurrent] = *((const aiVector3D*)(&g_avNormals[std::min( - int(pcVertices[iIndex].normalIndex), - int(sizeof(g_avNormals) / sizeof(g_avNormals[0]))-1)])); - - //pcMesh->mNormals[iBase+c].z *= -1.0f; + MD2::LookupNormalIndex(pcVertices[iIndex].normalIndex,pcMesh->mNormals[iCurrent]); std::swap ( pcMesh->mNormals[iCurrent].y,pcMesh->mNormals[iCurrent].z ); // read texture coordinates @@ -698,12 +471,12 @@ void MDLImporter::InternReadFile_Quake1( ) if (0 == pcTriangles->facesfront && 0 != pcTexCoords[iIndex].onseam) { - s += this->m_pcHeader->skinwidth * 0.5f; + 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) / this->m_pcHeader->skinwidth; - pcMesh->mTextureCoords[0][iCurrent].y = 1.0f-(t + 0.5f) / this->m_pcHeader->skinheight; + 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; @@ -714,38 +487,78 @@ void MDLImporter::InternReadFile_Quake1( ) return; } // ------------------------------------------------------------------------------------------------ -void MDLImporter::InternReadFile_GameStudio( ) +void MDLImporter::SetupMaterialProperties_3DGS_MDL5_Quake1( ) +{ + // get a pointer to the file header + const MDL::Header* const pcHeader = (const MDL::Header*)this->mBuffer; + + // allocate ONE material + pScene->mMaterials = new aiMaterial*[1]; + pScene->mMaterials[0] = new MaterialHelper(); + pScene->mNumMaterials = 1; + + // setup the material properties + const int iMode = (int)aiShadingMode_Gouraud; + MaterialHelper* const pcHelper = (MaterialHelper*)pScene->mMaterials[0]; + pcHelper->AddProperty(&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->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(&clr, 1,AI_MATKEY_COLOR_DIFFUSE); + pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_SPECULAR); + + clr.r *= 0.05f;clr.g *= 0.05f; + clr.b *= 0.05f;clr.a = 1.0f; + pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_AMBIENT); +} +// ------------------------------------------------------------------------------------------------ +void MDLImporter::InternReadFile_3DGS_MDL345( ) { ai_assert(NULL != pScene); - if(0 == this->m_pcHeader->num_frames) - { - throw new ImportErrorException( "[3DGS MDL] No frames found"); - } - - // allocate enough storage to hold all vertices and triangles - aiMesh* pcMesh = new aiMesh(); + // the header of MDL 3/4/5 is nearly identical to the original Quake1 header + const MDL::Header* pcHeader = (const MDL::Header*)this->mBuffer; + this->ValidateHeader_Quake1(pcHeader); // current cursor position in the file - const unsigned char* szCurrent = (const unsigned char*)(this->m_pcHeader+1); + const unsigned char* szCurrent = (const unsigned char*)(pcHeader+1); // need to read all textures - for (unsigned int i = 0; i < (unsigned int)this->m_pcHeader->num_skins;++i) + for (unsigned int i = 0; i < (unsigned int)pcHeader->num_skins;++i) { - union{const MDL::Skin* pcSkin;const MDL::GroupSkin* pcGroupSkin;}; + const MDL::Skin* pcSkin; pcSkin = (const MDL::Skin*)szCurrent; // create one output image - unsigned int iSkip = 0; + unsigned int iSkip = i ? 0xffffffff : 0; if (5 <= this->iGSFileVersion) { // MDL5 format could contain MIPmaps - this->CreateTextureARGB8_GS5((unsigned char*)pcSkin + sizeof(uint32_t), + this->CreateTexture_3DGS_MDL5((unsigned char*)pcSkin + sizeof(uint32_t), pcSkin->group,&iSkip); } else { - this->CreateTextureARGB8_GS4((unsigned char*)pcSkin + sizeof(uint32_t), + this->CreateTexture_3DGS_MDL4((unsigned char*)pcSkin + sizeof(uint32_t), pcSkin->group,&iSkip); } // need to skip one image @@ -754,76 +567,65 @@ void MDLImporter::InternReadFile_GameStudio( ) } // get a pointer to the texture coordinates const MDL::TexCoord_MDL3* pcTexCoords = (const MDL::TexCoord_MDL3*)szCurrent; - szCurrent += sizeof(MDL::TexCoord_MDL3) * this->m_pcHeader->synctype; + szCurrent += sizeof(MDL::TexCoord_MDL3) * pcHeader->synctype; - // NOTE: for MDLn formats syntype corresponds to the number of UV coords + // NOTE: for MDLn formats "synctype" corresponds to the number of UV coords // get a pointer to the triangles const MDL::Triangle_MDL3* pcTriangles = (const MDL::Triangle_MDL3*)szCurrent; - szCurrent += sizeof(MDL::Triangle_MDL3) * this->m_pcHeader->num_tris; + szCurrent += sizeof(MDL::Triangle_MDL3) * pcHeader->num_tris; - pcMesh->mNumVertices = this->m_pcHeader->num_tris * 3; - pcMesh->mNumFaces = this->m_pcHeader->num_tris; + VALIDATE_FILE_SIZE(szCurrent); + + // setup materials + this->SetupMaterialProperties_3DGS_MDL5_Quake1(); + + // allocate enough storage to hold all vertices and triangles + aiMesh* pcMesh = new aiMesh(); + + pcMesh->mNumVertices = pcHeader->num_tris * 3; + pcMesh->mNumFaces = pcHeader->num_tris; pcMesh->mFaces = new aiFace[pcMesh->mNumFaces]; - pcMesh->mNumUVComponents[0] = 2; // there won't be more than one mesh inside the file - pScene->mNumMaterials = 1; pScene->mRootNode = new aiNode(); pScene->mRootNode->mNumMeshes = 1; pScene->mRootNode->mMeshes = new unsigned int[1]; pScene->mRootNode->mMeshes[0] = 0; - pScene->mMaterials = new aiMaterial*[1]; - pScene->mMaterials[0] = new MaterialHelper(); pScene->mNumMeshes = 1; pScene->mMeshes = new aiMesh*[1]; pScene->mMeshes[0] = pcMesh; - std::vector vPositions; - std::vector vTexCoords; - std::vector vNormals; + // allocate output storage + pcMesh->mNumVertices = (unsigned int)pcHeader->num_tris*3; + pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices]; + pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices]; - vPositions.resize(pScene->mMeshes[0]->mNumFaces*3,aiVector3D()); - vTexCoords.resize(pScene->mMeshes[0]->mNumFaces*3,aiVector3D()); - vNormals.resize(pScene->mMeshes[0]->mNumFaces*3,aiVector3D()); - - // setup the material properties - const int iMode = (int)aiShadingMode_Gouraud; - MaterialHelper* pcHelper = (MaterialHelper*)pScene->mMaterials[0]; - pcHelper->AddProperty(&iMode, 1, AI_MATKEY_SHADING_MODEL); - - aiColor3D clr; - clr.b = clr.g = clr.r = 1.0f; - pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_DIFFUSE); - pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_SPECULAR); - - clr.b = clr.g = clr.r = 0.05f; - pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_AMBIENT); - - if (0 != this->m_pcHeader->num_skins) + if (pcHeader->synctype) { - aiString szString; - memcpy(szString.data,AI_MAKE_EMBEDDED_TEXNAME(0),3); - szString.length = 2; - pcHelper->AddProperty(&szString,AI_MATKEY_TEXTURE_DIFFUSE(0)); + pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices]; + pcMesh->mNumUVComponents[0] = 2; } // now get a pointer to the first frame in the file const MDL::Frame* pcFrames = (const MDL::Frame*)szCurrent; // byte packed vertices - if (0 == pcFrames->type || 3 == this->iGSFileVersion) + // *********************************************************************** + if (0 == pcFrames->type || 3 >= this->iGSFileVersion) { - const MDL::SimpleFrame* pcFirstFrame = (const MDL::SimpleFrame*) - (szCurrent + sizeof(uint32_t)); + const MDL::SimpleFrame* pcFirstFrame = + (const MDL::SimpleFrame*)(szCurrent + sizeof(uint32_t)); // get a pointer to the vertices - const MDL::Vertex* pcVertices = (const MDL::Vertex*) ((pcFirstFrame->name) + - sizeof(pcFirstFrame->name)); + 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) this->m_pcHeader->num_tris;++i) + 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; @@ -833,54 +635,33 @@ void MDLImporter::InternReadFile_GameStudio( ) { // read vertices unsigned int iIndex = pcTriangles->index_xyz[c]; - if (iIndex >= (unsigned int)this->m_pcHeader->num_verts) + if (iIndex >= (unsigned int)pcHeader->num_verts) { - iIndex = this->m_pcHeader->num_verts-1; - DefaultLogger::get()->warn("Index overflow in MDL3/4/5/6 vertex list"); + iIndex = pcHeader->num_verts-1; + DefaultLogger::get()->warn("Index overflow in MDLn vertex list"); } - aiVector3D& vec = vPositions[iCurrent]; - vec.x = (float)pcVertices[iIndex].v[0] * this->m_pcHeader->scale[0]; - vec.x += this->m_pcHeader->translate[0]; + aiVector3D& vec = pcMesh->mVertices[iCurrent]; + vec.x = (float)pcVertices[iIndex].v[0] * pcHeader->scale[0]; + vec.x += pcHeader->translate[0]; // (flip z and y component) - vec.z = (float)pcVertices[iIndex].v[1] * this->m_pcHeader->scale[1]; - vec.z += this->m_pcHeader->translate[1]; + vec.z = (float)pcVertices[iIndex].v[1] * pcHeader->scale[1]; + vec.z += pcHeader->translate[1]; - vec.y = (float)pcVertices[iIndex].v[2] * this->m_pcHeader->scale[2]; - vec.y += this->m_pcHeader->translate[2]; + vec.y = (float)pcVertices[iIndex].v[2] * pcHeader->scale[2]; + vec.y += pcHeader->translate[2]; // read the normal vector from the precalculated normal table - vNormals[iCurrent] = *((const aiVector3D*)(&g_avNormals[std::min( - int(pcVertices[iIndex].normalIndex), - int(sizeof(g_avNormals) / sizeof(g_avNormals[0]))-1)])); - - //vNormals[iBase+c].z *= -1.0f; - std::swap ( vNormals[iCurrent].y,vNormals[iCurrent].z ); + MD2::LookupNormalIndex(pcVertices[iIndex].normalIndex,pcMesh->mNormals[iCurrent]); + std::swap ( pcMesh->mNormals[iCurrent].y,pcMesh->mNormals[iCurrent].z ); // read texture coordinates - iIndex = pcTriangles->index_uv[c]; - - // validate UV indices - if (iIndex >= (unsigned int)this->m_pcHeader->synctype) + if (pcHeader->synctype) { - iIndex = this->m_pcHeader->synctype-1; - DefaultLogger::get()->warn("Index overflow in MDL3/4/5/6 UV coord list"); + this->ImportUVCoordinate_3DGS_MDL345(pcMesh->mTextureCoords[0][iCurrent], + pcTexCoords,pcTriangles->index_uv[c]); } - - float s = (float)pcTexCoords[iIndex].u; - float t = (float)pcTexCoords[iIndex].v; - - // Scale s and t to range from 0.0 to 1.0 - if (5 != this->iGSFileVersion && - this->m_pcHeader->skinwidth && this->m_pcHeader->skinheight) - { - s = (s + 0.5f) / this->m_pcHeader->skinwidth; - t = 1.0f-(t + 0.5f) / this->m_pcHeader->skinheight; - } - - vTexCoords[iCurrent].x = s; - vTexCoords[iCurrent].y = t; } pcMesh->mFaces[i].mIndices[0] = iTemp+2; pcMesh->mFaces[i].mIndices[1] = iTemp+1; @@ -889,20 +670,23 @@ void MDLImporter::InternReadFile_GameStudio( ) } } - // short packed vertices (duplicating the code is smaller than using templates ....) + // 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)); + 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)); + 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) this->m_pcHeader->num_tris;++i) + 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; @@ -912,54 +696,33 @@ void MDLImporter::InternReadFile_GameStudio( ) { // read vertices unsigned int iIndex = pcTriangles->index_xyz[c]; - if (iIndex >= (unsigned int)this->m_pcHeader->num_verts) + if (iIndex >= (unsigned int)pcHeader->num_verts) { - iIndex = this->m_pcHeader->num_verts-1; - DefaultLogger::get()->warn("Index overflow in MDL3/4/5/6 vertex list"); + iIndex = pcHeader->num_verts-1; + DefaultLogger::get()->warn("Index overflow in MDLn vertex list"); } - aiVector3D& vec = vPositions[iCurrent]; - vec.x = (float)pcVertices[iIndex].v[0] * this->m_pcHeader->scale[0]; - vec.x += this->m_pcHeader->translate[0]; + aiVector3D& vec = pcMesh->mVertices[iCurrent]; + vec.x = (float)pcVertices[iIndex].v[0] * pcHeader->scale[0]; + vec.x += pcHeader->translate[0]; // (flip z and y component) - vec.z = (float)pcVertices[iIndex].v[1] * this->m_pcHeader->scale[1]; - vec.z += this->m_pcHeader->translate[1]; + vec.z = (float)pcVertices[iIndex].v[1] * pcHeader->scale[1]; + vec.z += pcHeader->translate[1]; - vec.y = (float)pcVertices[iIndex].v[2] * this->m_pcHeader->scale[2]; - vec.y += this->m_pcHeader->translate[2]; + vec.y = (float)pcVertices[iIndex].v[2] * pcHeader->scale[2]; + vec.y += pcHeader->translate[2]; // read the normal vector from the precalculated normal table - vNormals[iCurrent] = *((const aiVector3D*)(&g_avNormals[std::min( - int(pcVertices[iIndex].normalIndex), - int(sizeof(g_avNormals) / sizeof(g_avNormals[0]))-1)])); - - std::swap ( vNormals[iCurrent].y,vNormals[iCurrent].z ); + MD2::LookupNormalIndex(pcVertices[iIndex].normalIndex,pcMesh->mNormals[iCurrent]); + std::swap ( pcMesh->mNormals[iCurrent].y,pcMesh->mNormals[iCurrent].z ); // read texture coordinates - iIndex = pcTriangles->index_uv[c]; - - // validate UV indices - if (iIndex >= (unsigned int) this->m_pcHeader->synctype) + if (pcHeader->synctype) { - iIndex = this->m_pcHeader->synctype-1; - DefaultLogger::get()->warn("Index overflow in MDL3/4/5/6 UV coord list"); + this->ImportUVCoordinate_3DGS_MDL345(pcMesh->mTextureCoords[0][iCurrent], + pcTexCoords,pcTriangles->index_uv[c]); } - - float s = (float)pcTexCoords[iIndex].u; - float t = (float)pcTexCoords[iIndex].v; - - - // Scale s and t to range from 0.0 to 1.0 - if (5 != this->iGSFileVersion && - this->m_pcHeader->skinwidth && this->m_pcHeader->skinheight) - { - s = (s + 0.5f) / this->m_pcHeader->skinwidth; - t = 1.0f-(t + 0.5f) / this->m_pcHeader->skinheight; - } - - vTexCoords[iCurrent].x = s; - vTexCoords[iCurrent].y = t; } pcMesh->mFaces[i].mIndices[0] = iTemp+2; pcMesh->mFaces[i].mIndices[1] = iTemp+1; @@ -970,287 +733,93 @@ void MDLImporter::InternReadFile_GameStudio( ) // For MDL5 we will need to build valid texture coordinates // basing upon the file loaded (only support one file as skin) - if (5 == this->iGSFileVersion) - { - if (0 != this->m_pcHeader->num_skins && 0 != this->pScene->mNumTextures) - { - 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 (0 == pcTex->mHeight) - { - uint32_t* piPtr = (uint32_t*)pcTex->pcData; - - piPtr += 3; - iHeight = (unsigned int)*piPtr++; - iWidth = (unsigned int)*piPtr; - } - else - { - iWidth = pcTex->mWidth; - iHeight = pcTex->mHeight; - } - - for (std::vector::iterator - i = vTexCoords.begin(); - i != vTexCoords.end();++i) - { - (*i).x /= iWidth; - (*i).y /= iHeight; - (*i).y = 1.0f- (*i).y; // DX to OGL - } - } - } - - // allocate output storage - pScene->mMeshes[0]->mNumVertices = (unsigned int)vPositions.size(); - pScene->mMeshes[0]->mVertices = new aiVector3D[vPositions.size()]; - pScene->mMeshes[0]->mNormals = new aiVector3D[vPositions.size()]; - pScene->mMeshes[0]->mTextureCoords[0] = new aiVector3D[vPositions.size()]; - - // memcpy() the data to the c-syle arrays - memcpy(pScene->mMeshes[0]->mVertices, &vPositions[0], - vPositions.size() * sizeof(aiVector3D)); - memcpy(pScene->mMeshes[0]->mNormals, &vNormals[0], - vPositions.size() * sizeof(aiVector3D)); - memcpy(pScene->mMeshes[0]->mTextureCoords[0], &vTexCoords[0], - vPositions.size() * sizeof(aiVector3D)); + if (0x5 == this->iGSFileVersion) + this->CalculateUVCoordinates_MDL5(); return; } // ------------------------------------------------------------------------------------------------ -void MDLImporter::ParseSkinLump_GameStudioA7( - const unsigned char* szCurrent, - const unsigned char** szCurrentOut, - std::vector& pcMats) +void MDLImporter::ImportUVCoordinate_3DGS_MDL345( + aiVector3D& vOut, + const MDL::TexCoord_MDL3* pcSrc, + unsigned int iIndex) { - ai_assert(NULL != szCurrent); - ai_assert(NULL != szCurrentOut); + ai_assert(NULL != pcSrc); - *szCurrentOut = szCurrent; - const MDL::Skin_MDL7* pcSkin = (const MDL::Skin_MDL7*)szCurrent; - szCurrent += 12; + const MDL::Header* const pcHeader = (const MDL::Header*)this->mBuffer; - // allocate an output material - MaterialHelper* pcMatOut = new MaterialHelper(); - pcMats.push_back(pcMatOut); - - aiTexture* pcNew = NULL; - - // get the type of the skin - unsigned int iMasked = (unsigned int)(pcSkin->typ & 0xF); - - // skip length of file name - szCurrent += AI_MDL7_MAX_TEXNAMESIZE; - - if (0x1 == iMasked) + // validate UV indices + if (iIndex >= (unsigned int) pcHeader->synctype) { - // ***** REFERENCE TO ANOTHER SKIN INDEX ***** - - // NOTE: Documentation - if you can call it a documentation, I prefer - // the expression "rubbish" - states it is currently unused. However, - // I don't know what ideas the terrible developers of Conitec will - // have tomorrow, so Im going to implement it. - int referrer = pcSkin->width; - pcMatOut->AddProperty(&referrer,1,"quakquakquak"); + iIndex = pcHeader->synctype-1; + DefaultLogger::get()->warn("Index overflow in MDLn UV coord list"); } - else if (0x6 == iMasked) + + 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 != this->iGSFileVersion) { - // ***** EMBEDDED DDS FILE ***** - if (1 != pcSkin->height) - { - DefaultLogger::get()->warn("Found a reference to an embedded DDS texture, " - "but texture height is not equal to 1, which is not supported by MED"); - } - - pcNew = new aiTexture(); - pcNew->mHeight = 0; - pcNew->achFormatHint[0] = 'd'; - pcNew->achFormatHint[1] = 'd'; - pcNew->achFormatHint[2] = 's'; - pcNew->achFormatHint[3] = '\0'; - - pcNew->pcData = (aiTexel*) new unsigned char[pcNew->mWidth]; - memcpy(pcNew->pcData,szCurrent,pcNew->mWidth); - szCurrent += pcSkin->width; + s = (s + 0.5f) / pcHeader->skinwidth; + t = 1.0f-(t + 0.5f) / pcHeader->skinheight; } - if (0x7 == iMasked) + + vOut.x = s; + vOut.y = t; + vOut.z = 0.0f; +} +// ------------------------------------------------------------------------------------------------ +void MDLImporter::CalculateUVCoordinates_MDL5() +{ + const MDL::Header* const pcHeader = (const MDL::Header*)this->mBuffer; + if (pcHeader->num_skins && this->pScene->mNumTextures) { - // ***** REFERENCE TO EXTERNAL FILE ***** - if (1 != pcSkin->height) + 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) { - DefaultLogger::get()->warn("Found a reference to an external texture, " - "but texture height is not equal to 1, which is not supported by MED"); - } + const uint32_t* piPtr = (uint32_t*)pcTex->pcData; - aiString szFile; - const size_t iLen = strlen((const char*)szCurrent); - size_t iLen2 = iLen+1; - iLen2 = iLen2 > MAXLEN ? MAXLEN : iLen2; - memcpy(szFile.data,(const char*)szCurrent,iLen2); - szFile.length = iLen; - - szCurrent += iLen2; - - // place this as diffuse texture - pcMatOut->AddProperty(&szFile,AI_MATKEY_TEXTURE_DIFFUSE(0)); - } - else if (0 != iMasked || 0 == pcSkin->typ) - { - // ***** STANDARD COLOR TEXTURE ***** - pcNew = new aiTexture(); - if (0 == pcSkin->height || 0 == pcSkin->width) - { - DefaultLogger::get()->warn("Found embedded texture, but its width " - "an height are both 0. Is this a joke?"); - - // generate an empty chess pattern - pcNew->mWidth = pcNew->mHeight = 8; - pcNew->pcData = new aiTexel[64]; - for (unsigned int x = 0; x < 8;++x) + piPtr += 3; + iHeight = (unsigned int)*piPtr++; + iWidth = (unsigned int)*piPtr; + if (!iHeight || !iWidth) { - for (unsigned int y = 0; y < 8;++y) - { - bool bSet = false; - if (0 == x % 2 && 0 != y % 2 || - 0 != x % 2 && 0 == y % 2)bSet = true; - - aiTexel* pc = &pcNew->pcData[y * 8 + x]; - if (bSet)pc->r = pc->b = pc->g = 0xFF; - else pc->r = pc->b = pc->g = 0; - pc->a = 0xFF; - } + 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 { - // it is a standard color texture. Fill in width and height - // and call the same function we used for loading MDL5 files - - pcNew->mWidth = pcSkin->width; - pcNew->mHeight = pcSkin->height; - - unsigned int iSkip = 0; - this->ParseTextureColorData(szCurrent,iMasked,&iSkip,pcNew); - - // skip length of texture data - szCurrent += iSkip; + iWidth = pcTex->mWidth; + iHeight = pcTex->mHeight; } - } - - // check whether a material definition is contained in the skin - if (pcSkin->typ & AI_MDL7_SKINTYPE_MATERIAL) - { - const MDL::Material_MDL7* pcMatIn = (const MDL::Material_MDL7*)szCurrent; - szCurrent = (unsigned char*)(pcMatIn+1); - aiColor4D clrTemp; - - // read diffuse color - clrTemp.a = 1.0f; //pcMatIn->Diffuse.a; - clrTemp.r = pcMatIn->Diffuse.r; - clrTemp.g = pcMatIn->Diffuse.g; - clrTemp.b = pcMatIn->Diffuse.b; - pcMatOut->AddProperty(&clrTemp,1,AI_MATKEY_COLOR_DIFFUSE); - - // read specular color - clrTemp.a = 1.0f; //pcMatIn->Specular.a; - clrTemp.r = pcMatIn->Specular.r; - clrTemp.g = pcMatIn->Specular.g; - clrTemp.b = pcMatIn->Specular.b; - pcMatOut->AddProperty(&clrTemp,1,AI_MATKEY_COLOR_SPECULAR); - - // read ambient color - clrTemp.a = 1.0f; //pcMatIn->Ambient.a; - clrTemp.r = pcMatIn->Ambient.r; - clrTemp.g = pcMatIn->Ambient.g; - clrTemp.b = pcMatIn->Ambient.b; - pcMatOut->AddProperty(&clrTemp,1,AI_MATKEY_COLOR_AMBIENT); - - // read emissive color - clrTemp.a = 1.0f; //pcMatIn->Emissive.a; - clrTemp.r = pcMatIn->Emissive.r; - clrTemp.g = pcMatIn->Emissive.g; - clrTemp.b = pcMatIn->Emissive.b; - pcMatOut->AddProperty(&clrTemp,1,AI_MATKEY_COLOR_EMISSIVE); - - // FIX: Take the opacity from the ambient color - // the doc says something else, but it is fact that MED exports the - // opacity like this .... ARRRGGHH! - clrTemp.a = pcMatIn->Ambient.a; - pcMatOut->AddProperty(&clrTemp.a,1,AI_MATKEY_OPACITY); - - // read phong power - int iShadingMode = (int)aiShadingMode_Gouraud; - if (0.0f != pcMatIn->Power) + if (1 != iWidth || 1 != iHeight) { - iShadingMode = (int)aiShadingMode_Phong; - pcMatOut->AddProperty(&pcMatIn->Power,1,AI_MATKEY_SHININESS); + 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) + { + // width and height can't be 0 here + 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 + } } - pcMatOut->AddProperty(&iShadingMode,1,AI_MATKEY_SHADING_MODEL); } - - // if an ASCII effect description (HLSL?) is contained in the file, - // we can simply ignore it ... - if (pcSkin->typ & AI_MDL7_SKINTYPE_MATERIAL_ASCDEF) - { - int32_t iMe = *((int32_t*)szCurrent); - szCurrent += sizeof(char) * iMe + sizeof(int32_t); - } - - // if an embedded texture has been loaded setup the corresponding - // data structures in the aiScene instance - if (NULL != pcNew) - { - // place this as diffuse texture - char szCurrent[5]; - sprintf(szCurrent,"*%i",this->pScene->mNumTextures); - - aiString szFile; - const size_t iLen = strlen((const char*)szCurrent); - size_t iLen2 = iLen+1; - iLen2 = iLen2 > MAXLEN ? MAXLEN : iLen2; - memcpy(szFile.data,(const char*)szCurrent,iLen2); - szFile.length = iLen; - - pcMatOut->AddProperty(&szFile,AI_MATKEY_TEXTURE_DIFFUSE(0)); - - // store the texture - aiTexture** pc = this->pScene->mTextures; - this->pScene->mTextures = new aiTexture*[this->pScene->mNumTextures+1]; - for (unsigned int i = 0; i < this->pScene->mNumTextures;++i) - this->pScene->mTextures[i] = pc[i]; - - this->pScene->mTextures[this->pScene->mNumTextures] = pcNew; - this->pScene->mNumTextures++; - delete[] pc; - } - - // place the name of the skin in the material - const size_t iLen = strlen(pcSkin->texture_name); - if (0 != iLen) - { - aiString szFile; - memcpy(szFile.data,pcSkin->texture_name,sizeof(pcSkin->texture_name)); - szFile.length = iLen; - - pcMatOut->AddProperty(&szFile,AI_MATKEY_NAME); - } - - *szCurrentOut = szCurrent; - return; } - -#define _AI_MDL7_ACCESS(_data, _index, _limit, _type) \ - (*((const _type*)(((const char*)_data) + _index * _limit))) - -#define _AI_MDL7_ACCESS_VERT(_data, _index, _limit) \ - _AI_MDL7_ACCESS(_data,_index,_limit,MDL::Vertex_MDL7) - // ------------------------------------------------------------------------------------------------ -void MDLImporter::ValidateHeader_GameStudioA7(const MDL::Header_MDL7* pcHeader) +void MDLImporter::ValidateHeader_3DGS_MDL7(const MDL::Header_MDL7* pcHeader) { ai_assert(NULL != pcHeader); @@ -1274,7 +843,7 @@ void MDLImporter::ValidateHeader_GameStudioA7(const MDL::Header_MDL7* pcHeader) } // if there are no groups ... how should we load such a file? - if(0 == pcHeader->groups_num) + if(!pcHeader->groups_num) { // LOG throw new ImportErrorException( "[3DGS MDL7] No frames found"); @@ -1282,35 +851,29 @@ void MDLImporter::ValidateHeader_GameStudioA7(const MDL::Header_MDL7* pcHeader) return; } // ------------------------------------------------------------------------------------------------ -void MDLImporter::CalculateAbsBoneAnimMatrices(const MDL::Bone_MDL7* pcBones, - aiBone** apcOutBones) +void MDLImporter::CalcAbsBoneMatrices_3DGS_MDL7(const MDL::Bone_MDL7* pcBones, + MDL::IntBone_MDL7** apcOutBones) { ai_assert(NULL != pcBones); ai_assert(NULL != apcOutBones); - const MDL::Header_MDL7* pcHeader = (const MDL::Header_MDL7*)this->m_pcHeader; + const MDL::Header_MDL7* const pcHeader = (const MDL::Header_MDL7*)this->mBuffer; // 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. - - std::vector abHadMe; - abHadMe.resize(pcHeader->bones_num,false); - uint16_t iParent = 0xffff; - int32_t iIterations = 0; + uint32_t iIterations = 0; while (iIterations++ < pcHeader->bones_num) { - for (int32_t iBone = 0; iBone < pcHeader->bones_num;++iBone) + for (uint32_t iBone = 0; iBone < pcHeader->bones_num;++iBone) { - if (abHadMe[iBone])continue; - const MDL::Bone_MDL7* pcBone = &pcBones[iBone]; - abHadMe[iBone] = true; + const MDL::Bone_MDL7* pcBone = _AI_MDL7_ACCESS_PTR(pcBones,iBone, + pcHeader->bone_stc_size,MDL::Bone_MDL7); if (iParent == pcBone->parent_index) { - // yeah, calculate my matrix! I'm happy now - + // extract from MDL7 readme ... /************************************************************ The animation matrix is then calculated the following way: @@ -1326,325 +889,342 @@ void MDLImporter::CalculateAbsBoneAnimMatrices(const MDL::Bone_MDL7* pcBones, laM = sm1 * laM; laM = laM * sm2; - *************************************************************/ - aiVector3D vAbsPos; + *************************************************************/ + + MDL::IntBone_MDL7* const pcOutBone = apcOutBones[iBone]; + + // store the parent index of the bone + pcOutBone->iParent = pcBone->parent_index; if (0xffff != iParent) { - const aiBone* pcParentBone = apcOutBones[iParent]; - vAbsPos.x = pcParentBone->mOffsetMatrix.a3; - vAbsPos.y = pcParentBone->mOffsetMatrix.b3; - vAbsPos.z = pcParentBone->mOffsetMatrix.c3; + 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 :-( +# if (_MSC_VER >= 1400) + pcOutBone->mName.length = ::sprintf_s(pcOutBone->mName.data, + MAXLEN,"UnnamedBone_%i",iBone); +# else + pcOutBone->mName.length = ::sprintf(pcOutBone->mName.data, + "UnnamedBone_%i",iBone); +# endif + } + 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'; } - vAbsPos.x -= pcBone->x; // TODO: + or -? - vAbsPos.y -= pcBone->y; - vAbsPos.z -= pcBone->z; - aiBone* pcOutBone = apcOutBones[iBone]; - pcOutBone->mOffsetMatrix.a3 = vAbsPos.x; - pcOutBone->mOffsetMatrix.b3 = vAbsPos.y; - pcOutBone->mOffsetMatrix.c3 = vAbsPos.z; } } ++iParent; } } // ------------------------------------------------------------------------------------------------ -void MDLImporter::InternReadFile_GameStudioA7( ) +MDL::IntBone_MDL7** MDLImporter::LoadBones_3DGS_MDL7() { - ai_assert(NULL != pScene); + // again, get a pointer to the file header, the bone data + // is stored directly behind it + const MDL::Header_MDL7* const pcHeader = (const MDL::Header_MDL7*)this->mBuffer; + const MDL::Bone_MDL7* pcBones = (const MDL::Bone_MDL7*)(pcHeader+1); - // current cursor position in the file - const MDL::Header_MDL7* pcHeader = (const MDL::Header_MDL7*)this->m_pcHeader; - const unsigned char* szCurrent = (const unsigned char*)(pcHeader+1); - - // validate the header of the file. There are some structure - // sizes that are expected by the loader to be constant - this->ValidateHeader_GameStudioA7(pcHeader); - - // load all bones (they are shared by all groups, so - // we'll need to add them to all groups later) - const MDL::Bone_MDL7* pcBones = (const MDL::Bone_MDL7*)szCurrent; - szCurrent += pcHeader->bones_num * pcHeader->bone_stc_size; - - aiBone** apcBonesOut = NULL; - unsigned int iNumBonesOut = 0; - if (pcHeader->bone_stc_size != sizeof(MDL::Bone_MDL7)) + if (pcHeader->bones_num) { - DefaultLogger::get()->warn("[3DGS MDL7] Unknown size of bone data structure. " - "Ignoring bones ..."); + // 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 ... + this->CalcAbsBoneMatrices_3DGS_MDL7(pcBones,apcBonesOut); + return apcBonesOut; + } + return NULL; +} +// ------------------------------------------------------------------------------------------------ +void MDLImporter::ReadFaces_3DGS_MDL7( + const MDL::IntGroupInfo_MDL7& groupInfo, + MDL::IntGroupData_MDL7& groupData) +{ + // get a pointer to the file header + const MDL::Header_MDL7* const pcHeader = (const MDL::Header_MDL7*)this->mBuffer; + const 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) + { + // 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) + { + // LOG + 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; + + // swap z and y axis + aiVector3D& vPosition = groupData.vPositions[ iOutIndex ]; + vPosition.x = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts,iIndex,pcHeader->mainvertex_stc_size) .x; + vPosition.z = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts,iIndex,pcHeader->mainvertex_stc_size) .y; + vPosition.y = _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]; + vNormal.z = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts,iIndex,pcHeader->mainvertex_stc_size) .norm[1]; + vNormal.y = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts,iIndex,pcHeader->mainvertex_stc_size) .norm[2]; + + // FIX: It seems to be necessary to invert all normals + // FIX2: No, it is not necessary :-) +#if 0 + vNormal.x *= -1.0f; + vNormal.y *= -1.0f; + vNormal.z *= -1.0f; +#endif + } + 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); + + std::swap(groupData.vNormals[iOutIndex].z,groupData.vNormals[iOutIndex].y); + } + // 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) + { + 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) + { + 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) + { + 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 = (const MDL::Triangle_MDL7*)((const char*)pcGroupTris + + pcHeader->triangle_stc_size); + } +} +// ------------------------------------------------------------------------------------------------ +bool MDLImporter::ProcessFrames_3DGS_MDL7(const MDL::IntGroupInfo_MDL7& groupInfo, + MDL::IntSharedData_MDL7& shared, + const unsigned char* szCurrent, + const unsigned char** szCurrentOut) +{ + ai_assert(NULL != szCurrent && NULL != szCurrentOut); + + // get a pointer to the file header + const MDL::Header_MDL7* const pcHeader = (const MDL::Header_MDL7*)this->mBuffer; + + // if we have no bones we can simply skip all frames, + // otherwise we'll need to process them. + for(unsigned int iFrame = 0; iFrame < (unsigned int)groupInfo.pcGroup->numframes;++iFrame) + { + MDL::IntFrameInfo_MDL7 frame((const MDL::Frame_MDL7*)szCurrent,iFrame); + + 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 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; + } + + // parse bone trafo matrix keys (only if there are bones ...) + if (shared.apcOutBones) + { + this->ParseBoneTrafoKeys_3DGS_MDL7(groupInfo,frame,shared); + } + szCurrent += iAdd; + } + *szCurrentOut = szCurrent; + return true; +} +// ------------------------------------------------------------------------------------------------ +void MDLImporter::SortByMaterials_3DGS_MDL7( + const MDL::IntGroupInfo_MDL7& groupInfo, + MDL::IntGroupData_MDL7& groupData, + MDL::IntSplittedGroupData_MDL7& splittedGroupData) +{ + const unsigned int iNumMaterials = (unsigned int)splittedGroupData.shared.pcMats.size(); + + // if we don't need a second set of texture coordinates there is no reason to keep it in memory ... + if (!groupData.bNeed2UV) + { + groupData.vTextureCoords2.clear(); + + // allocate the array + splittedGroupData.aiSplit = new std::vector*[iNumMaterials]; + + for (unsigned int m = 0; m < iNumMaterials;++m) + splittedGroupData.aiSplit[m] = new std::vector(); + + // 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 + splittedGroupData.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 splittedGroupData.aiSplit[groupData.pcFaces[iFace]. + iMatIndex[0]]->push_back(iFace); + } } else { - // create an output bone array - iNumBonesOut = pcHeader->bones_num; - apcBonesOut = new aiBone*[iNumBonesOut]; - for (unsigned int crank = 0; crank < iNumBonesOut;++crank) - apcBonesOut[crank] = new aiBone(); + // we need to build combined materials for each combination of + std::vector avMats; + avMats.reserve(iNumMaterials*2); - // and calculate absolute bone animation matrices - // aiBone.mTransformation member - this->CalculateAbsBoneAnimMatrices(pcBones,apcBonesOut); - } + std::vector* > aiTempSplit; + aiTempSplit.reserve(iNumMaterials*2); - // allocate a material list - std::vector pcMats; + for (unsigned int m = 0; m < iNumMaterials;++m) + aiTempSplit[m] = new std::vector(); - // vector to hold all created meshes - std::vector avOutList; - avOutList.reserve(pcHeader->groups_num); - - // read all groups - for (unsigned int iGroup = 0; iGroup < (unsigned int)pcHeader->groups_num;++iGroup) - { - const MDL::Group_MDL7* pcGroup = (const MDL::Group_MDL7*)szCurrent; - szCurrent = (const unsigned char*)(pcGroup+1); - - if (1 != pcGroup->typ) + // iterate through all faces and sort by material + for (unsigned int iFace = 0; iFace < (unsigned int)groupInfo.pcGroup->numtris;++iFace) { - // Not a triangle-based mesh - DefaultLogger::get()->warn("[3DGS MDL7] Mesh group is not basing on" - "triangles. Continuing happily"); - } - - // read all skins - pcMats.reserve(pcMats.size() + pcGroup->numskins); - for (unsigned int iSkin = 0; iSkin < (unsigned int)pcGroup->numskins;++iSkin) - { - this->ParseSkinLump_GameStudioA7(szCurrent,&szCurrent,pcMats); - } - // if we have absolutely no skin loaded we need to generate a default material - if (pcMats.empty()) - { - const int iMode = (int)aiShadingMode_Gouraud; - pcMats.push_back(new MaterialHelper()); - MaterialHelper* pcHelper = (MaterialHelper*)pcMats[0]; - pcHelper->AddProperty(&iMode, 1, AI_MATKEY_SHADING_MODEL); - - aiColor3D clr; - clr.b = clr.g = clr.r = 0.6f; - pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_DIFFUSE); - pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_SPECULAR); - - clr.b = clr.g = clr.r = 0.05f; - pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_AMBIENT); - } - - // now get a pointer to all texture coords in the group - const MDL::TexCoord_MDL7* pcGroupUVs = (const MDL::TexCoord_MDL7*)szCurrent; - szCurrent += pcHeader->skinpoint_stc_size * pcGroup->num_stpts; - - // now get a pointer to all triangle in the group - const MDL::Triangle_MDL7* pcGroupTris = (const MDL::Triangle_MDL7*)szCurrent; - szCurrent += pcHeader->triangle_stc_size * pcGroup->numtris; - - // now get a pointer to all vertices in the group - const MDL::Vertex_MDL7* pcGroupVerts = (const MDL::Vertex_MDL7*)szCurrent; - szCurrent += pcHeader->mainvertex_stc_size * pcGroup->numverts; - - // build output vectors - std::vector vPositions; - vPositions.resize(pcGroup->numtris * 3); - - std::vector vNormals; - vNormals.resize(pcGroup->numtris * 3); - - std::vector vTextureCoords1; - vTextureCoords1.resize(pcGroup->numtris * 3, - aiVector3D(std::numeric_limits::quiet_NaN(),0.0f,0.0f)); - - std::vector vTextureCoords2; - - bool bNeed2UV = false; - if (pcHeader->triangle_stc_size >= sizeof(MDL::Triangle_MDL7)) - { - vTextureCoords2.resize(pcGroup->numtris * 3, - aiVector3D(std::numeric_limits::quiet_NaN(),0.0f,0.0f)); - bNeed2UV = true; - } - MDL::IntFace_MDL7* pcFaces = new MDL::IntFace_MDL7[pcGroup->numtris]; - - // iterate through all triangles and build valid display lists - for (unsigned int iTriangle = 0; iTriangle < (unsigned int)pcGroup->numtris; ++iTriangle) - { - // iterate through all indices of the current triangle - for (unsigned int c = 0; c < 3;++c) + // check range + unsigned int iMatIndex = groupData.pcFaces[iFace].iMatIndex[0]; + if (iMatIndex >= iNumMaterials) { - // validate the vertex index - unsigned int iIndex = pcGroupTris->v_index[c]; - if(iIndex > (unsigned int)pcGroup->numverts) - { - // LOG - iIndex = pcGroup->numverts-1; - - DefaultLogger::get()->warn("Index overflow in MDL7 vertex list"); - } - unsigned int iOutIndex = iTriangle * 3 + c; - - // write the output face index - pcFaces[iTriangle].mIndices[c] = iTriangle * 3 + (2-c); - - // swap z and y axis - vPositions[iOutIndex].x = _AI_MDL7_ACCESS_VERT(pcGroupVerts,iIndex,pcHeader->mainvertex_stc_size) .x; - vPositions[iOutIndex].z = _AI_MDL7_ACCESS_VERT(pcGroupVerts,iIndex,pcHeader->mainvertex_stc_size) .y; - vPositions[iOutIndex].y = _AI_MDL7_ACCESS_VERT(pcGroupVerts,iIndex,pcHeader->mainvertex_stc_size) .z; - - // now read the normal vector - if (AI_MDL7_FRAMEVERTEX030305_STCSIZE <= pcHeader->mainvertex_stc_size) - { - // read the full normal vector - vNormals[iOutIndex].x = _AI_MDL7_ACCESS_VERT(pcGroupVerts,iIndex,pcHeader->mainvertex_stc_size) .norm[0]; - vNormals[iOutIndex].z = _AI_MDL7_ACCESS_VERT(pcGroupVerts,iIndex,pcHeader->mainvertex_stc_size) .norm[1]; - vNormals[iOutIndex].y = _AI_MDL7_ACCESS_VERT(pcGroupVerts,iIndex,pcHeader->mainvertex_stc_size) .norm[2]; - - // FIX: It seems to be necessary to invert all normals - vNormals[iOutIndex].x *= -1.0f; - vNormals[iOutIndex].y *= -1.0f; - vNormals[iOutIndex].z *= -1.0f; - } - else if (AI_MDL7_FRAMEVERTEX120503_STCSIZE <= pcHeader->mainvertex_stc_size) - { - // read the normal vector from Quake2's smart table - vNormals[iOutIndex] = *((const aiVector3D*)(&g_avNormals[std::min( - int(_AI_MDL7_ACCESS_VERT(pcGroupVerts,iIndex,pcHeader->mainvertex_stc_size) .norm162index), - int(sizeof(g_avNormals) / sizeof(g_avNormals[0]))-1)])); - - std::swap(vNormals[iOutIndex].z,vNormals[iOutIndex].y); - } - - - // validate and process the first uv coordinate set - // ************************************************************* - const unsigned int iMin = sizeof(MDL::Triangle_MDL7)- - sizeof(MDL::SkinSet_MDL7)-sizeof(uint32_t); - - const unsigned int iMin2 = sizeof(MDL::Triangle_MDL7)- - sizeof(MDL::SkinSet_MDL7); - - if (pcHeader->triangle_stc_size >= iMin) - { - iIndex = pcGroupTris->skinsets[0].st_index[c]; - if(iIndex > (unsigned int)pcGroup->num_stpts) - { - iIndex = pcGroup->num_stpts-1; - } - - float u = pcGroupUVs[iIndex].u; - float v = 1.0f-pcGroupUVs[iIndex].v; - - vTextureCoords1[iOutIndex].x = u; - vTextureCoords1[iOutIndex].y = v; - - // assign the material index, but only if it is existing - if (pcHeader->triangle_stc_size >= iMin2) - { - pcFaces[iTriangle].iMatIndex[0] = pcGroupTris->skinsets[0].material; - } - } - // validate and process the second uv coordinate set - // ************************************************************* - if (pcHeader->triangle_stc_size >= sizeof(MDL::Triangle_MDL7)) - { - iIndex = pcGroupTris->skinsets[1].st_index[c]; - if(iIndex > (unsigned int)pcGroup->num_stpts) - { - iIndex = pcGroup->num_stpts-1; - } - - float u = pcGroupUVs[iIndex].u; - float v = 1.0f-pcGroupUVs[iIndex].v; - - vTextureCoords2[iOutIndex].x = u; - vTextureCoords2[iOutIndex].y = v; - - // check whether we do really need the second texture - // coordinate set ... wastes memory and loading time - if (0 != iIndex && (u != vTextureCoords1[iOutIndex].x || - v != vTextureCoords1[iOutIndex].y)) - { - bNeed2UV = true; - } - // if the material differs, we need a second skin, too - if (pcGroupTris->skinsets[1].material != pcGroupTris->skinsets[0].material) - { - bNeed2UV = true; - } - - // assign the material index - pcFaces[iTriangle].iMatIndex[1] = pcGroupTris->skinsets[1].material; - } + // sometimes MED writes -1, but normally only if there is only + // one skin assigned. No warning in this case + if(0xffffffff != iMatIndex) + DefaultLogger::get()->warn("Index overflow in MDL7 material list [#1]"); + iMatIndex = iNumMaterials-1; } + unsigned int iMatIndex2 = groupData.pcFaces[iFace].iMatIndex[1]; - // get the next triangle in the list - pcGroupTris = (const MDL::Triangle_MDL7*)((const char*)pcGroupTris + pcHeader->triangle_stc_size); - } - - // if we don't need a second set of texture coordinates there is no reason to keep it in memory ... - std::vector** aiSplit; - unsigned int iNumMaterials = 0; - if (!bNeed2UV) - { - vTextureCoords2.clear(); - - // allocate the array - aiSplit = new std::vector*[pcMats.size()]; - iNumMaterials = (unsigned int)pcMats.size(); - - for (unsigned int m = 0; m < pcMats.size();++m) - aiSplit[m] = new std::vector(); - - // iterate through all faces and sort by material - for (unsigned int iFace = 0; iFace < (unsigned int)pcGroup->numtris;++iFace) + unsigned int iNum = iMatIndex; + if (0xffffffff != iMatIndex2 && iMatIndex != iMatIndex2) { - // check range - if (pcFaces[iFace].iMatIndex[0] >= iNumMaterials) - { - // use the last material instead - 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 != pcFaces[iFace].iMatIndex[0]) - DefaultLogger::get()->warn("Index overflow in MDL7 material list [#0]"); - } - else aiSplit[pcFaces[iFace].iMatIndex[0]]->push_back(iFace); - } - } - else - { - // we need to build combined materials for each combination of - std::vector avMats; - avMats.reserve(pcMats.size()*2); - - std::vector* > aiTempSplit; - aiTempSplit.reserve(pcMats.size()*2); - - for (unsigned int m = 0; m < pcMats.size();++m) - aiTempSplit[m] = new std::vector(); - - // iterate through all faces and sort by material - for (unsigned int iFace = 0; iFace < (unsigned int)pcGroup->numtris;++iFace) - { - // check range - unsigned int iMatIndex = pcFaces[iFace].iMatIndex[0]; - if (iMatIndex >= iNumMaterials) - { - iMatIndex = iNumMaterials-1; - - // sometimes MED writes -1, but normally only if there is only - // one skin assigned. No warning in this case - if(0xFFFFFFFF != iMatIndex) - DefaultLogger::get()->warn("Index overflow in MDL7 material list [#1]"); - } - unsigned int iMatIndex2 = pcFaces[iFace].iMatIndex[1]; if (iMatIndex2 >= iNumMaterials) { // sometimes MED writes -1, but normally only if there is only // one skin assigned. No warning in this case - if(0xFFFFFFFF != iMatIndex2) - DefaultLogger::get()->warn("Index overflow in MDL7 material list [#2]"); + DefaultLogger::get()->warn("Index overflow in MDL7 material list [#2]"); + iMatIndex2 = iNumMaterials-1; } - // do a slow O(log(n)*n) seach in the list ... - unsigned int iNum = 0; + // do a slow seach in the list ... + iNum = 0; bool bFound = false; for (std::vector::iterator i = avMats.begin(); @@ -1665,7 +1245,8 @@ void MDLImporter::InternReadFile_GameStudioA7( ) sHelper.pcMat = new MaterialHelper(); sHelper.iOldMatIndices[0] = iMatIndex; sHelper.iOldMatIndices[1] = iMatIndex2; - this->JoinSkins_GameStudioA7(pcMats[iMatIndex],pcMats[iMatIndex2],sHelper.pcMat); + this->JoinSkins_3DGS_MDL7(splittedGroupData.shared.pcMats[iMatIndex], + splittedGroupData.shared.pcMats[iMatIndex2],sHelper.pcMat); // and add it to the list avMats.push_back(sHelper); @@ -1676,152 +1257,580 @@ void MDLImporter::InternReadFile_GameStudioA7( ) { aiTempSplit.push_back(new std::vector()); } - aiTempSplit[iNum]->push_back(iFace); } - - // now add the newly created materials to the old list - if (0 == iGroup) - { - pcMats.resize(avMats.size()); - for (unsigned int o = 0; o < avMats.size();++o) - pcMats[o] = avMats[o].pcMat; - } - else - { - // TODO: This might result in redundant materials ... - unsigned int iOld = (unsigned int)pcMats.size(); - pcMats.resize(pcMats.size() + avMats.size()); - for (unsigned int o = iOld; o < avMats.size();++o) - pcMats[o] = avMats[o].pcMat; - } - iNumMaterials = (unsigned int)pcMats.size(); - - // and build the final face-to-material array - aiSplit = new std::vector*[aiTempSplit.size()]; - for (unsigned int m = 0; m < iNumMaterials;++m) - aiSplit[m] = aiTempSplit[m]; - - // no need to delete the member of aiTempSplit + aiTempSplit[iNum]->push_back(iFace); } - // now generate output meshes - unsigned int iOldSize = (unsigned int)avOutList.size(); - this->GenerateOutputMeshes_GameStudioA7( - (const std::vector**)aiSplit,pcMats, - avOutList,pcFaces,vPositions,vNormals, vTextureCoords1,vTextureCoords2); - - // store the group index temporarily - ai_assert(AI_MAX_NUMBER_OF_TEXTURECOORDS >= 3); - for (unsigned int l = iOldSize;l < avOutList.size();++l) + // now add the newly created materials to the old list + if (0 == groupInfo.iIndex) { - avOutList[l]->mNumUVComponents[2] = iGroup; + splittedGroupData.shared.pcMats.resize(avMats.size()); + for (unsigned int o = 0; o < avMats.size();++o) + splittedGroupData.shared.pcMats[o] = avMats[o].pcMat; + } + else + { + // TODO: This might result in redundant materials ... + splittedGroupData.shared.pcMats.resize(iNumMaterials + avMats.size()); + for (unsigned int o = iNumMaterials; o < avMats.size();++o) + splittedGroupData.shared.pcMats[o] = avMats[o].pcMat; } - // delete the face-to-material helper array + // and build the final face-to-material array + splittedGroupData.aiSplit = new std::vector*[aiTempSplit.size()]; for (unsigned int m = 0; m < iNumMaterials;++m) - delete aiSplit[m]; - delete[] aiSplit; + splittedGroupData.aiSplit[m] = aiTempSplit[m]; + } +} +// ------------------------------------------------------------------------------------------------ +void MDLImporter::InternReadFile_3DGS_MDL7( ) +{ + ai_assert(NULL != pScene); - // now we need to skip all faces - for(unsigned int iFrame = 0; iFrame < (unsigned int)pcGroup->numframes;++iFrame) + MDL::IntSharedData_MDL7 sharedData; + + // current cursor position in the file + const MDL::Header_MDL7* const pcHeader = (const MDL::Header_MDL7*)this->mBuffer; + const unsigned char* szCurrent = (const unsigned char*)(pcHeader+1); + + // 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 + // TODO (aramis): Make apcBonesOut an MDL::IntBone_MDL7* + szCurrent += pcHeader->bones_num * pcHeader->bone_stc_size; + sharedData.apcOutBones = this->LoadBones_3DGS_MDL7(); + + // vector to held all created meshes + std::vector* avOutList; + + // 3 meshes per group - that should be OK for most models + avOutList = new std::vector[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 + char* aszGroupNameBuffer = new char[AI_MDL7_MAX_GROUPNAMESIZE*pcHeader->groups_num]; + + // read all groups + for (unsigned int iGroup = 0; iGroup < (unsigned int)pcHeader->groups_num;++iGroup) + { + MDL::IntGroupInfo_MDL7 groupInfo((const MDL::Group_MDL7*)szCurrent,iGroup); + szCurrent = (const unsigned char*)(groupInfo.pcGroup+1); + + VALIDATE_FILE_SIZE(szCurrent); + + if (1 != groupInfo.pcGroup->typ) { - const MDL::Frame_MDL7* pcFrame = (const MDL::Frame_MDL7*)szCurrent; + // Not a triangle-based mesh + DefaultLogger::get()->warn("[3DGS MDL7] Mesh group is not basing on" + "triangles. Continuing happily"); + } - unsigned int iAdd = pcHeader->frame_stc_size + - pcFrame->vertices_count * pcHeader->framevertex_stc_size + - pcFrame->transmatrix_count * pcHeader->bonetrans_stc_size; + // store the name of the group + ::memcpy(&aszGroupNameBuffer[iGroup*AI_MDL7_MAX_GROUPNAMESIZE], + groupInfo.pcGroup->name,AI_MDL7_MAX_GROUPNAMESIZE); - if (((unsigned int)szCurrent - (unsigned int)pcHeader) + iAdd > (unsigned int)pcHeader->data_size) + // 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) + { + this->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 MaterialHelper()); + MaterialHelper* pcHelper = (MaterialHelper*)sharedData.pcMats[0]; + pcHelper->AddProperty(&iMode, 1, AI_MATKEY_SHADING_MODEL); + + aiColor3D clr; + clr.b = clr.g = clr.r = 0.6f; + pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_DIFFUSE); + pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_SPECULAR); + + clr.b = clr.g = clr.r = 0.05f; + pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_AMBIENT); + + aiString szName; + szName.Set(AI_DEFAULT_MATERIAL_NAME); + pcHelper->AddProperty(&szName,AI_MATKEY_NAME); + } + + // now get a pointer to all texture coords in the group + groupInfo.pcGroupUVs = (const MDL::TexCoord_MDL7*)szCurrent; + szCurrent += pcHeader->skinpoint_stc_size * groupInfo.pcGroup->num_stpts; + + // now get a pointer to all triangle in the group + groupInfo.pcGroupTris = (const MDL::Triangle_MDL7*)szCurrent; + szCurrent += pcHeader->triangle_stc_size * groupInfo.pcGroup->numtris; + + // now get a pointer to all vertices in the group + groupInfo.pcGroupVerts = (const MDL::Vertex_MDL7*)szCurrent; + szCurrent += pcHeader->mainvertex_stc_size * groupInfo.pcGroup->numverts; + + VALIDATE_FILE_SIZE(szCurrent); + + MDL::IntSplittedGroupData_MDL7 splittedGroupData(sharedData,avOutList[iGroup]); + if (groupInfo.pcGroup->numtris && groupInfo.pcGroup->numverts) + { + MDL::IntGroupData_MDL7 groupData; + + // 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,0xffffffff); + + // it is also possible that there are 0 UV coordinate sets + if (groupInfo.pcGroup->num_stpts) { - DefaultLogger::get()->warn("Index overflow in frame area. Ignoring frames"); - // don't parse more groups if we can't even read one - goto __BREAK_OUT; + 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 + this->ReadFaces_3DGS_MDL7(groupInfo, groupData); + + // sort by materials + this->SortByMaterials_3DGS_MDL7(groupInfo, groupData, + splittedGroupData); + + for (unsigned int qq = 0; qq < sharedData.pcMats.size();++qq) + { + if (!splittedGroupData.aiSplit[qq]->empty()) + sharedData.abNeedMaterials[qq] = true; } - szCurrent += iAdd; + // now generate output meshes + this->GenerateOutputMeshes_3DGS_MDL7(groupData, + splittedGroupData); + } + else DefaultLogger::get()->warn("[3DGS MDL7] Mesh group consists of 0 " + "vertices or faces. It will be skipped."); + + // process all frames + if(!ProcessFrames_3DGS_MDL7(groupInfo,sharedData,szCurrent,&szCurrent)) + { + break; } } -__BREAK_OUT: // EVIL ;-) + + // generate a nodegraph and subnodes for each group + this->pScene->mRootNode = new aiNode(); // now we need to build a final mesh list - this->pScene->mNumMeshes = (unsigned int)avOutList.size(); - this->pScene->mMeshes = new aiMesh*[avOutList.size()]; - - for (unsigned int i = 0; i < avOutList.size();++i) + for (uint32_t i = 0; i < pcHeader->groups_num;++i) { - this->pScene->mMeshes[i] = avOutList[i]; + this->pScene->mNumMeshes += (unsigned int)avOutList[i].size(); + } + this->pScene->mMeshes = new aiMesh*[this->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) + { + this->pScene->mMeshes[p++] = avOutList[i][a]; + } + if (!avOutList[i].empty())++this->pScene->mRootNode->mNumChildren; + } + // we will later need an extra node to serve as parent for all bones + if (sharedData.apcOutBones)++this->pScene->mRootNode->mNumChildren; + this->pScene->mRootNode->mChildren = new aiNode*[this->pScene->mRootNode->mNumChildren]; + p = 0; + for (uint32_t i = 0; i < pcHeader->groups_num;++i) + { + if (avOutList[i].empty())continue; + + aiNode* const pcNode = this->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) + { +#if _MSC_VER >= 1400 + ::sprintf_s(szBuffer,AI_MDL7_MAX_GROUPNAMESIZE,"Group_%i",p); +#else + ::sprintf(szBuffer,"Group_%i",p); +#endif + } + ::strcpy(pcNode->mName.data,szBuffer); + pcNode->mName.length = ::strlen(szBuffer); + ++p; + } } - // build a final material list. Offset all mesh material indices - this->pScene->mNumMaterials = (unsigned int)pcMats.size(); - this->pScene->mMaterials = new aiMaterial*[this->pScene->mNumMaterials]; - for (unsigned int i = 0; i < this->pScene->mNumMaterials;++i) - this->pScene->mMaterials[i] = pcMats[i]; - + // if there is only one root node with a single child we can optimize it a bit ... + if (1 == this->pScene->mRootNode->mNumChildren && !sharedData.apcOutBones) + { + aiNode* pcOldRoot = this->pScene->mRootNode; + this->pScene->mRootNode = pcOldRoot->mChildren[0]; + pcOldRoot->mChildren[0] = NULL; + delete pcOldRoot; + + this->pScene->mRootNode->mParent = NULL; + } + else this->pScene->mRootNode->mName.Set("mesh_root"); + + delete[] avOutList; + delete[] aszGroupNameBuffer; + DEBUG_INVALIDATE_PTR(avOutList); + DEBUG_INVALIDATE_PTR(aszGroupNameBuffer); + + // build a final material list. + this->CopyMaterials_3DGS_MDL7(sharedData); + + // handle materials that are just referencing another material correctly + this->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 = this->pScene->mRootNode->mChildren[ + this->pScene->mRootNode->mNumChildren-1] = new aiNode(); + + pc->mName.Set("skeleton_root"); + + // add bones to the nodegraph + this->AddBonesToNodeGraph_3DGS_MDL7((const Assimp::MDL::IntBone_MDL7 **) + sharedData.apcOutBones,pc,0xffff); + + // this steps build a valid output animation + this->BuildOutputAnims_3DGS_MDL7((const Assimp::MDL::IntBone_MDL7 **) + sharedData.apcOutBones); + } +} +// ------------------------------------------------------------------------------------------------ +void MDLImporter::CopyMaterials_3DGS_MDL7(MDL::IntSharedData_MDL7 &shared) +{ + unsigned int iNewNumMaterials = 0; + unsigned int p = 0; + for (;p < shared.pcMats.size();++p) + if (shared.abNeedMaterials[p])++iNewNumMaterials; + + this->pScene->mMaterials = new aiMaterial*[iNewNumMaterials]; + if ((unsigned int)shared.pcMats.size() == iNewNumMaterials) + { + this->pScene->mNumMaterials = (unsigned int)shared.pcMats.size(); + for (unsigned int i = 0; i < this->pScene->mNumMaterials;++i) + this->pScene->mMaterials[i] = shared.pcMats[i]; + } + else + { + p = 0; + const unsigned int iMSB = 0x1u << (sizeof (unsigned int)*8-1); + for (unsigned int i = 0; i < (unsigned int)shared.pcMats.size();++i) + { + if (!shared.abNeedMaterials[i]) + { + // destruction is done by the destructor of sh + delete shared.pcMats[i]; + DEBUG_INVALIDATE_PTR(shared.pcMats[i]); + continue; + } + this->pScene->mMaterials[p] = shared.pcMats[i]; + + if (p != i) + { + // replace the material index and MSB in all material + // indices that have been replaced to make sure they won't be + // replaced again (this won't work if there are more than + // 2^31 materials in the model - but this shouldn't care :-)). + for (unsigned int qq = 0; qq < this->pScene->mNumMeshes;++qq) + { + aiMesh* const pcMesh = this->pScene->mMeshes[qq]; + if (i == pcMesh->mMaterialIndex) + { + pcMesh->mMaterialIndex = p | iMSB; + } + } + } + ++p; + } + this->pScene->mNumMaterials = iNewNumMaterials; + + // Remove the MSB from all material indices + for (unsigned int qq = 0; qq < this->pScene->mNumMeshes;++qq) + { + this->pScene->mMeshes[qq]->mMaterialIndex &= ~iMSB; + } + } +} +// ------------------------------------------------------------------------------------------------ +void MDLImporter::HandleMaterialReferences_3DGS_MDL7() +{ // search for referrer materials + // (there is no test file but Conitec's docs say it is supported ... :cry: ) for (unsigned int i = 0; i < this->pScene->mNumMaterials;++i) { int iIndex = 0; if (AI_SUCCESS == aiGetMaterialInteger(this->pScene->mMaterials[i], - "quakquakquak", &iIndex) ) + AI_MDL7_REFERRER_MATERIAL, &iIndex) ) { - for (unsigned int a = 0; a < avOutList.size();++a) + for (unsigned int a = 0; a < this->pScene->mNumMeshes;++a) { - if (i == avOutList[a]->mMaterialIndex) + aiMesh* const pcMesh = this->pScene->mMeshes[a]; + if (i == pcMesh->mMaterialIndex) { - avOutList[a]->mMaterialIndex = iIndex; + pcMesh->mMaterialIndex = iIndex; } } - // TODO: Remove the material from the list + // collapse the rest of the array + delete this->pScene->mMaterials[i]; + for (unsigned int pp = i; pp < this->pScene->mNumMaterials-1;++pp) + { + this->pScene->mMaterials[pp] = this->pScene->mMaterials[pp+1]; + for (unsigned int a = 0; a < this->pScene->mNumMeshes;++a) + { + aiMesh* const pcMesh = this->pScene->mMeshes[a]; + if (pcMesh->mMaterialIndex > i)--pcMesh->mMaterialIndex; + } + } + --this->pScene->mNumMaterials; } } - - // now generate a nodegraph whose rootnode references all meshes - this->pScene->mRootNode = new aiNode(); - this->pScene->mRootNode->mNumMeshes = this->pScene->mNumMeshes; - this->pScene->mRootNode->mMeshes = new unsigned int[this->pScene->mRootNode->mNumMeshes]; - for (unsigned int i = 0; i < this->pScene->mRootNode->mNumMeshes;++i) - this->pScene->mRootNode->mMeshes[i] = i; - - // seems we're finished now - return; } // ------------------------------------------------------------------------------------------------ -void MDLImporter::GenerateOutputMeshes_GameStudioA7( - const std::vector** aiSplit, - const std::vector& pcMats, - std::vector& avOutList, - const MDL::IntFace_MDL7* pcFaces, - const std::vector& vPositions, - const std::vector& vNormals, - const std::vector& vTextureCoords1, - const std::vector& vTextureCoords2) +void MDLImporter::ParseBoneTrafoKeys_3DGS_MDL7( + const MDL::IntGroupInfo_MDL7& groupInfo, + IntFrameInfo_MDL7& frame, + MDL::IntSharedData_MDL7& shared) { - ai_assert(NULL != aiSplit); - ai_assert(NULL != pcFaces); - for (unsigned int i = 0; i < pcMats.size();++i) + // get a pointer to the header ... + 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 (!aiSplit[i]->empty()) + 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 + { + this->AddAnimationBoneTrafoKey_3DGS_MDL7(frame.iIndex, + pcBoneTransforms,shared.apcOutBones); + } + pcBoneTransforms = (const MDL::BoneTransform_MDL7*)( + (const char*)pcBoneTransforms + pcHeader->bonetrans_stc_size); + } + } + else + { + DefaultLogger::get()->warn("Found animation keyframes " + "in a group that is not the first. They will be igored, " + "the format specification says this should not occur"); + } + } +} +// ------------------------------------------------------------------------------------------------ +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 ); + + this->AddBonesToNodeGraph_3DGS_MDL7(apcBones,pcNode,i); + } +} +// ------------------------------------------------------------------------------------------------ +void MDLImporter::BuildOutputAnims_3DGS_MDL7( + const MDL::IntBone_MDL7** apcBonesOut) +{ + ai_assert(NULL != apcBonesOut); + + // get a pointer to the header ... + const MDL::Header_MDL7* const pcHeader = (const MDL::Header_MDL7*)this->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->mNumBones; + } + } + if (pcAnim->mDuration) + { + pcAnim->mBones = new aiBoneAnim*[pcAnim->mNumBones]; + + 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]; + + aiBoneAnim* const pcBoneAnim = pcAnim->mBones[iCnt++] = new aiBoneAnim(); + pcBoneAnim->mBoneName = aiString( intBone->mName ); + + // allocate enough storahe for all keys + pcBoneAnim->mNumPositionKeys = (unsigned int)intBone->pkeyPositions.size(); + pcBoneAnim->mNumScalingKeys = (unsigned int)intBone->pkeyPositions.size(); + pcBoneAnim->mNumRotationKeys = (unsigned int)intBone->pkeyPositions.size(); + + pcBoneAnim->mPositionKeys = new aiVectorKey[pcBoneAnim->mNumPositionKeys]; + pcBoneAnim->mScalingKeys = new aiVectorKey[pcBoneAnim->mNumPositionKeys]; + pcBoneAnim->mRotationKeys = new aiQuatKey[pcBoneAnim->mNumPositionKeys]; + + // copy all keys + for (unsigned int qq = 0; qq < pcBoneAnim->mNumPositionKeys;++qq) + { + pcBoneAnim->mPositionKeys[qq] = intBone->pkeyPositions[qq]; + pcBoneAnim->mScalingKeys[qq] = intBone->pkeyScalings[qq]; + pcBoneAnim->mRotationKeys[qq] = intBone->pkeyRotations[qq]; + } + } + } + + // store the output animation + this->pScene->mNumAnimations = 1; + this->pScene->mAnimations = new aiAnimation*[1]; + this->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 ); +} +// ------------------------------------------------------------------------------------------------ +void MDLImporter::GenerateOutputMeshes_3DGS_MDL7( + MDL::IntGroupData_MDL7& groupData, + MDL::IntSplittedGroupData_MDL7& splittedGroupData) +{ + const MDL::IntSharedData_MDL7& shared = splittedGroupData.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::size_type i = 0; i < shared.pcMats.size();++i) + { + if (!splittedGroupData.aiSplit[i]->empty()) { // allocate the output mesh aiMesh* pcMesh = new aiMesh(); - pcMesh->mNumUVComponents[0] = 2; - pcMesh->mMaterialIndex = i; + pcMesh->mMaterialIndex = (unsigned int)i; // allocate output storage - pcMesh->mNumFaces = (unsigned int)aiSplit[i]->size(); + pcMesh->mNumFaces = (unsigned int)splittedGroupData.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]; - pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices]; - if (!vTextureCoords2.empty()) + if (!groupData.vTextureCoords1.empty()) { - pcMesh->mNumUVComponents[1] = 2; - pcMesh->mTextureCoords[1] = new aiVector3D[pcMesh->mNumVertices]; + 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 @@ -1831,41 +1840,101 @@ void MDLImporter::GenerateOutputMeshes_GameStudioA7( pcMesh->mFaces[iFace].mNumIndices = 3; pcMesh->mFaces[iFace].mIndices = new unsigned int[3]; - unsigned int iSrcFace = aiSplit[i]->operator[](iFace); - const MDL::IntFace_MDL7& oldFace = pcFaces[iSrcFace]; + unsigned int iSrcFace = splittedGroupData.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) { - pcMesh->mVertices[iCurrent] = vPositions[oldFace.mIndices[c]]; - pcMesh->mNormals[iCurrent] = vNormals[oldFace.mIndices[c]]; - pcMesh->mTextureCoords[0][iCurrent] = vTextureCoords1[oldFace.mIndices[c]]; + const uint32_t iIndex = oldFace.mIndices[c]; + pcMesh->mVertices[iCurrent] = groupData.vPositions[iIndex]; + pcMesh->mNormals[iCurrent] = groupData.vNormals[iIndex]; - if (!vTextureCoords2.empty()) + if (!groupData.vTextureCoords1.empty()) { - pcMesh->mTextureCoords[1][iCurrent] = vTextureCoords2[oldFace.mIndices[c]]; + pcMesh->mTextureCoords[0][iCurrent] = groupData.vTextureCoords1[iIndex]; + if (!groupData.vTextureCoords2.empty()) + { + pcMesh->mTextureCoords[1][iCurrent] = groupData.vTextureCoords2[iIndex]; + } } - - pcMesh->mFaces[iFace].mIndices[c] = iCurrent; - ++iCurrent; + 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 > aaiVWeightList; + aaiVWeightList.resize(iNumOutBones); + + int iCurrent = 0; + for (unsigned int iFace = 0; iFace < pcMesh->mNumFaces;++iFace) + { + unsigned int iSrcFace = splittedGroupData.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 (0xffffffff != 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 >::const_iterator + kimmi = aaiVWeightList.begin(); + kimmi != aaiVWeightList.end();++kimmi) + { + if (!(*kimmi).empty())++pcMesh->mNumBones; + } + pcMesh->mBones = new aiBone*[pcMesh->mNumBones]; + iCurrent = 0; + for (std::vector >::const_iterator + kimmi = aaiVWeightList.begin(); + kimmi != aaiVWeightList.end();++kimmi,++iCurrent) + { + if ((*kimmi).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)(*kimmi).size(); + pcBone->mWeights = new aiVertexWeight[pcBone->mNumWeights]; + + for (unsigned int weight = 0; weight < pcBone->mNumWeights;++weight) + { + pcBone->mWeights[weight].mVertexId = (*kimmi)[weight]; + pcBone->mWeights[weight].mWeight = 1.0f; + } + } + } // add the mesh to the list of output meshes - avOutList.push_back(pcMesh); + splittedGroupData.avOutList.push_back(pcMesh); } } - return; } // ------------------------------------------------------------------------------------------------ -void MDLImporter::JoinSkins_GameStudioA7( +void MDLImporter::JoinSkins_3DGS_MDL7( MaterialHelper* pcMat1, MaterialHelper* pcMat2, MaterialHelper* pcMatOut) { - ai_assert(NULL != pcMat1); - ai_assert(NULL != pcMat2); - ai_assert(NULL != 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 @@ -1884,7 +1953,6 @@ void MDLImporter::JoinSkins_GameStudioA7( pcMatOut->AddProperty(&iVal,1,AI_MATKEY_UVWSRC_DIFFUSE(1)); pcMatOut->AddProperty(&sString,AI_MATKEY_TEXTURE_DIFFUSE(1)); } - return; } // ------------------------------------------------------------------------------------------------ void MDLImporter::FlipNormals(aiMesh* pcMesh) @@ -1923,8 +1991,8 @@ void MDLImporter::FlipNormals(aiMesh* pcMesh) vMax0.z = std::max(vMax0.z,vWithNormal.z); } - if (fabsf((vMax0.x - vMin0.x) * (vMax0.y - vMin0.y) * (vMax0.z - vMin0.z)) <= - fabsf((vMax1.x - vMin1.x) * (vMax1.y - vMin1.y) * (vMax1.z - vMin1.z))) + if (::fabsf((vMax0.x - vMin0.x) * (vMax0.y - vMin0.y) * (vMax0.z - vMin0.z)) <= + ::fabsf((vMax1.x - vMin1.x) * (vMax1.y - vMin1.y) * (vMax1.z - vMin1.z))) { DefaultLogger::get()->info("The models normals are facing inwards " "(or the model is too planar or concave). Flipping the normal set ..."); @@ -1934,11 +2002,9 @@ void MDLImporter::FlipNormals(aiMesh* pcMesh) pcMesh->mNormals[i] *= -1.0f; } } - return; } // ------------------------------------------------------------------------------------------------ void MDLImporter::InternReadFile_HL2( ) { const MDL::Header_HL2* pcHeader = (const MDL::Header_HL2*)this->mBuffer; - return; } \ No newline at end of file diff --git a/code/MDLLoader.h b/code/MDLLoader.h index d5c82bfd0..76add04a6 100644 --- a/code/MDLLoader.h +++ b/code/MDLLoader.h @@ -61,6 +61,11 @@ class MaterialHelper; using namespace MDL; + +#if (!defined VALIDATE_FILE_SIZE) +# define VALIDATE_FILE_SIZE(msg) this->SizeCheck(msg,__FILE__,__LINE__) +#endif + // --------------------------------------------------------------------------- /** Used to load MDL files */ @@ -104,120 +109,57 @@ protected: protected: // ------------------------------------------------------------------- - /** Import a quake 1 MDL file + /** Import a quake 1 MDL file (IDPO) */ void InternReadFile_Quake1( ); // ------------------------------------------------------------------- - /** Import a GameStudio A4/A5 file + /** Import a GameStudio A4/A5 file (MDL 3,4,5) */ - void InternReadFile_GameStudio( ); + void InternReadFile_3DGS_MDL345( ); // ------------------------------------------------------------------- - /** Import a GameStudio A7 file + /** Import a GameStudio A7 file (MDL 7) */ - void InternReadFile_GameStudioA7( ); + void InternReadFile_3DGS_MDL7( ); // ------------------------------------------------------------------- - /** Import a CS:S/HL2 MDL file + /** Import a CS:S/HL2 MDL file (not fully implemented) */ void InternReadFile_HL2( ); + + // ******************************************************************* + // Debugging/validation functions + + // ------------------------------------------------------------------- - /** Load a paletized texture from the file and convert it to 32bpp + /** Check whether a given position is inside the valid range + * Throw a new ImportErrorException if it is not + * \param szPos Cursor position + * \param szFile Name of the source file from which the function was called + * \param iLine Source code line from which the function was called */ - void CreateTextureARGB8(const unsigned char* szData); + void SizeCheck(const void* szPos); + void SizeCheck(const void* szPos, const char* szFile, unsigned int iLine); - // ------------------------------------------------------------------- - /** Used to load textures from MDL3/4 - * \param szData Input data - * \param iType Color data type - * \param piSkip Receive: Size to skip - */ - void CreateTextureARGB8_GS4(const unsigned char* szData, - unsigned int iType, - unsigned int* piSkip); - - // ------------------------------------------------------------------- - /** Used to load textures from MDL5 - * \param szData Input data - * \param iType Color data type - * \param piSkip Receive: Size to skip - */ - void CreateTextureARGB8_GS5(const unsigned char* szData, - unsigned int iType, - unsigned int* piSkip); - - // ------------------------------------------------------------------- - /** Parse a skin lump in a MDL7 file with all of its features - * \param szCurrent Current data pointer - * \param szCurrentOut Output data pointer - * \param pcMats Material list for this group. To be filled ... - */ - void ParseSkinLump_GameStudioA7( - const unsigned char* szCurrent, - const unsigned char** szCurrentOut, - std::vector& pcMats); - - // ------------------------------------------------------------------- - /** Parse texture color data for MDL5, MDL6 and MDL7 formats - * \param szData Current data pointer - * \param iType type of the texture data. No DDS or external - * \param piSkip Receive the number of bytes to skip - * \param pcNew Must point to fully initialized data. Width and - * height must be set. - */ - void ParseTextureColorData(const unsigned char* szData, - unsigned int iType, - unsigned int* piSkip, - aiTexture* pcNew); - // ------------------------------------------------------------------- /** Validate the header data structure of a game studio MDL7 file * \param pcHeader Input header to be validated */ - void ValidateHeader_GameStudioA7(const MDL::Header_MDL7* pcHeader); + void ValidateHeader_3DGS_MDL7(const MDL::Header_MDL7* pcHeader); // ------------------------------------------------------------------- - /** Join two materials / skins. Setup UV source ... etc - * \param pcMat1 First input material - * \param pcMat2 Second input material - * \param pcMatOut Output material instance to be filled. Must be empty + /** Validate the header data structure of a Quake 1 model + * \param pcHeader Input header to be validated */ - void JoinSkins_GameStudioA7(MaterialHelper* pcMat1, - MaterialHelper* pcMat2, - MaterialHelper* pcMatOut); - - // ------------------------------------------------------------------- - /** Generate the final output meshes for a7 models - * \param aiSplit Face-per-material list - * \param pcMats List of all materials - * \param avOutList Output: List of all meshes - * \param pcFaces List of all input faces - * \param vPositions List of all input vectors - * \param vNormals List of all input normal vectors - * \param vTextureCoords1 List of all input UV coords #1 - * \param vTextureCoords2 List of all input UV coords #2 - */ - void GenerateOutputMeshes_GameStudioA7( - const std::vector** aiSplit, - const std::vector& pcMats, - std::vector& avOutList, - const MDL::IntFace_MDL7* pcFaces, - const std::vector& vPositions, - const std::vector& vNormals, - const std::vector& vTextureCoords1, - const std::vector& vTextureCoords2); + void ValidateHeader_Quake1(const MDL::Header* pcHeader); - // ------------------------------------------------------------------- - /** Calculate absolute bone animation matrices for each bone - * \param pcBones Pointer to the bone section in the file - * \param apcOutBones Output bones array - */ - void CalculateAbsBoneAnimMatrices(const MDL::Bone_MDL7* pcBones, - aiBone** apcOutBones); + // ******************************************************************* + // Material import + // ------------------------------------------------------------------- /** Try to load a palette from the current directory (colormap.lmp) @@ -230,6 +172,262 @@ protected: */ void FreePalette(const unsigned char* pszColorMap); + + // ------------------------------------------------------------------- + /** Load a paletized texture from the file and convert it to 32bpp + */ + void CreateTextureARGB8_3DGS_MDL3(const unsigned char* szData); + + // ------------------------------------------------------------------- + /** Used to load textures from MDL3/4 + * \param szData Input data + * \param iType Color data type + * \param piSkip Receive: Size to skip, in bytes + */ + void CreateTexture_3DGS_MDL4(const unsigned char* szData, + unsigned int iType, + unsigned int* piSkip); + + // ------------------------------------------------------------------- + /** Used to load textures from MDL5 + * \param szData Input data + * \param iType Color data type + * \param piSkip Receive: Size to skip, in bytes + */ + void CreateTexture_3DGS_MDL5(const unsigned char* szData, + unsigned int iType, + unsigned int* piSkip); + + + // ------------------------------------------------------------------- + /** Checks whether a texture can be replaced with a single color + * This is useful for all file formats before MDL7 (all those + * that are not containing material colors separate from textures). + * MED seems to write dummy 8x8 monochrome images instead. + * \param pcTexture Input texture + * \return aiColor.r is set to qnan if the function fails and no + * color can be found. + */ + aiColor4D ReplaceTextureWithColor(const aiTexture* pcTexture); + + + // ******************************************************************* + // Quake1, MDL 3,4,5 import + + + // ------------------------------------------------------------------- + /** Converts the absolute texture coordinates in MDL5 files to + * relative in a range between 0 and 1 + */ + void CalculateUVCoordinates_MDL5(); + + // ------------------------------------------------------------------- + /** Read an UV coordinate from the file. If the file format is not + * MDL5, the function calculates relative texture coordinates + * \param vOut Receives the output UV coord + * \param pcSrc UV coordinate buffer + * \param UV coordinate index + */ + void ImportUVCoordinate_3DGS_MDL345( aiVector3D& vOut, + const MDL::TexCoord_MDL3* pcSrc, + unsigned int iIndex); + + // ------------------------------------------------------------------- + /** Setup the material properties for Quake and MDL<7 models. + * These formats don't support more than one material per mesh, + * therefore the method processes only ONE skin and removes + * all others. + */ + void SetupMaterialProperties_3DGS_MDL5_Quake1( ); + + + // ******************************************************************* + // MDL7 import + + // ------------------------------------------------------------------- + /** Parse a skin lump in a MDL7/HMP7 file with all of its features + * variant 1: Current cursor position is the beginning of the skin header + * \param szCurrent Current data pointer + * \param szCurrentOut Output data pointer + * \param pcMats Material list for this group. To be filled ... + */ + void ParseSkinLump_3DGS_MDL7( + const unsigned char* szCurrent, + const unsigned char** szCurrentOut, + std::vector& pcMats); + + // ------------------------------------------------------------------- + /** Parse a skin lump in a MDL7/HMP7 file with all of its features + * variant 2: Current cursor position is the beginning of the skin data + * \param szCurrent Current data pointer + * \param szCurrentOut Output data pointer + * \param pcMatOut Output material + * \param iType header.typ + * \param iWidth header.width + * \param iHeight header.height + */ + void ParseSkinLump_3DGS_MDL7( + const unsigned char* szCurrent, + const unsigned char** szCurrentOut, + MaterialHelper* pcMatOut, + unsigned int iType, + unsigned int iWidth, + unsigned int iHeight); + + // ------------------------------------------------------------------- + /** Skip a skin lump in a MDL7/HMP7 file + * \param szCurrent Current data pointer + * \param szCurrentOut Output data pointer. Points to the byte just + * behind the last byte of the skin. + * \param iType header.typ + * \param iWidth header.width + * \param iHeight header.height + */ + void SkipSkinLump_3DGS_MDL7(const unsigned char* szCurrent, + const unsigned char** szCurrentOut, + unsigned int iType, + unsigned int iWidth, + unsigned int iHeight); + + // ------------------------------------------------------------------- + /** Parse texture color data for MDL5, MDL6 and MDL7 formats + * \param szData Current data pointer + * \param iType type of the texture data. No DDS or external + * \param piSkip Receive the number of bytes to skip + * \param pcNew Must point to fully initialized data. Width and + * height must be set. If pcNew->pcData is set to 0xffffffff, + * piSkip will receive the size of the texture, in bytes, but no + * color data will be read. + */ + void ParseTextureColorData(const unsigned char* szData, + unsigned int iType, + unsigned int* piSkip, + aiTexture* pcNew); + + // ------------------------------------------------------------------- + /** Join two materials / skins. Setup UV source ... etc + * \param pcMat1 First input material + * \param pcMat2 Second input material + * \param pcMatOut Output material instance to be filled. Must be empty + */ + void JoinSkins_3DGS_MDL7(MaterialHelper* pcMat1, + MaterialHelper* pcMat2, + MaterialHelper* pcMatOut); + + // ------------------------------------------------------------------- + /** Add a bone transformation key to an animation + * \param iTrafo Index of the transformation (always==frame index?) + * No need to validate this index, it is always valid. + * \param pcBoneTransforms Bone transformation for this index + * \param apcOutBones Output bones array + */ + void AddAnimationBoneTrafoKey_3DGS_MDL7(unsigned int iTrafo, + const MDL::BoneTransform_MDL7* pcBoneTransforms, + MDL::IntBone_MDL7** apcBonesOut); + + // ------------------------------------------------------------------- + /** Load the bone list of a MDL7 file + * \return If the bones could be loaded successfully, a valid + * array containing pointers to a temporary bone + * representation. NULL if the bones could not be loaded. + */ + MDL::IntBone_MDL7** LoadBones_3DGS_MDL7(); + + // ------------------------------------------------------------------- + /** Load bone transformation keyframes from a file chunk + * \param groupInfo -> doc of data structure + * \param frame -> doc of data structure + * \param shared -> doc of data structure + */ + void ParseBoneTrafoKeys_3DGS_MDL7( + const MDL::IntGroupInfo_MDL7& groupInfo, + IntFrameInfo_MDL7& frame, + MDL::IntSharedData_MDL7& shared); + + // ------------------------------------------------------------------- + /** Calculate absolute bone animation matrices for each bone + * \param pcBones Pointer to the bone section in the file + * \param apcOutBones Output bones array + */ + void CalcAbsBoneMatrices_3DGS_MDL7(const MDL::Bone_MDL7* pcBones, + MDL::IntBone_MDL7** apcOutBones); + + // ------------------------------------------------------------------- + /** Add all bones to the nodegraph (as children of the root node) + * \param apcBonesOut List of bones + * \param pcParent Parent node. New nodes will be added to this node + * \param iParentIndex Index of the parent bone + */ + void AddBonesToNodeGraph_3DGS_MDL7(const MDL::IntBone_MDL7** apcBonesOut, + aiNode* pcParent,uint16_t iParentIndex); + + // ------------------------------------------------------------------- + /** Build output animations + * \param apcBonesOut List of bones + */ + void BuildOutputAnims_3DGS_MDL7(const MDL::IntBone_MDL7** apcBonesOut); + + // ------------------------------------------------------------------- + /** Handles materials that are just referencing another material + * There is no test file for this feature, but Conitec's doc + * say it is used. + */ + void HandleMaterialReferences_3DGS_MDL7(); + + // ------------------------------------------------------------------- + /** Copies only the material that are referenced by at least one + * mesh to the final output material list. All other materials + * will be discarded. + * \param shared -> doc of data structure + */ + void CopyMaterials_3DGS_MDL7(MDL::IntSharedData_MDL7 &shared); + + // ------------------------------------------------------------------- + /** Process the frame section at the end of a group + * \param groupInfo -> doc of data structure + * \param shared -> doc of data structure + * \param szCurrent Pointer to the start of the frame section + * \param szCurrentOut Receives a pointer to the first byte of the + * next data section. + * \return false to read no further groups (a small workaround for + * some tiny and unsolved problems ... ) + */ + bool ProcessFrames_3DGS_MDL7(const MDL::IntGroupInfo_MDL7& groupInfo, + MDL::IntSharedData_MDL7& shared, + const unsigned char* szCurrent, + const unsigned char** szCurrentOut); + + // ------------------------------------------------------------------- + /** Sort all faces by their materials. If the mesh is using + * multiple materials per face (that are blended together) the function + * might create new materials. + * \param groupInfo -> doc of data structure + * \param groupData -> doc of data structure + * \param splittedGroupData -> doc of data structure + */ + void SortByMaterials_3DGS_MDL7( + const MDL::IntGroupInfo_MDL7& groupInfo, + MDL::IntGroupData_MDL7& groupData, + MDL::IntSplittedGroupData_MDL7& splittedGroupData); + + // ------------------------------------------------------------------- + /** Read all faces and vertices from a MDL7 group. The function fills + * preallocated memory buffers. + * \param groupInfo -> doc of data structure + * \param groupData -> doc of data structure + */ + void ReadFaces_3DGS_MDL7(const MDL::IntGroupInfo_MDL7& groupInfo, + MDL::IntGroupData_MDL7& groupData); + + // ------------------------------------------------------------------- + /** Generate the final output meshes for a7 models + * \param groupData -> doc of data structure + * \param splittedGroupData -> doc of data structure + */ + void GenerateOutputMeshes_3DGS_MDL7( + MDL::IntGroupData_MDL7& groupData, + MDL::IntSplittedGroupData_MDL7& splittedGroupData); + // ------------------------------------------------------------------- /** Try to determine whether the normals of the model are flipped * Some MDL7 models seem to have flipped normals (and there is also @@ -239,16 +437,13 @@ protected: */ void FlipNormals(aiMesh* pcMesh); -private: - - /** Header of the MDL file */ - const MDL::Header* m_pcHeader; +protected: /** Buffer to hold the loaded file */ unsigned char* mBuffer; - /** For GameStudio MDL files: The number in the magic - word, either 3,4 or 5*/ + /** For GameStudio MDL files: The number in the magic word, either 3,4 or 5 + * (MDL7 doesn't need this, the format has a separate loader) */ unsigned int iGSFileVersion; /** Output I/O handler. used to load external lmp files @@ -258,6 +453,10 @@ private: /** Output scene to be filled */ aiScene* pScene; + + /** Size of the input file in bytes + */ + unsigned int iFileSize; }; }; // end of namespace Assimp diff --git a/code/MDLMaterialLoader.cpp b/code/MDLMaterialLoader.cpp new file mode 100644 index 000000000..eef6f3468 --- /dev/null +++ b/code/MDLMaterialLoader.cpp @@ -0,0 +1,774 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (ASSIMP) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2008, ASSIMP Development 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 Development 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 Implementation of the material part of the MDL importer class */ + +// internal headers +#include "MaterialSystem.h" +#include "MDLLoader.h" +#include "MDLDefaultColorMap.h" +#include "qnan.h" + +// public ASSIMP headers +#include "../include/DefaultLogger.h" +#include "../include/IOStream.h" +#include "../include/IOSystem.h" +#include "../include/aiMesh.h" +#include "../include/aiScene.h" +#include "../include/aiAssert.h" + +using namespace Assimp; + +// ------------------------------------------------------------------------------------------------ +void MDLImporter::SearchPalette(const unsigned char** pszColorMap) +{ + // now try to find the color map in the current directory + IOStream* pcStream = this->pIOHandler->Open("colormap.lmp","rb"); + + const unsigned char* szColorMap = (const unsigned char*)::g_aclrDefaultColorMap; + if(pcStream) + { + if (pcStream->FileSize() >= 768) + { + szColorMap = new unsigned char[256*3]; + pcStream->Read(const_cast(szColorMap),256*3,1); + + DefaultLogger::get()->info("Found valid colormap.lmp in directory. " + "It will be used to decode embedded textures in palletized formats."); + } + delete pcStream; + pcStream = NULL; + } + *pszColorMap = szColorMap; + return; +} +// ------------------------------------------------------------------------------------------------ +void MDLImporter::FreePalette(const unsigned char* szColorMap) +{ + if (szColorMap != (const unsigned char*)::g_aclrDefaultColorMap) + { + delete[] szColorMap; + } + return; +} +// ------------------------------------------------------------------------------------------------ +aiColor4D MDLImporter::ReplaceTextureWithColor(const aiTexture* pcTexture) +{ + ai_assert(NULL != pcTexture); + + aiColor4D clrOut; + clrOut.r = std::numeric_limits::quiet_NaN(); + if (!pcTexture->mHeight || !pcTexture->mWidth)return clrOut; + + const unsigned int iNumPixels = pcTexture->mHeight*pcTexture->mWidth; + const aiTexel* pcTexel = pcTexture->pcData+1; + const aiTexel* const pcTexelEnd = &pcTexture->pcData[iNumPixels]; + + while (pcTexel != pcTexelEnd) + { + if (*pcTexel != *(pcTexel-1)) + { + pcTexel = NULL;break; + } + ++pcTexel; + } + if (pcTexel) + { + clrOut.r = pcTexture->pcData->r / 255.0f; + clrOut.g = pcTexture->pcData->g / 255.0f; + clrOut.b = pcTexture->pcData->b / 255.0f; + clrOut.a = pcTexture->pcData->a / 255.0f; + } + return clrOut; +} +// ------------------------------------------------------------------------------------------------ +void MDLImporter::CreateTextureARGB8_3DGS_MDL3(const unsigned char* szData) +{ + const MDL::Header* pcHeader = (const MDL::Header*)this->mBuffer; + VALIDATE_FILE_SIZE(szData + pcHeader->skinwidth * + pcHeader->skinheight); + + // allocate a new texture object + aiTexture* pcNew = new aiTexture(); + pcNew->mWidth = pcHeader->skinwidth; + pcNew->mHeight = pcHeader->skinheight; + + pcNew->pcData = new aiTexel[pcNew->mWidth * pcNew->mHeight]; + + const unsigned char* szColorMap; + this->SearchPalette(&szColorMap); + + // copy texture data + for (unsigned int i = 0; i < pcNew->mWidth*pcNew->mHeight;++i) + { + const unsigned char val = szData[i]; + const unsigned char* sz = &szColorMap[val*3]; + + pcNew->pcData[i].a = 0xFF; + pcNew->pcData[i].r = *sz++; + pcNew->pcData[i].g = *sz++; + pcNew->pcData[i].b = *sz; + } + + this->FreePalette(szColorMap); + + // store the texture + aiTexture** pc = this->pScene->mTextures; + this->pScene->mTextures = new aiTexture*[this->pScene->mNumTextures+1]; + for (unsigned int i = 0; i < this->pScene->mNumTextures;++i) + this->pScene->mTextures[i] = pc[i]; + + this->pScene->mTextures[this->pScene->mNumTextures] = pcNew; + this->pScene->mNumTextures++; + delete[] pc; + return; +} +// ------------------------------------------------------------------------------------------------ +void MDLImporter::CreateTexture_3DGS_MDL4(const unsigned char* szData, + unsigned int iType, + unsigned int* piSkip) +{ + const MDL::Header* pcHeader = (const MDL::Header*)this->mBuffer; + + ai_assert(NULL != piSkip); + + if (iType == 1 || iType > 3) + { + DefaultLogger::get()->error("Unsupported texture file format"); + return; + } + + bool bNoRead = *piSkip == 0xffffffff; + + // allocate a new texture object + aiTexture* pcNew = new aiTexture(); + pcNew->mWidth = pcHeader->skinwidth; + pcNew->mHeight = pcHeader->skinheight; + + if (bNoRead)pcNew->pcData = (aiTexel*)0xffffffff; + this->ParseTextureColorData(szData,iType,piSkip,pcNew); + + // store the texture + if (!bNoRead) + { + if (!this->pScene->mNumTextures) + { + this->pScene->mNumTextures = 1; + this->pScene->mTextures = new aiTexture*[1]; + this->pScene->mTextures[0] = pcNew; + } + else + { + aiTexture** pc = this->pScene->mTextures; + this->pScene->mTextures = new aiTexture*[this->pScene->mNumTextures+1]; + for (unsigned int i = 0; i < this->pScene->mNumTextures;++i) + this->pScene->mTextures[i] = pc[i]; + this->pScene->mTextures[this->pScene->mNumTextures] = pcNew; + this->pScene->mNumTextures++; + delete[] pc; + } + } + else delete pcNew; + return; +} +// ------------------------------------------------------------------------------------------------ +void MDLImporter::ParseTextureColorData(const unsigned char* szData, + unsigned int iType, + unsigned int* piSkip, + aiTexture* pcNew) +{ + // allocate storage for the texture image + if ((aiTexel*)0xffffffff != pcNew->pcData) + pcNew->pcData = new aiTexel[pcNew->mWidth * pcNew->mHeight]; + + // R5G6B5 format (with or without MIPs) + // **************************************************************** + if (2 == iType || 10 == iType) + { + VALIDATE_FILE_SIZE(szData + pcNew->mWidth*pcNew->mHeight*2); + + // copy texture data + unsigned int i; + if ((aiTexel*)0xffffffff != pcNew->pcData) + { + for (i = 0; i < pcNew->mWidth*pcNew->mHeight;++i) + { + MDL::RGB565 val = ((MDL::RGB565*)szData)[i]; + + pcNew->pcData[i].a = 0xFF; + pcNew->pcData[i].r = (unsigned char)val.b << 3; + pcNew->pcData[i].g = (unsigned char)val.g << 2; + pcNew->pcData[i].b = (unsigned char)val.r << 3; + } + } + else i = pcNew->mWidth*pcNew->mHeight; + *piSkip = i * 2; + + // apply MIP maps + if (10 == iType) + { + *piSkip += ((i >> 2) + (i >> 4) + (i >> 6)) << 1; + VALIDATE_FILE_SIZE(szData + *piSkip); + } + } + // ARGB4 format (with or without MIPs) + // **************************************************************** + else if (3 == iType || 11 == iType) + { + VALIDATE_FILE_SIZE(szData + pcNew->mWidth*pcNew->mHeight*4); + + // copy texture data + unsigned int i; + if ((aiTexel*)0xffffffff != pcNew->pcData) + { + for (i = 0; i < pcNew->mWidth*pcNew->mHeight;++i) + { + MDL::ARGB4 val = ((MDL::ARGB4*)szData)[i]; + + pcNew->pcData[i].a = (unsigned char)val.a << 4; + pcNew->pcData[i].r = (unsigned char)val.r << 4; + pcNew->pcData[i].g = (unsigned char)val.g << 4; + pcNew->pcData[i].b = (unsigned char)val.b << 4; + } + } + else i = pcNew->mWidth*pcNew->mHeight; + *piSkip = i * 2; + + // apply MIP maps + if (11 == iType) + { + *piSkip += ((i >> 2) + (i >> 4) + (i >> 6)) << 1; + VALIDATE_FILE_SIZE(szData + *piSkip); + } + } + // RGB8 format (with or without MIPs) + // **************************************************************** + else if (4 == iType || 12 == iType) + { + VALIDATE_FILE_SIZE(szData + pcNew->mWidth*pcNew->mHeight*3); + + // copy texture data + unsigned int i; + if ((aiTexel*)0xffffffff != pcNew->pcData) + { + for (i = 0; i < pcNew->mWidth*pcNew->mHeight;++i) + { + const unsigned char* _szData = &szData[i*3]; + + pcNew->pcData[i].a = 0xFF; + pcNew->pcData[i].b = *_szData++; + pcNew->pcData[i].g = *_szData++; + pcNew->pcData[i].r = *_szData; + } + } + else i = pcNew->mWidth*pcNew->mHeight; + + + // apply MIP maps + *piSkip = i * 3; + if (12 == iType) + { + *piSkip += ((i >> 2) + (i >> 4) + (i >> 6)) *3; + VALIDATE_FILE_SIZE(szData + *piSkip); + } + } + // ARGB8 format (with ir without MIPs) + // **************************************************************** + else if (5 == iType || 13 == iType) + { + VALIDATE_FILE_SIZE(szData + pcNew->mWidth*pcNew->mHeight*4); + + // copy texture data + unsigned int i; + if ((aiTexel*)0xffffffff != pcNew->pcData) + { + for (i = 0; i < pcNew->mWidth*pcNew->mHeight;++i) + { + const unsigned char* _szData = &szData[i*4]; + + pcNew->pcData[i].b = *_szData++; + pcNew->pcData[i].g = *_szData++; + pcNew->pcData[i].r = *_szData++; + pcNew->pcData[i].a = *_szData; + } + } + else i = pcNew->mWidth*pcNew->mHeight; + + // apply MIP maps + *piSkip = i << 2; + if (13 == iType) + { + *piSkip += ((i >> 2) + (i >> 4) + (i >> 6)) << 2; + } + } + // palletized 8 bit texture. As for Quake 1 + // **************************************************************** + else if (0 == iType) + { + VALIDATE_FILE_SIZE(szData + pcNew->mWidth*pcNew->mHeight); + + // copy texture data + unsigned int i; + if ((aiTexel*)0xffffffff != pcNew->pcData) + { + + const unsigned char* szColorMap; + this->SearchPalette(&szColorMap); + + for (i = 0; i < pcNew->mWidth*pcNew->mHeight;++i) + { + const unsigned char val = szData[i]; + const unsigned char* sz = &szColorMap[val*3]; + + pcNew->pcData[i].a = 0xFF; + pcNew->pcData[i].r = *sz++; + pcNew->pcData[i].g = *sz++; + pcNew->pcData[i].b = *sz; + } + this->FreePalette(szColorMap); + + } + else i = pcNew->mWidth*pcNew->mHeight; + *piSkip = i; + + // FIXME: Also support for MIP maps? + } + return; +} +// ------------------------------------------------------------------------------------------------ +void MDLImporter::CreateTexture_3DGS_MDL5(const unsigned char* szData, + unsigned int iType, + unsigned int* piSkip) +{ + ai_assert(NULL != piSkip); + + bool bNoRead = *piSkip == 0xffffffff; + + // allocate a new texture object + aiTexture* pcNew = new aiTexture(); + + VALIDATE_FILE_SIZE(szData+8); + + // first read the size of the texture + pcNew->mWidth = *((uint32_t*)szData); + szData += sizeof(uint32_t); + + pcNew->mHeight = *((uint32_t*)szData); + szData += sizeof(uint32_t); + + if (bNoRead)pcNew->pcData = (aiTexel*)0xffffffff; + + // this should not occur - at least the docs say it shouldn't + // however, you can easily try out what MED does if you have + // a model with a DDS texture and export it to MDL5 ... + // yes, you're right. It embedds the DDS texture ... :cry: + if (6 == iType) + { + // this is a compressed texture in DDS format + *piSkip = pcNew->mWidth; + VALIDATE_FILE_SIZE(szData + *piSkip); + + if (!bNoRead) + { + // place a hint and let the application know that it's + // a DDS file + pcNew->mHeight = 0; + pcNew->achFormatHint[0] = 'd'; + pcNew->achFormatHint[1] = 'd'; + pcNew->achFormatHint[2] = 's'; + pcNew->achFormatHint[3] = '\0'; + + pcNew->pcData = (aiTexel*) new unsigned char[pcNew->mWidth]; + ::memcpy(pcNew->pcData,szData,pcNew->mWidth); + } + } + else + { + // parse the color data of the texture + this->ParseTextureColorData(szData,iType, + piSkip,pcNew); + } + *piSkip += sizeof(uint32_t) * 2; + + if (!bNoRead) + { + // store the texture + if (!this->pScene->mNumTextures) + { + this->pScene->mNumTextures = 1; + this->pScene->mTextures = new aiTexture*[1]; + this->pScene->mTextures[0] = pcNew; + } + else + { + aiTexture** pc = this->pScene->mTextures; + this->pScene->mTextures = new aiTexture*[this->pScene->mNumTextures+1]; + for (unsigned int i = 0; i < this->pScene->mNumTextures;++i) + this->pScene->mTextures[i] = pc[i]; + + this->pScene->mTextures[this->pScene->mNumTextures] = pcNew; + this->pScene->mNumTextures++; + delete[] pc; + } + } + else delete pcNew; + return; +} +// ------------------------------------------------------------------------------------------------ +void MDLImporter::ParseSkinLump_3DGS_MDL7( + const unsigned char* szCurrent, + const unsigned char** szCurrentOut, + MaterialHelper* pcMatOut, + unsigned int iType, + unsigned int iWidth, + unsigned int iHeight) +{ + aiTexture* pcNew = NULL; + + // get the type of the skin + unsigned int iMasked = (unsigned int)(iType & 0xF); + + if (0x1 == iMasked) + { + // ***** REFERENCE TO ANOTHER SKIN INDEX ***** + + // NOTE: Documentation - if you can call it a documentation, I prefer + // the expression "rubbish" - states it is currently unused. However, + // I don't know what ideas the terrible developers of Conitec will + // have tomorrow, so Im going to implement it. + int referrer = (int)iWidth; + pcMatOut->AddProperty(&referrer,1,AI_MDL7_REFERRER_MATERIAL); + } + else if (0x6 == iMasked) + { + // ***** EMBEDDED DDS FILE ***** + if (1 != iHeight) + { + DefaultLogger::get()->warn("Found a reference to an embedded DDS texture, " + "but texture height is not equal to 1, which is not supported by MED"); + } + + pcNew = new aiTexture(); + pcNew->mHeight = 0; + pcNew->mWidth = iWidth; + pcNew->achFormatHint[0] = 'd'; + pcNew->achFormatHint[1] = 'd'; + pcNew->achFormatHint[2] = 's'; + pcNew->achFormatHint[3] = '\0'; + + pcNew->pcData = (aiTexel*) new unsigned char[pcNew->mWidth]; + memcpy(pcNew->pcData,szCurrent,pcNew->mWidth); + szCurrent += iWidth; + } + if (0x7 == iMasked) + { + // ***** REFERENCE TO EXTERNAL FILE ***** + if (1 != iHeight) + { + DefaultLogger::get()->warn("Found a reference to an external texture, " + "but texture height is not equal to 1, which is not supported by MED"); + } + + aiString szFile; + const size_t iLen = strlen((const char*)szCurrent); + size_t iLen2 = iLen+1; + iLen2 = iLen2 > MAXLEN ? MAXLEN : iLen2; + memcpy(szFile.data,(const char*)szCurrent,iLen2); + szFile.length = iLen; + + szCurrent += iLen2; + + // place this as diffuse texture + pcMatOut->AddProperty(&szFile,AI_MATKEY_TEXTURE_DIFFUSE(0)); + } + else if (iMasked || !iType || (iType && iWidth && iHeight)) + { + // ***** STANDARD COLOR TEXTURE ***** + pcNew = new aiTexture(); + if (!iHeight || !iWidth) + { + DefaultLogger::get()->warn("Found embedded texture, but its width " + "an height are both 0. Is this a joke?"); + + // generate an empty chess pattern + pcNew->mWidth = pcNew->mHeight = 8; + pcNew->pcData = new aiTexel[64]; + for (unsigned int x = 0; x < 8;++x) + { + for (unsigned int y = 0; y < 8;++y) + { + bool bSet = false; + if (0 == x % 2 && 0 != y % 2 || + 0 != x % 2 && 0 == y % 2)bSet = true; + + aiTexel* pc = &pcNew->pcData[y * 8 + x]; + if (bSet)pc->r = pc->b = pc->g = 0xFF; + else pc->r = pc->b = pc->g = 0; + pc->a = 0xFF; + } + } + } + else + { + // it is a standard color texture. Fill in width and height + // and call the same function we used for loading MDL5 files + + pcNew->mWidth = iWidth; + pcNew->mHeight = iHeight; + + unsigned int iSkip = 0; + this->ParseTextureColorData(szCurrent,iMasked,&iSkip,pcNew); + + // skip length of texture data + szCurrent += iSkip; + } + } + + // sometimes there are MDL7 files which have a monochrome + // texture instead of material colors ... posssible they have + // been converted to MDL7 from other formats, such as MDL5 + aiColor4D clrTexture = this->ReplaceTextureWithColor(pcNew); + + // check whether a material definition is contained in the skin + if (iType & AI_MDL7_SKINTYPE_MATERIAL) + { + const MDL::Material_MDL7* pcMatIn = (const MDL::Material_MDL7*)szCurrent; + szCurrent = (unsigned char*)(pcMatIn+1); + VALIDATE_FILE_SIZE(szCurrent); + + aiColor3D clrTemp; + +#define COLOR_MULTIPLY_RGB() \ + if (is_not_qnan(clrTexture.r)) \ + { \ + clrTemp.r *= clrTexture.r; \ + clrTemp.g *= clrTexture.g; \ + clrTemp.b *= clrTexture.b; \ + } + + // read diffuse color + clrTemp.r = pcMatIn->Diffuse.r; + clrTemp.g = pcMatIn->Diffuse.g; + clrTemp.b = pcMatIn->Diffuse.b; + COLOR_MULTIPLY_RGB(); + pcMatOut->AddProperty(&clrTemp,1,AI_MATKEY_COLOR_DIFFUSE); + + // read specular color + clrTemp.r = pcMatIn->Specular.r; + clrTemp.g = pcMatIn->Specular.g; + clrTemp.b = pcMatIn->Specular.b; + COLOR_MULTIPLY_RGB(); + pcMatOut->AddProperty(&clrTemp,1,AI_MATKEY_COLOR_SPECULAR); + + // read ambient color + clrTemp.r = pcMatIn->Ambient.r; + clrTemp.g = pcMatIn->Ambient.g; + clrTemp.b = pcMatIn->Ambient.b; + COLOR_MULTIPLY_RGB(); + pcMatOut->AddProperty(&clrTemp,1,AI_MATKEY_COLOR_AMBIENT); + + // read emissive color + clrTemp.r = pcMatIn->Emissive.r; + clrTemp.g = pcMatIn->Emissive.g; + clrTemp.b = pcMatIn->Emissive.b; + pcMatOut->AddProperty(&clrTemp,1,AI_MATKEY_COLOR_EMISSIVE); + + // FIX: Take the opacity from the ambient color + // the doc says something else, but it is fact that MED exports the + // opacity like this .... ARRRGGHH! + clrTemp.r = pcMatIn->Ambient.a; + if (is_not_qnan(clrTexture.r))clrTemp.r *= clrTexture.a; + pcMatOut->AddProperty(&clrTemp.r,1,AI_MATKEY_OPACITY); + + // read phong power + int iShadingMode = (int)aiShadingMode_Gouraud; + if (0.0f != pcMatIn->Power) + { + iShadingMode = (int)aiShadingMode_Phong; + pcMatOut->AddProperty(&pcMatIn->Power,1,AI_MATKEY_SHININESS); + } + pcMatOut->AddProperty(&iShadingMode,1,AI_MATKEY_SHADING_MODEL); + } + else if (is_not_qnan(clrTexture.r)) + { + pcMatOut->AddProperty(&clrTexture,1,AI_MATKEY_COLOR_DIFFUSE); + pcMatOut->AddProperty(&clrTexture,1,AI_MATKEY_COLOR_SPECULAR); + } + // if the texture could be replaced by a single material color + // we don't need the texture anymore + if (is_not_qnan(clrTexture.r)) + { + delete pcNew; + pcNew = NULL; + } + + // if an ASCII effect description (HLSL?) is contained in the file, + // we can simply ignore it ... + if (iType & AI_MDL7_SKINTYPE_MATERIAL_ASCDEF) + { + VALIDATE_FILE_SIZE(szCurrent); + int32_t iMe = *((int32_t*)szCurrent); + szCurrent += sizeof(char) * iMe + sizeof(int32_t); + VALIDATE_FILE_SIZE(szCurrent); + } + + // if an embedded texture has been loaded setup the corresponding + // data structures in the aiScene instance + if (pcNew && this->pScene->mNumTextures <= 999) + { + + // place this as diffuse texture + char szCurrent[5]; + ::sprintf(szCurrent,"*%i",this->pScene->mNumTextures); + + aiString szFile; + const size_t iLen = strlen((const char*)szCurrent); + size_t iLen2 = iLen+1; + iLen2 = iLen2 > MAXLEN ? MAXLEN : iLen2; + ::memcpy(szFile.data,(const char*)szCurrent,iLen2); + szFile.length = iLen; + + pcMatOut->AddProperty(&szFile,AI_MATKEY_TEXTURE_DIFFUSE(0)); + + // store the texture + aiTexture** pc = this->pScene->mTextures; + this->pScene->mTextures = new aiTexture*[this->pScene->mNumTextures+1]; + for (unsigned int i = 0; i < this->pScene->mNumTextures;++i) + this->pScene->mTextures[i] = pc[i]; + + this->pScene->mTextures[this->pScene->mNumTextures] = pcNew; + this->pScene->mNumTextures++; + delete[] pc; + + } + VALIDATE_FILE_SIZE(szCurrent); + *szCurrentOut = szCurrent; +} +// ------------------------------------------------------------------------------------------------ +void MDLImporter::SkipSkinLump_3DGS_MDL7( + const unsigned char* szCurrent, + const unsigned char** szCurrentOut, + unsigned int iType, + unsigned int iWidth, + unsigned int iHeight) +{ + // get the type of the skin + unsigned int iMasked = (unsigned int)(iType & 0xF); + + if (0x6 == iMasked) + { + szCurrent += iWidth; + } + if (0x7 == iMasked) + { + const size_t iLen = ::strlen((const char*)szCurrent); + szCurrent += iLen+1; + } + else if (iMasked || !iType) + { + if (iMasked || !iType || (iType && iWidth && iHeight)) + { + // ParseTextureColorData(..., aiTexture::pcData == 0xffffffff) will simply + // return the size of the color data in bytes in iSkip + unsigned int iSkip = 0; + + aiTexture tex; + tex.pcData = reinterpret_cast(0xffffffff); + tex.mHeight = iHeight; + tex.mWidth = iWidth; + this->ParseTextureColorData(szCurrent,iMasked,&iSkip,&tex); + + // skip length of texture data + szCurrent += iSkip; + } + } + + // check whether a material definition is contained in the skin + if (iType & AI_MDL7_SKINTYPE_MATERIAL) + { + const MDL::Material_MDL7* pcMatIn = (const MDL::Material_MDL7*)szCurrent; + szCurrent = (unsigned char*)(pcMatIn+1); + } + + // if an ASCII effect description (HLSL?) is contained in the file, + // we can simply ignore it ... + if (iType & AI_MDL7_SKINTYPE_MATERIAL_ASCDEF) + { + int32_t iMe = *((int32_t*)szCurrent); + szCurrent += sizeof(char) * iMe + sizeof(int32_t); + } + *szCurrentOut = szCurrent; +} +// ------------------------------------------------------------------------------------------------ +void MDLImporter::ParseSkinLump_3DGS_MDL7( + const unsigned char* szCurrent, + const unsigned char** szCurrentOut, + std::vector& pcMats) +{ + ai_assert(NULL != szCurrent); + ai_assert(NULL != szCurrentOut); + + *szCurrentOut = szCurrent; + const MDL::Skin_MDL7* pcSkin = (const MDL::Skin_MDL7*)szCurrent; + szCurrent += 12; + + // allocate an output material + MaterialHelper* pcMatOut = new MaterialHelper(); + pcMats.push_back(pcMatOut); + + // skip length of file name + szCurrent += AI_MDL7_MAX_TEXNAMESIZE; + + this->ParseSkinLump_3DGS_MDL7(szCurrent,szCurrentOut,pcMatOut, + pcSkin->typ,pcSkin->width,pcSkin->height); + + // place the name of the skin in the material + const size_t iLen = strlen(pcSkin->texture_name); + if (0 != iLen) + { + aiString szFile; + memcpy(szFile.data,pcSkin->texture_name,sizeof(pcSkin->texture_name)); + szFile.length = iLen; + + pcMatOut->AddProperty(&szFile,AI_MATKEY_NAME); + } + return; +} \ No newline at end of file diff --git a/code/MaterialSystem.cpp b/code/MaterialSystem.cpp index 8df13b05e..9048b9113 100644 --- a/code/MaterialSystem.cpp +++ b/code/MaterialSystem.cpp @@ -465,6 +465,7 @@ aiReturn aiGetMaterialTexture(const aiMaterial* pcMat, }; char szKey[256]; + if (iIndex > 100)return AI_FAILURE; // get the path to the texture #if _MSC_VER >= 1400 diff --git a/code/MaterialSystem.h b/code/MaterialSystem.h index d90790a86..a0eccebcb 100644 --- a/code/MaterialSystem.h +++ b/code/MaterialSystem.h @@ -47,6 +47,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace Assimp { + // --------------------------------------------------------------------------- /** Internal material helper class. Can be used to fill an aiMaterial structure easily. */ @@ -209,4 +210,4 @@ inline aiReturn MaterialHelper::AddProperty (const int* pInput, } -#endif //!! AI_MATERIALSYSTEM_H_INC \ No newline at end of file +#endif //!! AI_MATERIALSYSTEM_H_INC diff --git a/code/ParsingUtils.h b/code/ParsingUtils.h new file mode 100644 index 000000000..16020372d --- /dev/null +++ b/code/ParsingUtils.h @@ -0,0 +1,115 @@ +/* +Open Asset Import Library (ASSIMP) +---------------------------------------------------------------------- + +Copyright (c) 2006-2008, ASSIMP Development 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 Development 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 Defines helper functions for text parsing */ +#ifndef AI_PARSING_UTILS_H_INC +#define AI_PARSING_UTILS_H_INC + +// --------------------------------------------------------------------------------- +template +inline bool IsSpace( const char_t in) +{ + return (in == (char_t)' ' || in == (char_t)'\t'); +} +// --------------------------------------------------------------------------------- +template +inline bool IsLineEnd( const char_t in) +{ + return (in == (char_t)'\r' || in == (char_t)'\n' || in == (char_t)'\0'); +} +// --------------------------------------------------------------------------------- +template +inline bool IsSpaceOrNewLine( const char_t in) +{ + return IsSpace(in) || IsLineEnd(in); +} +// --------------------------------------------------------------------------------- +template +inline bool SkipSpaces( const char_t* in, const char_t** out) +{ + while (*in == (char_t)' ' || *in == (char_t)'\t')in++; + *out = in; + return !IsLineEnd(*in); +} +// --------------------------------------------------------------------------------- +template +inline bool SkipSpaces( const char_t** inout) +{ + return SkipSpaces(*inout,inout); +} +// --------------------------------------------------------------------------------- +template +inline bool SkipLine( const char_t* in, const char_t** out) +{ + while (*in != (char_t)'\r' && *in != (char_t)'\n' && *in != (char_t)'\0')in++; + + if (*in == (char_t)'\0') + { + *out = in; + return false; + } + in++; + *out = in; + return true; +} +// --------------------------------------------------------------------------------- +template +inline bool SkipLine( const char_t** inout) +{ + return SkipLine(*inout,inout); +} +// --------------------------------------------------------------------------------- +template +inline void SkipSpacesAndLineEnd( const char_t* in, const char_t** out) +{ + while (*in == (char_t)' ' || *in == (char_t)'\t' || + *in == (char_t)'\r' || *in == (char_t)'\n')in++; + *out = in; +} +// --------------------------------------------------------------------------------- +template +inline bool SkipSpacesAndLineEnd( const char_t** inout) +{ + return SkipSpacesAndLineEnd(*inout,inout); +} + + +#endif // ! AI_PARSING_UTILS_H_INC \ No newline at end of file diff --git a/code/PlyLoader.cpp b/code/PlyLoader.cpp index d9594e92e..51625e941 100644 --- a/code/PlyLoader.cpp +++ b/code/PlyLoader.cpp @@ -120,6 +120,7 @@ void PLYImporter::InternReadFile( this->mBuffer[1] != 'L' && this->mBuffer[1] != 'l' || this->mBuffer[2] != 'Y' && this->mBuffer[2] != 'y') { + delete[] this->mBuffer; throw new ImportErrorException( "Invalid .ply file: Magic number \'ply\' is no there"); } char* szMe = (char*)&this->mBuffer[3]; @@ -136,6 +137,7 @@ void PLYImporter::InternReadFile( SkipLine(szMe,(const char**)&szMe); if(!PLY::DOM::ParseInstance(szMe,&sPlyDom, (unsigned int)fileSize)) { + delete[] this->mBuffer; throw new ImportErrorException( "Invalid .ply file: Unable to build DOM (#1)"); } } @@ -156,16 +158,19 @@ void PLYImporter::InternReadFile( SkipLine(szMe,(const char**)&szMe); if(!PLY::DOM::ParseInstanceBinary(szMe,&sPlyDom,bIsBE, (unsigned int)fileSize)) { + delete[] this->mBuffer; throw new ImportErrorException( "Invalid .ply file: Unable to build DOM (#2)"); } } else { + delete[] this->mBuffer; throw new ImportErrorException( "Invalid .ply file: Unknown file format"); } } else { + delete[] this->mBuffer; throw new ImportErrorException( "Invalid .ply file: Missing format specification"); } this->pcDOM = &sPlyDom; @@ -193,6 +198,7 @@ void PLYImporter::InternReadFile( { if (avPositions.size() < 3) { + delete[] this->mBuffer; throw new ImportErrorException( "Invalid .ply file: Not enough vertices to build " "a face list. "); } @@ -230,6 +236,7 @@ void PLYImporter::InternReadFile( if (avMeshes.empty()) { + delete[] this->mBuffer; throw new ImportErrorException( "Invalid .ply file: Unable to extract mesh data "); } diff --git a/code/PlyParser.h b/code/PlyParser.h index 7c19f43c0..d7d368d93 100644 --- a/code/PlyParser.h +++ b/code/PlyParser.h @@ -48,6 +48,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include "ParsingUtils.h" + #include "../include/aiTypes.h" #include "../include/aiMesh.h" #include "../include/aiAnim.h" @@ -551,48 +553,6 @@ TYPE PLY::PropertyInstance::ConvertTo( } }; -// --------------------------------------------------------------------------------- -inline bool IsSpace( const char in) -{ - return (in == ' ' || in == '\t'); -} -// --------------------------------------------------------------------------------- -inline bool IsLineEnd( const char in) -{ - return (in == '\r' || in == '\n' || in == '\0'); -} -// --------------------------------------------------------------------------------- -inline bool IsSpaceOrNewLine( const char in) -{ - return IsSpace(in) || IsLineEnd(in); -} -// --------------------------------------------------------------------------------- -inline bool SkipSpaces( const char* in, const char** out) -{ - while (*in == ' ' || *in == '\t')in++; - *out = in; - return !IsLineEnd(*in); -} -// --------------------------------------------------------------------------------- -inline bool SkipLine( const char* in, const char** out) -{ - while (*in != '\r' && *in != '\n' && *in != '\0')in++; - - if (*in == '\0') - { - *out = in; - return false; - } - in++; - *out = in; - return true; -} -// --------------------------------------------------------------------------------- -inline void SkipSpacesAndLineEnd( const char* in, const char** out) -{ - while (*in == ' ' || *in == '\t' || *in == '\r' || *in == '\n')in++; - *out = in; -} }; #endif // !! include guard \ No newline at end of file diff --git a/code/SMDLoader.cpp b/code/SMDLoader.cpp new file mode 100644 index 000000000..7c9169952 --- /dev/null +++ b/code/SMDLoader.cpp @@ -0,0 +1,1070 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (ASSIMP) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2008, ASSIMP Development 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 Development 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 Implementation of the SMD importer class */ + +#include "MaterialSystem.h" +#include "SMDLoader.h" +#include "StringComparison.h" +#include "fast_atof.h" + +#include "../include/DefaultLogger.h" +#include "../include/IOStream.h" +#include "../include/IOSystem.h" +#include "../include/aiMesh.h" +#include "../include/aiScene.h" +#include "../include/aiAssert.h" + +#include + +using namespace Assimp; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +SMDImporter::SMDImporter() +{ + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +SMDImporter::~SMDImporter() +{ + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool SMDImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const +{ + // simple check of file extension is enough for the moment + std::string::size_type pos = pFile.find_last_of('.'); + // no file extension - can't read + if( pos == std::string::npos) + return false; + std::string extension = pFile.substr( pos); + + if (extension.length() < 4)return false; + if (extension[0] != '.')return false; + + // VTA is not really supported as it contains vertex animations. + // However, at least the first keyframe can be loaded + if ((extension[1] != 's' && extension[1] != 'S') || + (extension[2] != 'm' && extension[2] != 'M') || + (extension[3] != 'd' && extension[3] != 'D')) + { + if ((extension[1] != 'v' && extension[1] != 'V') || + (extension[2] != 't' && extension[2] != 'T') || + (extension[3] != 'a' && extension[3] != 'A')) + { + return false; + } + } + return true; +} +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void SMDImporter::InternReadFile( + const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) +{ + boost::scoped_ptr file( pIOHandler->Open( pFile)); + + // Check whether we can read from the file + if( file.get() == NULL) + { + throw new ImportErrorException( "Failed to open SMD/VTA file " + pFile + "."); + } + + this->iFileSize = (unsigned int)file->FileSize(); + + // allocate storage and copy the contents of the file to a memory buffer + this->pScene = pScene; + this->mBuffer = new char[this->iFileSize+1]; + file->Read( (void*)mBuffer, 1, this->iFileSize); + this->iSmallestFrame = (1 << 31); + this->bHasUVs = true; + this->iLineNumber = 1; + + // reserve enough space for ... hm ... 10 textures + this->aszTextures.reserve(10); + + // reserve enough space for ... hm ... 1000 triangles + this->asTriangles.reserve(1000); + + // reserve enough space for ... hm ... 20 bones + this->asBones.reserve(20); + + try + { + // parse the file ... + this->ParseFile(); + + // now fix invalid time values and make sure the animation starts at frame 0 + this->FixTimeValues(); + + // compute absolute bone transformation matrices + this->ComputeAbsoluteBoneTransformations(); + + // create output meshes + this->CreateOutputMeshes(); + + // build the output animation + this->CreateOutputAnimations(); + + // build output nodes (bones are added as empty dummy nodes) + this->CreateOutputNodes(); + } + catch (ImportErrorException* ex) + { + delete[] this->mBuffer; + throw ex; + } + + // delete the file buffer + delete[] this->mBuffer; +} +// ------------------------------------------------------------------------------------------------ +// Write an error message with line number to the log file +void SMDImporter::LogErrorNoThrow(const char* msg) +{ + char szTemp[1024]; + +#if _MSC_VER >= 1400 + sprintf_s(szTemp,"Line %i: %s",this->iLineNumber,msg); +#else + ai_assert(strlen(msg) < 1000); + sprintf(szTemp,"Line %i: %s",this->iLineNumber,msg); +#endif + + DefaultLogger::get()->error(szTemp); +} +// ------------------------------------------------------------------------------------------------ +// Write a warning with line number to the log file +void SMDImporter::LogWarning(const char* msg) +{ + char szTemp[1024]; + +#if _MSC_VER >= 1400 + sprintf_s(szTemp,"Line %i: %s",this->iLineNumber,msg); +#else + ai_assert(strlen(msg) < 1000); + sprintf(szTemp,"Line %i: %s",this->iLineNumber,msg); +#endif + DefaultLogger::get()->warn(szTemp); +} +// ------------------------------------------------------------------------------------------------ +// Fix invalid time values in the file +void SMDImporter::FixTimeValues() +{ + double dDelta = (double)this->iSmallestFrame; + double dMax = 0.0f; + for (std::vector::iterator + iBone = this->asBones.begin(); + iBone != this->asBones.end();++iBone) + { + for (std::vector::iterator + iKey = (*iBone).sAnim.asKeys.begin(); + iKey != (*iBone).sAnim.asKeys.end();++iKey) + { + (*iKey).dTime -= dDelta; + dMax = std::max(dMax, (*iKey).dTime); + } + } + this->dLengthOfAnim = dMax; +} +// ------------------------------------------------------------------------------------------------ +// create output meshes +void SMDImporter::CreateOutputMeshes() +{ + // we need to sort all faces by their material index + // in opposition to other loaders we can be sure that each + // material is at least used once. + this->pScene->mNumMeshes = (unsigned int) this->aszTextures.size(); + this->pScene->mMeshes = new aiMesh*[this->pScene->mNumMeshes]; + + typedef std::vector FaceList; + FaceList* aaiFaces = new FaceList[this->pScene->mNumMeshes]; + + // approximate the space that will be required + unsigned int iNum = (unsigned int)this->asTriangles.size() / this->pScene->mNumMeshes; + iNum += iNum >> 1; + for (unsigned int i = 0; i < this->pScene->mNumMeshes;++i) + { + aaiFaces[i].reserve(iNum); + } + + // collect all faces + iNum = 0; + for (std::vector::const_iterator + iFace = this->asTriangles.begin(); + iFace != this->asTriangles.end();++iFace,++iNum) + { + if (0xffffffff == (*iFace).iTexture)aaiFaces[(*iFace).iTexture].push_back( 0 ); + else if ((*iFace).iTexture >= this->aszTextures.size()) + { + DefaultLogger::get()->error("[SMD/VTA] Material index overflow in face"); + aaiFaces[(*iFace).iTexture].push_back((unsigned int)this->aszTextures.size()-1); + } + else aaiFaces[(*iFace).iTexture].push_back(iNum); + } + + // now create the output meshes + for (unsigned int i = 0; i < this->pScene->mNumMeshes;++i) + { + aiMesh*& pcMesh = this->pScene->mMeshes[i] = new aiMesh(); + ai_assert(!aaiFaces[i].empty()); // should not be empty ... + + pcMesh->mNumVertices = (unsigned int)aaiFaces[i].size()*3; + pcMesh->mNumFaces = (unsigned int)aaiFaces[i].size(); + pcMesh->mMaterialIndex = i; + + // storage for bones + typedef std::pair TempWeightListEntry; + typedef std::vector< TempWeightListEntry > TempBoneWeightList; + + TempBoneWeightList* aaiBones = new TempBoneWeightList[this->asBones.size()](); + + // try to reserve enough memory without wasting too much + for (unsigned int iBone = 0; iBone < this->asBones.size();++iBone) + { + aaiBones[iBone].reserve(pcMesh->mNumVertices/this->asBones.size()); + } + + // allocate storage + pcMesh->mFaces = new aiFace[pcMesh->mNumFaces]; + aiVector3D* pcNormals = pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices]; + aiVector3D* pcVerts = pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices]; + + aiVector3D* pcUVs = NULL; + if (this->bHasUVs) + { + pcUVs = pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices]; + pcMesh->mNumUVComponents[0] = 2; + } + + iNum = 0; + for (unsigned int iFace = 0; iFace < pcMesh->mNumFaces;++iFace) + { + pcMesh->mFaces[iFace].mIndices = new unsigned int[3]; + pcMesh->mFaces[iFace].mNumIndices = 3; + + // fill the vertices (hardcode the loop for performance) + unsigned int iSrcFace = aaiFaces[i][iFace]; + SMD::Face& face = this->asTriangles[iSrcFace]; + + *pcVerts++ = face.avVertices[0].pos; + *pcVerts++ = face.avVertices[1].pos; + *pcVerts++ = face.avVertices[2].pos; + + // fill the normals + *pcNormals++ = face.avVertices[0].nor; + *pcNormals++ = face.avVertices[1].nor; + *pcNormals++ = face.avVertices[2].nor; + + // fill the texture coordinates + if (pcUVs) + { + *pcUVs++ = face.avVertices[0].uv; + *pcUVs++ = face.avVertices[1].uv; + *pcUVs++ = face.avVertices[2].uv; + } + + for (unsigned int iVert = 0; iVert < 3;++iVert) + { + float fSum = 0.0f; + for (unsigned int iBone = 0;iBone < face.avVertices[0].aiBoneLinks.size();++iBone) + { + TempWeightListEntry& pairval = face.avVertices[iVert].aiBoneLinks[iBone]; + + if (pairval.first >= this->asBones.size()) + { + DefaultLogger::get()->error("[SMD/VTA] Bone index overflow. " + "The bone index will be ignored, the weight will be assigned " + "to the vertex' parent node"); + continue; + } + aaiBones[pairval.first].push_back(TempWeightListEntry(iNum,pairval.second)); + + fSum += pairval.second; + } + // if the sum of all vertex weights is not 1.0 we must assign + // the rest to the vertex' parent node. Well, at least the doc says + // we should ... + if (fSum <= 0.995f) + { + aaiBones[face.avVertices[iVert].iParentNode].push_back( + TempWeightListEntry(iNum,1.0f-fSum)); + } + + pcMesh->mFaces[iFace].mIndices[iVert] = iNum++; + } + } + + // now build all bones of the mesh + iNum = 0; + for (unsigned int iBone = 0; iBone < this->asBones.size();++iBone) + { + if (!aaiBones[iBone].empty())++iNum; + } + pcMesh->mNumBones = iNum; + pcMesh->mBones = new aiBone*[pcMesh->mNumBones]; + iNum = 0; + for (unsigned int iBone = 0; iBone < this->asBones.size();++iBone) + { + if (aaiBones[iBone].empty())continue; + aiBone*& bone = pcMesh->mBones[iNum] = new aiBone(); + + bone->mNumWeights = (unsigned int)aaiBones[iBone].size(); + bone->mWeights = new aiVertexWeight[bone->mNumWeights]; + bone->mOffsetMatrix = this->asBones[iBone].mOffsetMatrix; + bone->mName.Set( this->asBones[iBone].mName ); + + this->asBones[iBone].bIsUsed = true; + + for (unsigned int iWeight = 0; iWeight < bone->mNumWeights;++iWeight) + { + bone->mWeights[iWeight].mVertexId = aaiBones[iBone][iWeight].first; + bone->mWeights[iWeight].mWeight = aaiBones[iBone][iWeight].second; + } + ++iNum; + } + + delete[] aaiBones; + } + delete[] aaiFaces; +} +// ------------------------------------------------------------------------------------------------ +// add bone child nodes +void SMDImporter::AddBoneChildren(aiNode* pcNode, uint32_t iParent) +{ + ai_assert(NULL != pcNode && 0 != pcNode->mNumChildren && NULL != pcNode->mChildren); + + // first count ... + for (unsigned int i = 0; i < this->asBones.size();++i) + { + SMD::Bone& bone = this->asBones[i]; + if (bone.iParent == iParent)++pcNode->mNumChildren; + } + + // now allocate the output array + pcNode->mChildren = new aiNode*[pcNode->mNumChildren]; + + // and fill all subnodes + unsigned int qq = 0; + for (unsigned int i = 0; i < this->asBones.size();++i) + { + SMD::Bone& bone = this->asBones[i]; + if (bone.iParent != iParent)continue; + + aiNode* pc = pcNode->mChildren[qq++] = new aiNode(); + pc->mName.Set(bone.mName); + + // store the local transformation matrix of the bind pose + pc->mTransformation = bone.sAnim.asKeys[bone.sAnim.iFirstTimeKey].matrix; + } +} +// ------------------------------------------------------------------------------------------------ +// create output nodes +void SMDImporter::CreateOutputNodes() +{ + // create one root node that renders all meshes + this->pScene->mRootNode = new aiNode(); + ::strcpy(this->pScene->mRootNode->mName.data, "SMD_root"); + this->pScene->mRootNode->mName.length = 8; + this->pScene->mRootNode->mNumMeshes = this->pScene->mNumMeshes; + this->pScene->mRootNode->mMeshes = new unsigned int[this->pScene->mNumMeshes]; + for (unsigned int i = 0; i < this->pScene->mNumMeshes;++i) + this->pScene->mRootNode->mMeshes[i] = i; + + // now add all bones as dummy sub nodes to the graph + this->AddBoneChildren(this->pScene->mRootNode,(uint32_t)-1); +} +// ------------------------------------------------------------------------------------------------ +// create output animations +void SMDImporter::CreateOutputAnimations() +{ + this->pScene->mNumAnimations = 1; + this->pScene->mAnimations = new aiAnimation*[1]; + aiAnimation*& anim = this->pScene->mAnimations[0] = new aiAnimation(); + + anim->mDuration = this->dLengthOfAnim; + anim->mTicksPerSecond = 25.0; // FIXME: is this correct? + + // this->pScene->mAnimations[0]->mNumBones = 0; + for (std::vector::const_iterator + i = this->asBones.begin(); + i != this->asBones.end();++i) + { + if ((*i).bIsUsed)++anim->mNumBones; + } + aiBoneAnim** pp = anim->mBones = new aiBoneAnim*[anim->mNumBones]; + + // now build valid keys + unsigned int a = 0; + for (std::vector::const_iterator + i = this->asBones.begin(); + i != this->asBones.end();++i) + { + if (!(*i).bIsUsed)continue; + + aiBoneAnim* p = pp[a] = new aiBoneAnim(); + + // copy the name of the bone + p->mBoneName.length = (*i).mName.length(); + ::memcpy(p->mBoneName.data,(*i).mName.c_str(),p->mBoneName.length); + p->mBoneName.data[p->mBoneName.length] = '\0'; + + p->mNumRotationKeys = (unsigned int) (*i).sAnim.asKeys.size(); + if (p->mNumRotationKeys) + { + p->mNumPositionKeys = p->mNumRotationKeys; + aiVectorKey* pVecKeys = p->mPositionKeys = new aiVectorKey[p->mNumRotationKeys]; + aiQuatKey* pRotKeys = p->mRotationKeys = new aiQuatKey[p->mNumRotationKeys]; + + for (std::vector::const_iterator + qq = (*i).sAnim.asKeys.begin(); + qq != (*i).sAnim.asKeys.end(); ++qq) + { + pRotKeys->mTime = pVecKeys->mTime = (*qq).dTime; + + // compute the rotation quaternion from the euler angles + pRotKeys->mValue = aiQuaternion( (*qq).vRot.x, (*qq).vRot.y, (*qq).vRot.z ); + pVecKeys->mValue = (*qq).vPos; + + ++pVecKeys; ++pRotKeys; + } + } + ++a; + + // there are no scaling keys ... + } +} +// ------------------------------------------------------------------------------------------------ +void SMDImporter::ComputeAbsoluteBoneTransformations() +{ + // for each bone: determine the key with the lowest time value + // theoretically the SMD format should have all keyframes + // in order. However, I've seen a file where this wasn't true. + for (unsigned int i = 0; i < this->asBones.size();++i) + { + SMD::Bone& bone = this->asBones[i]; + + uint32_t iIndex = 0; + double dMin = 10e10; + for (unsigned int i = 0; i < bone.sAnim.asKeys.size();++i) + { + double d = std::min(bone.sAnim.asKeys[i].dTime,dMin); + if (d < dMin) { + dMin = d; iIndex = i; + } + } + bone.sAnim.iFirstTimeKey = iIndex; + } + + unsigned int iParent = 0; + while (iParent < this->asBones.size()) + { + for (unsigned int iBone = 0; iBone < this->asBones.size();++iBone) + { + SMD::Bone& bone = this->asBones[iBone]; + + if (iParent == bone.iParent) + { + SMD::Bone& parentBone = this->asBones[iParent]; + + + uint32_t iIndex = bone.sAnim.iFirstTimeKey; + const aiMatrix4x4& mat = bone.sAnim.asKeys[iIndex].matrix; + aiMatrix4x4& matOut = bone.sAnim.asKeys[iIndex].matrixAbsolute; + + // the same for the parent bone ... + iIndex = parentBone.sAnim.iFirstTimeKey; + const aiMatrix4x4& mat2 = parentBone.sAnim.asKeys[iIndex].matrix; + + // compute the absolute transformation matrix + matOut = mat * mat2; + } + } + ++iParent; + } + + // store the inverse of the absolute transformation matrix + // of the first key as bone offset matrix + for (iParent = 0; iParent < this->asBones.size();++iParent) + { + SMD::Bone& bone = this->asBones[iParent]; + aiMatrix4x4& mat = bone.sAnim.asKeys[bone.sAnim.iFirstTimeKey].matrixAbsolute; + bone.mOffsetMatrix = mat; + bone.mOffsetMatrix.Inverse(); + } +} +// ------------------------------------------------------------------------------------------------ +// create output materials +void SMDImporter::CreateOutputMaterials() +{ + this->pScene->mNumMaterials = (unsigned int)this->aszTextures.size(); + this->pScene->mMaterials = new aiMaterial*[std::min(1u, this->pScene->mNumMaterials)]; + + for (unsigned int iMat = 0; iMat < this->pScene->mNumMaterials;++iMat) + { + MaterialHelper* pcMat = new MaterialHelper(); + this->pScene->mMaterials[iMat] = pcMat; + + aiString szName; +#if _MSC_VER >= 1400 + szName.length = (size_t)::sprintf_s(szName.data,"Texture_%i",iMat); +#else + szName.length = (size_t)::sprintf(szName.data,"Texture_%i",iMat); +#endif + pcMat->AddProperty(&szName,AI_MATKEY_NAME); + + strcpy(szName.data, this->aszTextures[iMat].c_str() ); + szName.length = this->aszTextures[iMat].length(); + pcMat->AddProperty(&szName,AI_MATKEY_TEXTURE_DIFFUSE(0)); + } + + // create a default material if necessary + if (0 == this->pScene->mNumMaterials) + { + this->pScene->mNumMaterials = 1; + + MaterialHelper* pcHelper = new MaterialHelper(); + this->pScene->mMaterials[0] = pcHelper; + + int iMode = (int)aiShadingMode_Gouraud; + pcHelper->AddProperty(&iMode, 1, AI_MATKEY_SHADING_MODEL); + + aiColor3D clr; + clr.b = clr.g = clr.r = 0.7f; + pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_DIFFUSE); + pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_SPECULAR); + + clr.b = clr.g = clr.r = 0.05f; + pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_AMBIENT); + + aiString szName; + szName.Set(AI_DEFAULT_MATERIAL_NAME); + pcHelper->AddProperty(&szName,AI_MATKEY_NAME); + } +} +// ------------------------------------------------------------------------------------------------ +// Parse the file +void SMDImporter::ParseFile() +{ + const char* szCurrent = this->mBuffer; + + // read line per line ... + while (true) + { + if(!SkipSpaces(szCurrent,&szCurrent)) break; + + // "version \n", should be 1 for hl and hl˛ SMD files + if (0 == ASSIMP_strincmp(szCurrent,"version",7) && + IsSpaceOrNewLine(*(szCurrent+7))) + { + szCurrent += 8; + if(!SkipSpaces(szCurrent,&szCurrent)) break; + if (1 != strtol10(szCurrent,&szCurrent)) + { + DefaultLogger::get()->warn("SMD.version is not 1. This " + "file format is not known. Continuing happily ..."); + } + } + // "nodes\n" - Starts the node section + else if (0 == ASSIMP_strincmp(szCurrent,"nodes",5) && + IsSpaceOrNewLine(*(szCurrent+5))) + { + szCurrent += 6; + this->ParseNodesSection(szCurrent,&szCurrent); + } + // "triangles\n" - Starts the triangle section + else if (0 == ASSIMP_strincmp(szCurrent,"triangles",9) && + IsSpaceOrNewLine(*(szCurrent+9))) + { + szCurrent += 10; + this->ParseTrianglesSection(szCurrent,&szCurrent); + } + // "vertexanimation\n" - Starts the vertex animation section + else if (0 == ASSIMP_strincmp(szCurrent,"vertexanimation",15) && + IsSpaceOrNewLine(*(szCurrent+15))) + { + this->bHasUVs = false; + szCurrent += 16; + this->ParseVASection(szCurrent,&szCurrent); + } + // "skeleton\n" - Starts the skeleton section + else if (0 == ASSIMP_strincmp(szCurrent,"skeleton",8) && + IsSpaceOrNewLine(*(szCurrent+8))) + { + szCurrent += 9; + this->ParseSkeletonSection(szCurrent,&szCurrent); + } + else SkipLine(szCurrent,&szCurrent); + } + + // if there are no triangles, we can't load the model + if (this->asTriangles.empty()) + { + throw new ImportErrorException("No triangles have been found in the file"); + } + // check whether all bones have been initialized + for (std::vector::const_iterator + i = this->asBones.begin(); + i != this->asBones.end();++i) + { + if (!(*i).mName.length()) + { + DefaultLogger::get()->warn("Not all bones have been initialized"); + break; + } + } + return; +} +// ------------------------------------------------------------------------------------------------ +unsigned int SMDImporter::GetTextureIndex(const std::string& filename) +{ + unsigned int iIndex = 0; + for (std::vector::const_iterator + i = this->aszTextures.begin(); + i != this->aszTextures.end();++i,++iIndex) + { + // case-insensitive ... just for safety + if (0 == ASSIMP_stricmp ( filename.c_str(),(*i).c_str()))return iIndex; + } + iIndex = (unsigned int)this->aszTextures.size(); + this->aszTextures.push_back(filename); + return iIndex; +} +// ------------------------------------------------------------------------------------------------ +// Parse the nodes section of the file +void SMDImporter::ParseNodesSection(const char* szCurrent, + const char** szCurrentOut) +{ + while (true) + { + // "end\n" - Ends the nodes section + if (0 == ASSIMP_strincmp(szCurrent,"end",3) && + IsSpaceOrNewLine(*(szCurrent+3))) + { + szCurrent += 4; + break; + } + this->ParseNodeInfo(szCurrent,&szCurrent); + } + *szCurrentOut = szCurrent; + SkipSpacesAndLineEnd(szCurrent,&szCurrent); +} +// ------------------------------------------------------------------------------------------------ +// Parse the triangles section of the file +void SMDImporter::ParseTrianglesSection(const char* szCurrent, + const char** szCurrentOut) +{ + // parse a triangle, parse another triangle, parse the next triangle ... + // and so on until we reach a token that looks quite similar to "end" + while (true) + { + // "end\n" - Ends the triangles section + if (0 == ASSIMP_strincmp(szCurrent,"end",3) && + IsSpaceOrNewLine(*(szCurrent+3))) + { + szCurrent += 4; + break; + } + this->ParseTriangle(szCurrent,&szCurrent); + } + *szCurrentOut = szCurrent; + SkipSpacesAndLineEnd(szCurrent,&szCurrent); +} +// ------------------------------------------------------------------------------------------------ +// Parse the vertex animation section of the file +void SMDImporter::ParseVASection(const char* szCurrent, + const char** szCurrentOut) +{ + unsigned int iCurIndex = 0; + while (true) + { + // "end\n" - Ends the "vertexanimation" section + if (0 == ASSIMP_strincmp(szCurrent,"end",3) && + IsSpaceOrNewLine(*(szCurrent+3))) + { + szCurrent += 4; + SkipLine(szCurrent,&szCurrent); + break; + } + // "time \n" + else if (0 == ASSIMP_strincmp(szCurrent,"time",4) && + IsSpaceOrNewLine(*(szCurrent+4))) + { + szCurrent += 5; + // NOTE: The doc says that time values COULD be negative ... + int iTime = 0; + if(!this->ParseSignedInt(szCurrent,&szCurrent,iTime) || iTime)break; + SkipLine(szCurrent,&szCurrent); + } + else + { + this->ParseVertex(szCurrent,&szCurrent,this->asTriangles.back().avVertices[iCurIndex],true); + if(3 == ++iCurIndex) + { + this->asTriangles.push_back(SMD::Face()); + iCurIndex = 0; + } + } + } + + if (iCurIndex) + { + // no degenerates, so let this triangle + this->aszTextures.pop_back(); + } + + *szCurrentOut = szCurrent; + SkipSpacesAndLineEnd(szCurrent,&szCurrent); +} +// ------------------------------------------------------------------------------------------------ +// Parse the skeleton section of the file +void SMDImporter::ParseSkeletonSection(const char* szCurrent, + const char** szCurrentOut) +{ + int iTime = 0; + while (true) + { + // "end\n" - Ends the skeleton section + if (0 == ASSIMP_strincmp(szCurrent,"end",3) && + IsSpaceOrNewLine(*(szCurrent+3))) + { + szCurrent += 4; + SkipLine(szCurrent,&szCurrent); + break; + } + // "time \n" - Specifies the current animation frame + else if (0 == ASSIMP_strincmp(szCurrent,"time",4) && + IsSpaceOrNewLine(*(szCurrent+4))) + { + szCurrent += 5; + // NOTE: The doc says that time values COULD be negative ... + if(!this->ParseSignedInt(szCurrent,&szCurrent,iTime))break; + + this->iSmallestFrame = std::min(this->iSmallestFrame,iTime); + SkipLine(szCurrent,&szCurrent); + } + else this->ParseSkeletonElement(szCurrent,&szCurrent,iTime); + } + *szCurrentOut = szCurrent; +} +// ------------------------------------------------------------------------------------------------ +// Parse a node line +void SMDImporter::ParseNodeInfo(const char* szCurrent, + const char** szCurrentOut) +{ + unsigned int iBone = 0; + if(!this->ParseUnsignedInt(szCurrent,&szCurrent,iBone) || !SkipSpaces(szCurrent,&szCurrent)) + { + this->LogErrorNoThrow("Unexpected EOF/EOL while parsing bone index"); + goto __RETURN; // YEAH!!! + } + // add our bone to the list + if (iBone >= this->asBones.size())this->asBones.resize(iBone+1); + SMD::Bone& bone = this->asBones[iBone]; + + bool bQuota = true; + if ('\"' != *szCurrent) + { + this->LogWarning("Bone name is expcted to be enclosed in " + "double quotation marks. "); + bQuota = false; + } + else ++szCurrent; + + const char* szEnd = szCurrent; + while (true) + { + if (bQuota && '\"' == *szEnd) + { + iBone = (unsigned int)(szEnd - szCurrent); + ++szEnd; + break; + } + else if (IsSpaceOrNewLine(*szEnd)) + { + iBone = (unsigned int)(szEnd - szCurrent); + break; + } + else if (!(*szEnd)) + { + this->LogErrorNoThrow("Unexpected EOF/EOL while parsing bone name"); + goto __RETURN; // YEAH!!! + } + ++szEnd; + } + bone.mName = std::string(szCurrent,iBone); + // the only negative bone parent index that could occur is -1 AFAIK + if(!this->ParseSignedInt(szCurrent,&szCurrent,(int&)bone.iParent)) + { + this->LogErrorNoThrow("Unexpected EOF/EOL while parsing bone parent index. Assuming -1"); + goto __RETURN; // YEAH!!! + } + + // go to the beginning of the next line +__RETURN: + SkipLine(szCurrent,&szCurrent); + *szCurrentOut = szCurrent; +} +// ------------------------------------------------------------------------------------------------ +// Parse a skeleton element +void SMDImporter::ParseSkeletonElement(const char* szCurrent, + const char** szCurrentOut,int iTime) +{ + aiVector3D vPos; + aiVector3D vRot; + + unsigned int iBone = 0; + if(!this->ParseUnsignedInt(szCurrent,&szCurrent,iBone)) + { + DefaultLogger::get()->error("Unexpected EOF/EOL while parsing bone index"); + goto __RETURN; // YEAH!!! + } + if (iBone >= this->asBones.size()) + { + this->LogErrorNoThrow("Bone index in skeleton section is out of range"); + goto __RETURN; // YEAH!!! + } + SMD::Bone& bone = this->asBones[iBone]; + + bone.sAnim.asKeys.push_back(SMD::Bone::Animation::MatrixKey()); + SMD::Bone::Animation::MatrixKey& key = bone.sAnim.asKeys.back(); + + key.dTime = (double)iTime; + if(!this->ParseFloat(szCurrent,&szCurrent,vPos.x)) + { + this->LogErrorNoThrow("Unexpected EOF/EOL while parsing bone.pos.x"); + goto __RETURN; // YEAH!!! + } + if(!this->ParseFloat(szCurrent,&szCurrent,vPos.y)) + { + this->LogErrorNoThrow("Unexpected EOF/EOL while parsing bone.pos.y"); + goto __RETURN; // YEAH!!! + } + if(!this->ParseFloat(szCurrent,&szCurrent,vPos.z)) + { + this->LogErrorNoThrow("Unexpected EOF/EOL while parsing bone.pos.z"); + goto __RETURN; // YEAH!!! + } + if(!this->ParseFloat(szCurrent,&szCurrent,vRot.x)) + { + this->LogErrorNoThrow("Unexpected EOF/EOL while parsing bone.rot.x"); + goto __RETURN; // YEAH!!! + } + if(!this->ParseFloat(szCurrent,&szCurrent,vRot.y)) + { + this->LogErrorNoThrow("Unexpected EOF/EOL while parsing bone.rot.y"); + goto __RETURN; // YEAH!!! + } + if(!this->ParseFloat(szCurrent,&szCurrent,vRot.z)) + { + this->LogErrorNoThrow("Unexpected EOF/EOL while parsing bone.rot.z"); + goto __RETURN; // YEAH!!! + } + // build the transformation matrix of the key + key.matrix.FromEulerAngles(vRot.x,vRot.y,vRot.z); + { + aiMatrix4x4 mTemp; + mTemp.a4 = vPos.x; + mTemp.b4 = vPos.y; + mTemp.c4 = vPos.z; + key.matrix = key.matrix * mTemp; + } + + // go to the beginning of the next line +__RETURN: + SkipLine(szCurrent,&szCurrent); + *szCurrentOut = szCurrent; +} +// ------------------------------------------------------------------------------------------------ +// Parse a triangle +void SMDImporter::ParseTriangle(const char* szCurrent, + const char** szCurrentOut) +{ + this->asTriangles.push_back(SMD::Face()); + SMD::Face& face = this->asTriangles.back(); + + if(!SkipSpaces(szCurrent,&szCurrent)) + { + this->LogErrorNoThrow("Unexpected EOF/EOL while parsing a triangle"); + return; + } + + // read the texture file name + const char* szEnd = szCurrent; + while (!IsSpaceOrNewLine(*szEnd++)); + + face.iTexture = this->GetTextureIndex(std::string(szCurrent, + (uintptr_t)szEnd-(uintptr_t)szCurrent)); + + // load three vertices + for (unsigned int iVert = 0; iVert < 3;++iVert) + { + this->ParseVertex(szCurrent,&szCurrent, + face.avVertices[iVert]); + } +} +// ------------------------------------------------------------------------------------------------ +// Parse a float +bool SMDImporter::ParseFloat(const char* szCurrent, + const char** szCurrentOut, float& out) +{ + if(!SkipSpaces(szCurrent,&szCurrent)) + { + return false; + } + *szCurrentOut = fast_atof_move(szCurrent,out); + return true; +} +// ------------------------------------------------------------------------------------------------ +// Parse an unsigned int +bool SMDImporter::ParseUnsignedInt(const char* szCurrent, + const char** szCurrentOut, uint32_t& out) +{ + if(!SkipSpaces(szCurrent,&szCurrent)) + { + return false; + } + out = (uint32_t)strtol10(szCurrent,szCurrentOut); + return true; +} +// ------------------------------------------------------------------------------------------------ +// Parse a signed int +bool SMDImporter::ParseSignedInt(const char* szCurrent, + const char** szCurrentOut, int32_t& out) +{ + if(!SkipSpaces(szCurrent,&szCurrent)) + { + return false; + } + // handle signs + bool bInv = false; + if ('-' == *szCurrent) + { + ++szCurrent; + bInv = true; + } + else if ('+' == *szCurrent)++szCurrent; + + // parse the integer + out = (int32_t)strtol10(szCurrent,szCurrentOut); + if (bInv)out = -out; + return true; +} +// ------------------------------------------------------------------------------------------------ +// Parse a vertex +void SMDImporter::ParseVertex(const char* szCurrent, + const char** szCurrentOut, SMD::Vertex& vertex, + bool bVASection /*= false*/) +{ + if(!this->ParseSignedInt(szCurrent,&szCurrent,(int32_t&)vertex.iParentNode)) + { + this->LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.parent"); + goto __RETURN; // YEAH!!! + } + if(!this->ParseFloat(szCurrent,&szCurrent,vertex.pos.x)) + { + this->LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.pos.x"); + goto __RETURN; // YEAH!!! + } + if(!this->ParseFloat(szCurrent,&szCurrent,vertex.pos.y)) + { + this->LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.pos.y"); + goto __RETURN; // YEAH!!! + } + if(!this->ParseFloat(szCurrent,&szCurrent,vertex.pos.z)) + { + this->LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.pos.z"); + goto __RETURN; // YEAH!!! + } + if(!this->ParseFloat(szCurrent,&szCurrent,vertex.nor.x)) + { + this->LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.nor.x"); + goto __RETURN; // YEAH!!! + } + if(!this->ParseFloat(szCurrent,&szCurrent,vertex.nor.y)) + { + this->LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.nor.y"); + goto __RETURN; // YEAH!!! + } + if(!this->ParseFloat(szCurrent,&szCurrent,vertex.nor.z)) + { + this->LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.nor.z"); + goto __RETURN; // YEAH!!! + } + + if (bVASection)goto __RETURN; + + if(!this->ParseFloat(szCurrent,&szCurrent,vertex.uv.x)) + { + this->LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.uv.x"); + goto __RETURN; // YEAH!!! + } + if(!this->ParseFloat(szCurrent,&szCurrent,vertex.uv.y)) + { + this->LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.uv.y"); + goto __RETURN; // YEAH!!! + } + + // now read the number of bones affecting this vertex + // all elements from now are fully optional, we don't need them + unsigned int iSize = 0; + if(!this->ParseUnsignedInt(szCurrent,&szCurrent,iSize))goto __RETURN; + vertex.aiBoneLinks.resize(iSize,std::pair(-1,0.0f)); + + for (std::vector >::iterator + i = vertex.aiBoneLinks.begin(); + i != vertex.aiBoneLinks.end();++i) + { + if(!this->ParseUnsignedInt(szCurrent,&szCurrent,(*i).first))goto __RETURN; + if(!this->ParseFloat(szCurrent,&szCurrent,(*i).second))goto __RETURN; + } + + // go to the beginning of the next line +__RETURN: + SkipLine(szCurrent,&szCurrent); + *szCurrentOut = szCurrent; + return; +} \ No newline at end of file diff --git a/code/SMDLoader.h b/code/SMDLoader.h new file mode 100644 index 000000000..cab60b39f --- /dev/null +++ b/code/SMDLoader.h @@ -0,0 +1,408 @@ +/* +Open Asset Import Library (ASSIMP) +---------------------------------------------------------------------- + +Copyright (c) 2006-2008, ASSIMP Development 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 Development 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 Definition of SMD importer class +//! + +#ifndef AI_SMDLOADER_H_INCLUDED +#define AI_SMDLOADER_H_INCLUDED + +#include "BaseImporter.h" +#include "ParsingUtils.h" + +#include "../include/aiTypes.h" +#include "../include/aiTexture.h" +#include "../include/aiAnim.h" +#include "../include/aiMaterial.h" +struct aiNode; + +#include + +namespace Assimp { +class MaterialHelper; + +namespace SMD { + +// --------------------------------------------------------------------------- +/** Data structure for a vertex in a SMD file +*/ +struct Vertex +{ + Vertex() : iParentNode(0xffffffff) + {} + + //! Vertex position, normal and texture coordinate + aiVector3D pos,nor,uv; + + //! Vertex parent node + unsigned int iParentNode; + + //! Links to bones: pair.first is the bone index, + //! pair.second is the vertex weight. + //! WARN: The remaining weight (to reach 1.0f) is assigned + //! to the parent node/bone + std::vector > aiBoneLinks; +}; + +// --------------------------------------------------------------------------- +/** Data structure for a face in a SMD file +*/ +struct Face +{ + Face() : iTexture(0x0) + {} + + //! Texture index for the face + unsigned int iTexture; + + //! The three vertices of the face + Vertex avVertices[3]; +}; + +// --------------------------------------------------------------------------- +/** Data structure for a bone in a SMD file +*/ +struct Bone +{ + //! Default constructor + Bone() : iParent(0xffffffff), bIsUsed(false) + { + } + + //! Destructor + ~Bone() + { + } + + //! Name of the bone + std::string mName; + + //! Parent of the bone + uint32_t iParent; + + //! Animation of the bone + struct Animation + { + //! Public default constructor + Animation() + { + asKeys.reserve(20); + } + + //! Data structure for a matrix key + struct MatrixKey + { + //! Matrix at this time + aiMatrix4x4 matrix; + + //! Absolute transformation matrix + aiMatrix4x4 matrixAbsolute; + + //! Position + aiVector3D vPos; + + //! Rotation (euler angles) + aiVector3D vRot; + + //! Current time. may be negative, this + //! will be fixed later + double dTime; + }; + + //! Index of the key with the smallest time value + uint32_t iFirstTimeKey; + + //! Array of matrix keys + std::vector asKeys; + + } sAnim; + + //! Offset matrix of the bone + aiMatrix4x4 mOffsetMatrix; + + //! true if the bone is referenced by at least one mesh + bool bIsUsed; +}; + +}; //! namespace SMD + +// --------------------------------------------------------------------------- +/** Used to load Half-life 1 and 2 SMD models +*/ +class SMDImporter : public BaseImporter +{ + friend class Importer; + +protected: + /** Constructor to be privately used by Importer */ + SMDImporter(); + + /** Destructor, private as well */ + ~SMDImporter(); + +public: + + // ------------------------------------------------------------------- + /** Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. */ + bool CanRead( const std::string& pFile, IOSystem* pIOHandler) const; + +protected: + + + // ------------------------------------------------------------------- + /** Called by Importer::GetExtensionList() for each loaded importer. + * See BaseImporter::GetExtensionList() for details + */ + void GetExtensionList(std::string& append) + { + append.append("*.smd;*.vta"); + } + + // ------------------------------------------------------------------- + /** Imports the given file into the given scene structure. + * See BaseImporter::InternReadFile() for details + */ + void InternReadFile( const std::string& pFile, aiScene* pScene, + IOSystem* pIOHandler); + +protected: + + // ------------------------------------------------------------------- + /** Parse the SMD file and create the output scene + */ + void ParseFile(); + + // ------------------------------------------------------------------- + /** Parse the triangles section of the SMD file + * \param szCurrent Current position in the file. Points to the first + * data line of the section. + * \param szCurrentOut Receives a pointer to the heading line of + * the next section (or to EOF) + */ + void ParseTrianglesSection(const char* szCurrent, + const char** szCurrentOut); + + // ------------------------------------------------------------------- + /** Parse the vertex animation section in VTA files + * \param szCurrent Current position in the file. Points to the first + * data line of the section. + * \param szCurrentOut Receives a pointer to the heading line of + * the next section (or to EOF) + */ + void ParseVASection(const char* szCurrent, + const char** szCurrentOut); + + // ------------------------------------------------------------------- + /** Parse the nodes section of the SMD file + * \param szCurrent Current position in the file. Points to the first + * data line of the section. + * \param szCurrentOut Receives a pointer to the heading line of + * the next section (or to EOF) + */ + void ParseNodesSection(const char* szCurrent, + const char** szCurrentOut); + + // ------------------------------------------------------------------- + /** Parse the skeleton section of the SMD file + * \param szCurrent Current position in the file. Points to the first + * data line of the section. + * \param szCurrentOut Receives a pointer to the heading line of + * the next section (or to EOF) + */ + void ParseSkeletonSection(const char* szCurrent, + const char** szCurrentOut); + + // ------------------------------------------------------------------- + /** Parse a single triangle in the SMD file + * \param szCurrent Current position in the file. Points to the first + * data line of the section. + * \param szCurrentOut Receives the output cursor position + */ + void ParseTriangle(const char* szCurrent, + const char** szCurrentOut); + + + // ------------------------------------------------------------------- + /** Parse a single vertex in the SMD file + * \param szCurrent Current position in the file. Points to the first + * data line of the section. + * \param szCurrentOut Receives the output cursor position + * \param vertex Vertex to be filled + */ + void ParseVertex(const char* szCurrent, + const char** szCurrentOut, SMD::Vertex& vertex, + bool bVASection = false); + + // ------------------------------------------------------------------- + /** Get the index of a texture. If the texture was not yet known + * it will be added to the internal texture list. + * \param filename Name of the texture + * \return Value texture index + */ + unsigned int GetTextureIndex(const std::string& filename); + + // ------------------------------------------------------------------- + /** Computes absolute bone transformations + * All output transformations are in worldspace. + */ + void ComputeAbsoluteBoneTransformations(); + + + // ------------------------------------------------------------------- + /** Parse a line in the skeleton section + */ + void ParseSkeletonElement(const char* szCurrent, + const char** szCurrentOut,int iTime); + + // ------------------------------------------------------------------- + /** Parse a line in the nodes section + */ + void ParseNodeInfo(const char* szCurrent, + const char** szCurrentOut); + + + // ------------------------------------------------------------------- + /** Parse a floating-point value + */ + bool ParseFloat(const char* szCurrent, + const char** szCurrentOut, float& out); + + // ------------------------------------------------------------------- + /** Parse an unsigned integer. There may be no sign! + */ + bool ParseUnsignedInt(const char* szCurrent, + const char** szCurrentOut, uint32_t& out); + + // ------------------------------------------------------------------- + /** Parse a signed integer. Signs (+,-) are handled. + */ + bool ParseSignedInt(const char* szCurrent, + const char** szCurrentOut, int32_t& out); + + // ------------------------------------------------------------------- + /** Fix invalid time values in the file + */ + void FixTimeValues(); + + // ------------------------------------------------------------------- + /** Add all children of a bone as subnodes to a node + * \param pcNode Parent node + * \param iParent Parent bone index + */ + void AddBoneChildren(aiNode* pcNode, uint32_t iParent); + + // ------------------------------------------------------------------- + /** Build output meshes/materials/nodes/animations + */ + void CreateOutputMeshes(); + void CreateOutputNodes(); + void CreateOutputAnimations(); + void CreateOutputMaterials(); + + + // ------------------------------------------------------------------- + /** Print a log message together with the current line number + */ + void LogErrorNoThrow(const char* msg); + void LogWarning(const char* msg); + + + // ------------------------------------------------------------------- + inline bool SkipLine( const char* in, const char** out) + { + ::SkipLine(in,out); + ++iLineNumber; + return true; + } + // ------------------------------------------------------------------- + inline void SkipSpacesAndLineEnd( const char* in, const char** out) + { + ::SkipSpacesAndLineEnd(in,out); + ++iLineNumber; + } + +private: + + /** Buffer to hold the loaded file */ + const char* mBuffer; + + /** Output scene to be filled + */ + aiScene* pScene; + + /** Size of the input file in bytes + */ + unsigned int iFileSize; + + /** Array of textures found in the file + */ + std::vector aszTextures; + + /** Array of triangles found in the file + */ + std::vector asTriangles; + + /** Array of bones found in the file + */ + std::vector asBones; + + /** Smallest frame index found in the skeleton + */ + int iSmallestFrame; + + /** Length of the whole animation, in frames + */ + double dLengthOfAnim; + + /** Do we have texture coordinates? + */ + bool bHasUVs; + + /** Current line numer + */ + unsigned int iLineNumber; + +}; +}; // end of namespace Assimp + +#endif // AI_SMDIMPORTER_H_INC \ No newline at end of file diff --git a/code/SplitLargeMeshes.h b/code/SplitLargeMeshes.h index dea9fd3e0..f0344f894 100644 --- a/code/SplitLargeMeshes.h +++ b/code/SplitLargeMeshes.h @@ -54,8 +54,8 @@ namespace Assimp class SplitLargeMeshesProcess_Triangle; class SplitLargeMeshesProcess_Vertex; -// NOTE: If you change these limits, don't forget to also change the -// corresponding values in the Assimp ports +// NOTE: If you change these limits, don't forget to change the +// corresponding values in all Assimp ports // ********************************************************** // Java: PostProcessStep.java, @@ -64,10 +64,14 @@ class SplitLargeMeshesProcess_Vertex; // ********************************************************** // default limit for vertices -#define AI_SLM_DEFAULT_MAX_VERTICES 1000000 +#if (!defined AI_SLM_DEFAULT_MAX_VERTICES) +# define AI_SLM_DEFAULT_MAX_VERTICES 1000000 +#endif // default limit for triangles -#define AI_SLM_DEFAULT_MAX_TRIANGLES 1000000 +#if (!defined AI_SLM_DEFAULT_MAX_TRIANGLES) +# define AI_SLM_DEFAULT_MAX_TRIANGLES 1000000 +#endif // --------------------------------------------------------------------------- /** Postprocessing filter to split large meshes into submeshes diff --git a/code/TextureTransform.cpp b/code/TextureTransform.cpp index c616ac5b2..1fdb09ea2 100644 --- a/code/TextureTransform.cpp +++ b/code/TextureTransform.cpp @@ -55,8 +55,7 @@ namespace Assimp void TextureTransform::PreProcessUVTransform( Dot3DS::Texture& rcIn) { - std::string s; - std::stringstream ss; + char szTemp[512]; int iField; if (rcIn.mOffsetU) @@ -66,10 +65,10 @@ void TextureTransform::PreProcessUVTransform( if (aiTextureMapMode_Wrap == rcIn.mMapMode) { float fNew = rcIn.mOffsetU-(float)iField; - ss << "[wrap] Found texture coordinate U offset " << rcIn.mOffsetU << ". " - "This can be optimized to " << fNew; - ss >> s; - DefaultLogger::get()->info(s); + sprintf(szTemp,"[wrap] Found texture coordinate U offset %f. " + "This can be optimized to %f",rcIn.mOffsetU,fNew); + + DefaultLogger::get()->info(szTemp); rcIn.mOffsetU = fNew; } else if (aiTextureMapMode_Mirror == rcIn.mMapMode) @@ -77,18 +76,18 @@ void TextureTransform::PreProcessUVTransform( if (0 != (iField % 2))iField--; float fNew = rcIn.mOffsetU-(float)iField; - ss << "[mirror] Found texture coordinate U offset " << rcIn.mOffsetU << ". " - "This can be optimized to " << fNew; - ss >> s; - DefaultLogger::get()->info(s); + sprintf(szTemp,"[mirror] Found texture coordinate U offset %f. " + "This can be optimized to %f",rcIn.mOffsetU,fNew); + + DefaultLogger::get()->info(szTemp); rcIn.mOffsetU = fNew; } else if (aiTextureMapMode_Clamp == rcIn.mMapMode) { - ss << "[clamp] Found texture coordinate U offset " << rcIn.mOffsetU << ". " - "This can be clamped to 1.0f"; - ss >> s; - DefaultLogger::get()->info(s); + sprintf(szTemp,"[clamp] Found texture coordinate U offset %f. " + "This can be clamped to 1.0f",rcIn.mOffsetU); + + DefaultLogger::get()->info(szTemp); rcIn.mOffsetU = 1.0f; } } @@ -100,10 +99,10 @@ void TextureTransform::PreProcessUVTransform( if (aiTextureMapMode_Wrap == rcIn.mMapMode) { float fNew = rcIn.mOffsetV-(float)iField; - ss << "[wrap] Found texture coordinate V offset " << rcIn.mOffsetV << ". " - "This can be optimized to " << fNew; - ss >> s; - DefaultLogger::get()->info(s); + sprintf(szTemp,"[wrap] Found texture coordinate V offset %f. " + "This can be optimized to %f",rcIn.mOffsetV,fNew); + + DefaultLogger::get()->info(szTemp); rcIn.mOffsetV = fNew; } else if (aiTextureMapMode_Mirror == rcIn.mMapMode) @@ -111,18 +110,18 @@ void TextureTransform::PreProcessUVTransform( if (0 != (iField % 2))iField--; float fNew = rcIn.mOffsetV-(float)iField; - ss << "[mirror] Found texture coordinate V offset " << rcIn.mOffsetV << ". " - "This can be optimized to " << fNew; - ss >> s; - DefaultLogger::get()->info(s); + sprintf(szTemp,"[mirror] Found texture coordinate V offset %f. " + "This can be optimized to %f",rcIn.mOffsetV,fNew); + + DefaultLogger::get()->info(szTemp); rcIn.mOffsetV = fNew; } else if (aiTextureMapMode_Clamp == rcIn.mMapMode) { - ss << "[clamp] Found texture coordinate V offset " << rcIn.mOffsetV << ". " - "This can be clamped to 1.0f"; - ss >> s; - DefaultLogger::get()->info(s); + sprintf(szTemp,"[clamp] Found texture coordinate U offset %f. " + "This can be clamped to 1.0f",rcIn.mOffsetV); + + DefaultLogger::get()->info(szTemp); rcIn.mOffsetV = 1.0f; } } @@ -132,9 +131,11 @@ void TextureTransform::PreProcessUVTransform( if (iField = (int)(rcIn.mRotation / 3.141592654f)) { float fNew = rcIn.mRotation-(float)iField*3.141592654f; - ss << "[wrap] Found texture coordinate rotation " << rcIn.mRotation << ". " - "This can be optimized to " << fNew; - DefaultLogger::get()->info(s); + + sprintf(szTemp,"[wrap] Found texture coordinate rotation %f. " + "This can be optimized to %f",rcIn.mRotation,fNew); + DefaultLogger::get()->info(szTemp); + rcIn.mRotation = fNew; } } @@ -300,10 +301,16 @@ void TextureTransform::BakeScaleNOffset( // it is more efficient this way ... if (!pcMesh->mTextureCoords[0])return; - if (1 == pcSrc->iBakeUVTransform) + if (0x1 == pcSrc->iBakeUVTransform) { char szTemp[512]; - sprintf(szTemp,"Transforming existing UV channel. Source UV: %i" + int iLen; +#if _MSC_VER >= 1400 + iLen = ::sprintf_s(szTemp, +#else + iLen = ::sprintf(szTemp, +#endif + "Transforming existing UV channel. Source UV: %i" " OffsetU: %f" " OffsetV: %f" " ScaleU: %f" @@ -314,7 +321,9 @@ void TextureTransform::BakeScaleNOffset( pcSrc->pcSingleTexture->mScaleU, pcSrc->pcSingleTexture->mScaleV, pcSrc->pcSingleTexture->mRotation); - DefaultLogger::get()->info(std::string(szTemp)); + + ai_assert(0 < iLen); + DefaultLogger::get()->info(std::string(szTemp,iLen)); if (!pcSrc->pcSingleTexture->mRotation) { @@ -349,7 +358,7 @@ void TextureTransform::BakeScaleNOffset( } } } - else if (2 == pcSrc->iBakeUVTransform) + else if (0x2 == pcSrc->iBakeUVTransform) { // first save all texture coordinate sets aiVector3D* apvOriginalSets[AI_MAX_NUMBER_OF_TEXTURECOORDS]; @@ -367,13 +376,14 @@ void TextureTransform::BakeScaleNOffset( // now we need to find all textures in the material // which require scaling/offset operations std::vector sOps; - AddToList(sOps,&pcSrc->sTexDiffuse); - AddToList(sOps,&pcSrc->sTexSpecular); - AddToList(sOps,&pcSrc->sTexEmissive); - AddToList(sOps,&pcSrc->sTexOpacity); - AddToList(sOps,&pcSrc->sTexBump); - AddToList(sOps,&pcSrc->sTexShininess); - AddToList(sOps,&pcSrc->sTexAmbient); + sOps.reserve(10); + TextureTransform::AddToList(sOps,&pcSrc->sTexDiffuse); + TextureTransform::AddToList(sOps,&pcSrc->sTexSpecular); + TextureTransform::AddToList(sOps,&pcSrc->sTexEmissive); + TextureTransform::AddToList(sOps,&pcSrc->sTexOpacity); + TextureTransform::AddToList(sOps,&pcSrc->sTexBump); + TextureTransform::AddToList(sOps,&pcSrc->sTexShininess); + TextureTransform::AddToList(sOps,&pcSrc->sTexAmbient); // check the list and find out how many we won't be able // to generate. @@ -446,7 +456,13 @@ void TextureTransform::BakeScaleNOffset( pcMesh->mTextureCoords[iNum] = _pvOut; char szTemp[512]; - sprintf(szTemp,"Generating additional UV channel. Source UV: %i" + int iLen; +#if _MSC_VER >= 1400 + iLen = ::sprintf_s(szTemp, +#else + iLen = ::sprintf(szTemp, +#endif + "Generating additional UV channel. Source UV: %i" " OffsetU: %f" " OffsetV: %f" " ScaleU: %f" @@ -457,7 +473,8 @@ void TextureTransform::BakeScaleNOffset( (**i).fScaleU, (**i).fScaleV, (**i).fRotation); - DefaultLogger::get()->info(std::string(szTemp)); + ai_assert(0 < iLen); + DefaultLogger::get()->info(std::string(szTemp,iLen)); const aiVector3D* pvBase = _pvBase; aiVector3D* pvOut = _pvOut; @@ -516,6 +533,12 @@ void TextureTransform::BakeScaleNOffset( if (apvOriginalSets[iNum])delete[] apvOriginalSets[iNum]; } } + + // setup bitflags to indicate which texture coordinate + // channels are used (this class works for 2d texture coordinates only) + + unsigned int iIndex = 0; + while (pcMesh->HasTextureCoords(iIndex))pcMesh->mNumUVComponents[iIndex++] = 2; return; } // ------------------------------------------------------------------------------------------------ diff --git a/code/ValidateDataStructure.cpp b/code/ValidateDataStructure.cpp index ad41d503c..28130ada8 100644 --- a/code/ValidateDataStructure.cpp +++ b/code/ValidateDataStructure.cpp @@ -342,6 +342,8 @@ void ValidateDSProcess::Validate( const aiMesh* pMesh) void ValidateDSProcess::Validate( const aiMesh* pMesh, const aiBone* pBone) { + this->Validate(&pBone->mName); + // check whether all vertices affected by this bone are valid for (unsigned int i = 0; i < pBone->mNumWeights;++i) { @@ -359,6 +361,8 @@ void ValidateDSProcess::Validate( const aiMesh* pMesh, // ------------------------------------------------------------------------------------------------ void ValidateDSProcess::Validate( const aiAnimation* pAnimation) { + this->Validate(&pAnimation->mName); + // validate all materials if (pAnimation->mNumBones) { @@ -480,7 +484,7 @@ void ValidateDSProcess::Validate( const aiMaterial* pMaterial) // check whether there are material keys that are obviously not legal for (unsigned int i = 0; i < pMaterial->mNumProperties;++i) { - aiMaterialProperty* prop = pMaterial->mProperties[i]; + const aiMaterialProperty* prop = pMaterial->mProperties[i]; if (!prop) { this->ReportError("aiMaterial::mProperties[%i] is NULL (aiMaterial::mNumProperties is %i)", @@ -491,9 +495,39 @@ void ValidateDSProcess::Validate( const aiMaterial* pMaterial) this->ReportError("aiMaterial::mProperties[%i].mDataLength or " "aiMaterial::mProperties[%i].mData is 0",i,i); } + // check all predefined types + if (aiPTI_String == prop->mType) + { + if (prop->mDataLength < sizeof(aiString)) + { + this->ReportError("aiMaterial::mProperties[%i].mDataLength is " + "too small to contain a string (%i, needed: %i)", + i,prop->mDataLength,sizeof(aiString)); + } + this->Validate((const aiString*)prop->mData); + } + else if (aiPTI_Float == prop->mType) + { + if (prop->mDataLength < sizeof(float)) + { + this->ReportError("aiMaterial::mProperties[%i].mDataLength is " + "too small to contain a float (%i, needed: %i)", + i,prop->mDataLength,sizeof(float)); + } + } + else if (aiPTI_Integer == prop->mType) + { + if (prop->mDataLength < sizeof(int)) + { + this->ReportError("aiMaterial::mProperties[%i].mDataLength is " + "too small to contain an integer (%i, needed: %i)", + i,prop->mDataLength,sizeof(int)); + } + } // TODO: check whether there is a key with an unknown name ... } + // make some more specific tests float fTemp; int iShading; if (AI_SUCCESS == aiGetMaterialInteger( pMaterial,AI_MATKEY_SHADING_MODEL,&iShading)) @@ -518,7 +552,6 @@ void ValidateDSProcess::Validate( const aiMaterial* pMaterial) }; } - // check whether there are invalid texture keys SearchForInvalidTextures(pMaterial,"diffuse"); SearchForInvalidTextures(pMaterial,"specular"); @@ -563,6 +596,8 @@ void ValidateDSProcess::Validate( const aiTexture* pTexture) void ValidateDSProcess::Validate( const aiAnimation* pAnimation, const aiBoneAnim* pBoneAnim) { + this->Validate(&pBoneAnim->mBoneName); + // check whether there is a bone with this name ... unsigned int i = 0; for (; i < this->mScene->mNumMeshes;++i) @@ -649,6 +684,8 @@ void ValidateDSProcess::Validate( const aiNode* pNode) if (pNode != this->mScene->mRootNode && !pNode->mParent) this->ReportError("A node has no valid parent (aiNode::mParent is NULL)"); + this->Validate(&pNode->mName); + // validate all meshes if (pNode->mNumMeshes) { @@ -687,3 +724,25 @@ void ValidateDSProcess::Validate( const aiNode* pNode) } } } +// ------------------------------------------------------------------------------------------------ +void ValidateDSProcess::Validate( const aiString* pString) +{ + if (pString->length > MAXLEN) + { + this->ReportError("aiString::length is too large (%i, maximum is %i)", + pString->length,MAXLEN); + } + const char* sz = pString->data; + while (true) + { + if ('\0' == *sz) + { + if (pString->length != (unsigned int)(sz-pString->data)) + this->ReportError("aiString::data is invalid: the terminal zero is at a wrong offset"); + break; + } + else if (sz >= &pString->data[MAXLEN]) + this->ReportError("aiString::data is invalid. There is no terminal character"); + ++sz; + } +} diff --git a/code/ValidateDataStructure.h b/code/ValidateDataStructure.h index 49408c1a1..d72145088 100644 --- a/code/ValidateDataStructure.h +++ b/code/ValidateDataStructure.h @@ -53,6 +53,7 @@ struct aiBoneAnim; struct aiTexture; struct aiMaterial; struct aiNode; +struct aiString; namespace Assimp { @@ -158,6 +159,12 @@ protected: */ void Validate( const aiNode* pNode); + // ------------------------------------------------------------------- + /** Validates a string + * @param pString Input string + */ + void Validate( const aiString* pString); + private: aiScene* mScene; diff --git a/code/XFileImporter.cpp b/code/XFileImporter.cpp index bc4c8e43b..f31b1a9fc 100644 --- a/code/XFileImporter.cpp +++ b/code/XFileImporter.cpp @@ -579,174 +579,188 @@ void XFileImporter::ConvertMaterials( aiScene* pScene, const std::vectorAddProperty( &tex, AI_MATKEY_TEXTURE_DIFFUSE(0)); + // if there is only one texture assume it contains the diffuse color + aiString tex; + tex.Set( oldMat.mTextures[0]); + mat->AddProperty( &tex, AI_MATKEY_TEXTURE_DIFFUSE(0)); } + } else - { + { // Otherwise ... try to search for typical strings in the // texture's file name like 'bump' or 'diffuse' unsigned int iHM = 0,iNM = 0,iDM = 0,iSM = 0,iAM = 0,iEM = 0; for( unsigned int b = 0; b < oldMat.mTextures.size(); b++) - { + { std::string sz = oldMat.mTextures[b]; + if (!sz.length())continue; + char key[256]; // find the file name const size_t iLen = sz.length(); std::string::size_type s = sz.rfind('\\',iLen-1); if (std::string::npos == s) - { + { s = sz.rfind('/',iLen-1); if (std::string::npos == s)s = 0; - } + } // cut off the file extension std::string::size_type sExt = sz.rfind('.',iLen-1); if (std::string::npos != sExt) - { + { sz[sExt] = '\0'; - } + } // bump map std::string::size_type s2 = sz.find("bump",s); if (std::string::npos == s2) + { + if (std::string::npos == (s2 = sz.find("BUMP",s))) { - s2 = sz.find("BUMP",s); - if (std::string::npos == s2) + if (std::string::npos == (s2 = sz.find("Bump",s))) { - s2 = sz.find("Bump",s); - if (std::string::npos == s2) + if (std::string::npos == (s2 = sz.find("height",s))) { - s2 = sz.find("height",s); - if (std::string::npos == s2) + if (std::string::npos == (s2 = sz.find("HEIGHT",s))) { - s2 = sz.find("HEIGHT",s); - if (std::string::npos == s2) - { s2 = sz.find("Height",s); - } } } } } + } if (std::string::npos != s2) - { - sprintf(key,AI_MATKEY_TEXTURE_HEIGHT_ "[%i]",iHM++); - } + { +#if _MSC_VER >= 1400 + ::sprintf_s(key,AI_MATKEY_TEXTURE_HEIGHT_ "[%i]",iHM++); +#else + ::sprintf(key,AI_MATKEY_TEXTURE_HEIGHT_ "[%i]",iHM++); +#endif + } else - { + { // Normal map std::string::size_type s2 = sz.find("normal",s); if (std::string::npos == s2) + { + if (std::string::npos == (s2 = sz.find("NORMAL",s))) { - s2 = sz.find("NORMAL",s); - if (std::string::npos == s2) - { - s2 = sz.find("nm",s); // not really unique - if (std::string::npos == s2) + if (std::string::npos == (s2 = sz.find("nm",s))) + { + if (std::string::npos == (s2 = sz.find("Normal",s))) { - s2 = sz.find("Normal",s); - if (std::string::npos == s2) - { s2 = sz.find("NM",s); - } } } } + } if (std::string::npos != s2) - { - sprintf(key,AI_MATKEY_TEXTURE_NORMALS_ "[%i]",iNM++); - } + { +#if _MSC_VER >= 1400 + ::sprintf_s(key,AI_MATKEY_TEXTURE_NORMALS_ "[%i]",iNM++); +#else + ::sprintf(key,AI_MATKEY_TEXTURE_NORMALS_ "[%i]",iNM++); +#endif + } else - { + { // specular color texture (not unique, too. Could // also be the material's shininess) std::string::size_type s2 = sz.find("spec",s); if (std::string::npos == s2) + { + if (std::string::npos == (s2 = sz.find("Spec",s))) { - s2 = sz.find("Spec",s); - if (std::string::npos == s2) + if (std::string::npos == (sz.find("SPEC",s))) { - s2 = sz.find("SPEC",s); - if (std::string::npos == s2) + if (std::string::npos == (s2 = sz.find("Glanz",s))) { - s2 = sz.find("Glanz",s); - if (std::string::npos == s2) - { s2 = sz.find("glanz",s); - } - } - } - } - if (std::string::npos != s2) - { - sprintf(key,AI_MATKEY_TEXTURE_SPECULAR_ "[%i]",iSM++); - } - else - { - // ambient color texture - std::string::size_type s2 = sz.find("ambi",s); - if (std::string::npos == s2) - { - s2 = sz.find("AMBI",s); - if (std::string::npos == s2) - { - s2 = sz.find("umgebungsfarbe",s); - if (std::string::npos == s2) - { - s2 = sz.find("Ambi",s); - } - } - } - if (std::string::npos != s2) - { - sprintf(key,AI_MATKEY_TEXTURE_AMBIENT_ "[%i]",iAM++); - } - else - { - // emissive color texture - std::string::size_type s2 = sz.find("emissive",s); - if (std::string::npos == s2) - { - s2 = sz.find("EMISSIVE",s); - if (std::string::npos == s2) - { - // self illumination - s2 = sz.find("self",s); - if (std::string::npos == s2) - { - s2 = sz.find("Emissive",s); - } - } - } - if (std::string::npos != s2) - { - sprintf(key,AI_MATKEY_TEXTURE_EMISSIVE_ "[%i]",iEM++); - } - else - { - // assume it is a diffuse texture - sprintf(key,AI_MATKEY_TEXTURE_DIFFUSE_ "[%i]",iDM++); } } } } + if (std::string::npos != s2) + { +#if _MSC_VER >= 1400 + ::sprintf_s(key,AI_MATKEY_TEXTURE_SPECULAR_ "[%i]",iSM++); +#else + ::sprintf(key,AI_MATKEY_TEXTURE_SPECULAR_ "[%i]",iSM++); +#endif + } + else + { + // ambient color texture + std::string::size_type s2 = sz.find("ambi",s); + if (std::string::npos == s2) + { + if (std::string::npos == (s2 = sz.find("AMBI",s))) + { + if (std::string::npos == (s2 = sz.find("env",s))) + { + s2 = sz.find("Ambi",s); + } + } + } + if (std::string::npos != s2) + { +#if _MSC_VER >= 1400 + ::sprintf_s(key,AI_MATKEY_TEXTURE_AMBIENT_ "[%i]",iAM++); +#else + ::sprintf(key,AI_MATKEY_TEXTURE_AMBIENT_ "[%i]",iAM++); +#endif + } + else + { + // emissive color texture + std::string::size_type s2 = sz.find("emissive",s); + if (std::string::npos == s2) + { + s2 = sz.find("EMISSIVE",s); + if (std::string::npos == s2) + { + // self illumination + if (std::string::npos == (s2 = sz.find("self",s))) + { + s2 = sz.find("Emissive",s); + } + } + } + if (std::string::npos != s2) + { +#if _MSC_VER >= 1400 + ::sprintf_s(key,AI_MATKEY_TEXTURE_EMISSIVE_ "[%i]",iEM++); +#else + ::sprintf(key,AI_MATKEY_TEXTURE_EMISSIVE_ "[%i]",iEM++); +#endif + } + else + { + // assume it is a diffuse texture +#if _MSC_VER >= 1400 + ::sprintf_s(key,AI_MATKEY_TEXTURE_DIFFUSE_ "[%i]",iDM++); +#else + ::sprintf(key,AI_MATKEY_TEXTURE_DIFFUSE_ "[%i]",iDM++); +#endif + } + } + } } + } aiString tex; tex.Set( oldMat.mTextures[b] ); mat->AddProperty( &tex, key); - } - } + + } pScene->mMaterials[pScene->mNumMaterials] = mat; mImportedMats[oldMat.mName] = pScene->mNumMaterials; pScene->mNumMaterials++; diff --git a/code/XFileParser.cpp b/code/XFileParser.cpp index 735923958..a7f15d7ea 100644 --- a/code/XFileParser.cpp +++ b/code/XFileParser.cpp @@ -44,6 +44,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "XFileHelper.h" #include "BaseImporter.h" #include "fast_atof.h" +#include "../include/DefaultLogger.h" #include @@ -150,11 +151,11 @@ void XFileParser::ParseFile() if( objectName == "}") { // whatever? - // os::Printer::log("} found in dataObject", ELL_WARNING); + DefaultLogger::get()->warn("} found in dataObject"); } else { // unknown format - //os::Printer::log("Unknown data object in animation of .x file", objectName.c_str(), ELL_WARNING); + DefaultLogger::get()->warn("Unknown data object in animation of .x file"); ParseUnknownDataObject(); } } @@ -248,7 +249,7 @@ void XFileParser::ParseDataObjectFrame( Node* pParent) ParseDataObjectMesh( mesh); } else { - // os::Printer::log("Unknown data object in frame in x file", objectName.c_str(), ELL_WARNING); + DefaultLogger::get()->warn("Unknown data object in frame in x file"); ParseUnknownDataObject(); } } @@ -338,7 +339,7 @@ void XFileParser::ParseDataObjectMesh( Mesh* pMesh) ParseDataObjectSkinWeights( pMesh); else { - //os::Printer::log("Unknown data object in mesh in x file", objectName.c_str(), ELL_WARNING); + DefaultLogger::get()->warn("Unknown data object in mesh in x file"); ParseUnknownDataObject(); } } @@ -533,7 +534,7 @@ void XFileParser::ParseDataObjectMeshMaterialList( Mesh* pMesh) // ignore } else { - // os::Printer::log("Unknown data object in material list in x file", objectName.c_str(), ELL_WARNING); + DefaultLogger::get()->warn("Unknown data object in material list in x file"); ParseUnknownDataObject(); } } @@ -571,7 +572,7 @@ void XFileParser::ParseDataObjectMaterial( Material* pMaterial) pMaterial->mTextures.push_back( texname); } else { - // os::Printer::log("Unknown data object in material in .x file", objectName.c_str(), ELL_WARNING); + DefaultLogger::get()->warn("Unknown data object in material in x file"); ParseUnknownDataObject(); } } @@ -608,7 +609,7 @@ void XFileParser::ParseDataObjectAnimationSet() ParseDataObjectAnimation( anim); else { - // os::Printer::log("Unknown data object in animation set in x file", objectName.c_str(), ELL_WARNING); + DefaultLogger::get()->warn("Unknown data object in animation set in x file"); ParseUnknownDataObject(); } } @@ -644,7 +645,7 @@ void XFileParser::ParseDataObjectAnimation( Animation* pAnim) CheckForClosingBrace(); } else { - //os::Printer::log("Unknown data object in animation in x file", objectName.c_str(), ELL_WARNING); + DefaultLogger::get()->warn("Unknown data object in animation in x file"); ParseUnknownDataObject(); } } @@ -748,6 +749,12 @@ void XFileParser::ParseDataObjectTextureFilename( std::string& pName) readHeadOfDataObject(); GetNextTokenAsString( pName); CheckForClosingBrace(); + + // FIX: some files (e.g. AnimationTest.x) have "" as texture file name + if (!pName.length()) + { + DefaultLogger::get()->warn("Length of texture file name is zero. Skipping this texture."); + } } // ------------------------------------------------------------------------------------------------ diff --git a/code/extra/MakeVerboseFormat.cpp b/code/extra/MakeVerboseFormat.cpp index 4acde9d55..b052c368d 100644 --- a/code/extra/MakeVerboseFormat.cpp +++ b/code/extra/MakeVerboseFormat.cpp @@ -49,17 +49,17 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; - +// ------------------------------------------------------------------------------------------------ MakeVerboseFormatProcess::MakeVerboseFormatProcess() { // nothing to do here } - +// ------------------------------------------------------------------------------------------------ MakeVerboseFormatProcess::~MakeVerboseFormatProcess() { // nothing to do here } -// ------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------ // Executes the post processing step on the given imported data. void MakeVerboseFormatProcess::Execute( aiScene* pScene) { @@ -76,8 +76,7 @@ void MakeVerboseFormatProcess::Execute( aiScene* pScene) else DefaultLogger::get()->debug("MakeVerboseFormatProcess. There was nothing to do."); } - -// ------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------ // Executes the post processing step on the given imported data. bool MakeVerboseFormatProcess::MakeVerboseFormat(aiMesh* pcMesh) { diff --git a/code/qnan.h b/code/qnan.h new file mode 100644 index 000000000..af515f56f --- /dev/null +++ b/code/qnan.h @@ -0,0 +1,42 @@ + + +#if (!defined AI_QNAN_H_INCLUDED) +#define AI_QNAN_H_INCLUDED + +#if (!defined ASSIMP_BUILD_CPP_09) +# include +#endif + +inline bool is_qnan(const float in) +{ + // _isnan() takes a double as argument and would + // require a cast. Therefore we must do it on our own ... + // Another method would be to check whether in != in. + // This should also wor since nan compares to inequal, + // even when compared with itself. However, this could + // case problems with other special floats like snan or inf + union _tagFPUNION + { + float f; + int32_t i; + } FPUNION1,FPUNION2; + + // use a compile-time asertion if possible +#if (defined ASSIMP_BUILD_CPP_09) + static_assert(sizeof(float)==sizeof(int32_t), + "A float seems not to be 4 bytes on this platform"); +#else + BOOST_STATIC_ASSERT(sizeof(float)==sizeof(int32_t)); +#endif + + FPUNION1.f = in; + FPUNION2.f = std::numeric_limits::quiet_NaN(); + return FPUNION1.i == FPUNION2.i; +} + +inline bool is_not_qnan(const float in) +{ + return !is_qnan(in); +} + +#endif // !! AI_QNAN_H_INCLUDED \ No newline at end of file diff --git a/doc/dox.h b/doc/dox.h index 61a9c6c47..d6fa6bb3f 100644 --- a/doc/dox.h +++ b/doc/dox.h @@ -528,9 +528,9 @@ void ConvertMaterial( aiMaterial* matIn, D3DMATERIAL9* matOut ) Textures: -Textures can have various types and purposes. Sometimes ASSIMP is not able to -determine the exact purpose of a texture. Normally it will assume diffuse as default -purpose. Possible purposes for a texture: +Textures can have various types and intended purposes. Sometimes ASSIMP is not able to +determine the exact designated use of a texture. Normally it will assume a texture to be +a diffuse color map by default. Texture types: 1. Diffuse textures. Diffuse textures are combined with the result of the diffuse lighting term.
@@ -549,10 +549,10 @@ normally grayscale images, black stands for fully transparent, white for fully o 6. Height maps. Height maps specify the relative height of a point on a triangle on a per-texel base. Normally height maps (sometimes called "Bump maps") are converted to normal maps before rendering. Height maps are normally grayscale textures. Height maps could also -be used as displacement maps on a highly tesselated surface. +be used as displacement maps on highly tesselated surfaces.
7. Normal maps. Normal maps contain normal vectors for a single texel, in tangent space. -They are not bound to an object. However, all lighting omputations must be done in tangent space. +They are not bound to an object. However, all lighting computations must be done in tangent space. There are many resources on Normal Mapping on the internet.
8. Shininess maps Shininess maps (sometimes called "Gloss" or "SpecularMap") specify @@ -560,7 +560,7 @@ the shininess of a texel mapped on a surface. They are normally used together wi to make flat surfaces look as if they were real 3d objects.
-Textures are generally defined by a set of parameters, including +Textures are generally defined by a set of parameters including
1. The path to the texture. This property is always set. If it is not set, a texture is not existing. This can either be a valid path (beware, sometimes @@ -585,26 +585,31 @@ else // your loading code to load from a path ... 2. An UV coordinate index. This is an index into the UV coordinate set list of the corresponding mesh. Note: Some formats don't define this, so beware, it could be that a second diffuse texture in a mesh was originally intended to use a second UV channel although -ASSIMP states it uses the first one. UV coordinate source indices are defined by the +ASSIMP says it uses the first one. UV coordinate source indices are defined by the AI_MATKEY_UVWSRC_() material property. Assume 0 as default value if this property is not set.
3. A blend factor. This is used if multiple textures are assigned to a slot, e.g. two or more textures on the diffuse channel. A texture's color value is multiplied with its -blend factor before it is combined with the previous color value (from the last texture) using -a specific blend operation (see 4.). Blend factor are defined by the +blend factor before it is combined with the previous color value (from the last texture or the +diffuse/specular/ambient/emissive base color) using +a blend operation (see 4.). Blend factor are defined by the AI_MATKEY_TEXBLEND_() material property. Assume 1.0f as default value if this property is not set.
4. A blend operation. This is used if multiple textures are assigned to a slot, e.g. two or more textures on the diffuse channel. After a texture's color value has been multiplied -with its blend factor, the blend operation is used to combine it with the previous color value. +with its blend factor, the blend operation is used to combine it with the previous color value +(from the last texture or the diffuse/specular/ambient/emissive base color). Blend operations are stored as integer property, however their type is aiTextureOp. Blend factor are defined by the AI_TEXOP_BLEND_() material property. Assume -aiTextureOp_Multiply as default value if this property is not set. The blend operation for -the first texture in a texture slot (e.g. diffuse 0) specifies how the diffuse base color/ -vertex color have to be combined with the texture color value. +aiTextureOp_Multiply as default value if this property is not set.
+5. Mapping modes for all axes The mapping mode for an axis specifies how the rendering +system should deal with UV coordinates beyond the 0-1 range. Mapping modes are +defined by the AI_MATKEY_MAPPINGMODE__() material property. + is either U,V or W. The data type is int, however the real type is aiTextureMapMode. +The default value is aiTextureMapMode_Wrap. You can use the aiGetMaterialTexture() function to read all texture parameters at once (maybe if you're too lazy to read 4 or 5 values manually if there's a smart helper function @@ -612,13 +617,15 @@ doing all the work for you ...). @code if (AI_SUCCESS != aiGetMaterialTexture( - pcMat, // Material object - 0, // first texture in the diffuse slot - AI_TEXTYPE_DIFFUSE, // purpose of texture is diffuse + pcMat, // aiMaterial structure + 0, // we want the first diffuse texture + AI_TEXTYPE_DIFFUSE, // we want the first diffuse texture &path, // receives the path of the texture &uv, // receives the UV index of the texture &blend, // receives the blend factor of the texture &op, // receives the blend operation of the texture + &mmodes, // receives an array of three aiMappingMode's, each specifying + // the mapping mode for a particular axis. Order: UV(W) // (you may also specify 0 for a parameter if you don't need it) )) { @@ -626,6 +633,17 @@ if (AI_SUCCESS != aiGetMaterialTexture( } @endcode +
+As you can see, there's much undefined and subject to speculations. When implementing +ASSIMP's material system the most important point was to keep it as flexible as possible. +The first step you should do when you implement ASSIMP materials into your application is +to make a list of all material properties your rendering engine supports, too. Then I suggest +you to take a look at the remaining material properties: many of them can be simplified and replaced +with other properties, e.g. a diffuse texture blend factor can often be premultiplied +with the diffuse base color! At last a few properties you do not support will remain. Forget them. +Most models won't look worse if only small details of its material cannot be rendered as it was intended +by the artist. + @section bones Bones A mesh may have a set of bones in the form of aiBone structures.. Bones are a means to deform a mesh diff --git a/include/IOStream.h b/include/IOStream.h index 68c8aea67..60e3ce1e8 100644 --- a/include/IOStream.h +++ b/include/IOStream.h @@ -27,7 +27,7 @@ namespace Assimp * implementation for IOSystem that creates instances of your custom IO class. */ // --------------------------------------------------------------------------- -class IOStream +class ASSIMP_API IOStream { protected: /** Constructor protected, use IOSystem::Open() to create an instance. */ diff --git a/include/IOSystem.h b/include/IOSystem.h index 2ff354dd1..7fa0937c8 100644 --- a/include/IOSystem.h +++ b/include/IOSystem.h @@ -11,6 +11,8 @@ #include +#include "aiDefines.h" + namespace Assimp { @@ -23,7 +25,7 @@ class IOStream; * to the importer library. If you implement this interface, you also want to * supply a custom implementation for IOStream. */ -class IOSystem +class ASSIMP_API IOSystem { public: /** Constructor. Create an instance of your derived class and assign it to diff --git a/include/LogStream.h b/include/LogStream.h index f87dfe84e..5d72a99cb 100644 --- a/include/LogStream.h +++ b/include/LogStream.h @@ -9,7 +9,7 @@ namespace Assimp /** @class LogStream * @brief Abstract interface for log stream implementations. */ -class LogStream +class ASSIMP_API LogStream { protected: /** @brief Default constructor */ diff --git a/include/Logger.h b/include/Logger.h index 5ae8e543e..27d8af3fb 100644 --- a/include/Logger.h +++ b/include/Logger.h @@ -1,7 +1,48 @@ +/* +Open Asset Import Library (ASSIMP) +---------------------------------------------------------------------- + +Copyright (c) 2006-2008, ASSIMP Development 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 Development 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. + +---------------------------------------------------------------------- +*/ + #ifndef AI_LOGGER_H_INC #define AI_LOGGER_H_INC #include +#include "aiDefines.h" namespace Assimp { @@ -12,7 +53,7 @@ class LogStream; /** @class Logger * @brief Abstract interface for logger implementations. */ -class Logger +class ASSIMP_API Logger { public: /** @enum LogSeverity diff --git a/include/aiDefines.h b/include/aiDefines.h new file mode 100644 index 000000000..47bbb644b --- /dev/null +++ b/include/aiDefines.h @@ -0,0 +1,97 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (ASSIMP) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2008, ASSIMP Development 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 Development 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. +--------------------------------------------------------------------------- +*/ + +#ifndef AI_DEFINES_H_INC +#define AI_DEFINES_H_INC + +// compiler specific includes and definitions +#if (defined _MSC_VER) + + // include stdint.h from the C98 standard +# include "Compiler/VisualStudio/stdint.h" + +# undef ASSIMP_API + + // ************************************************************ + // Define ASSIMP_BUILD_DLL_EXPORT to build a DLL of the library + // ************************************************************ +# if (defined ASSIMP_BUILD_DLL_EXPORT) +# define ASSIMP_API __declspec(dllexport) + + // ************************************************************ + // Define ASSIMP_DLL before including Assimp to use ASSIMP in + // an external DLL (otherwise a static library is used) + // ************************************************************ +# elif (defined ASSIMP_DLL) +# define ASSIMP_API __declspec(dllimport) +# else +# define ASSIMP_API +# endif + +#endif // (defined _MSC_VER) + +#ifdef __cplusplus +# define C_STRUCT +#else + // ************************************************************ + // To build the documentation, make sure ASSIMP_DOXYGEN_BUILD + // is defined by Doxygen's preprocessor. The corresponding + // entries in the DoxyFile look like this: +#if 0 + ENABLE_PREPROCESSING = YES + MACRO_EXPANSION = YES + EXPAND_ONLY_PREDEF = YES + SEARCH_INCLUDES = YES + INCLUDE_PATH = + INCLUDE_FILE_PATTERNS = + PREDEFINED = ASSIMP_DOXYGEN_BUILD=1 + EXPAND_AS_DEFINED = C_STRUCT + SKIP_FUNCTION_MACROS = YES +#endif + // ************************************************************ +# if (defined ASSIMP_DOXYGEN_BUILD) +# define C_STRUCT +# else +# define C_STRUCT struct +# endif +#endif + +#endif // !! AI_DEFINES_H_INC diff --git a/include/aiMaterial.h b/include/aiMaterial.h index f2c0bd27d..0a18b60d3 100644 --- a/include/aiMaterial.h +++ b/include/aiMaterial.h @@ -52,6 +52,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. extern "C" { #endif +// Default material name +#define AI_DEFAULT_MATERIAL_NAME "aiDefaultMat" + // --------------------------------------------------------------------------- /** Defines type identifiers for use within the material system. * @@ -235,6 +238,18 @@ struct aiMaterialProperty char* mData; }; +#ifdef __cplusplus +} // need to end extern C block to allow template member functions +#endif + +#define AI_TEXTYPE_OPACITY 0x0 +#define AI_TEXTYPE_SPECULAR 0x1 +#define AI_TEXTYPE_AMBIENT 0x2 +#define AI_TEXTYPE_EMISSIVE 0x3 +#define AI_TEXTYPE_HEIGHT 0x4 +#define AI_TEXTYPE_NORMALS 0x5 +#define AI_TEXTYPE_SHININESS 0x6 +#define AI_TEXTYPE_DIFFUSE 0x7 // --------------------------------------------------------------------------- /** Data structure for a material @@ -245,13 +260,67 @@ struct aiMaterialProperty * enough for nearly all purposes. */ // --------------------------------------------------------------------------- -struct aiMaterial +struct ASSIMP_API aiMaterial { #ifdef __cplusplus protected: aiMaterial() {} public: -#endif // __cplusplus + + // ------------------------------------------------------------------- + /** Retrieve an array of Type values with a specific key + * from the material + * + * @param pKey Key to search for. One of the AI_MATKEY_XXX constants. + * @param pOut Pointer to a buffer to receive the result. + * @param pMax Specifies the size of the given buffer, in Type's. + * Receives the number of values (not bytes!) read. + * NULL is a valid value for this parameter. + */ + template + inline aiReturn Get(const char* pKey,Type* pOut, + unsigned int* pMax); + + // ------------------------------------------------------------------- + /** Retrieve a Type value with a specific key + * from the material + * + * @param pKey Key to search for. One of the AI_MATKEY_XXX constants. + * @param pOut Reference to receive the output value + */ + template + inline aiReturn Get(const char* pKey,Type& pOut); + + // ------------------------------------------------------------------- + /** Helper function to get a texture from a material + * + * This function is provided just for convinience. + * @param iIndex Index of the texture to retrieve. If the index is too + * large the function fails. + * @param iTexType One of the AI_TEXTYPE constants. Specifies the type of + * the texture to retrieve (e.g. diffuse, specular, height map ...) + * @param szPath Receives the output path + * NULL is no allowed as value + * @param piUVIndex Receives the UV index of the texture. + * NULL is allowed as value. + * @param pfBlendFactor Receives the blend factor for the texture + * NULL is allowed as value. + * @param peTextureOp Receives the texture operation to perform between + * this texture and the previous texture. NULL is allowed as value. + * @param peMapMode Receives the mapping modes to be used for the texture. + * The parameter may be NULL but if it is a valid pointer it MUST + * point to an array of 3 aiTextureMapMode variables (one for each + * axis: UVW order (=XYZ)). + */ + // ------------------------------------------------------------------- + inline aiReturn GetTexture(unsigned int iIndex, + unsigned int iTexType, + C_STRUCT aiString* szPath, + unsigned int* piUVIndex = NULL, + float* pfBlendFactor = NULL, + aiTextureOp* peTextureOp = NULL, + aiTextureMapMode* peMapMode = NULL); +#endif /** List of all material properties loaded. */ @@ -263,6 +332,11 @@ public: unsigned int mNumAllocated; }; +#ifdef __cplusplus +extern "C" { +#endif + + // --------------------------------------------------------------------------- /** @def AI_BUILD_KEY * Builds a material texture key with a dynamic index. @@ -300,13 +374,27 @@ public: * @param out Array of chars to receive the output value. It must be * sufficiently large. This will be checked via a static assertion for * C++0x. For MSVC8 and later versions the security enhanced version of - * sprintf() will be used. However, if your buffer is at least 512 bytes + * sprintf() will be used. However, if your buffer is at least 256 bytes * long you'll never overrun. */ +// --------------------------------------------------------------------------- #if _MSC_VER >= 1400 + + // MSVC 8+. Use the sprintf_s function with security enhancements # define AI_BUILD_KEY(base,index,out) \ ::sprintf_s(out,"%s[%i]",base,index); + +#elif (defined ASSIMP_BUILD_CPP_09) + + // C++09 compiler. Use a static assertion to validate the size + // of the output buffer +# define AI_BUILD_KEY(base,index,out) \ + static_assert(sizeof(out) >= 180,"Output buffer is too small"); \ + ::sprintf(out,"%s[%i]",base,index); + #else + + // otherwise ... simply hope the buffer is large enough :-) # define AI_BUILD_KEY(base,index,out) \ ::sprintf(out,"%s[%i]",base,index); #endif @@ -416,7 +504,7 @@ public: // --------------------------------------------------------------------------- /** @def AI_MATKEY_TEXTURE_DIFFUSE -* Defines a specified diffuse texture channel of the material +* Defines a specific diffuse texture channel of the material *
* Type: string (aiString)
* Default value: none
@@ -429,7 +517,7 @@ public: #define AI_MATKEY_TEXTURE_DIFFUSE_ "$tex.file.diffuse" /** @def AI_MATKEY_TEXTURE_AMBIENT - * Defines a specified ambient texture channel of the material + * Defines a specific ambient texture channel of the material *
* Type: string (aiString)
* Default value: none
@@ -442,7 +530,7 @@ public: #define AI_MATKEY_TEXTURE_AMBIENT_ "$tex.file.ambient" /** @def AI_MATKEY_TEXTURE_SPECULAR - * Defines a specified specular texture channel of the material + * Defines a specific specular texture channel of the material *
* Type: string (aiString)
* Default value: none
@@ -455,7 +543,7 @@ public: #define AI_MATKEY_TEXTURE_SPECULAR_ "$tex.file.specular" /** @def AI_MATKEY_TEXTURE_EMISSIVE - * Defines a specified emissive texture channel of the material + * Defines a specific emissive texture channel of the material *
* Type: string (aiString)
* Default value: none
@@ -468,7 +556,7 @@ public: #define AI_MATKEY_TEXTURE_EMISSIVE_ "$tex.file.emissive" /** @def AI_MATKEY_TEXTURE_NORMALS - * Defines a specified normal texture channel of the material + * Defines a specific normal texture channel of the material *
* Type: string (aiString)
* Default value: none
@@ -498,7 +586,7 @@ public: #define AI_MATKEY_TEXTURE_HEIGHT_ "$tex.file.height" /** @def AI_MATKEY_TEXTURE_SHININESS - * Defines a specified shininess texture channel of the material + * Defines a specific shininess texture channel of the material *
* Type: string (aiString)
* Default value: none
@@ -511,7 +599,7 @@ public: #define AI_MATKEY_TEXTURE_SHININESS_ "$tex.file.shininess" /** @def AI_MATKEY_TEXTURE_OPACITY - * Defines a specified opacity texture channel of the material + * Defines a specific opacity texture channel of the material *
* Type: string (aiString)
* Default value: none
@@ -772,7 +860,7 @@ public: * structure or NULL if the key has not been found. */ // --------------------------------------------------------------------------- -aiReturn aiGetMaterialProperty(const C_STRUCT aiMaterial* pMat, +ASSIMP_API aiReturn aiGetMaterialProperty(const C_STRUCT aiMaterial* pMat, const char* pKey, const C_STRUCT aiMaterialProperty** pPropOut); @@ -788,7 +876,7 @@ aiReturn aiGetMaterialProperty(const C_STRUCT aiMaterial* pMat, * Receives the number of values (not bytes!) read. */ // --------------------------------------------------------------------------- -aiReturn aiGetMaterialFloatArray(const C_STRUCT aiMaterial* pMat, +ASSIMP_API aiReturn aiGetMaterialFloatArray(const C_STRUCT aiMaterial* pMat, const char* pKey, float* pOut, unsigned int* pMax); @@ -817,7 +905,7 @@ inline aiReturn aiGetMaterialFloat(const C_STRUCT aiMaterial* pMat, * Receives the number of values (not bytes!) read. */ // --------------------------------------------------------------------------- -aiReturn aiGetMaterialIntegerArray(const C_STRUCT aiMaterial* pMat, +ASSIMP_API aiReturn aiGetMaterialIntegerArray(const C_STRUCT aiMaterial* pMat, const char* pKey, int* pOut, unsigned int* pMax); @@ -844,7 +932,7 @@ inline aiReturn aiGetMaterialInteger(const C_STRUCT aiMaterial* pMat, * @param pOut Pointer to a buffer to receive the result. */ // --------------------------------------------------------------------------- -aiReturn aiGetMaterialColor(const C_STRUCT aiMaterial* pMat, +ASSIMP_API aiReturn aiGetMaterialColor(const C_STRUCT aiMaterial* pMat, const char* pKey, aiColor4D* pOut); @@ -857,20 +945,11 @@ aiReturn aiGetMaterialColor(const C_STRUCT aiMaterial* pMat, * @param pOut Pointer to a buffer to receive the result. */ // --------------------------------------------------------------------------- -aiReturn aiGetMaterialString(const C_STRUCT aiMaterial* pMat, +ASSIMP_API aiReturn aiGetMaterialString(const C_STRUCT aiMaterial* pMat, const char* pKey, aiString* pOut); -#define AI_TEXTYPE_OPACITY 0x0 -#define AI_TEXTYPE_SPECULAR 0x1 -#define AI_TEXTYPE_AMBIENT 0x2 -#define AI_TEXTYPE_EMISSIVE 0x3 -#define AI_TEXTYPE_HEIGHT 0x4 -#define AI_TEXTYPE_NORMALS 0x5 -#define AI_TEXTYPE_SHININESS 0x6 -#define AI_TEXTYPE_DIFFUSE 0x7 - // --------------------------------------------------------------------------- /** Helper function to get a texture from a material * @@ -895,7 +974,7 @@ aiReturn aiGetMaterialString(const C_STRUCT aiMaterial* pMat, */ // --------------------------------------------------------------------------- #ifdef __cplusplus -aiReturn aiGetMaterialTexture(const C_STRUCT aiMaterial* pMat, +ASSIMP_API aiReturn aiGetMaterialTexture(const C_STRUCT aiMaterial* pMat, unsigned int iIndex, unsigned int iTexType, C_STRUCT aiString* szPath, @@ -920,5 +999,4 @@ aiReturn aiGetMaterialTexture(const C_STRUCT aiMaterial* pMat, #include "aiMaterial.inl" #endif //!__cplusplus - #endif //!!AI_MATERIAL_H_INC diff --git a/include/aiMaterial.inl b/include/aiMaterial.inl index 117178e6a..60dff55f5 100644 --- a/include/aiMaterial.inl +++ b/include/aiMaterial.inl @@ -46,46 +46,21 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_MATERIAL_INL_INC #define AI_MATERIAL_INL_INC - // --------------------------------------------------------------------------- -/** @brief A class that provides easy access to the property list of a - * material (aiMaterial) via template methods. You can cast an - * aiMaterial* to aiMaterialCPP* - * @note This extra class is necessary since template methods - * are not allowed within C-linkage blocks (extern "C") - */ -class aiMaterialCPP : public aiMaterial +inline aiReturn aiMaterial::GetTexture(unsigned int iIndex, + unsigned int iTexType, + aiString* szPath, + unsigned int* piUVIndex , + float* pfBlendFactor , + aiTextureOp* peTextureOp , + aiTextureMapMode* peMapMode ) { -public: - - // ------------------------------------------------------------------- - /** Retrieve an array of Type values with a specific key - * from the material - * - * @param pKey Key to search for. One of the AI_MATKEY_XXX constants. - * @param pOut Pointer to a buffer to receive the result. - * @param pMax Specifies the size of the given buffer, in Type's. - * Receives the number of values (not bytes!) read. - * NULL is a valid value for this parameter. - */ - template - inline aiReturn Get(const char* pKey,Type* pOut, - unsigned int* pMax); - - // ------------------------------------------------------------------- - /** Retrieve a Type value with a specific key - * from the material - * - * @param pKey Key to search for. One of the AI_MATKEY_XXX constants. - * @param pOut Reference to receive the output value - */ - template - inline aiReturn Get(const char* pKey,Type& pOut); -}; - + return aiGetMaterialTexture(this,iIndex,iTexType,szPath, + piUVIndex,pfBlendFactor,peTextureOp,peMapMode); +} // --------------------------------------------------------------------------- template -inline aiReturn aiMaterialCPP::Get(const char* pKey,Type* pOut, +inline aiReturn aiMaterial::Get(const char* pKey,Type* pOut, unsigned int* pMax) { unsigned int iNum = pMax ? *pMax : 1; @@ -105,7 +80,7 @@ inline aiReturn aiMaterialCPP::Get(const char* pKey,Type* pOut, } // --------------------------------------------------------------------------- template -inline aiReturn aiMaterialCPP::Get(const char* pKey,Type& pOut) +inline aiReturn aiMaterial::Get(const char* pKey,Type& pOut) { aiMaterialProperty* prop; aiReturn ret = aiGetMaterialProperty(this,pKey,&prop); @@ -120,39 +95,39 @@ inline aiReturn aiMaterialCPP::Get(const char* pKey,Type& pOut) } // --------------------------------------------------------------------------- template <> -inline aiReturn aiMaterialCPP::Get(const char* pKey,float* pOut, +inline aiReturn aiMaterial::Get(const char* pKey,float* pOut, unsigned int* pMax) { return aiGetMaterialFloatArray(this,pKey,pOut,pMax); } // --------------------------------------------------------------------------- template <> -inline aiReturn aiMaterialCPP::Get(const char* pKey,int* pOut, +inline aiReturn aiMaterial::Get(const char* pKey,int* pOut, unsigned int* pMax) { return aiGetMaterialIntegerArray(this,pKey,pOut,pMax); } // --------------------------------------------------------------------------- template <> -inline aiReturn aiMaterialCPP::Get(const char* pKey,float& pOut) +inline aiReturn aiMaterial::Get(const char* pKey,float& pOut) { return aiGetMaterialFloat(this,pKey,&pOut); } // --------------------------------------------------------------------------- template <> -inline aiReturn aiMaterialCPP::Get(const char* pKey,int& pOut) +inline aiReturn aiMaterial::Get(const char* pKey,int& pOut) { return aiGetMaterialInteger(this,pKey,&pOut); } // --------------------------------------------------------------------------- template <> -inline aiReturn aiMaterialCPP::Get(const char* pKey,aiColor4D& pOut) +inline aiReturn aiMaterial::Get(const char* pKey,aiColor4D& pOut) { return aiGetMaterialColor(this,pKey,&pOut); } // --------------------------------------------------------------------------- template <> -inline aiReturn aiMaterialCPP::Get(const char* pKey,aiString& pOut) +inline aiReturn aiMaterial::Get(const char* pKey,aiString& pOut) { return aiGetMaterialString(this,pKey,&pOut); } diff --git a/include/aiMatrix3x3.h b/include/aiMatrix3x3.h index bc3e6592b..03fb43708 100644 --- a/include/aiMatrix3x3.h +++ b/include/aiMatrix3x3.h @@ -9,7 +9,7 @@ extern "C" { struct aiMatrix4x4; // --------------------------------------------------------------------------- -/** Represents a column-major 3x3 matrix +/** Represents a row-major 3x3 matrix */ // --------------------------------------------------------------------------- struct aiMatrix3x3 @@ -28,7 +28,8 @@ struct aiMatrix3x3 c1(_c1), c2(_c2), c3(_c3) {} - /** Construction from a 4x4 matrix. The remaining parts of the matrix are ignored. */ + /** Construction from a 4x4 matrix. The remaining parts of the + matrix are ignored. */ explicit aiMatrix3x3( const aiMatrix4x4& pMatrix); aiMatrix3x3& operator *= (const aiMatrix3x3& m); diff --git a/include/aiMatrix3x3.inl b/include/aiMatrix3x3.inl index 015588aa2..8848b2fdc 100644 --- a/include/aiMatrix3x3.inl +++ b/include/aiMatrix3x3.inl @@ -50,5 +50,6 @@ inline aiMatrix3x3& aiMatrix3x3::Transpose() } + #endif // __cplusplus #endif // AI_MATRIX3x3_INL_INC \ No newline at end of file diff --git a/include/aiMatrix4x4.h b/include/aiMatrix4x4.h index 642a98613..3502741c4 100644 --- a/include/aiMatrix4x4.h +++ b/include/aiMatrix4x4.h @@ -7,6 +7,7 @@ extern "C" { #endif struct aiMatrix3x3; +struct aiQuaternion; // Set packing to 4 #if defined(_MSC_VER) || defined(__BORLANDC__) || defined (__BCPLUSPLUS__) @@ -19,7 +20,7 @@ struct aiMatrix3x3; #endif // --------------------------------------------------------------------------- -/** Represents a column-major 4x4 matrix, +/** Represents a row-major 4x4 matrix, * use this for homogenious coordinates */ // --------------------------------------------------------------------------- @@ -57,6 +58,35 @@ struct aiMatrix4x4 inline bool operator== (const aiMatrix4x4 m) const; inline bool operator!= (const aiMatrix4x4 m) const; + + /** \brief Decompose a trafo matrix into its original components + * \param scaling Receives the output scaling for the x,y,z axes + * \param rotation Receives the output rotation as a hamilton + * quaternion + * \param position Receives the output position for the x,y,z axes + */ + inline void Decompose (aiVector3D& scaling, aiQuaternion& rotation, + aiVector3D& position) const; + + + /** \brief Decompose a trafo matrix with no scaling into its + * original components + * \param rotation Receives the output rotation as a hamilton + * quaternion + * \param position Receives the output position for the x,y,z axes + */ + inline void DecomposeNoScaling (aiQuaternion& rotation, + aiVector3D& position) const; + + + /** \brief Creates a trafo matrix from a set of euler angles + * \param x Rotation angle for the x-axis, in radians + * \param y Rotation angle for the y-axis, in radians + * \param z Rotation angle for the z-axis, in radians + */ + inline void FromEulerAngles(float x, float y, float z); + + #endif // __cplusplus float a1, a2, a3, a4; diff --git a/include/aiMatrix4x4.inl b/include/aiMatrix4x4.inl index 84c853e2a..bafb0dda7 100644 --- a/include/aiMatrix4x4.inl +++ b/include/aiMatrix4x4.inl @@ -11,6 +11,9 @@ #include #include +#include "aiAssert.h" +#include "aiQuaternion.h" + // --------------------------------------------------------------------------- inline aiMatrix4x4::aiMatrix4x4( const aiMatrix3x3& m) { @@ -144,6 +147,96 @@ inline bool aiMatrix4x4::operator!= (const aiMatrix4x4 m) const { return !(*this == m); } +// --------------------------------------------------------------------------- +inline void aiMatrix4x4::Decompose (aiVector3D& scaling, aiQuaternion& rotation, + aiVector3D& position) const +{ + const aiMatrix4x4& _this = *this; + + // extract translation + position.x = _this[0][3]; + position.y = _this[1][3]; + position.z = _this[2][3]; + + // extract the rows of the matrix + aiVector3D vRows[3] = { + aiVector3D(_this[0][0],_this[1][1],_this[2][0]), + aiVector3D(_this[0][1],_this[1][1],_this[2][1]), + aiVector3D(_this[0][2],_this[1][2],_this[2][2]) + }; + + // extract the scaling factors + scaling.x = vRows[0].Length(); + scaling.y = vRows[1].Length(); + scaling.z = vRows[2].Length(); + + // and remove all scaling from the matrix + if(scaling.x) + { + vRows[0].x /= scaling.x; + vRows[0].y /= scaling.x; + vRows[0].z /= scaling.x; + } + if(scaling.y) + { + vRows[1].x /= scaling.y; + vRows[1].y /= scaling.y; + vRows[1].z /= scaling.y; + } + if(scaling.z) + { + vRows[2].x /= scaling.z; + vRows[2].y /= scaling.z; + vRows[2].z /= scaling.z; + } + + // build a 3x3 rotation matrix + aiMatrix3x3 m(vRows[0].x,vRows[0].y,vRows[0].z, + vRows[1].x,vRows[1].y,vRows[1].z, + vRows[2].x,vRows[2].y,vRows[2].z); + + // and generate the rotation quaternion from it + rotation = aiQuaternion(m); +} +// --------------------------------------------------------------------------- +inline void aiMatrix4x4::DecomposeNoScaling (aiQuaternion& rotation, + aiVector3D& position) const +{ + const aiMatrix4x4& _this = *this; + + // extract translation + position.x = _this[0][3]; + position.y = _this[1][3]; + position.z = _this[2][3]; + + // extract rotation + rotation = aiQuaternion((aiMatrix3x3)_this); +} +// --------------------------------------------------------------------------- +inline void aiMatrix4x4::FromEulerAngles(float x, float y, float z) +{ + aiMatrix4x4& _this = *this; + + const float A = ::cosf(x); + const float B = ::sinf(x); + const float C = ::cosf(y); + const float D = ::sinf(y); + const float E = ::cosf(z); + const float F = ::sinf(z); + const float AD = A * D; + const float BD = B * D; + _this.a1 = C * E; + _this.a2 = -C * F; + _this.a3 = D; + _this.b1 = BD * E + A * F; + _this.b2 = -BD * F + A * E; + _this.b3 = -B * C; + _this.c1 = -AD * E + B * F; + _this.c2 = AD * F + B * E; + _this.c3 = A * C; + _this.a4 = _this.b4 = _this.c4 = _this.d1 = _this.d2 = _this.d3 = 0.0f; + _this.d4 = 1.0f; +} #endif // __cplusplus #endif // AI_MATRIX4x4_INL_INC diff --git a/include/aiPostProcess.h b/include/aiPostProcess.h index 86d782764..4adff7a26 100644 --- a/include/aiPostProcess.h +++ b/include/aiPostProcess.h @@ -130,8 +130,10 @@ enum aiPostProcessSteps * to a maximum value. If any vertex is affected by more than that number * of bones, the least important vertex weights are removed and the remaining * vertex weights are renormalized so that the weights still sum up to 1. - * At the moment the maximum bone count is hardcoded to 4. - * + * The default bone weight limit is 4 (defined as AI_LMW_MAX_WEIGHTS in + * LimitBoneWeightsProcess.h), but you can use the aiSetBoneWeightLimit + * function to supply your own limit to the post processing step. + * * If you intend to perform the skinning in hardware, this post processing step * might be of interest for you. */ @@ -156,8 +158,7 @@ enum aiPostProcessSteps * \note The default value is AI_SLM_DEFAULT_MAX_VERTICES, defined in * the internal header file SplitLargeMeshes.h */ -aiReturn aiSetVertexSplitLimit(unsigned int pLimit); - +ASSIMP_API aiReturn aiSetVertexSplitLimit(unsigned int pLimit); // --------------------------------------------------------------------------- /** \brief Set the maximum number of triangles in a mesh. @@ -168,7 +169,17 @@ aiReturn aiSetVertexSplitLimit(unsigned int pLimit); * \note The default value is AI_SLM_DEFAULT_MAX_TRIANGLES, defined in * the internal header file SplitLargeMeshes.h */ -aiReturn aiSetTriangleSplitLimit(unsigned int pLimit); +ASSIMP_API aiReturn aiSetTriangleSplitLimit(unsigned int pLimit); + +// --------------------------------------------------------------------------- +/** \brief Set the maximum number of bones affecting a single vertex + * + * This is used by the aiProcess_LimitBoneWeights PostProcess-Step. + * \param pLimit Bone limit + * \note The default value is AI_LMW_MAX_WEIGHTS, defined in + * the internal header file LimitBoneWeightsProcess.h + */ +ASSIMP_API aiReturn aiSetBoneWeightLimit(unsigned int pLimit); #ifdef __cplusplus } // end of extern "C" diff --git a/include/aiQuaternion.h b/include/aiQuaternion.h index 58e6c0d44..ea221525b 100644 --- a/include/aiQuaternion.h +++ b/include/aiQuaternion.h @@ -16,9 +16,13 @@ struct aiQuaternion #ifdef __cplusplus aiQuaternion() : w(0.0f), x(0.0f), y(0.0f), z(0.0f) {} aiQuaternion(float _w, float _x, float _y, float _z) : w(_w), x(_x), y(_y), z(_z) {} + /** Construct from rotation matrix. Result is undefined if the matrix is not orthonormal. */ aiQuaternion( const aiMatrix3x3& pRotMatrix); + /** Construct from euler angles */ + aiQuaternion( float rotx, float roty, float rotz); + /** Returns a matrix representation of the quaternion */ aiMatrix3x3 GetMatrix() const; #endif // __cplusplus @@ -73,6 +77,24 @@ inline aiQuaternion::aiQuaternion( const aiMatrix3x3 &pRotMatrix) } } +// --------------------------------------------------------------------------- +// Construction from euler angles +inline aiQuaternion::aiQuaternion( float fPitch, float fYaw, float fRoll ) +{ + const float fSinPitch(sin(fPitch*0.5F)); + const float fCosPitch(cos(fPitch*0.5F)); + const float fSinYaw(sin(fYaw*0.5F)); + const float fCosYaw(cos(fYaw*0.5F)); + const float fSinRoll(sin(fRoll*0.5F)); + const float fCosRoll(cos(fRoll*0.5F)); + const float fCosPitchCosYaw(fCosPitch*fCosYaw); + const float fSinPitchSinYaw(fSinPitch*fSinYaw); + x = fSinRoll * fCosPitchCosYaw - fCosRoll * fSinPitchSinYaw; + y = fCosRoll * fSinPitch * fCosYaw + fSinRoll * fCosPitch * fSinYaw; + z = fCosRoll * fCosPitch * fSinYaw - fSinRoll * fSinPitch * fCosYaw; + w = fCosRoll * fCosPitchCosYaw + fSinRoll * fSinPitchSinYaw; +} + // --------------------------------------------------------------------------- // Returns a matrix representation of the quaternion inline aiMatrix3x3 aiQuaternion::GetMatrix() const @@ -91,6 +113,8 @@ inline aiMatrix3x3 aiQuaternion::GetMatrix() const return resMatrix; } + + } // end extern "C" #endif // __cplusplus diff --git a/include/aiScene.h b/include/aiScene.h index b0dc10208..4187f3c48 100644 --- a/include/aiScene.h +++ b/include/aiScene.h @@ -94,6 +94,7 @@ struct aiNode /** Constructor */ aiNode() { + // set all members to zero by default mParent = NULL; mNumChildren = 0; mChildren = NULL; mNumMeshes = 0; mMeshes = NULL; @@ -102,6 +103,7 @@ struct aiNode /** Destructor */ ~aiNode() { + // delete al children recursively for( unsigned int a = 0; a < mNumChildren; a++) delete mChildren[a]; delete [] mChildren; @@ -127,6 +129,8 @@ struct aiScene */ C_STRUCT aiNode* mRootNode; + + /** The number of meshes in the scene. */ unsigned int mNumMeshes; @@ -137,6 +141,8 @@ struct aiScene */ C_STRUCT aiMesh** mMeshes; + + /** The number of materials in the scene. */ unsigned int mNumMaterials; @@ -147,6 +153,8 @@ struct aiScene */ C_STRUCT aiMaterial** mMaterials; + + /** The number of animations in the scene. */ unsigned int mNumAnimations; @@ -157,20 +165,25 @@ struct aiScene */ C_STRUCT aiAnimation** mAnimations; + + /** The number of textures embedded into the file */ unsigned int mNumTextures; /** The array of embedded textures. * * Not many file formats embedd their textures into the file. - * Examples include Quake's MDL format (which is also used by + * An example is Quake's MDL format (which is also used by * some GameStudio™ versions) */ C_STRUCT aiTexture** mTextures; #ifdef __cplusplus + + //! Default constructor aiScene() { + // set all members to zero by default mRootNode = NULL; mNumMeshes = 0; mMeshes = NULL; mNumMaterials = 0; mMaterials = NULL; @@ -178,8 +191,10 @@ struct aiScene mNumTextures = 0; mTextures = NULL; } + //! Destructor ~aiScene() { + // delete all subobjects recursively delete mRootNode; for( unsigned int a = 0; a < mNumMeshes; a++) delete mMeshes[a]; diff --git a/include/aiTexture.h b/include/aiTexture.h index 7c26cce41..67cadef32 100644 --- a/include/aiTexture.h +++ b/include/aiTexture.h @@ -68,6 +68,21 @@ struct aiTexel unsigned char g; unsigned char r; unsigned char a; + + //! Comparison operator + bool operator== (const aiTexel& other) const + { + return b == other.b && r == other.r && + g == other.g && a == other.a; + } + + //! Negative comparison operator + bool operator!= (const aiTexel& other) const + { + return b != other.b || r != other.r || + g != other.g || a != other.a; + } + } PACK_STRUCT; // reset packing to the original value diff --git a/include/aiTypes.h b/include/aiTypes.h index 9ea2ea0e0..d201f21ad 100644 --- a/include/aiTypes.h +++ b/include/aiTypes.h @@ -45,10 +45,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -#if (defined _MSC_VER) -# include "Compiler/VisualStudio/stdint.h" -#endif // (defined _MSC_VER) +#include "aiDefines.h" +// include math helper classes and their implementations #include "aiVector3D.h" #include "aiMatrix3x3.h" #include "aiMatrix4x4.h" @@ -59,13 +58,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifdef __cplusplus # include extern "C" { -# define C_STRUCT -#else -# if (defined ASSIMP_DOXYGEN_BUILD) -# define C_STRUCT -# else -# define C_STRUCT struct -# endif #endif /** Maximum dimension for strings, ASSIMP strings are zero terminated */ @@ -145,14 +137,14 @@ struct aiString inline aiString() : length(0) { - // empty + data[0] = '\0'; } //! construction from a given std::string inline aiString(const aiString& rOther) : length(rOther.length) { - memcpy( data, rOther.data, rOther.length); + ::memcpy( data, rOther.data, rOther.length); this->data[this->length] = '\0'; } @@ -162,7 +154,7 @@ struct aiString if( pString.length() > MAXLEN - 1) return; length = pString.length(); - memcpy( data, pString.c_str(), length); + ::memcpy( data, pString.c_str(), length); data[length] = 0; } @@ -177,7 +169,7 @@ struct aiString bool operator!=(const aiString& other) const { return (this->length != other.length || - 0 != strcmp(this->data,other.data)); + 0 != ::strcmp(this->data,other.data)); } @@ -194,11 +186,10 @@ struct aiString // --------------------------------------------------------------------------- /** Standard return type for all library functions. * -* To check whether a function failed or not check against -* AI_SUCCESS. +* To check whether or not a function failed check against +* AI_SUCCESS. The error codes are mainly used by the C-API. */ // --------------------------------------------------------------------------- - enum aiReturn { //! Indicates that a function was successful diff --git a/include/assimp.h b/include/assimp.h index b54fd2d33..e26195817 100644 --- a/include/assimp.h +++ b/include/assimp.h @@ -71,7 +71,8 @@ struct aiString; * @return Pointer to the imported data or NULL if the import failed. */ // --------------------------------------------------------------------------- -const C_STRUCT aiScene* aiImportFile( const char* pFile, unsigned int pFlags); +ASSIMP_API const C_STRUCT aiScene* aiImportFile( const char* pFile, + unsigned int pFlags); // --------------------------------------------------------------------------- @@ -94,8 +95,8 @@ const C_STRUCT aiScene* aiImportFile( const char* pFile, unsigned int pFlags); * to this function. Therefore the C-API is thread-safe. */ // --------------------------------------------------------------------------- -const C_STRUCT aiScene* aiImportFileEx( const C_STRUCT aiFileIO* pFile); - +ASSIMP_API const C_STRUCT aiScene* aiImportFileEx( + const C_STRUCT aiFileIO* pFile); // --------------------------------------------------------------------------- @@ -105,7 +106,7 @@ const C_STRUCT aiScene* aiImportFileEx( const C_STRUCT aiFileIO* pFile); * @param pScene The imported data to release. NULL is a valid value. */ // --------------------------------------------------------------------------- -void aiReleaseImport( const C_STRUCT aiScene* pScene); +ASSIMP_API void aiReleaseImport( const C_STRUCT aiScene* pScene); // --------------------------------------------------------------------------- @@ -115,7 +116,7 @@ void aiReleaseImport( const C_STRUCT aiScene* pScene); * import process. NULL if there was no error. */ // --------------------------------------------------------------------------- -const char* aiGetErrorString(); +ASSIMP_API const char* aiGetErrorString(); // --------------------------------------------------------------------------- @@ -126,7 +127,7 @@ const char* aiGetErrorString(); * @return 1 if the extension is supported, 0 otherwise */ // --------------------------------------------------------------------------- -int aiIsExtensionSupported(const char* szExtension); +ASSIMP_API int aiIsExtensionSupported(const char* szExtension); // --------------------------------------------------------------------------- @@ -138,7 +139,7 @@ int aiIsExtensionSupported(const char* szExtension); * Format of the list: "*.3ds;*.obj;*.dae". NULL is not a valid parameter. */ // --------------------------------------------------------------------------- -void aiGetExtensionList(C_STRUCT aiString* szOut); +ASSIMP_API void aiGetExtensionList(C_STRUCT aiString* szOut); #ifdef __cplusplus } diff --git a/include/assimp.hpp b/include/assimp.hpp index 10f15572d..a4d3f0b57 100644 --- a/include/assimp.hpp +++ b/include/assimp.hpp @@ -50,6 +50,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include "aiDefines.h" + struct aiScene; namespace Assimp @@ -81,7 +83,7 @@ class IOSystem; * @note One Importer instance is not thread-safe. If you use multiple * threads for loading each thread should manage its own Importer instance. */ -class Importer +class ASSIMP_API Importer { // used internally friend class BaseProcess; @@ -101,6 +103,7 @@ public: */ ~Importer(); + // ------------------------------------------------------------------- /** Supplies a custom IO handler to the importer to use to open and * access files. If you need the importer to use custion IO logic to @@ -112,10 +115,31 @@ public: * afterwards. The previously assigned handler will be deleted. * * @param pIOHandler The IO handler to be used in all file accesses - * of the Importer. NULL resets it to the default handler. + * of the Importer. NULL resets it to the default handler. */ void SetIOHandler( IOSystem* pIOHandler); + + // ------------------------------------------------------------------- + /** Retrieves the IO handler that is currently set. + * You can use IsDefaultIOHandler() to check whether the returned + * interface is the default IO handler provided by ASSIMP. The default + * handler is active as long the application doesn't supply its own + * custom IO handler via SetIOHandler(). + * @return A valid IOSystem interface + */ + IOSystem* GetIOHandler(); + + + // ------------------------------------------------------------------- + /** Checks whether a default IO handler is active + * A default handler is active as long the application doesn't + * supply its own custom IO handler via SetIOHandler(). + * @return true by default + */ + bool IsDefaultIOHandler(); + + // ------------------------------------------------------------------- /** Reads the given file and returns its contents if successful. * @@ -127,18 +151,19 @@ public: * GetErrorString(). * @param pFile Path and filename to the file to be imported. * @param pFlags Optional post processing steps to be executed after - * a successful import. Provide a bitwise combination of the #aiPostProcessSteps - * flags. + * a successful import. Provide a bitwise combination of the + * #aiPostProcessSteps flags. * @return A pointer to the imported data, NULL if the import failed. */ const aiScene* ReadFile( const std::string& pFile, unsigned int pFlags); + // ------------------------------------------------------------------- /** Returns an error description of an error that occured in ReadFile(). * * Returns an empty string if no error occured. * @return A description of the last error, an empty string if no - * error occured. + * error occured. */ inline const std::string& GetErrorString() const { return mErrorString; } @@ -147,20 +172,20 @@ public: // ------------------------------------------------------------------- /** Returns whether a given file extension is supported by ASSIMP * - * @param szExtension Extension for which the function queries support. - * Must include a leading dot '.'. Example: ".3ds", ".md3" + * @param szExtension Extension to be checked. + * Must include a leading dot '.'. Example: ".3ds", ".md3" * @return true if the extension is supported, false otherwise */ bool IsExtensionSupported(const std::string& szExtension); // ------------------------------------------------------------------- - /** Get a full list of all file extensions generally supported by ASSIMP. + /** Get a full list of all file extensions supported by ASSIMP. * * If a file extension is contained in the list this does, of course, not * mean that ASSIMP is able to load all files with this extension. * @param szOut String to receive the extension list. - * Format of the list: "*.3ds;*.obj;*.dae". NULL is not a valid parameter. + * Format of the list: "*.3ds;*.obj;*.dae". */ void GetExtensionList(std::string& szOut); @@ -174,12 +199,15 @@ public: {return this->mScene;} private: + /** Empty copy constructor. */ Importer(const Importer &other); protected: + /** IO handler to use for all file accesses. */ IOSystem* mIOHandler; + bool mIsDefaultHandler; /** Format-specific importer worker objects - * one for each format we can read. */ diff --git a/port/jAssimp/assimp.iml b/port/jAssimp/assimp.iml index 7328c2227..c375dc905 100644 --- a/port/jAssimp/assimp.iml +++ b/port/jAssimp/assimp.iml @@ -7,7 +7,7 @@ - + diff --git a/code/jAssimp/BuildHeader.bat b/port/jAssimp/jni_bridge/BuildHeader.bat similarity index 100% rename from code/jAssimp/BuildHeader.bat rename to port/jAssimp/jni_bridge/BuildHeader.bat diff --git a/code/jAssimp/JNICalls.cpp b/port/jAssimp/jni_bridge/JNICalls.cpp similarity index 100% rename from code/jAssimp/JNICalls.cpp rename to port/jAssimp/jni_bridge/JNICalls.cpp diff --git a/code/jAssimp/JNIEnvironment.cpp b/port/jAssimp/jni_bridge/JNIEnvironment.cpp similarity index 100% rename from code/jAssimp/JNIEnvironment.cpp rename to port/jAssimp/jni_bridge/JNIEnvironment.cpp diff --git a/code/jAssimp/JNIEnvironment.h b/port/jAssimp/jni_bridge/JNIEnvironment.h similarity index 100% rename from code/jAssimp/JNIEnvironment.h rename to port/jAssimp/jni_bridge/JNIEnvironment.h diff --git a/code/jAssimp/JNILogger.cpp b/port/jAssimp/jni_bridge/JNILogger.cpp similarity index 100% rename from code/jAssimp/JNILogger.cpp rename to port/jAssimp/jni_bridge/JNILogger.cpp diff --git a/code/jAssimp/JNILogger.h b/port/jAssimp/jni_bridge/JNILogger.h similarity index 100% rename from code/jAssimp/JNILogger.h rename to port/jAssimp/jni_bridge/JNILogger.h diff --git a/code/jAssimp/assimp_Animation.h b/port/jAssimp/jni_bridge/assimp_Animation.h similarity index 100% rename from code/jAssimp/assimp_Animation.h rename to port/jAssimp/jni_bridge/assimp_Animation.h diff --git a/code/jAssimp/assimp_Importer.h b/port/jAssimp/jni_bridge/assimp_Importer.h similarity index 100% rename from code/jAssimp/assimp_Importer.h rename to port/jAssimp/jni_bridge/assimp_Importer.h diff --git a/code/jAssimp/assimp_Material.h b/port/jAssimp/jni_bridge/assimp_Material.h similarity index 100% rename from code/jAssimp/assimp_Material.h rename to port/jAssimp/jni_bridge/assimp_Material.h diff --git a/code/jAssimp/assimp_Mesh.h b/port/jAssimp/jni_bridge/assimp_Mesh.h similarity index 100% rename from code/jAssimp/assimp_Mesh.h rename to port/jAssimp/jni_bridge/assimp_Mesh.h diff --git a/code/jAssimp/assimp_Node.h b/port/jAssimp/jni_bridge/assimp_Node.h similarity index 100% rename from code/jAssimp/assimp_Node.h rename to port/jAssimp/jni_bridge/assimp_Node.h diff --git a/code/jAssimp/assimp_PostProcessStep.h b/port/jAssimp/jni_bridge/assimp_PostProcessStep.h similarity index 100% rename from code/jAssimp/assimp_PostProcessStep.h rename to port/jAssimp/jni_bridge/assimp_PostProcessStep.h diff --git a/code/jAssimp/assimp_Scene.h b/port/jAssimp/jni_bridge/assimp_Scene.h similarity index 100% rename from code/jAssimp/assimp_Scene.h rename to port/jAssimp/jni_bridge/assimp_Scene.h diff --git a/code/jAssimp/assimp_Texture.h b/port/jAssimp/jni_bridge/assimp_Texture.h similarity index 100% rename from code/jAssimp/assimp_Texture.h rename to port/jAssimp/jni_bridge/assimp_Texture.h diff --git a/port/jAssimp/src/assimp/Bone.java b/port/jAssimp/src/assimp/Bone.java new file mode 100644 index 000000000..1a3babbc1 --- /dev/null +++ b/port/jAssimp/src/assimp/Bone.java @@ -0,0 +1,11 @@ +package assimp; + +/** + * Created by IntelliJ IDEA. + * User: Alex + * Date: 08.06.2008 + * Time: 15:31:01 + * To change this template use File | Settings | File Templates. + */ +public class Bone { +} diff --git a/port/jAssimp/src/assimp/IOStream.java b/port/jAssimp/src/assimp/IOStream.java new file mode 100644 index 000000000..f5963153c --- /dev/null +++ b/port/jAssimp/src/assimp/IOStream.java @@ -0,0 +1,14 @@ +package assimp; + +/** + * Created by IntelliJ IDEA. + * User: Alex + * Date: 15.06.2008 + * Time: 19:51:45 + * To change this template use File | Settings | File Templates. + */ +public interface IOStream { + + + +} diff --git a/port/jAssimp/src/assimp/IOSystem.java b/port/jAssimp/src/assimp/IOSystem.java new file mode 100644 index 000000000..b9ac9fa97 --- /dev/null +++ b/port/jAssimp/src/assimp/IOSystem.java @@ -0,0 +1,73 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (ASSIMP) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2008, ASSIMP Development 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 Development 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. +--------------------------------------------------------------------------- +*/ + + +package assimp; + +import java.io.InputStream; +import java.io.FileNotFoundException; + + +/** + * + */ +public interface IOSystem { + + /** + * Called to check whether a file is existing + * + * @param file Filename + * @return true if the file is existing and accessible + */ + boolean Exists(String file); + + + /** + * Open a file and return an IOStream interface + * to access it. + * + * @param file File name of the file to be opened + * @return A valid IOStream interface + * @throws FileNotFoundException if the file can't be accessed + */ + IOStream Open(String file) throws FileNotFoundException; + +} diff --git a/port/jAssimp/src/assimp/Importer.java b/port/jAssimp/src/assimp/Importer.java index 7fad5cf4a..e519dbd2b 100644 --- a/port/jAssimp/src/assimp/Importer.java +++ b/port/jAssimp/src/assimp/Importer.java @@ -43,6 +43,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package assimp; import java.util.Vector; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.File; +import java.lang.ref.Reference; /** * Main class of jAssimp. The class is a simple wrapper for the native @@ -59,6 +64,65 @@ import java.util.Vector; */ public class Importer { + /** + * Default implementation of IOStream. + *
+ * This might become a performance bottleneck: The application + * needs to map the data read by this interface into a C-style + * array. For every single read operation! Therefore it is a good + * optimization to use the default C IOStream handler if no custom + * java handler was specified. Assuming that the Java Runtime is using + * the fXXX-family of functions internally too, the result should be + * the same. The problem is: we can't be sure we'll be able to open + * the file for reading from both Java and native code. Therefore we + * need to close our Java FileReader handle before + * control is given to native code. + */ + private class DefaultIOStream implements IOStream { + + private FileReader reader = null; + + /** + * Construction with a given path + * @param file Path to the file to be opened + * @throws FileNotFoundException If the file isn't accessible at all + */ + public DefaultIOStream(final String file) throws FileNotFoundException { + reader = new FileReader(file); + } + + } + + /** + * Default implementation of IOSystem. + */ + private class DefaultIOSystem implements IOSystem { + + /** + * Called to check whether a file is existing + * + * @param file Filename + * @return true if the file is existing and accessible + */ + public boolean Exists(String file) { + File f = new File(file); + return f.exists(); + } + + /** + * Open a file and return an IOStream interface + * to access it. + * + * @param file File name of the file to be opened + * @return A valid IOStream interface + * @throws FileNotFoundException if the file can't be accessed + */ + public IOStream Open(String file) throws FileNotFoundException { + return new DefaultIOStream(file); + } + } + + /** * List of all postprocess steps to apply to the model * Empty by default. @@ -83,17 +147,27 @@ public class Importer { */ private String path = null; + /** + * I/O system to be used + */ + private IOSystem ioSystem = null; + /** * Public constructor. Initialises the JNI bridge to the native * ASSIMP library. A native Assimp::Importer object is constructed and * initialized. The flag list is set to zero, a default I/O handler - * is constructed. + * is initialized. * + * @param iVersion Version of the JNI interface to be used. * @throws NativeError Thrown if the jassimp library could not be loaded * or if the entry point to the module wasn't found. if this exception * is not thrown, you can assume that jAssimp is fully available. */ - public Importer() throws NativeError { + public Importer(int iVersion) throws NativeError { + + // allocate a default I/O system + ioSystem = new DefaultIOSystem(); + /** try to load the jassimp library. First try to load the * x64 version, in case of failure the x86 version */ @@ -111,7 +185,7 @@ public class Importer { // now create the native Importer class and setup our internal // data structures outside the VM. try { - if (0xffffffffffffffffl == (this.m_iNativeHandle = _NativeInitContext())) { + if (0xffffffffffffffffl == (this.m_iNativeHandle = _NativeInitContext(iVersion))) { throw new NativeError( "Unable to initialize the native library context." + "The initialization routine has failed"); @@ -125,6 +199,32 @@ public class Importer { return; } + public Importer() throws NativeError { + this(0); + } + + /** + * Get the I/O system (IOSystem) to be used for loading + * assets. If no custom implementation was provided via setIoSystem() + * a default implementation will be used. Use isDefaultIoSystem() + * to check this. + * @return Always a valid IOSystem object, never null. + */ + public IOSystem getIoSystem() { + return ioSystem; + } + + + /** + * Checks whether a default IO system is currently being used to load + * assets. Using the default IO system has many performance benefits, + * but it is possible to provide a custom IO system (setIoSystem()). + * This allows applications to add support for archives like ZIP. + * @return true if a default IOSystem is active, + */ + public boolean isDefaultIoSystem() { + return ioSystem instanceof DefaultIOSystem; + } /** * Add a postprocess step to the list of steps to be executed on @@ -213,6 +313,8 @@ public class Importer { else if (step.equals(PostProcessStep.GenSmoothNormals)) flags |= 0x40; else if (step.equals(PostProcessStep.SplitLargeMeshes)) flags |= 0x80; else if (step.equals(PostProcessStep.PreTransformVertices)) flags |= 0x100; + else if (step.equals(PostProcessStep.LimitBoneWeights)) flags |= 0x200; + else if (step.equals(PostProcessStep.ValidateDataStructure)) flags |= 0x400; } // now load the mesh @@ -227,6 +329,7 @@ public class Importer { } catch (NativeError exc) { + // delete everything ... this.scene = null; this.path = null; throw exc; @@ -247,9 +350,8 @@ public class Importer { final Importer importer = (Importer) o; - if (m_iNativeHandle != importer.m_iNativeHandle) return false; + return m_iNativeHandle == importer.m_iNativeHandle; - return true; } /** @@ -268,7 +370,6 @@ public class Importer { if (0xffffffff == _NativeFreeContext(this.m_iNativeHandle)) { throw new NativeError("Unable to destroy the native library context"); } - return; } /** @@ -306,9 +407,10 @@ public class Importer { * library functions are available, too. If they are not, an * UnsatisfiedLinkError will be thrown during model loading. * + * @param version Version of the JNI bridge requested * @return Unique handle for the class or 0xffffffff if an error occured */ - private native int _NativeInitContext(); + private native int _NativeInitContext(int version); /** * JNI bridge call. For internal use only diff --git a/port/jAssimp/src/assimp/Material.java b/port/jAssimp/src/assimp/Material.java index 04538ee67..b9a6f1f93 100644 --- a/port/jAssimp/src/assimp/Material.java +++ b/port/jAssimp/src/assimp/Material.java @@ -43,6 +43,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package assimp; + /** * Class to wrap materials. Materials are represented in ASSIMP as a list of * key/value pairs, the key being a String and the value being @@ -52,6 +53,74 @@ package assimp; * @version 1.0 */ public class Material extends Mappable { + + + public static final String MATKEY_NAME = "$mat.name"; + + + /** + * Specifies the blend operation to be used to combine the Nth + * diffuse texture with the (N-1)th diffuse texture (or the diffuse + * base color for the first diffuse texture) + *
+ * Type: int (TextureOp)
+ * Default value: 0
+ * Requires: MATKEY_TEXTURE_DIFFUSE(0)
+ */ + public static String MATKEY_TEXOP_DIFFUSE(int N) { + return "$tex.op.diffuse[" + N + "]"; + } + + /** + * @see MATKEY_TEXOP_DIFFUSE() + */ + public static String MATKEY_TEXOP_SPECULAR(int N) { + return "$tex.op.specular[" + N + "]"; + } + + /** + * @see MATKEY_TEXOP_DIFFUSE() + */ + public static String MATKEY_TEXOP_AMBIENT(int N) { + return "$tex.op.ambient[" + N + "]"; + } + + /** + * @see MATKEY_TEXOP_DIFFUSE() + */ + public static String MATKEY_TEXOP_EMISSIVE(int N) { + return "$tex.op.emissive[" + N + "]"; + } + + /** + * @see MATKEY_TEXOP_DIFFUSE() + */ + public static String MATKEY_TEXOP_NORMALS(int N) { + return "$tex.op.normals[" + N + "]"; + } + + /** + * @see MATKEY_TEXOP_DIFFUSE() + */ + public static String MATKEY_TEXOP_HEIGHT(int N) { + return "$tex.op.height[" + N + "]"; + } + + /** + * @see MATKEY_TEXOP_DIFFUSE() + */ + public static String MATKEY_TEXOP_SHININESS(int N) { + return "$tex.op.shininess[" + N + "]"; + } + + /** + * @see MATKEY_TEXOP_DIFFUSE() + */ + public static String MATKEY_TEXOP_OPACITY(int N) { + return "$tex.op.opacity[" + N + "]"; + } + + /** * Construction from a given parent object and array index * diff --git a/port/jAssimp/src/assimp/Mesh.java b/port/jAssimp/src/assimp/Mesh.java index ecac0353a..18be10065 100644 --- a/port/jAssimp/src/assimp/Mesh.java +++ b/port/jAssimp/src/assimp/Mesh.java @@ -183,29 +183,9 @@ public class Mesh extends Mappable { assert (parent instanceof Scene); Scene sc = (Scene) parent; - if (0xffffffff == (this.m_iPresentFlags = this._NativeGetPresenceFlags( - sc.getImporter().getContext(), this.getArrayIndex()))) { - throw new NativeError("Unable to obtain a list of vertex presence flags"); - } - if (0xffffffff == (this.m_iNumVertices = this._NativeGetNumVertices( - sc.getImporter().getContext(), this.getArrayIndex()))) { - throw new NativeError("Unable to obtain the number of vertices in the mesh"); - } - if (0xffffffff == (this.m_iNumFaces = this._NativeGetNumFaces( - sc.getImporter().getContext(), this.getArrayIndex()))) { - throw new NativeError("Unable to obtain the number of faces in the mesh"); - } - if (0xffffffff == (this.m_iNumBones = this._NativeGetNumBones( - sc.getImporter().getContext(), this.getArrayIndex()))) { - throw new NativeError("Unable to obtain the number of bones in the mesh"); - } - if (0xffffffff == (this.m_iMaterialIndex = this._NativeGetMaterialIndex( - sc.getImporter().getContext(), this.getArrayIndex()))) { - throw new NativeError("Unable to obtain the material index of the mesh"); - } - if (0xffffffff == this._NativeGetNumUVComponents( - sc.getImporter().getContext(), this.getArrayIndex(), this.m_aiNumUVComponents)) { - throw new NativeError("Unable to obtain the number of UV components"); + if (0xffffffff == this._NativeInitMembers( + sc.getImporter().getContext(), this.getArrayIndex())) { + throw new NativeError("Unable to intiailise class members via JNI"); } } @@ -821,18 +801,12 @@ public class Mesh extends Mappable { /** * JNI bridge function - for internal use only - * Retrieve the number of vertices in the mesh + * Initialise class members * * @param context Current importer context (imp.hashCode) * @return Number of vertices in the mesh */ - private native int _NativeGetNumVertices(long context, long index); - - private native int _NativeGetNumFaces(long context, long index); - - private native int _NativeGetNumBones(long context, long index); - - private native int _NativeGetMaterialIndex(long context, long index); + private native int _NativeInitMembers(long context, long index); /** * JNI bridge function - for internal use only diff --git a/port/jAssimp/src/assimp/Node.java b/port/jAssimp/src/assimp/Node.java index cdd79b8f8..bfbb5e44b 100644 --- a/port/jAssimp/src/assimp/Node.java +++ b/port/jAssimp/src/assimp/Node.java @@ -1,11 +1,223 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (ASSIMP) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2008, ASSIMP Development 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 Development 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. +--------------------------------------------------------------------------- +*/ + + package assimp; +import java.util.Vector; + + /** - * Created by IntelliJ IDEA. - * User: Alex - * Date: 22.05.2008 - * Time: 13:05:12 - * To change this template use File | Settings | File Templates. + * A node in the imported hierarchy. + *

+ * Each node has name, a parent node (except for the root node), + * a transformation relative to its parent and possibly several child nodes. + * Simple file formats don't support hierarchical structures, for these formats + * the imported scene does consist of only a single root node with no childs. + * + * @author Aramis (Alexander Gessler) + * @version 1.0 */ public class Node { + + + /** + * List of all meshes of this node. + * The array contains indices into the Scene's mesh list + */ + private int[] meshIndices = null; + + + /** + * Local transformation matrix of the node + * Stored in row-major order. + */ + private float[] nodeTransform = null; + + + /** + * Name of the node + * The name might be empty (length of zero) but all nodes which + * need to be accessed afterwards by bones or anims are usually named. + */ + private String name = ""; + + + /** + * List of all child nodes + * May be empty + */ + private Vector children = null; + private int numChildren = 0; // temporary + + /** + * Parent scene + */ + private Scene parentScene = null; + + + /** + * Parent node or null if we're the root node of the scene + */ + private Node parent = null; + + /** + * Constructs a new node and initializes it + * @param parentScene Parent scene object + * @param parentNode Parent node or null for root nodes + * @param index Unique index of the node + */ + public Node(Scene parentScene, Node parentNode, int index) { + + this.parentScene = parentScene; + this.parent = parentNode; + + // Initialize JNI class members, including numChildren + this._NativeInitMembers(parentScene.getImporter().getContext(),index); + + // get all children of the node + for (int i = 0; i < numChildren;++i) { + this.children.add(new Node(parentScene, this, ++index)); + } + } + + + /** + * Get a list of all meshes of this node + * + * @return Array containing indices into the Scene's mesh list + */ + int[] getMeshes() { + return meshIndices; + } + + + /** + * Get the local transformation matrix of the node in row-major + * order: + * + * a1 a2 a3 a4 (the translational part of the matrix is stored + * b1 b2 b3 b4 in (a4|b4|c4)) + * c1 c2 c3 c4 + * d1 d2 d3 d4 + * + * + * @return Row-major transformation matrix + */ + float[] getTransformRowMajor() { + return nodeTransform; + } + + + /** + * Get the local transformation matrix of the node in column-major + * order: + * + * a1 b1 c1 d1 (the translational part of the matrix is stored + * a2 b2 c2 d2 in (a4|b4|c4)) + * a3 b3 c3 d3 + * a4 b4 c4 d4 + * + * + * @return Column-major transformation matrix + */ + float[] getTransformColumnMajor() { + + float[] transform = new float[16]; + transform[0] = nodeTransform[0]; + transform[1] = nodeTransform[4]; + transform[2] = nodeTransform[8]; + transform[3] = nodeTransform[12]; + transform[4] = nodeTransform[1]; + transform[5] = nodeTransform[5]; + transform[6] = nodeTransform[9]; + transform[7] = nodeTransform[13]; + transform[8] = nodeTransform[2]; + transform[9] = nodeTransform[6]; + transform[10] = nodeTransform[10]; + transform[11] = nodeTransform[14]; + transform[12] = nodeTransform[3]; + transform[13] = nodeTransform[7]; + transform[15] = nodeTransform[11]; + transform[16] = nodeTransform[15]; + return transform; + } + + + private native int _NativeInitMembers(long context, int nodeIndex); + + + /** + * Get the name of the node. + * The name might be empty (length of zero) but all nodes which + * need to be accessed afterwards by bones or anims are usually named. + * + * @return Node name + */ + public String getName() { + return name; + } + + + /** + * Get the list of all child nodes of *this* node + * @return List of children. May be empty. + */ + public Vector getChildren() { + return children; + } + + /** + * Get the parent node of the node + * @return Parent node + */ + public Node getParent() { + return parent; + } + + /** + * Get the parent scene of the node + * @return Never null + */ + public Scene getParentScene() { + return parentScene; + } } diff --git a/port/jAssimp/src/assimp/PostProcessStep.java b/port/jAssimp/src/assimp/PostProcessStep.java index b1983d1de..a8e1bffa1 100644 --- a/port/jAssimp/src/assimp/PostProcessStep.java +++ b/port/jAssimp/src/assimp/PostProcessStep.java @@ -45,7 +45,7 @@ package assimp; /** * Enumeration class that defines postprocess steps that can be executed on a model * after it has been loaded. All PPSteps are implemented in C++, so their performance - * is awesome. Most steps are O(n * log(n)). + * is awesome. Most steps are O(n * log(n)). ;-) * * @author Aramis (Alexander Gessler) * @version 1.0 @@ -62,8 +62,15 @@ public class PostProcessStep { */ public static final int DEFAULT_TRIANGLE_SPLIT_LIMIT = 1000000; + /** + * Default bone weight limit for the LimitBoneWeight process + */ + public static final int DEFAULT_BONE_WEIGHT_LIMIT = 4; + + private static int s_iVertexSplitLimit = DEFAULT_VERTEX_SPLIT_LIMIT; private static int s_iTriangleSplitLimit = DEFAULT_TRIANGLE_SPLIT_LIMIT; + private static int s_iBoneWeightLimit = DEFAULT_BONE_WEIGHT_LIMIT; /** * Identifies and joins identical vertex data sets within all imported @@ -154,6 +161,31 @@ public class PostProcessStep { public static final PostProcessStep PreTransformVertices = new PostProcessStep("PreTransformVertices"); + /** + * Limits the number of bones simultaneously affecting a single vertex + * to a maximum value. If any vertex is affected by more than that number + * of bones, the least important vertex weights are removed and the remaining + * vertex weights are renormalized so that the weights still sum up to 1. + * The default bone weight limit is 4 (DEFAULT_BONE_WEIGHT_LIMIT). + * However, you can use setBoneWeightLimit() to supply your own limit. + * If you intend to perform the skinning in hardware, this post processing step + * might be of interest for you. + */ + public static final PostProcessStep LimitBoneWeights = + new PostProcessStep("LimitBoneWeights"); + + + /** + * Validates the aiScene data structure before it is returned. + * This makes sure that all indices are valid, all animations and + * bones are linked correctly, all material are correct and so on ... + * This is primarily intended for our internal debugging stuff, + * however, it could be of interest for applications like editors + * where stability is more important than loading performance. + */ + public static final PostProcessStep ValidateDataStructure = + new PostProcessStep("ValidateDataStructure"); + /** * Set the vertex split limit for the "SplitLargeMeshes" process @@ -191,6 +223,22 @@ public class PostProcessStep { return limit; } + /** + * Set the bone weight limit for the "LimitBoneWeights" process + * If a mesh exceeds this limit it will be splitted + * + * @param limit new bone weight limit. Pass 0xffffffff to disable it. + * @return Old bone weight limit + */ + public static synchronized int setSetBoneWeightLimit(int limit) { + if (s_iBoneWeightLimit != limit) { + // send to the JNI bridge ... + s_iBoneWeightLimit = limit; + _NativeSetBoneWeightLimit(limit); + } + return limit; + } + /** * JNI bridge call. For internal use only * @@ -205,6 +253,13 @@ public class PostProcessStep { */ private native static void _NativeSetTriangleSplitLimit(int limit); + /** + * JNI bridge call. For internal use only + * + * @param limit New bone weight limit + */ + private native static void _NativeSetBoneWeightLimit(int limit); + private final String myName; // for debug only diff --git a/port/jAssimp/src/assimp/ShadingMode.java b/port/jAssimp/src/assimp/ShadingMode.java new file mode 100644 index 000000000..cde2e282e --- /dev/null +++ b/port/jAssimp/src/assimp/ShadingMode.java @@ -0,0 +1,116 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (ASSIMP) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2008, ASSIMP Development 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 Development 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. +--------------------------------------------------------------------------- +*/ + +package assimp; + +/** + * Defines all shading models supported by the library + *

+ * NOTE: The list of shading modes has been taken from Blender3D. + * See Blender3D documentation for more information. The API does + * not distinguish between "specular" and "diffuse" shaders (thus the + * specular term for diffuse shading models like Oren-Nayar remains + * undefined) + */ +public class ShadingMode { + + /** + * Flat shading. Shading is done on per-face base, + * diffuse only. + */ + int Flat = 0x1; + + /** + * Diffuse gouraud shading. Shading on per-vertex base + */ + int Gouraud = 0x2; + + /** + * Diffuse/Specular Phong-Shading + *

+ * Shading is applied on per-pixel base. This is the + * slowest algorithm, but generates the best results. + */ + int Phong = 0x3; + + /** + * Diffuse/Specular Phong-Blinn-Shading + *

+ * Shading is applied on per-pixel base. This is a little + * bit faster than phong and in some cases even + * more realistic + */ + int Blinn = 0x4; + + /** + * Toon-Shading per pixel + *

+ * Shading is applied on per-pixel base. The output looks + * like a comic. Often combined with edge detection. + */ + int Toon = 0x5; + + /** + * OrenNayar-Shading per pixel + *

+ * Extension to standard lambertian shading, taking the + * roughness of the material into account + */ + int OrenNayar = 0x6; + + /** + * Minnaert-Shading per pixel + *

+ * Extension to standard lambertian shading, taking the + * "darkness" of the material into account + */ + int Minnaert = 0x7; + + /** + * CookTorrance-Shading per pixel + */ + int CookTorrance = 0x8; + + /** + * No shading at all + */ + int NoShading = 0x8; +} diff --git a/port/jAssimp/src/assimp/TextureMapMode.java b/port/jAssimp/src/assimp/TextureMapMode.java new file mode 100644 index 000000000..91a5a1166 --- /dev/null +++ b/port/jAssimp/src/assimp/TextureMapMode.java @@ -0,0 +1,52 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (ASSIMP) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2008, ASSIMP Development 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 Development 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. +--------------------------------------------------------------------------- +*/ + +package assimp; + +/** + * Created by IntelliJ IDEA. + * User: Alex + * Date: 08.06.2008 + * Time: 17:27:11 + * To change this template use File | Settings | File Templates. + */ +public class TextureMapMode { +} diff --git a/port/jAssimp/src/assimp/TextureOp.java b/port/jAssimp/src/assimp/TextureOp.java new file mode 100644 index 000000000..f08df043c --- /dev/null +++ b/port/jAssimp/src/assimp/TextureOp.java @@ -0,0 +1,71 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (ASSIMP) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2008, ASSIMP Development 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 Development 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. +--------------------------------------------------------------------------- +*/ + +package assimp; + +public class TextureOp { + + private TextureOp() {} + + /** T = T1 * T2 + */ + public static int Add = 0x0; + + /** T = T1 * T2 + */ + public static int Multiply = 0x1; + + /** T = T1 - T2 + */ + public static int Subtract = 0x2; + + /** T = T1 / T2 + */ + public static int Divide = 0x3; + + /** T = (T1 + T2) - (T1 * T2) + */ + public static int SmoothAdd = 0x4; + + /** T = T1 + (T2-0.5) + */ + public static int SignedAdd = 0x5; +} diff --git a/test/HMP/planar.hmp b/test/HMP/planar.hmp new file mode 100644 index 000000000..00804d81e Binary files /dev/null and b/test/HMP/planar.hmp differ diff --git a/test/HMP/terrain.hmp b/test/HMP/terrain.hmp new file mode 100644 index 000000000..7002b0ea7 Binary files /dev/null and b/test/HMP/terrain.hmp differ diff --git a/test/HMP/terrain_withtexture.hmp b/test/HMP/terrain_withtexture.hmp new file mode 100644 index 000000000..b78b32917 Binary files /dev/null and b/test/HMP/terrain_withtexture.hmp differ diff --git a/test/SMD/triangle.smd b/test/SMD/triangle.smd new file mode 100644 index 000000000..540e71a8c --- /dev/null +++ b/test/SMD/triangle.smd @@ -0,0 +1,15 @@ +version 1 + +nodes + 0 "TriangleTest0" -1 +end +skeleton +time 0 + 0 0.0 0.0 0.0 0.0 0.0 0.0 +end +triangles +none.quak +0 0.0 0.0 0.0 1.0 1.0 1.0 0.0 0.0 +0 1.0 0.0 0.0 1.0 1.0 1.0 1.0 0.0 +0 1.0 1.0 0.0 1.0 1.0 1.0 1.0 1.0 +end \ No newline at end of file diff --git a/tools/assimp_view/Material.cpp b/tools/assimp_view/Material.cpp index 50ebbc03b..a41b8606d 100644 --- a/tools/assimp_view/Material.cpp +++ b/tools/assimp_view/Material.cpp @@ -137,7 +137,6 @@ int CMaterialManager::SetDefaultTexture(IDirect3DTexture9** p_ppiOut) bool CMaterialManager::TryLongerPath(char* szTemp,aiString* p_szString) { char szTempB[MAX_PATH]; - strcpy(szTempB,szTemp); // go to the beginning of the file name @@ -146,7 +145,9 @@ bool CMaterialManager::TryLongerPath(char* szTemp,aiString* p_szString) char* szFile2 = szTemp + (szFile - szTempB)+1; szFile++; - char* szExt = strrchr(szFile,'.')+1; + char* szExt = strrchr(szFile,'.'); + if (!szExt)return false; + szExt++; *szFile = 0; strcat(szTempB,"*.*"); @@ -163,9 +164,10 @@ bool CMaterialManager::TryLongerPath(char* szTemp,aiString* p_szString) { if (!(strcmp(info.cFileName, ".") == 0 || strcmp(info.cFileName, "..") == 0)) { - char* szExtFound = strrchr(info.cFileName, '.')+1; - if ((char*)0x1 != szExtFound) + char* szExtFound = strrchr(info.cFileName, '.'); + if (szExtFound) { + ++szExtFound; if (0 == ASSIMP_stricmp(szExtFound,szExt)) { const unsigned int iSizeFound = (const unsigned int) ( diff --git a/workspaces/jidea5.1/jAssimp.ipr b/workspaces/jidea5.1/jAssimp.ipr index 20eae075e..ded5e0d28 100644 --- a/workspaces/jidea5.1/jAssimp.ipr +++ b/workspaces/jidea5.1/jAssimp.ipr @@ -49,7 +49,7 @@