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