From b422d4e30329e33082f3cbe0ac670122980aaf93 Mon Sep 17 00:00:00 2001 From: aramis_acg Date: Sun, 22 Jun 2008 10:09:26 +0000 Subject: [PATCH] Finished loading of MD2, MD3, MDL2, MDL3, MDL4, MDL5, MDL7, MDL. First WIP version of the SMD loader. Additionals checks added to the validation step. git-svn-id: https://assimp.svn.sourceforge.net/svnroot/assimp/trunk@60 67173fc5-114c-0410-ac8e-9d2fd5bffc1f --- code/3DSConverter.cpp | 49 +- code/3DSHelper.h | 56 +- code/3DSLoader.cpp | 50 +- code/ASELoader.cpp | 8 +- code/BaseImporter.h | 12 +- code/DefaultIOStream.cpp | 16 +- code/DefaultIOSystem.cpp | 6 +- code/HMPLoader.cpp | 449 +++ code/HMPLoader.h | 208 ++ code/Importer.cpp | 100 +- code/LimitBoneWeightsProcess.cpp | 64 +- code/LimitBoneWeightsProcess.h | 56 +- code/MD2FileData.h | 50 +- code/MD2Loader.cpp | 241 +- code/MD2Loader.h | 9 + code/MD3FileData.h | 46 +- code/MD3Loader.cpp | 129 +- code/MD3Loader.h | 10 + code/MD4Loader.cpp | 2 +- code/MDLFileData.h | 439 ++- code/MDLLoader.cpp | 2520 +++++++++-------- code/MDLLoader.h | 387 ++- code/MDLMaterialLoader.cpp | 774 +++++ code/MaterialSystem.cpp | 1 + code/MaterialSystem.h | 3 +- code/ParsingUtils.h | 115 + code/PlyLoader.cpp | 7 + code/PlyParser.h | 44 +- code/SMDLoader.cpp | 1070 +++++++ code/SMDLoader.h | 408 +++ code/SplitLargeMeshes.h | 12 +- code/TextureTransform.cpp | 107 +- code/ValidateDataStructure.cpp | 63 +- code/ValidateDataStructure.h | 7 + code/XFileImporter.cpp | 216 +- code/XFileParser.cpp | 23 +- code/extra/MakeVerboseFormat.cpp | 9 +- code/qnan.h | 42 + doc/dox.h | 50 +- include/IOStream.h | 2 +- include/IOSystem.h | 4 +- include/LogStream.h | 2 +- include/Logger.h | 43 +- include/aiDefines.h | 97 + include/aiMaterial.h | 130 +- include/aiMaterial.inl | 61 +- include/aiMatrix3x3.h | 5 +- include/aiMatrix3x3.inl | 1 + include/aiMatrix4x4.h | 32 +- include/aiMatrix4x4.inl | 93 + include/aiPostProcess.h | 21 +- include/aiQuaternion.h | 24 + include/aiScene.h | 17 +- include/aiTexture.h | 15 + include/aiTypes.h | 25 +- include/assimp.h | 15 +- include/assimp.hpp | 46 +- port/jAssimp/assimp.iml | 2 +- .../jAssimp/jni_bridge}/BuildHeader.bat | 0 .../jAssimp/jni_bridge}/JNICalls.cpp | 0 .../jAssimp/jni_bridge}/JNIEnvironment.cpp | 0 .../jAssimp/jni_bridge}/JNIEnvironment.h | 0 .../jAssimp/jni_bridge}/JNILogger.cpp | 0 .../jAssimp/jni_bridge}/JNILogger.h | 0 .../jAssimp/jni_bridge}/assimp_Animation.h | 0 .../jAssimp/jni_bridge}/assimp_Importer.h | 0 .../jAssimp/jni_bridge}/assimp_Material.h | 0 .../jAssimp/jni_bridge}/assimp_Mesh.h | 0 .../jAssimp/jni_bridge}/assimp_Node.h | 0 .../jni_bridge}/assimp_PostProcessStep.h | 0 .../jAssimp/jni_bridge}/assimp_Scene.h | 0 .../jAssimp/jni_bridge}/assimp_Texture.h | 0 port/jAssimp/src/assimp/Bone.java | 11 + port/jAssimp/src/assimp/IOStream.java | 14 + port/jAssimp/src/assimp/IOSystem.java | 73 + port/jAssimp/src/assimp/Importer.java | 116 +- port/jAssimp/src/assimp/Material.java | 69 + port/jAssimp/src/assimp/Mesh.java | 36 +- port/jAssimp/src/assimp/Node.java | 222 +- port/jAssimp/src/assimp/PostProcessStep.java | 57 +- port/jAssimp/src/assimp/ShadingMode.java | 116 + port/jAssimp/src/assimp/TextureMapMode.java | 52 + port/jAssimp/src/assimp/TextureOp.java | 71 + test/HMP/planar.hmp | Bin 0 -> 4476 bytes test/HMP/terrain.hmp | Bin 0 -> 4476 bytes test/HMP/terrain_withtexture.hmp | Bin 0 -> 605688 bytes test/SMD/triangle.smd | 15 + tools/assimp_view/Material.cpp | 10 +- workspaces/jidea5.1/jAssimp.ipr | 4 +- workspaces/vc8/assimp.vcproj | 95 +- 90 files changed, 7349 insertions(+), 2105 deletions(-) create mode 100644 code/HMPLoader.cpp create mode 100644 code/HMPLoader.h create mode 100644 code/MDLMaterialLoader.cpp create mode 100644 code/ParsingUtils.h create mode 100644 code/SMDLoader.cpp create mode 100644 code/SMDLoader.h create mode 100644 code/qnan.h create mode 100644 include/aiDefines.h rename {code/jAssimp => port/jAssimp/jni_bridge}/BuildHeader.bat (100%) rename {code/jAssimp => port/jAssimp/jni_bridge}/JNICalls.cpp (100%) rename {code/jAssimp => port/jAssimp/jni_bridge}/JNIEnvironment.cpp (100%) rename {code/jAssimp => port/jAssimp/jni_bridge}/JNIEnvironment.h (100%) rename {code/jAssimp => port/jAssimp/jni_bridge}/JNILogger.cpp (100%) rename {code/jAssimp => port/jAssimp/jni_bridge}/JNILogger.h (100%) rename {code/jAssimp => port/jAssimp/jni_bridge}/assimp_Animation.h (100%) rename {code/jAssimp => port/jAssimp/jni_bridge}/assimp_Importer.h (100%) rename {code/jAssimp => port/jAssimp/jni_bridge}/assimp_Material.h (100%) rename {code/jAssimp => port/jAssimp/jni_bridge}/assimp_Mesh.h (100%) rename {code/jAssimp => port/jAssimp/jni_bridge}/assimp_Node.h (100%) rename {code/jAssimp => port/jAssimp/jni_bridge}/assimp_PostProcessStep.h (100%) rename {code/jAssimp => port/jAssimp/jni_bridge}/assimp_Scene.h (100%) rename {code/jAssimp => port/jAssimp/jni_bridge}/assimp_Texture.h (100%) create mode 100644 port/jAssimp/src/assimp/Bone.java create mode 100644 port/jAssimp/src/assimp/IOStream.java create mode 100644 port/jAssimp/src/assimp/IOSystem.java create mode 100644 port/jAssimp/src/assimp/ShadingMode.java create mode 100644 port/jAssimp/src/assimp/TextureMapMode.java create mode 100644 port/jAssimp/src/assimp/TextureOp.java create mode 100644 test/HMP/planar.hmp create mode 100644 test/HMP/terrain.hmp create mode 100644 test/HMP/terrain_withtexture.hmp create mode 100644 test/SMD/triangle.smd 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 0000000000000000000000000000000000000000..00804d81ef7e9ac5526eb20ab7c30436333c628a GIT binary patch literal 4476 zcmeIuu?>JQ3`9`}Wsqz^K?77YkrdjvOF$2raPT?!9sCIU2{c3lC!d4} zSw-VZj5LIoGywywVk0Ty0kJf^#OO`4J`ZKuZFh!tg>936XLq_g|KI-Sndx4ScDD)o z42p)UtU1vgrnB*^j!?VvYlO~8Rh>JhyI&nu*@HvQPC7oEJGX_M{p5P&0iX~EqmVFC zw@E^ykK{8q2sk^d@LQz5xLTQv;(85p7kRUE4C%CyV*hup`%OzcCB@yEp9hS)uGcfM ze`~9e3I_EY?9UwOv~wKt7HYA-7lvREkQw&kP-mg`XD+V{f|$$UaA$F_4CQdRv+&MP zvy`phz3Nrwp{votEW9x^Zktww>X+-4#p_AOvR923X5o$D##hr~j!cH`bFip5WEQ#_ z9XSn{tmXAVW1S;K+=@p4mum8Bh`pJ?xsI4cX5xi|y_tdQXGb@wCBI^AH#)ANYh?yb z20qqtY_!n*y@27cd(GxEDD=oNNj68T%D~CO9B=*tuG|8wuLoQR2r<$l#J%HyEgOYv zHq`?-bE$l9$)(Fcax4U5p`mCj1OdR%YrvsNA-+8Xyf%^d;`$I>fLl%ZJ~@7=_TmTA zVo5($J1k)W~W~jbjo$%(*8SN(n$*|dM zTD7&NHU6{ozFIsl2a&;4*yuZU*R;BK_mEQiYB9rpGqCE6zJYb4FZRr|nm6^eTI?&& zIfLkXx-b6!`aVGS8Z-|N++(bxX)QehT%>(uZB1Pxy>qsGcGR{-+&^uN+4kfl;0woI zEHgO2Y1?iKu06*2ab6|hw@Y|ognL$3gY}==8o8`AZkFUz0l5?QTpi*<7 z%YgkWRfp5>L?8eF literal 0 HcmV?d00001 diff --git a/test/HMP/terrain_withtexture.hmp b/test/HMP/terrain_withtexture.hmp new file mode 100644 index 0000000000000000000000000000000000000000..b78b32917e9c483665ea637cea0c9ac121301b0e GIT binary patch literal 605688 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

7>s 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

c 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 z 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

  • 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 - - + diff --git a/workspaces/vc8/assimp.vcproj b/workspaces/vc8/assimp.vcproj index fb9d33b74..1c2e3bad1 100644 --- a/workspaces/vc8/assimp.vcproj +++ b/workspaces/vc8/assimp.vcproj @@ -48,7 +48,7 @@ RuntimeLibrary="1" EnableFunctionLevelLinking="true" WarningLevel="3" - Detect64BitPortabilityProblems="true" + Detect64BitPortabilityProblems="false" DebugInformationFormat="4" /> + + @@ -1021,10 +1030,18 @@ RelativePath="..\..\code\MaterialSystem.h" > + + + + @@ -1217,39 +1234,39 @@ Name="jAssimp" > @@ -1261,6 +1278,22 @@ > + + + + + + + + + + @@ -1481,6 +1518,22 @@ > + + + + + + + +