diff options
Diffstat (limited to 'src/3rdparty/assimp/code/MD3Loader.cpp')
-rw-r--r-- | src/3rdparty/assimp/code/MD3Loader.cpp | 1801 |
1 files changed, 919 insertions, 882 deletions
diff --git a/src/3rdparty/assimp/code/MD3Loader.cpp b/src/3rdparty/assimp/code/MD3Loader.cpp index 74c585aac..8294da489 100644 --- a/src/3rdparty/assimp/code/MD3Loader.cpp +++ b/src/3rdparty/assimp/code/MD3Loader.cpp @@ -3,12 +3,12 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2012, assimp team +Copyright (c) 2006-2016, assimp team All rights reserved. -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the following +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 @@ -25,1033 +25,1070 @@ conditions are met: derived from this software without specific prior written permission of the assimp team. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +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 +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 +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 +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 MD3Loader.cpp - * @brief Implementation of the MD3 importer class - * - * Sources: + * @brief Implementation of the MD3 importer class + * + * Sources: * http://www.gamers.org/dEngine/quake3/UQ3S * http://linux.ucla.edu/~phaethon/q3/formats/md3format.html * http://www.heppler.com/shader/shader/ */ -#include "AssimpPCH.h" + #ifndef ASSIMP_BUILD_NO_MD3_IMPORTER #include "MD3Loader.h" -#include "ByteSwap.h" #include "SceneCombiner.h" #include "GenericProperty.h" #include "RemoveComments.h" #include "ParsingUtils.h" #include "Importer.h" +#include <assimp/DefaultLogger.hpp> +#include <memory> +#include <assimp/IOSystem.hpp> +#include <assimp/material.h> +#include <assimp/scene.h> +#include <cctype> + + using namespace Assimp; static const aiImporterDesc desc = { - "Quake III Mesh Importer", - "", - "", - "", - aiImporterFlags_SupportBinaryFlavour, - 0, - 0, - 0, - 0, - "md3" + "Quake III Mesh Importer", + "", + "", + "", + aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "md3" }; // ------------------------------------------------------------------------------------------------ // Convert a Q3 shader blend function to the appropriate enum value Q3Shader::BlendFunc StringToBlendFunc(const std::string& m) { - if (m == "GL_ONE") { - return Q3Shader::BLEND_GL_ONE; - } - if (m == "GL_ZERO") { - return Q3Shader::BLEND_GL_ZERO; - } - if (m == "GL_SRC_ALPHA") { - return Q3Shader::BLEND_GL_SRC_ALPHA; - } - if (m == "GL_ONE_MINUS_SRC_ALPHA") { - return Q3Shader::BLEND_GL_ONE_MINUS_SRC_ALPHA; - } - if (m == "GL_ONE_MINUS_DST_COLOR") { - return Q3Shader::BLEND_GL_ONE_MINUS_DST_COLOR; - } - DefaultLogger::get()->error("Q3Shader: Unknown blend function: " + m); - return Q3Shader::BLEND_NONE; + if (m == "GL_ONE") { + return Q3Shader::BLEND_GL_ONE; + } + if (m == "GL_ZERO") { + return Q3Shader::BLEND_GL_ZERO; + } + if (m == "GL_SRC_ALPHA") { + return Q3Shader::BLEND_GL_SRC_ALPHA; + } + if (m == "GL_ONE_MINUS_SRC_ALPHA") { + return Q3Shader::BLEND_GL_ONE_MINUS_SRC_ALPHA; + } + if (m == "GL_ONE_MINUS_DST_COLOR") { + return Q3Shader::BLEND_GL_ONE_MINUS_DST_COLOR; + } + DefaultLogger::get()->error("Q3Shader: Unknown blend function: " + m); + return Q3Shader::BLEND_NONE; } // ------------------------------------------------------------------------------------------------ // Load a Quake 3 shader bool Q3Shader::LoadShader(ShaderData& fill, const std::string& pFile,IOSystem* io) { - boost::scoped_ptr<IOStream> file( io->Open( pFile, "rt")); - if (!file.get()) - return false; // if we can't access the file, don't worry and return - - DefaultLogger::get()->info("Loading Quake3 shader file " + pFile); - - // read file in memory - const size_t s = file->FileSize(); - std::vector<char> _buff(s+1); - file->Read(&_buff[0],s,1); - _buff[s] = 0; - - // remove comments from it (C++ style) - CommentRemover::RemoveLineComments("//",&_buff[0]); - const char* buff = &_buff[0]; - - Q3Shader::ShaderDataBlock* curData = NULL; - Q3Shader::ShaderMapBlock* curMap = NULL; - - // read line per line - for (;SkipSpacesAndLineEnd(&buff);SkipLine(&buff)) { - - if (*buff == '{') { - ++buff; - - // append to last section, if any - if (!curData) { - DefaultLogger::get()->error("Q3Shader: Unexpected shader section token \'{\'"); - return true; // still no failure, the file is there - } - - // read this data section - for (;SkipSpacesAndLineEnd(&buff);SkipLine(&buff)) { - if (*buff == '{') { - ++buff; - // add new map section - curData->maps.push_back(Q3Shader::ShaderMapBlock()); - curMap = &curData->maps.back(); - - for (;SkipSpacesAndLineEnd(&buff);SkipLine(&buff)) { - // 'map' - Specifies texture file name - if (TokenMatchI(buff,"map",3) || TokenMatchI(buff,"clampmap",8)) { - curMap->name = GetNextToken(buff); - } - // 'blendfunc' - Alpha blending mode - else if (TokenMatchI(buff,"blendfunc",9)) { - const std::string blend_src = GetNextToken(buff); - if (blend_src == "add") { - curMap->blend_src = Q3Shader::BLEND_GL_ONE; - curMap->blend_dest = Q3Shader::BLEND_GL_ONE; - } - else if (blend_src == "filter") { - curMap->blend_src = Q3Shader::BLEND_GL_DST_COLOR; - curMap->blend_dest = Q3Shader::BLEND_GL_ZERO; - } - else if (blend_src == "blend") { - curMap->blend_src = Q3Shader::BLEND_GL_SRC_ALPHA; - curMap->blend_dest = Q3Shader::BLEND_GL_ONE_MINUS_SRC_ALPHA; - } - else { - curMap->blend_src = StringToBlendFunc(blend_src); - curMap->blend_dest = StringToBlendFunc(GetNextToken(buff)); - } - } - // 'alphafunc' - Alpha testing mode - else if (TokenMatchI(buff,"alphafunc",9)) { - const std::string at = GetNextToken(buff); - if (at == "GT0") { - curMap->alpha_test = Q3Shader::AT_GT0; - } - else if (at == "LT128") { - curMap->alpha_test = Q3Shader::AT_LT128; - } - else if (at == "GE128") { - curMap->alpha_test = Q3Shader::AT_GE128; - } - } - else if (*buff == '}') { - ++buff; - // close this map section - curMap = NULL; - break; - } - } - - } - else if (*buff == '}') { - ++buff; - curData = NULL; - break; - } - - // 'cull' specifies culling behaviour for the model - else if (TokenMatchI(buff,"cull",4)) { - SkipSpaces(&buff); - if (!ASSIMP_strincmp(buff,"back",4)) { - curData->cull = Q3Shader::CULL_CCW; - } - else if (!ASSIMP_strincmp(buff,"front",5)) { - curData->cull = Q3Shader::CULL_CW; - } - else if (!ASSIMP_strincmp(buff,"none",4) || !ASSIMP_strincmp(buff,"disable",7)) { - curData->cull = Q3Shader::CULL_NONE; - } - else DefaultLogger::get()->error("Q3Shader: Unrecognized cull mode"); - } - } - } - - else { - // add new section - fill.blocks.push_back(Q3Shader::ShaderDataBlock()); - curData = &fill.blocks.back(); - - // get the name of this section - curData->name = GetNextToken(buff); - } - } - return true; + std::unique_ptr<IOStream> file( io->Open( pFile, "rt")); + if (!file.get()) + return false; // if we can't access the file, don't worry and return + + DefaultLogger::get()->info("Loading Quake3 shader file " + pFile); + + // read file in memory + const size_t s = file->FileSize(); + std::vector<char> _buff(s+1); + file->Read(&_buff[0],s,1); + _buff[s] = 0; + + // remove comments from it (C++ style) + CommentRemover::RemoveLineComments("//",&_buff[0]); + const char* buff = &_buff[0]; + + Q3Shader::ShaderDataBlock* curData = NULL; + Q3Shader::ShaderMapBlock* curMap = NULL; + + // read line per line + for (;SkipSpacesAndLineEnd(&buff);SkipLine(&buff)) { + + if (*buff == '{') { + ++buff; + + // append to last section, if any + if (!curData) { + DefaultLogger::get()->error("Q3Shader: Unexpected shader section token \'{\'"); + return true; // still no failure, the file is there + } + + // read this data section + for (;SkipSpacesAndLineEnd(&buff);SkipLine(&buff)) { + if (*buff == '{') { + ++buff; + // add new map section + curData->maps.push_back(Q3Shader::ShaderMapBlock()); + curMap = &curData->maps.back(); + + for (;SkipSpacesAndLineEnd(&buff);SkipLine(&buff)) { + // 'map' - Specifies texture file name + if (TokenMatchI(buff,"map",3) || TokenMatchI(buff,"clampmap",8)) { + curMap->name = GetNextToken(buff); + } + // 'blendfunc' - Alpha blending mode + else if (TokenMatchI(buff,"blendfunc",9)) { + const std::string blend_src = GetNextToken(buff); + if (blend_src == "add") { + curMap->blend_src = Q3Shader::BLEND_GL_ONE; + curMap->blend_dest = Q3Shader::BLEND_GL_ONE; + } + else if (blend_src == "filter") { + curMap->blend_src = Q3Shader::BLEND_GL_DST_COLOR; + curMap->blend_dest = Q3Shader::BLEND_GL_ZERO; + } + else if (blend_src == "blend") { + curMap->blend_src = Q3Shader::BLEND_GL_SRC_ALPHA; + curMap->blend_dest = Q3Shader::BLEND_GL_ONE_MINUS_SRC_ALPHA; + } + else { + curMap->blend_src = StringToBlendFunc(blend_src); + curMap->blend_dest = StringToBlendFunc(GetNextToken(buff)); + } + } + // 'alphafunc' - Alpha testing mode + else if (TokenMatchI(buff,"alphafunc",9)) { + const std::string at = GetNextToken(buff); + if (at == "GT0") { + curMap->alpha_test = Q3Shader::AT_GT0; + } + else if (at == "LT128") { + curMap->alpha_test = Q3Shader::AT_LT128; + } + else if (at == "GE128") { + curMap->alpha_test = Q3Shader::AT_GE128; + } + } + else if (*buff == '}') { + ++buff; + // close this map section + curMap = NULL; + break; + } + } + + } + else if (*buff == '}') { + ++buff; + curData = NULL; + break; + } + + // 'cull' specifies culling behaviour for the model + else if (TokenMatchI(buff,"cull",4)) { + SkipSpaces(&buff); + if (!ASSIMP_strincmp(buff,"back",4)) { + curData->cull = Q3Shader::CULL_CCW; + } + else if (!ASSIMP_strincmp(buff,"front",5)) { + curData->cull = Q3Shader::CULL_CW; + } + else if (!ASSIMP_strincmp(buff,"none",4) || !ASSIMP_strincmp(buff,"disable",7)) { + curData->cull = Q3Shader::CULL_NONE; + } + else DefaultLogger::get()->error("Q3Shader: Unrecognized cull mode"); + } + } + } + + else { + // add new section + fill.blocks.push_back(Q3Shader::ShaderDataBlock()); + curData = &fill.blocks.back(); + + // get the name of this section + curData->name = GetNextToken(buff); + } + } + return true; } // ------------------------------------------------------------------------------------------------ // Load a Quake 3 skin bool Q3Shader::LoadSkin(SkinData& fill, const std::string& pFile,IOSystem* io) { - boost::scoped_ptr<IOStream> file( io->Open( pFile, "rt")); - if (!file.get()) - return false; // if we can't access the file, don't worry and return + std::unique_ptr<IOStream> file( io->Open( pFile, "rt")); + if (!file.get()) + return false; // if we can't access the file, don't worry and return - DefaultLogger::get()->info("Loading Quake3 skin file " + pFile); + DefaultLogger::get()->info("Loading Quake3 skin file " + pFile); - // read file in memory - const size_t s = file->FileSize(); - std::vector<char> _buff(s+1);const char* buff = &_buff[0]; - file->Read(&_buff[0],s,1); - _buff[s] = 0; + // read file in memory + const size_t s = file->FileSize(); + std::vector<char> _buff(s+1);const char* buff = &_buff[0]; + file->Read(&_buff[0],s,1); + _buff[s] = 0; - // remove commas - std::replace(_buff.begin(),_buff.end(),',',' '); + // remove commas + std::replace(_buff.begin(),_buff.end(),',',' '); - // read token by token and fill output table - for (;*buff;) { - SkipSpacesAndLineEnd(&buff); + // read token by token and fill output table + for (;*buff;) { + SkipSpacesAndLineEnd(&buff); - // get first identifier - std::string ss = GetNextToken(buff); - - // ignore tokens starting with tag_ - if (!::strncmp(&ss[0],"tag_",std::min((size_t)4, ss.length()))) - continue; + // get first identifier + std::string ss = GetNextToken(buff); - fill.textures.push_back(SkinData::TextureEntry()); - SkinData::TextureEntry& s = fill.textures.back(); + // ignore tokens starting with tag_ + if (!::strncmp(&ss[0],"tag_",std::min((size_t)4, ss.length()))) + continue; - s.first = ss; - s.second = GetNextToken(buff); - } - return true; + fill.textures.push_back(SkinData::TextureEntry()); + SkinData::TextureEntry& s = fill.textures.back(); + + s.first = ss; + s.second = GetNextToken(buff); + } + return true; } // ------------------------------------------------------------------------------------------------ // Convert Q3Shader to material void Q3Shader::ConvertShaderToMaterial(aiMaterial* out, const ShaderDataBlock& shader) { - ai_assert(NULL != out); - - /* IMPORTANT: This is not a real conversion. Actually we're just guessing and - * hacking around to build an aiMaterial that looks nearly equal to the - * original Quake 3 shader. We're missing some important features like - * animatable material properties in our material system, but at least - * multiple textures should be handled correctly. - */ - - // Two-sided material? - if (shader.cull == Q3Shader::CULL_NONE) { - const int twosided = 1; - out->AddProperty(&twosided,1,AI_MATKEY_TWOSIDED); - } - - unsigned int cur_emissive = 0, cur_diffuse = 0, cur_lm =0; - - // Iterate through all textures - for (std::list< Q3Shader::ShaderMapBlock >::const_iterator it = shader.maps.begin(); it != shader.maps.end();++it) { - - // CONVERSION BEHAVIOUR: - // - // - // If the texture is additive - // - if it is the first texture, assume additive blending for the whole material - // - otherwise register it as emissive texture. - // - // If the texture is using standard blend (or if the blend mode is unknown) - // - if first texture: assume default blending for material - // - in any case: set it as diffuse texture - // - // If the texture is using 'filter' blending - // - take as lightmap - // - // Textures with alpha funcs - // - aiTextureFlags_UseAlpha is set (otherwise aiTextureFlags_NoAlpha is explicitly set) - aiString s((*it).name); - aiTextureType type; unsigned int index; - - if ((*it).blend_src == Q3Shader::BLEND_GL_ONE && (*it).blend_dest == Q3Shader::BLEND_GL_ONE) { - if (it == shader.maps.begin()) { - const int additive = aiBlendMode_Additive; - out->AddProperty(&additive,1,AI_MATKEY_BLEND_FUNC); - - index = cur_diffuse++; - type = aiTextureType_DIFFUSE; - } - else { - index = cur_emissive++; - type = aiTextureType_EMISSIVE; - } - } - else if ((*it).blend_src == Q3Shader::BLEND_GL_DST_COLOR && (*it).blend_dest == Q3Shader::BLEND_GL_ZERO) { - index = cur_lm++; - type = aiTextureType_LIGHTMAP; - } - else { - const int blend = aiBlendMode_Default; - out->AddProperty(&blend,1,AI_MATKEY_BLEND_FUNC); - - index = cur_diffuse++; - type = aiTextureType_DIFFUSE; - } - - // setup texture - out->AddProperty(&s,AI_MATKEY_TEXTURE(type,index)); - - // setup texture flags - const int use_alpha = ((*it).alpha_test != Q3Shader::AT_NONE ? aiTextureFlags_UseAlpha : aiTextureFlags_IgnoreAlpha); - out->AddProperty(&use_alpha,1,AI_MATKEY_TEXFLAGS(type,index)); - } - // If at least one emissive texture was set, set the emissive base color to 1 to ensure - // the texture is actually displayed. - if (0 != cur_emissive) { - aiColor3D one(1.f,1.f,1.f); - out->AddProperty(&one,1,AI_MATKEY_COLOR_EMISSIVE); - } + ai_assert(NULL != out); + + /* IMPORTANT: This is not a real conversion. Actually we're just guessing and + * hacking around to build an aiMaterial that looks nearly equal to the + * original Quake 3 shader. We're missing some important features like + * animatable material properties in our material system, but at least + * multiple textures should be handled correctly. + */ + + // Two-sided material? + if (shader.cull == Q3Shader::CULL_NONE) { + const int twosided = 1; + out->AddProperty(&twosided,1,AI_MATKEY_TWOSIDED); + } + + unsigned int cur_emissive = 0, cur_diffuse = 0, cur_lm =0; + + // Iterate through all textures + for (std::list< Q3Shader::ShaderMapBlock >::const_iterator it = shader.maps.begin(); it != shader.maps.end();++it) { + + // CONVERSION BEHAVIOUR: + // + // + // If the texture is additive + // - if it is the first texture, assume additive blending for the whole material + // - otherwise register it as emissive texture. + // + // If the texture is using standard blend (or if the blend mode is unknown) + // - if first texture: assume default blending for material + // - in any case: set it as diffuse texture + // + // If the texture is using 'filter' blending + // - take as lightmap + // + // Textures with alpha funcs + // - aiTextureFlags_UseAlpha is set (otherwise aiTextureFlags_NoAlpha is explicitly set) + aiString s((*it).name); + aiTextureType type; unsigned int index; + + if ((*it).blend_src == Q3Shader::BLEND_GL_ONE && (*it).blend_dest == Q3Shader::BLEND_GL_ONE) { + if (it == shader.maps.begin()) { + const int additive = aiBlendMode_Additive; + out->AddProperty(&additive,1,AI_MATKEY_BLEND_FUNC); + + index = cur_diffuse++; + type = aiTextureType_DIFFUSE; + } + else { + index = cur_emissive++; + type = aiTextureType_EMISSIVE; + } + } + else if ((*it).blend_src == Q3Shader::BLEND_GL_DST_COLOR && (*it).blend_dest == Q3Shader::BLEND_GL_ZERO) { + index = cur_lm++; + type = aiTextureType_LIGHTMAP; + } + else { + const int blend = aiBlendMode_Default; + out->AddProperty(&blend,1,AI_MATKEY_BLEND_FUNC); + + index = cur_diffuse++; + type = aiTextureType_DIFFUSE; + } + + // setup texture + out->AddProperty(&s,AI_MATKEY_TEXTURE(type,index)); + + // setup texture flags + const int use_alpha = ((*it).alpha_test != Q3Shader::AT_NONE ? aiTextureFlags_UseAlpha : aiTextureFlags_IgnoreAlpha); + out->AddProperty(&use_alpha,1,AI_MATKEY_TEXFLAGS(type,index)); + } + // If at least one emissive texture was set, set the emissive base color to 1 to ensure + // the texture is actually displayed. + if (0 != cur_emissive) { + aiColor3D one(1.f,1.f,1.f); + out->AddProperty(&one,1,AI_MATKEY_COLOR_EMISSIVE); + } } // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer MD3Importer::MD3Importer() -: configFrameID (0) -, configHandleMP (true) + : configFrameID (0) + , configHandleMP (true) + , configSpeedFlag() + , pcHeader() + , mBuffer() + , fileSize() + , mScene() + , mIOHandler() {} // ------------------------------------------------------------------------------------------------ -// Destructor, private as well +// Destructor, private as well MD3Importer::~MD3Importer() {} // ------------------------------------------------------------------------------------------------ -// Returns whether the class can handle the format of the given file. +// Returns whether the class can handle the format of the given file. bool MD3Importer::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const { - const std::string extension = GetExtension(pFile); - if (extension == "md3") - return true; - - // if check for extension is not enough, check for the magic tokens - if (!extension.length() || checkSig) { - uint32_t tokens[1]; - tokens[0] = AI_MD3_MAGIC_NUMBER_LE; - return CheckMagicToken(pIOHandler,pFile,tokens,1); - } - return false; + const std::string extension = GetExtension(pFile); + if (extension == "md3") + return true; + + // if check for extension is not enough, check for the magic tokens + if (!extension.length() || checkSig) { + uint32_t tokens[1]; + tokens[0] = AI_MD3_MAGIC_NUMBER_LE; + return CheckMagicToken(pIOHandler,pFile,tokens,1); + } + return false; } // ------------------------------------------------------------------------------------------------ void MD3Importer::ValidateHeaderOffsets() { - // Check magic number - if (pcHeader->IDENT != AI_MD3_MAGIC_NUMBER_BE && - pcHeader->IDENT != AI_MD3_MAGIC_NUMBER_LE) - throw DeadlyImportError( "Invalid MD3 file: Magic bytes not found"); - - // Check file format version - if (pcHeader->VERSION > 15) - DefaultLogger::get()->warn( "Unsupported MD3 file version. Continuing happily ..."); - - // Check some offset values whether they are valid - if (!pcHeader->NUM_SURFACES) - throw DeadlyImportError( "Invalid md3 file: NUM_SURFACES is 0"); - - if (pcHeader->OFS_FRAMES >= fileSize || pcHeader->OFS_SURFACES >= fileSize || - pcHeader->OFS_EOF > fileSize) { - throw DeadlyImportError("Invalid MD3 header: some offsets are outside the file"); + // Check magic number + if (pcHeader->IDENT != AI_MD3_MAGIC_NUMBER_BE && + pcHeader->IDENT != AI_MD3_MAGIC_NUMBER_LE) + throw DeadlyImportError( "Invalid MD3 file: Magic bytes not found"); + + // Check file format version + if (pcHeader->VERSION > 15) + DefaultLogger::get()->warn( "Unsupported MD3 file version. Continuing happily ..."); + + // Check some offset values whether they are valid + if (!pcHeader->NUM_SURFACES) + throw DeadlyImportError( "Invalid md3 file: NUM_SURFACES is 0"); + + if (pcHeader->OFS_FRAMES >= fileSize || pcHeader->OFS_SURFACES >= fileSize || + pcHeader->OFS_EOF > fileSize) { + throw DeadlyImportError("Invalid MD3 header: some offsets are outside the file"); + } + + if (pcHeader->NUM_SURFACES > AI_MAX_ALLOC(MD3::Surface)) { + throw DeadlyImportError("Invalid MD3 header: too many surfaces, would overflow"); } - if (pcHeader->NUM_FRAMES <= configFrameID ) - throw DeadlyImportError("The requested frame is not existing the file"); + if (pcHeader->OFS_SURFACES + pcHeader->NUM_SURFACES * sizeof(MD3::Surface) >= fileSize) { + throw DeadlyImportError("Invalid MD3 header: some surfaces are outside the file"); + } + + if (pcHeader->NUM_FRAMES <= configFrameID ) + throw DeadlyImportError("The requested frame is not existing the file"); } // ------------------------------------------------------------------------------------------------ void MD3Importer::ValidateSurfaceHeaderOffsets(const MD3::Surface* pcSurf) { - // Calculate the relative offset of the surface - const int32_t ofs = int32_t((const unsigned char*)pcSurf-this->mBuffer); - - // Check whether all data chunks are inside the valid range - if (pcSurf->OFS_TRIANGLES + ofs + pcSurf->NUM_TRIANGLES * sizeof(MD3::Triangle) > fileSize || - pcSurf->OFS_SHADERS + ofs + pcSurf->NUM_SHADER * sizeof(MD3::Shader) > fileSize || - pcSurf->OFS_ST + ofs + pcSurf->NUM_VERTICES * sizeof(MD3::TexCoord) > fileSize || - pcSurf->OFS_XYZNORMAL + ofs + pcSurf->NUM_VERTICES * sizeof(MD3::Vertex) > fileSize) { - - throw DeadlyImportError("Invalid MD3 surface header: some offsets are outside the file"); - } - - // Check whether all requirements for Q3 files are met. We don't - // care, but probably someone does. - if (pcSurf->NUM_TRIANGLES > AI_MD3_MAX_TRIANGLES) { - DefaultLogger::get()->warn("MD3: Quake III triangle limit exceeded"); - } - - if (pcSurf->NUM_SHADER > AI_MD3_MAX_SHADERS) { - DefaultLogger::get()->warn("MD3: Quake III shader limit exceeded"); - } - - if (pcSurf->NUM_VERTICES > AI_MD3_MAX_VERTS) { - DefaultLogger::get()->warn("MD3: Quake III vertex limit exceeded"); - } - - if (pcSurf->NUM_FRAMES > AI_MD3_MAX_FRAMES) { - DefaultLogger::get()->warn("MD3: Quake III frame limit exceeded"); - } + // Calculate the relative offset of the surface + const int32_t ofs = int32_t((const unsigned char*)pcSurf-this->mBuffer); + + // Check whether all data chunks are inside the valid range + if (pcSurf->OFS_TRIANGLES + ofs + pcSurf->NUM_TRIANGLES * sizeof(MD3::Triangle) > fileSize || + pcSurf->OFS_SHADERS + ofs + pcSurf->NUM_SHADER * sizeof(MD3::Shader) > fileSize || + pcSurf->OFS_ST + ofs + pcSurf->NUM_VERTICES * sizeof(MD3::TexCoord) > fileSize || + pcSurf->OFS_XYZNORMAL + ofs + pcSurf->NUM_VERTICES * sizeof(MD3::Vertex) > fileSize) { + + throw DeadlyImportError("Invalid MD3 surface header: some offsets are outside the file"); + } + + // Check whether all requirements for Q3 files are met. We don't + // care, but probably someone does. + if (pcSurf->NUM_TRIANGLES > AI_MD3_MAX_TRIANGLES) { + DefaultLogger::get()->warn("MD3: Quake III triangle limit exceeded"); + } + + if (pcSurf->NUM_SHADER > AI_MD3_MAX_SHADERS) { + DefaultLogger::get()->warn("MD3: Quake III shader limit exceeded"); + } + + if (pcSurf->NUM_VERTICES > AI_MD3_MAX_VERTS) { + DefaultLogger::get()->warn("MD3: Quake III vertex limit exceeded"); + } + + if (pcSurf->NUM_FRAMES > AI_MD3_MAX_FRAMES) { + DefaultLogger::get()->warn("MD3: Quake III frame limit exceeded"); + } } // ------------------------------------------------------------------------------------------------ const aiImporterDesc* MD3Importer::GetInfo () const { - return &desc; + return &desc; } // ------------------------------------------------------------------------------------------------ // Setup configuration properties void MD3Importer::SetupProperties(const Importer* pImp) { - // The - // AI_CONFIG_IMPORT_MD3_KEYFRAME option overrides the - // AI_CONFIG_IMPORT_GLOBAL_KEYFRAME option. - configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MD3_KEYFRAME,-1); - if(static_cast<unsigned int>(-1) == configFrameID) { - configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_GLOBAL_KEYFRAME,0); - } + // The + // AI_CONFIG_IMPORT_MD3_KEYFRAME option overrides the + // AI_CONFIG_IMPORT_GLOBAL_KEYFRAME option. + configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MD3_KEYFRAME,-1); + if(static_cast<unsigned int>(-1) == configFrameID) { + configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_GLOBAL_KEYFRAME,0); + } - // AI_CONFIG_IMPORT_MD3_HANDLE_MULTIPART - configHandleMP = (0 != pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MD3_HANDLE_MULTIPART,1)); + // AI_CONFIG_IMPORT_MD3_HANDLE_MULTIPART + configHandleMP = (0 != pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MD3_HANDLE_MULTIPART,1)); - // AI_CONFIG_IMPORT_MD3_SKIN_NAME - configSkinFile = (pImp->GetPropertyString(AI_CONFIG_IMPORT_MD3_SKIN_NAME,"default")); + // AI_CONFIG_IMPORT_MD3_SKIN_NAME + configSkinFile = (pImp->GetPropertyString(AI_CONFIG_IMPORT_MD3_SKIN_NAME,"default")); - // AI_CONFIG_IMPORT_MD3_SHADER_SRC - configShaderFile = (pImp->GetPropertyString(AI_CONFIG_IMPORT_MD3_SHADER_SRC,"")); + // AI_CONFIG_IMPORT_MD3_SHADER_SRC + configShaderFile = (pImp->GetPropertyString(AI_CONFIG_IMPORT_MD3_SHADER_SRC,"")); - // AI_CONFIG_FAVOUR_SPEED - configSpeedFlag = (0 != pImp->GetPropertyInteger(AI_CONFIG_FAVOUR_SPEED,0)); + // AI_CONFIG_FAVOUR_SPEED + configSpeedFlag = (0 != pImp->GetPropertyInteger(AI_CONFIG_FAVOUR_SPEED,0)); } // ------------------------------------------------------------------------------------------------ // Try to read the skin for a MD3 file void MD3Importer::ReadSkin(Q3Shader::SkinData& fill) const { - // skip any postfixes (e.g. lower_1.md3) - std::string::size_type s = filename.find_last_of('_'); - if (s == std::string::npos) { - s = filename.find_last_of('.'); - } - ai_assert(s != std::string::npos); - - const std::string skin_file = path + filename.substr(0,s) + "_" + configSkinFile + ".skin"; - Q3Shader::LoadSkin(fill,skin_file,mIOHandler); + // skip any postfixes (e.g. lower_1.md3) + std::string::size_type s = filename.find_last_of('_'); + if (s == std::string::npos) { + s = filename.find_last_of('.'); + if (s == std::string::npos) { + s = filename.size(); + } + } + ai_assert(s != std::string::npos); + + const std::string skin_file = path + filename.substr(0,s) + "_" + configSkinFile + ".skin"; + Q3Shader::LoadSkin(fill,skin_file,mIOHandler); } // ------------------------------------------------------------------------------------------------ // Try to read the shader for a MD3 file void MD3Importer::ReadShader(Q3Shader::ShaderData& fill) const { - // Determine Q3 model name from given path - const std::string::size_type s = path.find_last_of("\\/",path.length()-2); - const std::string model_file = path.substr(s+1,path.length()-(s+2)); - - // If no specific dir or file is given, use our default search behaviour - if (!configShaderFile.length()) { - if(!Q3Shader::LoadShader(fill,path + "..\\..\\..\\scripts\\" + model_file + ".shader",mIOHandler)) { - Q3Shader::LoadShader(fill,path + "..\\..\\..\\scripts\\" + filename + ".shader",mIOHandler); - } - } - else { - // If the given string specifies a file, load this file. - // Otherwise it's a directory. - const std::string::size_type st = configShaderFile.find_last_of('.'); - if (st == std::string::npos) { - - if(!Q3Shader::LoadShader(fill,configShaderFile + model_file + ".shader",mIOHandler)) { - Q3Shader::LoadShader(fill,configShaderFile + filename + ".shader",mIOHandler); - } - } - else { - Q3Shader::LoadShader(fill,configShaderFile,mIOHandler); - } - } + // Determine Q3 model name from given path + const std::string::size_type s = path.find_last_of("\\/",path.length()-2); + const std::string model_file = path.substr(s+1,path.length()-(s+2)); + + // If no specific dir or file is given, use our default search behaviour + if (!configShaderFile.length()) { + if(!Q3Shader::LoadShader(fill,path + "..\\..\\..\\scripts\\" + model_file + ".shader",mIOHandler)) { + Q3Shader::LoadShader(fill,path + "..\\..\\..\\scripts\\" + filename + ".shader",mIOHandler); + } + } + else { + // If the given string specifies a file, load this file. + // Otherwise it's a directory. + const std::string::size_type st = configShaderFile.find_last_of('.'); + if (st == std::string::npos) { + + if(!Q3Shader::LoadShader(fill,configShaderFile + model_file + ".shader",mIOHandler)) { + Q3Shader::LoadShader(fill,configShaderFile + filename + ".shader",mIOHandler); + } + } + else { + Q3Shader::LoadShader(fill,configShaderFile,mIOHandler); + } + } } // ------------------------------------------------------------------------------------------------ // Tiny helper to remove a single node from its parent' list void RemoveSingleNodeFromList(aiNode* nd) { - if (!nd || nd->mNumChildren || !nd->mParent)return; - aiNode* par = nd->mParent; - for (unsigned int i = 0; i < par->mNumChildren;++i) { - if (par->mChildren[i] == nd) { - --par->mNumChildren; - for (;i < par->mNumChildren;++i) { - par->mChildren[i] = par->mChildren[i+1]; - } - delete nd; - break; - } - } + if (!nd || nd->mNumChildren || !nd->mParent)return; + aiNode* par = nd->mParent; + for (unsigned int i = 0; i < par->mNumChildren;++i) { + if (par->mChildren[i] == nd) { + --par->mNumChildren; + for (;i < par->mNumChildren;++i) { + par->mChildren[i] = par->mChildren[i+1]; + } + delete nd; + break; + } + } } // ------------------------------------------------------------------------------------------------ // Read a multi-part Q3 player model bool MD3Importer::ReadMultipartFile() { - // check whether the file name contains a common postfix, e.g lower_2.md3 - std::string::size_type s = filename.find_last_of('_'), t = filename.find_last_of('.'); - ai_assert(t != std::string::npos); - if (s == std::string::npos) - s = t; - - const std::string mod_filename = filename.substr(0,s); - const std::string suffix = filename.substr(s,t-s); - - if (mod_filename == "lower" || mod_filename == "upper" || mod_filename == "head"){ - const std::string lower = path + "lower" + suffix + ".md3"; - const std::string upper = path + "upper" + suffix + ".md3"; - const std::string head = path + "head" + suffix + ".md3"; - - aiScene* scene_upper = NULL; - aiScene* scene_lower = NULL; - aiScene* scene_head = NULL; - std::string failure; - - aiNode* tag_torso, *tag_head; - std::vector<AttachmentInfo> attach; - - DefaultLogger::get()->info("Multi part MD3 player model: lower, upper and head parts are joined"); - - // ensure we won't try to load ourselves recursively - BatchLoader::PropertyMap props; - SetGenericProperty( props.ints, AI_CONFIG_IMPORT_MD3_HANDLE_MULTIPART, 0, NULL); - - // now read these three files - BatchLoader batch(mIOHandler); - const unsigned int _lower = batch.AddLoadRequest(lower,0,&props); - const unsigned int _upper = batch.AddLoadRequest(upper,0,&props); - const unsigned int _head = batch.AddLoadRequest(head,0,&props); - batch.LoadAll(); - - // now construct a dummy scene to place these three parts in - aiScene* master = new aiScene(); - aiNode* nd = master->mRootNode = new aiNode(); - nd->mName.Set("<MD3_Player>"); - - // ... and get them. We need all of them. - scene_lower = batch.GetImport(_lower); - if (!scene_lower) { - DefaultLogger::get()->error("M3D: Failed to read multi part model, lower.md3 fails to load"); - failure = "lower"; - goto error_cleanup; - } - - scene_upper = batch.GetImport(_upper); - if (!scene_upper) { - DefaultLogger::get()->error("M3D: Failed to read multi part model, upper.md3 fails to load"); - failure = "upper"; - goto error_cleanup; - } - - scene_head = batch.GetImport(_head); - if (!scene_head) { - DefaultLogger::get()->error("M3D: Failed to read multi part model, head.md3 fails to load"); - failure = "head"; - goto error_cleanup; - } - - // build attachment infos. search for typical Q3 tags - - // original root - scene_lower->mRootNode->mName.Set("lower"); - attach.push_back(AttachmentInfo(scene_lower, nd)); - - // tag_torso - tag_torso = scene_lower->mRootNode->FindNode("tag_torso"); - if (!tag_torso) { - DefaultLogger::get()->error("M3D: Failed to find attachment tag for multi part model: tag_torso expected"); - goto error_cleanup; - } - scene_upper->mRootNode->mName.Set("upper"); - attach.push_back(AttachmentInfo(scene_upper,tag_torso)); - - // tag_head - tag_head = scene_upper->mRootNode->FindNode("tag_head"); - if (!tag_head) { - DefaultLogger::get()->error("M3D: Failed to find attachment tag for multi part model: tag_head expected"); - goto error_cleanup; - } - scene_head->mRootNode->mName.Set("head"); - attach.push_back(AttachmentInfo(scene_head,tag_head)); - - // Remove tag_head and tag_torso from all other model parts ... - // this ensures (together with AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY) - // that tag_torso/tag_head is also the name of the (unique) output node - RemoveSingleNodeFromList (scene_upper->mRootNode->FindNode("tag_torso")); - RemoveSingleNodeFromList (scene_head-> mRootNode->FindNode("tag_head" )); - - // Undo the rotations which we applied to the coordinate systems. We're - // working in global Quake space here - scene_head->mRootNode->mTransformation = aiMatrix4x4(); - scene_lower->mRootNode->mTransformation = aiMatrix4x4(); - scene_upper->mRootNode->mTransformation = aiMatrix4x4(); - - // and merge the scenes - SceneCombiner::MergeScenes(&mScene,master, attach, - AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES | - AI_INT_MERGE_SCENE_GEN_UNIQUE_MATNAMES | - AI_INT_MERGE_SCENE_RESOLVE_CROSS_ATTACHMENTS | - (!configSpeedFlag ? AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY : 0)); - - // Now rotate the whole scene 90 degrees around the x axis to convert to internal coordinate system - mScene->mRootNode->mTransformation = aiMatrix4x4(1.f,0.f,0.f,0.f, - 0.f,0.f,1.f,0.f,0.f,-1.f,0.f,0.f,0.f,0.f,0.f,1.f); - - return true; + // check whether the file name contains a common postfix, e.g lower_2.md3 + std::string::size_type s = filename.find_last_of('_'), t = filename.find_last_of('.'); + + if (t == std::string::npos) + t = filename.size(); + if (s == std::string::npos) + s = t; + + const std::string mod_filename = filename.substr(0,s); + const std::string suffix = filename.substr(s,t-s); + + if (mod_filename == "lower" || mod_filename == "upper" || mod_filename == "head"){ + const std::string lower = path + "lower" + suffix + ".md3"; + const std::string upper = path + "upper" + suffix + ".md3"; + const std::string head = path + "head" + suffix + ".md3"; + + aiScene* scene_upper = NULL; + aiScene* scene_lower = NULL; + aiScene* scene_head = NULL; + std::string failure; + + aiNode* tag_torso, *tag_head; + std::vector<AttachmentInfo> attach; + + DefaultLogger::get()->info("Multi part MD3 player model: lower, upper and head parts are joined"); + + // ensure we won't try to load ourselves recursively + BatchLoader::PropertyMap props; + SetGenericProperty( props.ints, AI_CONFIG_IMPORT_MD3_HANDLE_MULTIPART, 0); + + // now read these three files + BatchLoader batch(mIOHandler); + const unsigned int _lower = batch.AddLoadRequest(lower,0,&props); + const unsigned int _upper = batch.AddLoadRequest(upper,0,&props); + const unsigned int _head = batch.AddLoadRequest(head,0,&props); + batch.LoadAll(); + + // now construct a dummy scene to place these three parts in + aiScene* master = new aiScene(); + aiNode* nd = master->mRootNode = new aiNode(); + nd->mName.Set("<MD3_Player>"); + + // ... and get them. We need all of them. + scene_lower = batch.GetImport(_lower); + if (!scene_lower) { + DefaultLogger::get()->error("M3D: Failed to read multi part model, lower.md3 fails to load"); + failure = "lower"; + goto error_cleanup; + } + + scene_upper = batch.GetImport(_upper); + if (!scene_upper) { + DefaultLogger::get()->error("M3D: Failed to read multi part model, upper.md3 fails to load"); + failure = "upper"; + goto error_cleanup; + } + + scene_head = batch.GetImport(_head); + if (!scene_head) { + DefaultLogger::get()->error("M3D: Failed to read multi part model, head.md3 fails to load"); + failure = "head"; + goto error_cleanup; + } + + // build attachment infos. search for typical Q3 tags + + // original root + scene_lower->mRootNode->mName.Set("lower"); + attach.push_back(AttachmentInfo(scene_lower, nd)); + + // tag_torso + tag_torso = scene_lower->mRootNode->FindNode("tag_torso"); + if (!tag_torso) { + DefaultLogger::get()->error("M3D: Failed to find attachment tag for multi part model: tag_torso expected"); + goto error_cleanup; + } + scene_upper->mRootNode->mName.Set("upper"); + attach.push_back(AttachmentInfo(scene_upper,tag_torso)); + + // tag_head + tag_head = scene_upper->mRootNode->FindNode("tag_head"); + if (!tag_head) { + DefaultLogger::get()->error("M3D: Failed to find attachment tag for multi part model: tag_head expected"); + goto error_cleanup; + } + scene_head->mRootNode->mName.Set("head"); + attach.push_back(AttachmentInfo(scene_head,tag_head)); + + // Remove tag_head and tag_torso from all other model parts ... + // this ensures (together with AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY) + // that tag_torso/tag_head is also the name of the (unique) output node + RemoveSingleNodeFromList (scene_upper->mRootNode->FindNode("tag_torso")); + RemoveSingleNodeFromList (scene_head-> mRootNode->FindNode("tag_head" )); + + // Undo the rotations which we applied to the coordinate systems. We're + // working in global Quake space here + scene_head->mRootNode->mTransformation = aiMatrix4x4(); + scene_lower->mRootNode->mTransformation = aiMatrix4x4(); + scene_upper->mRootNode->mTransformation = aiMatrix4x4(); + + // and merge the scenes + SceneCombiner::MergeScenes(&mScene,master, attach, + AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES | + AI_INT_MERGE_SCENE_GEN_UNIQUE_MATNAMES | + AI_INT_MERGE_SCENE_RESOLVE_CROSS_ATTACHMENTS | + (!configSpeedFlag ? AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY : 0)); + + // Now rotate the whole scene 90 degrees around the x axis to convert to internal coordinate system + mScene->mRootNode->mTransformation = aiMatrix4x4(1.f,0.f,0.f,0.f, + 0.f,0.f,1.f,0.f,0.f,-1.f,0.f,0.f,0.f,0.f,0.f,1.f); + + return true; error_cleanup: - delete scene_upper; - delete scene_lower; - delete scene_head; - delete master; - - if (failure == mod_filename) { - throw DeadlyImportError("MD3: failure to read multipart host file"); - } - } - return false; + delete scene_upper; + delete scene_lower; + delete scene_head; + delete master; + + if (failure == mod_filename) { + throw DeadlyImportError("MD3: failure to read multipart host file"); + } + } + return false; } // ------------------------------------------------------------------------------------------------ // Convert a MD3 path to a proper value void MD3Importer::ConvertPath(const char* texture_name, const char* header_name, std::string& out) const { - // If the MD3's internal path itself and the given path are using - // the same directory, remove it completely to get right output paths. - const char* end1 = ::strrchr(header_name,'\\'); - if (!end1)end1 = ::strrchr(header_name,'/'); - - const char* end2 = ::strrchr(texture_name,'\\'); - if (!end2)end2 = ::strrchr(texture_name,'/'); - - // HACK: If the paths starts with "models", ignore the - // next two hierarchy levels, it specifies just the model name. - // Ignored by Q3, it might be not equal to the real model location. - if (end2) { - - size_t len2; - const size_t len1 = (size_t)(end1 - header_name); - if (!ASSIMP_strincmp(texture_name,"models",6) && (texture_name[6] == '/' || texture_name[6] == '\\')) { - len2 = 6; // ignore the seventh - could be slash or backslash - - if (!header_name[0]) { - // Use the file name only - out = end2+1; - return; - } - } - else len2 = std::min (len1, (size_t)(end2 - texture_name )); - if (!ASSIMP_strincmp(texture_name,header_name,len2)) { - // Use the file name only - out = end2+1; - return; - } - } - // Use the full path - out = texture_name; + // If the MD3's internal path itself and the given path are using + // the same directory, remove it completely to get right output paths. + const char* end1 = ::strrchr(header_name,'\\'); + if (!end1)end1 = ::strrchr(header_name,'/'); + + const char* end2 = ::strrchr(texture_name,'\\'); + if (!end2)end2 = ::strrchr(texture_name,'/'); + + // HACK: If the paths starts with "models", ignore the + // next two hierarchy levels, it specifies just the model name. + // Ignored by Q3, it might be not equal to the real model location. + if (end2) { + + size_t len2; + const size_t len1 = (size_t)(end1 - header_name); + if (!ASSIMP_strincmp(texture_name,"models",6) && (texture_name[6] == '/' || texture_name[6] == '\\')) { + len2 = 6; // ignore the seventh - could be slash or backslash + + if (!header_name[0]) { + // Use the file name only + out = end2+1; + return; + } + } + else len2 = std::min (len1, (size_t)(end2 - texture_name )); + if (!ASSIMP_strincmp(texture_name,header_name,len2)) { + // Use the file name only + out = end2+1; + return; + } + } + // Use the full path + out = texture_name; } // ------------------------------------------------------------------------------------------------ -// Imports the given file into the given scene structure. -void MD3Importer::InternReadFile( const std::string& pFile, - aiScene* pScene, IOSystem* pIOHandler) +// Imports the given file into the given scene structure. +void MD3Importer::InternReadFile( const std::string& pFile, + aiScene* pScene, IOSystem* pIOHandler) { - mFile = pFile; - mScene = pScene; - mIOHandler = pIOHandler; - - // get base path and file name - // todo ... move to PathConverter - std::string::size_type s = mFile.find_last_of("/\\"); - if (s == std::string::npos) { - s = 0; - } - else ++s; - filename = mFile.substr(s), path = mFile.substr(0,s); - for( std::string::iterator it = filename .begin(); it != filename.end(); ++it) - *it = tolower( *it); - - // Load multi-part model file, if necessary - if (configHandleMP) { - if (ReadMultipartFile()) - return; - } - - boost::scoped_ptr<IOStream> file( pIOHandler->Open( pFile)); - - // Check whether we can read from the file - if( file.get() == NULL) - throw DeadlyImportError( "Failed to open MD3 file " + pFile + "."); - - // Check whether the md3 file is large enough to contain the header - fileSize = (unsigned int)file->FileSize(); - if( fileSize < sizeof(MD3::Header)) - throw DeadlyImportError( "MD3 File is too small."); - - // Allocate storage and copy the contents of the file to a memory buffer - std::vector<unsigned char> mBuffer2 (fileSize); - file->Read( &mBuffer2[0], 1, fileSize); - mBuffer = &mBuffer2[0]; - - pcHeader = (BE_NCONST MD3::Header*)mBuffer; - - // Ensure correct endianess + mFile = pFile; + mScene = pScene; + mIOHandler = pIOHandler; + + // get base path and file name + // todo ... move to PathConverter + std::string::size_type s = mFile.find_last_of("/\\"); + if (s == std::string::npos) { + s = 0; + } + else ++s; + filename = mFile.substr(s), path = mFile.substr(0,s); + for( std::string::iterator it = filename .begin(); it != filename.end(); ++it) + *it = tolower( *it); + + // Load multi-part model file, if necessary + if (configHandleMP) { + if (ReadMultipartFile()) + return; + } + + std::unique_ptr<IOStream> file( pIOHandler->Open( pFile)); + + // Check whether we can read from the file + if( file.get() == NULL) + throw DeadlyImportError( "Failed to open MD3 file " + pFile + "."); + + // Check whether the md3 file is large enough to contain the header + fileSize = (unsigned int)file->FileSize(); + if( fileSize < sizeof(MD3::Header)) + throw DeadlyImportError( "MD3 File is too small."); + + // Allocate storage and copy the contents of the file to a memory buffer + std::vector<unsigned char> mBuffer2 (fileSize); + file->Read( &mBuffer2[0], 1, fileSize); + mBuffer = &mBuffer2[0]; + + pcHeader = (BE_NCONST MD3::Header*)mBuffer; + + // Ensure correct endianness #ifdef AI_BUILD_BIG_ENDIAN - AI_SWAP4(pcHeader->VERSION); - AI_SWAP4(pcHeader->FLAGS); - AI_SWAP4(pcHeader->IDENT); - AI_SWAP4(pcHeader->NUM_FRAMES); - AI_SWAP4(pcHeader->NUM_SKINS); - AI_SWAP4(pcHeader->NUM_SURFACES); - AI_SWAP4(pcHeader->NUM_TAGS); - AI_SWAP4(pcHeader->OFS_EOF); - AI_SWAP4(pcHeader->OFS_FRAMES); - AI_SWAP4(pcHeader->OFS_SURFACES); - AI_SWAP4(pcHeader->OFS_TAGS); + AI_SWAP4(pcHeader->VERSION); + AI_SWAP4(pcHeader->FLAGS); + AI_SWAP4(pcHeader->IDENT); + AI_SWAP4(pcHeader->NUM_FRAMES); + AI_SWAP4(pcHeader->NUM_SKINS); + AI_SWAP4(pcHeader->NUM_SURFACES); + AI_SWAP4(pcHeader->NUM_TAGS); + AI_SWAP4(pcHeader->OFS_EOF); + AI_SWAP4(pcHeader->OFS_FRAMES); + AI_SWAP4(pcHeader->OFS_SURFACES); + AI_SWAP4(pcHeader->OFS_TAGS); #endif - // Validate the file header - ValidateHeaderOffsets(); - - // Navigate to the list of surfaces - BE_NCONST MD3::Surface* pcSurfaces = (BE_NCONST MD3::Surface*)(mBuffer + pcHeader->OFS_SURFACES); - - // Navigate to the list of tags - BE_NCONST MD3::Tag* pcTags = (BE_NCONST MD3::Tag*)(mBuffer + pcHeader->OFS_TAGS); - - // Allocate output storage - pScene->mNumMeshes = pcHeader->NUM_SURFACES; - pScene->mMeshes = new aiMesh*[pScene->mNumMeshes]; - - pScene->mNumMaterials = pcHeader->NUM_SURFACES; - pScene->mMaterials = new aiMaterial*[pScene->mNumMeshes]; - - // Set arrays to zero to ensue proper destruction if an exception is raised - ::memset(pScene->mMeshes,0,pScene->mNumMeshes*sizeof(aiMesh*)); - ::memset(pScene->mMaterials,0,pScene->mNumMaterials*sizeof(aiMaterial*)); - - // Now read possible skins from .skin file - Q3Shader::SkinData skins; - ReadSkin(skins); - - // And check whether we can locate a shader file for this model - Q3Shader::ShaderData shaders; - ReadShader(shaders); - - // Adjust all texture paths in the shader - const char* header_name = pcHeader->NAME; - if (shaders.blocks.size()) { - for (std::list< Q3Shader::ShaderDataBlock >::iterator dit = shaders.blocks.begin(); dit != shaders.blocks.end(); ++dit) { - ConvertPath((*dit).name.c_str(),header_name,(*dit).name); - - for (std::list< Q3Shader::ShaderMapBlock >::iterator mit = (*dit).maps.begin(); mit != (*dit).maps.end(); ++mit) { - ConvertPath((*mit).name.c_str(),header_name,(*mit).name); - } - } - } - - // Read all surfaces from the file - unsigned int iNum = pcHeader->NUM_SURFACES; - unsigned int iNumMaterials = 0; - while (iNum-- > 0) { - - // Ensure correct endianess + // Validate the file header + ValidateHeaderOffsets(); + + // Navigate to the list of surfaces + BE_NCONST MD3::Surface* pcSurfaces = (BE_NCONST MD3::Surface*)(mBuffer + pcHeader->OFS_SURFACES); + + // Navigate to the list of tags + BE_NCONST MD3::Tag* pcTags = (BE_NCONST MD3::Tag*)(mBuffer + pcHeader->OFS_TAGS); + + // Allocate output storage + pScene->mNumMeshes = pcHeader->NUM_SURFACES; + if (pcHeader->NUM_SURFACES == 0) { + throw DeadlyImportError("MD3: No surfaces"); + } else if (pcHeader->NUM_SURFACES > AI_MAX_ALLOC(aiMesh)) { + // We allocate pointers but check against the size of aiMesh + // since those pointers will eventually have to point to real objects + throw DeadlyImportError("MD3: Too many surfaces, would run out of memory"); + } + pScene->mMeshes = new aiMesh*[pScene->mNumMeshes]; + + pScene->mNumMaterials = pcHeader->NUM_SURFACES; + pScene->mMaterials = new aiMaterial*[pScene->mNumMeshes]; + + // Set arrays to zero to ensue proper destruction if an exception is raised + ::memset(pScene->mMeshes,0,pScene->mNumMeshes*sizeof(aiMesh*)); + ::memset(pScene->mMaterials,0,pScene->mNumMaterials*sizeof(aiMaterial*)); + + // Now read possible skins from .skin file + Q3Shader::SkinData skins; + ReadSkin(skins); + + // And check whether we can locate a shader file for this model + Q3Shader::ShaderData shaders; + ReadShader(shaders); + + // Adjust all texture paths in the shader + const char* header_name = pcHeader->NAME; + if (!shaders.blocks.empty()) { + for (std::list< Q3Shader::ShaderDataBlock >::iterator dit = shaders.blocks.begin(); dit != shaders.blocks.end(); ++dit) { + ConvertPath((*dit).name.c_str(),header_name,(*dit).name); + + for (std::list< Q3Shader::ShaderMapBlock >::iterator mit = (*dit).maps.begin(); mit != (*dit).maps.end(); ++mit) { + ConvertPath((*mit).name.c_str(),header_name,(*mit).name); + } + } + } + + // Read all surfaces from the file + unsigned int iNum = pcHeader->NUM_SURFACES; + unsigned int iNumMaterials = 0; + while (iNum-- > 0) { + + // Ensure correct endianness #ifdef AI_BUILD_BIG_ENDIAN - AI_SWAP4(pcSurfaces->FLAGS); - AI_SWAP4(pcSurfaces->IDENT); - AI_SWAP4(pcSurfaces->NUM_FRAMES); - AI_SWAP4(pcSurfaces->NUM_SHADER); - AI_SWAP4(pcSurfaces->NUM_TRIANGLES); - AI_SWAP4(pcSurfaces->NUM_VERTICES); - AI_SWAP4(pcSurfaces->OFS_END); - AI_SWAP4(pcSurfaces->OFS_SHADERS); - AI_SWAP4(pcSurfaces->OFS_ST); - AI_SWAP4(pcSurfaces->OFS_TRIANGLES); - AI_SWAP4(pcSurfaces->OFS_XYZNORMAL); + AI_SWAP4(pcSurfaces->FLAGS); + AI_SWAP4(pcSurfaces->IDENT); + AI_SWAP4(pcSurfaces->NUM_FRAMES); + AI_SWAP4(pcSurfaces->NUM_SHADER); + AI_SWAP4(pcSurfaces->NUM_TRIANGLES); + AI_SWAP4(pcSurfaces->NUM_VERTICES); + AI_SWAP4(pcSurfaces->OFS_END); + AI_SWAP4(pcSurfaces->OFS_SHADERS); + AI_SWAP4(pcSurfaces->OFS_ST); + AI_SWAP4(pcSurfaces->OFS_TRIANGLES); + AI_SWAP4(pcSurfaces->OFS_XYZNORMAL); #endif - // Validate the surface header - ValidateSurfaceHeaderOffsets(pcSurfaces); - - // Navigate to the vertex list of the surface - BE_NCONST MD3::Vertex* pcVertices = (BE_NCONST MD3::Vertex*) - (((uint8_t*)pcSurfaces) + pcSurfaces->OFS_XYZNORMAL); - - // Navigate to the triangle list of the surface - BE_NCONST MD3::Triangle* pcTriangles = (BE_NCONST MD3::Triangle*) - (((uint8_t*)pcSurfaces) + pcSurfaces->OFS_TRIANGLES); - - // Navigate to the texture coordinate list of the surface - BE_NCONST MD3::TexCoord* pcUVs = (BE_NCONST MD3::TexCoord*) - (((uint8_t*)pcSurfaces) + pcSurfaces->OFS_ST); - - // Navigate to the shader list of the surface - BE_NCONST MD3::Shader* pcShaders = (BE_NCONST MD3::Shader*) - (((uint8_t*)pcSurfaces) + pcSurfaces->OFS_SHADERS); - - // If the submesh is empty ignore it - if (0 == pcSurfaces->NUM_VERTICES || 0 == pcSurfaces->NUM_TRIANGLES) - { - pcSurfaces = (BE_NCONST MD3::Surface*)(((uint8_t*)pcSurfaces) + pcSurfaces->OFS_END); - pScene->mNumMeshes--; - continue; - } - - // Allocate output mesh - pScene->mMeshes[iNum] = new aiMesh(); - aiMesh* pcMesh = pScene->mMeshes[iNum]; - - std::string _texture_name; - const char* texture_name = NULL; - - // Check whether we have a texture record for this surface in the .skin file - std::list< Q3Shader::SkinData::TextureEntry >::iterator it = std::find( - skins.textures.begin(), skins.textures.end(), pcSurfaces->NAME ); - - if (it != skins.textures.end()) { - texture_name = &*( _texture_name = (*it).second).begin(); - DefaultLogger::get()->debug("MD3: Assigning skin texture " + (*it).second + " to surface " + pcSurfaces->NAME); - (*it).resolved = true; // mark entry as resolved - } - - // Get the first shader (= texture?) assigned to the surface - if (!texture_name && pcSurfaces->NUM_SHADER) { - texture_name = pcShaders->NAME; - } - - std::string convertedPath; - if (texture_name) { - ConvertPath(texture_name,header_name,convertedPath); - } - - const Q3Shader::ShaderDataBlock* shader = NULL; - - // Now search the current shader for a record with this name ( - // excluding texture file extension) - if (shaders.blocks.size()) { - - std::string::size_type s = convertedPath.find_last_of('.'); - if (s == std::string::npos) - s = convertedPath.length(); - - const std::string without_ext = convertedPath.substr(0,s); - std::list< Q3Shader::ShaderDataBlock >::const_iterator dit = std::find(shaders.blocks.begin(),shaders.blocks.end(),without_ext); - if (dit != shaders.blocks.end()) { - // Hurra, wir haben einen. Tolle Sache. - shader = &*dit; - DefaultLogger::get()->info("Found shader record for " +without_ext ); - } - else DefaultLogger::get()->warn("Unable to find shader record for " +without_ext ); - } - - aiMaterial* pcHelper = new aiMaterial(); - - const int iMode = (int)aiShadingMode_Gouraud; - pcHelper->AddProperty<int>(&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 = 0.05f; - pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_AMBIENT); - - clr.b = clr.g = clr.r = 1.0f; - pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_DIFFUSE); - pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_SPECULAR); - - // use surface name + skin_name as material name - aiString name; - name.Set("MD3_[" + configSkinFile + "][" + pcSurfaces->NAME + "]"); - pcHelper->AddProperty(&name,AI_MATKEY_NAME); - - if (!shader) { - // Setup dummy texture file name to ensure UV coordinates are kept during postprocessing - aiString szString; - if (convertedPath.length()) { - szString.Set(convertedPath); - } - else { - DefaultLogger::get()->warn("Texture file name has zero length. Using default name"); - szString.Set("dummy_texture.bmp"); - } - pcHelper->AddProperty(&szString,AI_MATKEY_TEXTURE_DIFFUSE(0)); - - // prevent transparency by default - int no_alpha = aiTextureFlags_IgnoreAlpha; - pcHelper->AddProperty(&no_alpha,1,AI_MATKEY_TEXFLAGS_DIFFUSE(0)); - } - else { - Q3Shader::ConvertShaderToMaterial(pcHelper,*shader); - } - - pScene->mMaterials[iNumMaterials] = (aiMaterial*)pcHelper; - pcMesh->mMaterialIndex = iNumMaterials++; - - // Ensure correct endianess + // Validate the surface header + ValidateSurfaceHeaderOffsets(pcSurfaces); + + // Navigate to the vertex list of the surface + BE_NCONST MD3::Vertex* pcVertices = (BE_NCONST MD3::Vertex*) + (((uint8_t*)pcSurfaces) + pcSurfaces->OFS_XYZNORMAL); + + // Navigate to the triangle list of the surface + BE_NCONST MD3::Triangle* pcTriangles = (BE_NCONST MD3::Triangle*) + (((uint8_t*)pcSurfaces) + pcSurfaces->OFS_TRIANGLES); + + // Navigate to the texture coordinate list of the surface + BE_NCONST MD3::TexCoord* pcUVs = (BE_NCONST MD3::TexCoord*) + (((uint8_t*)pcSurfaces) + pcSurfaces->OFS_ST); + + // Navigate to the shader list of the surface + BE_NCONST MD3::Shader* pcShaders = (BE_NCONST MD3::Shader*) + (((uint8_t*)pcSurfaces) + pcSurfaces->OFS_SHADERS); + + // If the submesh is empty ignore it + if (0 == pcSurfaces->NUM_VERTICES || 0 == pcSurfaces->NUM_TRIANGLES) + { + pcSurfaces = (BE_NCONST MD3::Surface*)(((uint8_t*)pcSurfaces) + pcSurfaces->OFS_END); + pScene->mNumMeshes--; + continue; + } + + // Allocate output mesh + pScene->mMeshes[iNum] = new aiMesh(); + aiMesh* pcMesh = pScene->mMeshes[iNum]; + + std::string _texture_name; + const char* texture_name = NULL; + + // Check whether we have a texture record for this surface in the .skin file + std::list< Q3Shader::SkinData::TextureEntry >::iterator it = std::find( + skins.textures.begin(), skins.textures.end(), pcSurfaces->NAME ); + + if (it != skins.textures.end()) { + texture_name = &*( _texture_name = (*it).second).begin(); + DefaultLogger::get()->debug("MD3: Assigning skin texture " + (*it).second + " to surface " + pcSurfaces->NAME); + (*it).resolved = true; // mark entry as resolved + } + + // Get the first shader (= texture?) assigned to the surface + if (!texture_name && pcSurfaces->NUM_SHADER) { + texture_name = pcShaders->NAME; + } + + std::string convertedPath; + if (texture_name) { + ConvertPath(texture_name,header_name,convertedPath); + } + + const Q3Shader::ShaderDataBlock* shader = NULL; + + // Now search the current shader for a record with this name ( + // excluding texture file extension) + if (!shaders.blocks.empty()) { + + std::string::size_type s = convertedPath.find_last_of('.'); + if (s == std::string::npos) + s = convertedPath.length(); + + const std::string without_ext = convertedPath.substr(0,s); + std::list< Q3Shader::ShaderDataBlock >::const_iterator dit = std::find(shaders.blocks.begin(),shaders.blocks.end(),without_ext); + if (dit != shaders.blocks.end()) { + // Hurra, wir haben einen. Tolle Sache. + shader = &*dit; + DefaultLogger::get()->info("Found shader record for " +without_ext ); + } + else DefaultLogger::get()->warn("Unable to find shader record for " +without_ext ); + } + + aiMaterial* pcHelper = new aiMaterial(); + + const int iMode = (int)aiShadingMode_Gouraud; + pcHelper->AddProperty<int>(&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 = 0.05f; + pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_AMBIENT); + + clr.b = clr.g = clr.r = 1.0f; + pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_DIFFUSE); + pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_SPECULAR); + + // use surface name + skin_name as material name + aiString name; + name.Set("MD3_[" + configSkinFile + "][" + pcSurfaces->NAME + "]"); + pcHelper->AddProperty(&name,AI_MATKEY_NAME); + + if (!shader) { + // Setup dummy texture file name to ensure UV coordinates are kept during postprocessing + aiString szString; + if (convertedPath.length()) { + szString.Set(convertedPath); + } + else { + DefaultLogger::get()->warn("Texture file name has zero length. Using default name"); + szString.Set("dummy_texture.bmp"); + } + pcHelper->AddProperty(&szString,AI_MATKEY_TEXTURE_DIFFUSE(0)); + + // prevent transparency by default + int no_alpha = aiTextureFlags_IgnoreAlpha; + pcHelper->AddProperty(&no_alpha,1,AI_MATKEY_TEXFLAGS_DIFFUSE(0)); + } + else { + Q3Shader::ConvertShaderToMaterial(pcHelper,*shader); + } + + pScene->mMaterials[iNumMaterials] = (aiMaterial*)pcHelper; + pcMesh->mMaterialIndex = iNumMaterials++; + + // Ensure correct endianness #ifdef AI_BUILD_BIG_ENDIAN - for (uint32_t i = 0; i < pcSurfaces->NUM_VERTICES;++i) { - AI_SWAP2( pcVertices[i].NORMAL ); - AI_SWAP2( pcVertices[i].X ); - AI_SWAP2( pcVertices[i].Y ); - AI_SWAP2( pcVertices[i].Z ); - - AI_SWAP4( pcUVs[i].U ); - AI_SWAP4( pcUVs[i].U ); - } - for (uint32_t i = 0; i < pcSurfaces->NUM_TRIANGLES;++i) { - AI_SWAP4(pcTriangles[i].INDEXES[0]); - AI_SWAP4(pcTriangles[i].INDEXES[1]); - AI_SWAP4(pcTriangles[i].INDEXES[2]); - } + for (uint32_t i = 0; i < pcSurfaces->NUM_VERTICES;++i) { + AI_SWAP2( pcVertices[i].NORMAL ); + AI_SWAP2( pcVertices[i].X ); + AI_SWAP2( pcVertices[i].Y ); + AI_SWAP2( pcVertices[i].Z ); + + AI_SWAP4( pcUVs[i].U ); + AI_SWAP4( pcUVs[i].U ); + } + for (uint32_t i = 0; i < pcSurfaces->NUM_TRIANGLES;++i) { + AI_SWAP4(pcTriangles[i].INDEXES[0]); + AI_SWAP4(pcTriangles[i].INDEXES[1]); + AI_SWAP4(pcTriangles[i].INDEXES[2]); + } #endif - // Fill mesh information - pcMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; - - pcMesh->mNumVertices = pcSurfaces->NUM_TRIANGLES*3; - 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->mNumUVComponents[0] = 2; - - // Fill in all triangles - unsigned int iCurrent = 0; - for (unsigned int i = 0; i < (unsigned int)pcSurfaces->NUM_TRIANGLES;++i) { - pcMesh->mFaces[i].mIndices = new unsigned int[3]; - pcMesh->mFaces[i].mNumIndices = 3; - - //unsigned int iTemp = iCurrent; - for (unsigned int c = 0; c < 3;++c,++iCurrent) { - pcMesh->mFaces[i].mIndices[c] = iCurrent; - - // Read vertices - aiVector3D& vec = pcMesh->mVertices[iCurrent]; - vec.x = pcVertices[ pcTriangles->INDEXES[c]].X*AI_MD3_XYZ_SCALE; - vec.y = pcVertices[ pcTriangles->INDEXES[c]].Y*AI_MD3_XYZ_SCALE; - vec.z = pcVertices[ pcTriangles->INDEXES[c]].Z*AI_MD3_XYZ_SCALE; - - // Convert the normal vector to uncompressed float3 format - aiVector3D& nor = pcMesh->mNormals[iCurrent]; - LatLngNormalToVec3(pcVertices[pcTriangles->INDEXES[c]].NORMAL,(float*)&nor); - - // Read texture coordinates - pcMesh->mTextureCoords[0][iCurrent].x = pcUVs[ pcTriangles->INDEXES[c]].U; - pcMesh->mTextureCoords[0][iCurrent].y = 1.0f-pcUVs[ pcTriangles->INDEXES[c]].V; - } - // Flip face order if necessary - if (!shader || shader->cull == Q3Shader::CULL_CW) { - std::swap(pcMesh->mFaces[i].mIndices[2],pcMesh->mFaces[i].mIndices[1]); - } - pcTriangles++; - } - - // Go to the next surface - pcSurfaces = (BE_NCONST MD3::Surface*)(((unsigned char*)pcSurfaces) + pcSurfaces->OFS_END); - } - - // For debugging purposes: check whether we found matches for all entries in the skins file - if (!DefaultLogger::isNullLogger()) { - for (std::list< Q3Shader::SkinData::TextureEntry>::const_iterator it = skins.textures.begin();it != skins.textures.end(); ++it) { - if (!(*it).resolved) { - DefaultLogger::get()->error("MD3: Failed to match skin " + (*it).first + " to surface " + (*it).second); - } - } - } - - if (!pScene->mNumMeshes) - throw DeadlyImportError( "MD3: File contains no valid mesh"); - pScene->mNumMaterials = iNumMaterials; - - // Now we need to generate an empty node graph - pScene->mRootNode = new aiNode("<MD3Root>"); - pScene->mRootNode->mNumMeshes = pScene->mNumMeshes; - pScene->mRootNode->mMeshes = new unsigned int[pScene->mNumMeshes]; - - // Attach tiny children for all tags - if (pcHeader->NUM_TAGS) { - pScene->mRootNode->mNumChildren = pcHeader->NUM_TAGS; - pScene->mRootNode->mChildren = new aiNode*[pcHeader->NUM_TAGS]; - - for (unsigned int i = 0; i < pcHeader->NUM_TAGS; ++i, ++pcTags) { - - aiNode* nd = pScene->mRootNode->mChildren[i] = new aiNode(); - nd->mName.Set((const char*)pcTags->NAME); - nd->mParent = pScene->mRootNode; - - AI_SWAP4(pcTags->origin.x); - AI_SWAP4(pcTags->origin.y); - AI_SWAP4(pcTags->origin.z); - - // Copy local origin, again flip z,y - nd->mTransformation.a4 = pcTags->origin.x; - nd->mTransformation.b4 = pcTags->origin.y; - nd->mTransformation.c4 = pcTags->origin.z; - - // Copy rest of transformation (need to transpose to match row-order matrix) - for (unsigned int a = 0; a < 3;++a) { - for (unsigned int m = 0; m < 3;++m) { - nd->mTransformation[m][a] = pcTags->orientation[a][m]; - AI_SWAP4(nd->mTransformation[m][a]); - } - } - } - } - - for (unsigned int i = 0; i < pScene->mNumMeshes;++i) - pScene->mRootNode->mMeshes[i] = i; - - // Now rotate the whole scene 90 degrees around the x axis to convert to internal coordinate system - pScene->mRootNode->mTransformation = aiMatrix4x4(1.f,0.f,0.f,0.f, - 0.f,0.f,1.f,0.f,0.f,-1.f,0.f,0.f,0.f,0.f,0.f,1.f); + // Fill mesh information + pcMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + + pcMesh->mNumVertices = pcSurfaces->NUM_TRIANGLES*3; + 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->mNumUVComponents[0] = 2; + + // Fill in all triangles + unsigned int iCurrent = 0; + for (unsigned int i = 0; i < (unsigned int)pcSurfaces->NUM_TRIANGLES;++i) { + pcMesh->mFaces[i].mIndices = new unsigned int[3]; + pcMesh->mFaces[i].mNumIndices = 3; + + //unsigned int iTemp = iCurrent; + for (unsigned int c = 0; c < 3;++c,++iCurrent) { + pcMesh->mFaces[i].mIndices[c] = iCurrent; + + // Read vertices + aiVector3D& vec = pcMesh->mVertices[iCurrent]; + uint32_t index = pcTriangles->INDEXES[c]; + if (index >= pcSurfaces->NUM_VERTICES) { + throw DeadlyImportError( "MD3: Invalid vertex index"); + } + vec.x = pcVertices[index].X*AI_MD3_XYZ_SCALE; + vec.y = pcVertices[index].Y*AI_MD3_XYZ_SCALE; + vec.z = pcVertices[index].Z*AI_MD3_XYZ_SCALE; + + // Convert the normal vector to uncompressed float3 format + aiVector3D& nor = pcMesh->mNormals[iCurrent]; + LatLngNormalToVec3(pcVertices[pcTriangles->INDEXES[c]].NORMAL,(float*)&nor); + + // Read texture coordinates + pcMesh->mTextureCoords[0][iCurrent].x = pcUVs[ pcTriangles->INDEXES[c]].U; + pcMesh->mTextureCoords[0][iCurrent].y = 1.0f-pcUVs[ pcTriangles->INDEXES[c]].V; + } + // Flip face order if necessary + if (!shader || shader->cull == Q3Shader::CULL_CW) { + std::swap(pcMesh->mFaces[i].mIndices[2],pcMesh->mFaces[i].mIndices[1]); + } + pcTriangles++; + } + + // Go to the next surface + pcSurfaces = (BE_NCONST MD3::Surface*)(((unsigned char*)pcSurfaces) + pcSurfaces->OFS_END); + } + + // For debugging purposes: check whether we found matches for all entries in the skins file + if (!DefaultLogger::isNullLogger()) { + for (std::list< Q3Shader::SkinData::TextureEntry>::const_iterator it = skins.textures.begin();it != skins.textures.end(); ++it) { + if (!(*it).resolved) { + DefaultLogger::get()->error("MD3: Failed to match skin " + (*it).first + " to surface " + (*it).second); + } + } + } + + if (!pScene->mNumMeshes) + throw DeadlyImportError( "MD3: File contains no valid mesh"); + pScene->mNumMaterials = iNumMaterials; + + // Now we need to generate an empty node graph + pScene->mRootNode = new aiNode("<MD3Root>"); + pScene->mRootNode->mNumMeshes = pScene->mNumMeshes; + pScene->mRootNode->mMeshes = new unsigned int[pScene->mNumMeshes]; + + // Attach tiny children for all tags + if (pcHeader->NUM_TAGS) { + pScene->mRootNode->mNumChildren = pcHeader->NUM_TAGS; + pScene->mRootNode->mChildren = new aiNode*[pcHeader->NUM_TAGS]; + + for (unsigned int i = 0; i < pcHeader->NUM_TAGS; ++i, ++pcTags) { + + aiNode* nd = pScene->mRootNode->mChildren[i] = new aiNode(); + nd->mName.Set((const char*)pcTags->NAME); + nd->mParent = pScene->mRootNode; + + AI_SWAP4(pcTags->origin.x); + AI_SWAP4(pcTags->origin.y); + AI_SWAP4(pcTags->origin.z); + + // Copy local origin, again flip z,y + nd->mTransformation.a4 = pcTags->origin.x; + nd->mTransformation.b4 = pcTags->origin.y; + nd->mTransformation.c4 = pcTags->origin.z; + + // Copy rest of transformation (need to transpose to match row-order matrix) + for (unsigned int a = 0; a < 3;++a) { + for (unsigned int m = 0; m < 3;++m) { + nd->mTransformation[m][a] = pcTags->orientation[a][m]; + AI_SWAP4(nd->mTransformation[m][a]); + } + } + } + } + + for (unsigned int i = 0; i < pScene->mNumMeshes;++i) + pScene->mRootNode->mMeshes[i] = i; + + // Now rotate the whole scene 90 degrees around the x axis to convert to internal coordinate system + pScene->mRootNode->mTransformation = aiMatrix4x4(1.f,0.f,0.f,0.f, + 0.f,0.f,1.f,0.f,0.f,-1.f,0.f,0.f,0.f,0.f,0.f,1.f); } #endif // !! ASSIMP_BUILD_NO_MD3_IMPORTER |