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 BaseImporter.cpp nuclear@0: * @brief Implementation of BaseImporter nuclear@0: */ nuclear@0: nuclear@0: #include "AssimpPCH.h" nuclear@0: #include "BaseImporter.h" nuclear@0: #include "FileSystemFilter.h" nuclear@0: nuclear@0: #include "Importer.h" nuclear@0: nuclear@0: using namespace Assimp; nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Constructor to be privately used by Importer nuclear@0: BaseImporter::BaseImporter() nuclear@0: : progress() nuclear@0: { nuclear@0: // nothing to do here nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Destructor, private as well nuclear@0: BaseImporter::~BaseImporter() nuclear@0: { nuclear@0: // nothing to do here nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Imports the given file and returns the imported data. nuclear@0: aiScene* BaseImporter::ReadFile(const Importer* pImp, const std::string& pFile, IOSystem* pIOHandler) nuclear@0: { nuclear@0: progress = pImp->GetProgressHandler(); nuclear@0: ai_assert(progress); nuclear@0: nuclear@0: // Gather configuration properties for this run nuclear@0: SetupProperties( pImp ); nuclear@0: nuclear@0: // Construct a file system filter to improve our success ratio at reading external files nuclear@0: FileSystemFilter filter(pFile,pIOHandler); nuclear@0: nuclear@0: // create a scene object to hold the data nuclear@0: ScopeGuard sc(new aiScene()); nuclear@0: nuclear@0: // dispatch importing nuclear@0: try nuclear@0: { nuclear@0: InternReadFile( pFile, sc, &filter); nuclear@0: nuclear@0: } catch( const std::exception& err ) { nuclear@0: // extract error description nuclear@0: mErrorText = err.what(); nuclear@0: DefaultLogger::get()->error(mErrorText); nuclear@0: return NULL; nuclear@0: } nuclear@0: nuclear@0: // return what we gathered from the import. nuclear@0: sc.dismiss(); nuclear@0: return sc; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void BaseImporter::SetupProperties(const Importer* /*pImp*/) nuclear@0: { nuclear@0: // the default implementation does nothing nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void BaseImporter::GetExtensionList(std::set& extensions) nuclear@0: { nuclear@0: const aiImporterDesc* desc = GetInfo(); nuclear@0: ai_assert(desc != NULL); nuclear@0: nuclear@0: const char* ext = desc->mFileExtensions; nuclear@0: ai_assert(ext != NULL); nuclear@0: nuclear@0: const char* last = ext; nuclear@0: do { nuclear@0: if (!*ext || *ext == ' ') { nuclear@0: extensions.insert(std::string(last,ext-last)); nuclear@0: ai_assert(ext-last > 0); nuclear@0: last = ext; nuclear@0: while(*last == ' ') { nuclear@0: ++last; nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: while(*ext++); nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: /*static*/ bool BaseImporter::SearchFileHeaderForToken(IOSystem* pIOHandler, nuclear@0: const std::string& pFile, nuclear@0: const char** tokens, nuclear@0: unsigned int numTokens, nuclear@0: unsigned int searchBytes /* = 200 */, nuclear@0: bool tokensSol /* false */) nuclear@0: { nuclear@0: ai_assert(NULL != tokens && 0 != numTokens && 0 != searchBytes); nuclear@0: if (!pIOHandler) nuclear@0: return false; nuclear@0: nuclear@0: boost::scoped_ptr pStream (pIOHandler->Open(pFile)); nuclear@0: if (pStream.get() ) { nuclear@0: // read 200 characters from the file nuclear@0: boost::scoped_array _buffer (new char[searchBytes+1 /* for the '\0' */]); nuclear@0: char* buffer = _buffer.get(); nuclear@0: nuclear@0: const unsigned int read = pStream->Read(buffer,1,searchBytes); nuclear@0: if (!read) nuclear@0: return false; nuclear@0: nuclear@0: for (unsigned int i = 0; i < read;++i) nuclear@0: buffer[i] = ::tolower(buffer[i]); nuclear@0: nuclear@0: // It is not a proper handling of unicode files here ... nuclear@0: // ehm ... but it works in most cases. nuclear@0: char* cur = buffer,*cur2 = buffer,*end = &buffer[read]; nuclear@0: while (cur != end) { nuclear@0: if (*cur) nuclear@0: *cur2++ = *cur; nuclear@0: ++cur; nuclear@0: } nuclear@0: *cur2 = '\0'; nuclear@0: nuclear@0: for (unsigned int i = 0; i < numTokens;++i) { nuclear@0: ai_assert(NULL != tokens[i]); nuclear@0: nuclear@0: nuclear@0: const char* r = strstr(buffer,tokens[i]); nuclear@0: if (!r) nuclear@0: continue; nuclear@0: // We got a match, either we don't care where it is, or it happens to nuclear@0: // be in the beginning of the file / line nuclear@0: if (!tokensSol || r == buffer || r[-1] == '\r' || r[-1] == '\n') { nuclear@0: DefaultLogger::get()->debug(std::string("Found positive match for header keyword: ") + tokens[i]); nuclear@0: return true; nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: return false; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Simple check for file extension nuclear@0: /*static*/ bool BaseImporter::SimpleExtensionCheck (const std::string& pFile, nuclear@0: const char* ext0, nuclear@0: const char* ext1, nuclear@0: const char* ext2) nuclear@0: { nuclear@0: std::string::size_type pos = pFile.find_last_of('.'); nuclear@0: nuclear@0: // no file extension - can't read nuclear@0: if( pos == std::string::npos) nuclear@0: return false; nuclear@0: nuclear@0: const char* ext_real = & pFile[ pos+1 ]; nuclear@0: if( !ASSIMP_stricmp(ext_real,ext0) ) nuclear@0: return true; nuclear@0: nuclear@0: // check for other, optional, file extensions nuclear@0: if (ext1 && !ASSIMP_stricmp(ext_real,ext1)) nuclear@0: return true; nuclear@0: nuclear@0: if (ext2 && !ASSIMP_stricmp(ext_real,ext2)) nuclear@0: return true; nuclear@0: nuclear@0: return false; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Get file extension from path nuclear@0: /*static*/ std::string BaseImporter::GetExtension (const std::string& pFile) nuclear@0: { nuclear@0: std::string::size_type pos = pFile.find_last_of('.'); nuclear@0: nuclear@0: // no file extension at all nuclear@0: if( pos == std::string::npos) nuclear@0: return ""; nuclear@0: nuclear@0: std::string ret = pFile.substr(pos+1); nuclear@0: std::transform(ret.begin(),ret.end(),ret.begin(),::tolower); // thanks to Andy Maloney for the hint nuclear@0: return ret; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Check for magic bytes at the beginning of the file. nuclear@0: /* static */ bool BaseImporter::CheckMagicToken(IOSystem* pIOHandler, const std::string& pFile, nuclear@0: const void* _magic, unsigned int num, unsigned int offset, unsigned int size) nuclear@0: { nuclear@0: ai_assert(size <= 16 && _magic); nuclear@0: nuclear@0: if (!pIOHandler) { nuclear@0: return false; nuclear@0: } nuclear@0: union { nuclear@0: const char* magic; nuclear@0: const uint16_t* magic_u16; nuclear@0: const uint32_t* magic_u32; nuclear@0: }; nuclear@0: magic = reinterpret_cast(_magic); nuclear@0: boost::scoped_ptr pStream (pIOHandler->Open(pFile)); nuclear@0: if (pStream.get() ) { nuclear@0: nuclear@0: // skip to offset nuclear@0: pStream->Seek(offset,aiOrigin_SET); nuclear@0: nuclear@0: // read 'size' characters from the file nuclear@0: union { nuclear@0: char data[16]; nuclear@0: uint16_t data_u16[8]; nuclear@0: uint32_t data_u32[4]; nuclear@0: }; nuclear@0: if(size != pStream->Read(data,1,size)) { nuclear@0: return false; nuclear@0: } nuclear@0: nuclear@0: for (unsigned int i = 0; i < num; ++i) { nuclear@0: // also check against big endian versions of tokens with size 2,4 nuclear@0: // that's just for convinience, the chance that we cause conflicts nuclear@0: // is quite low and it can save some lines and prevent nasty bugs nuclear@0: if (2 == size) { nuclear@0: uint16_t rev = *magic_u16; nuclear@0: ByteSwap::Swap(&rev); nuclear@0: if (data_u16[0] == *magic_u16 || data_u16[0] == rev) { nuclear@0: return true; nuclear@0: } nuclear@0: } nuclear@0: else if (4 == size) { nuclear@0: uint32_t rev = *magic_u32; nuclear@0: ByteSwap::Swap(&rev); nuclear@0: if (data_u32[0] == *magic_u32 || data_u32[0] == rev) { nuclear@0: return true; nuclear@0: } nuclear@0: } nuclear@0: else { nuclear@0: // any length ... just compare nuclear@0: if(!memcmp(magic,data,size)) { nuclear@0: return true; nuclear@0: } nuclear@0: } nuclear@0: magic += size; nuclear@0: } nuclear@0: } nuclear@0: return false; nuclear@0: } nuclear@0: nuclear@0: #include "ConvertUTF/ConvertUTF.h" nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void ReportResult(ConversionResult res) nuclear@0: { nuclear@0: if(res == sourceExhausted) { nuclear@0: DefaultLogger::get()->error("Source ends with incomplete character sequence, transformation to UTF-8 fails"); nuclear@0: } nuclear@0: else if(res == sourceIllegal) { nuclear@0: DefaultLogger::get()->error("Source contains illegal character sequence, transformation to UTF-8 fails"); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Convert to UTF8 data nuclear@0: void BaseImporter::ConvertToUTF8(std::vector& data) nuclear@0: { nuclear@0: ConversionResult result; nuclear@0: if(data.size() < 8) { nuclear@0: throw DeadlyImportError("File is too small"); nuclear@0: } nuclear@0: nuclear@0: // UTF 8 with BOM nuclear@0: if((uint8_t)data[0] == 0xEF && (uint8_t)data[1] == 0xBB && (uint8_t)data[2] == 0xBF) { nuclear@0: DefaultLogger::get()->debug("Found UTF-8 BOM ..."); nuclear@0: nuclear@0: std::copy(data.begin()+3,data.end(),data.begin()); nuclear@0: data.resize(data.size()-3); nuclear@0: return; nuclear@0: } nuclear@0: nuclear@0: // UTF 32 BE with BOM nuclear@0: if(*((uint32_t*)&data.front()) == 0xFFFE0000) { nuclear@0: nuclear@0: // swap the endianess .. nuclear@0: for(uint32_t* p = (uint32_t*)&data.front(), *end = (uint32_t*)&data.back(); p <= end; ++p) { nuclear@0: AI_SWAP4P(p); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // UTF 32 LE with BOM nuclear@0: if(*((uint32_t*)&data.front()) == 0x0000FFFE) { nuclear@0: DefaultLogger::get()->debug("Found UTF-32 BOM ..."); nuclear@0: nuclear@0: const uint32_t* sstart = (uint32_t*)&data.front()+1, *send = (uint32_t*)&data.back()+1; nuclear@0: char* dstart,*dend; nuclear@0: std::vector output; nuclear@0: do { nuclear@0: output.resize(output.size()?output.size()*3/2:data.size()/2); nuclear@0: dstart = &output.front(),dend = &output.back()+1; nuclear@0: nuclear@0: result = ConvertUTF32toUTF8((const UTF32**)&sstart,(const UTF32*)send,(UTF8**)&dstart,(UTF8*)dend,lenientConversion); nuclear@0: } while(result == targetExhausted); nuclear@0: nuclear@0: ReportResult(result); nuclear@0: nuclear@0: // copy to output buffer. nuclear@0: const size_t outlen = (size_t)(dstart-&output.front()); nuclear@0: data.assign(output.begin(),output.begin()+outlen); nuclear@0: return; nuclear@0: } nuclear@0: nuclear@0: // UTF 16 BE with BOM nuclear@0: if(*((uint16_t*)&data.front()) == 0xFFFE) { nuclear@0: nuclear@0: // swap the endianess .. nuclear@0: for(uint16_t* p = (uint16_t*)&data.front(), *end = (uint16_t*)&data.back(); p <= end; ++p) { nuclear@0: ByteSwap::Swap2(p); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // UTF 16 LE with BOM nuclear@0: if(*((uint16_t*)&data.front()) == 0xFEFF) { nuclear@0: DefaultLogger::get()->debug("Found UTF-16 BOM ..."); nuclear@0: nuclear@0: const uint16_t* sstart = (uint16_t*)&data.front()+1, *send = (uint16_t*)(&data.back()+1); nuclear@0: char* dstart,*dend; nuclear@0: std::vector output; nuclear@0: do { nuclear@0: output.resize(output.size()?output.size()*3/2:data.size()*3/4); nuclear@0: dstart = &output.front(),dend = &output.back()+1; nuclear@0: nuclear@0: result = ConvertUTF16toUTF8((const UTF16**)&sstart,(const UTF16*)send,(UTF8**)&dstart,(UTF8*)dend,lenientConversion); nuclear@0: } while(result == targetExhausted); nuclear@0: nuclear@0: ReportResult(result); nuclear@0: nuclear@0: // copy to output buffer. nuclear@0: const size_t outlen = (size_t)(dstart-&output.front()); nuclear@0: data.assign(output.begin(),output.begin()+outlen); nuclear@0: return; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void BaseImporter::TextFileToBuffer(IOStream* stream, nuclear@0: std::vector& data) nuclear@0: { nuclear@0: ai_assert(NULL != stream); nuclear@0: nuclear@0: const size_t fileSize = stream->FileSize(); nuclear@0: if(!fileSize) { nuclear@0: throw DeadlyImportError("File is empty"); nuclear@0: } nuclear@0: nuclear@0: data.reserve(fileSize+1); nuclear@0: data.resize(fileSize); nuclear@0: if(fileSize != stream->Read( &data[0], 1, fileSize)) { nuclear@0: throw DeadlyImportError("File read error"); nuclear@0: } nuclear@0: nuclear@0: ConvertToUTF8(data); nuclear@0: nuclear@0: // append a binary zero to simplify string parsing nuclear@0: data.push_back(0); nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: namespace Assimp nuclear@0: { nuclear@0: // Represents an import request nuclear@0: struct LoadRequest nuclear@0: { nuclear@0: LoadRequest(const std::string& _file, unsigned int _flags,const BatchLoader::PropertyMap* _map, unsigned int _id) nuclear@0: : file(_file), flags(_flags), refCnt(1),scene(NULL), loaded(false), id(_id) nuclear@0: { nuclear@0: if (_map) nuclear@0: map = *_map; nuclear@0: } nuclear@0: nuclear@0: const std::string file; nuclear@0: unsigned int flags; nuclear@0: unsigned int refCnt; nuclear@0: aiScene* scene; nuclear@0: bool loaded; nuclear@0: BatchLoader::PropertyMap map; nuclear@0: unsigned int id; nuclear@0: nuclear@0: bool operator== (const std::string& f) { nuclear@0: return file == f; nuclear@0: } nuclear@0: }; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // BatchLoader::pimpl data structure nuclear@0: struct Assimp::BatchData nuclear@0: { nuclear@0: BatchData() nuclear@0: : next_id(0xffff) nuclear@0: {} nuclear@0: nuclear@0: // IO system to be used for all imports nuclear@0: IOSystem* pIOSystem; nuclear@0: nuclear@0: // Importer used to load all meshes nuclear@0: Importer* pImporter; nuclear@0: nuclear@0: // List of all imports nuclear@0: std::list requests; nuclear@0: nuclear@0: // Base path nuclear@0: std::string pathBase; nuclear@0: nuclear@0: // Id for next item nuclear@0: unsigned int next_id; nuclear@0: }; nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: BatchLoader::BatchLoader(IOSystem* pIO) nuclear@0: { nuclear@0: ai_assert(NULL != pIO); nuclear@0: nuclear@0: data = new BatchData(); nuclear@0: data->pIOSystem = pIO; nuclear@0: nuclear@0: data->pImporter = new Importer(); nuclear@0: data->pImporter->SetIOHandler(data->pIOSystem); nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: BatchLoader::~BatchLoader() nuclear@0: { nuclear@0: // delete all scenes wthat have not been polled by the user nuclear@0: for (std::list::iterator it = data->requests.begin();it != data->requests.end(); ++it) { nuclear@0: nuclear@0: delete (*it).scene; nuclear@0: } nuclear@0: data->pImporter->SetIOHandler(NULL); /* get pointer back into our posession */ nuclear@0: delete data->pImporter; nuclear@0: delete data; nuclear@0: } nuclear@0: nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: unsigned int BatchLoader::AddLoadRequest (const std::string& file, nuclear@0: unsigned int steps /*= 0*/, const PropertyMap* map /*= NULL*/) nuclear@0: { nuclear@0: ai_assert(!file.empty()); nuclear@0: nuclear@0: // check whether we have this loading request already nuclear@0: std::list::iterator it; nuclear@0: for (it = data->requests.begin();it != data->requests.end(); ++it) { nuclear@0: nuclear@0: // Call IOSystem's path comparison function here nuclear@0: if (data->pIOSystem->ComparePaths((*it).file,file)) { nuclear@0: nuclear@0: if (map) { nuclear@0: if (!((*it).map == *map)) nuclear@0: continue; nuclear@0: } nuclear@0: else if (!(*it).map.empty()) nuclear@0: continue; nuclear@0: nuclear@0: (*it).refCnt++; nuclear@0: return (*it).id; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // no, we don't have it. So add it to the queue ... nuclear@0: data->requests.push_back(LoadRequest(file,steps,map,data->next_id)); nuclear@0: return data->next_id++; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: aiScene* BatchLoader::GetImport (unsigned int which) nuclear@0: { nuclear@0: for (std::list::iterator it = data->requests.begin();it != data->requests.end(); ++it) { nuclear@0: nuclear@0: if ((*it).id == which && (*it).loaded) { nuclear@0: nuclear@0: aiScene* sc = (*it).scene; nuclear@0: if (!(--(*it).refCnt)) { nuclear@0: data->requests.erase(it); nuclear@0: } nuclear@0: return sc; nuclear@0: } nuclear@0: } nuclear@0: return NULL; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void BatchLoader::LoadAll() nuclear@0: { nuclear@0: // no threaded implementation for the moment nuclear@0: for (std::list::iterator it = data->requests.begin();it != data->requests.end(); ++it) { nuclear@0: // force validation in debug builds nuclear@0: unsigned int pp = (*it).flags; nuclear@0: #ifdef _DEBUG nuclear@0: pp |= aiProcess_ValidateDataStructure; nuclear@0: #endif nuclear@0: // setup config properties if necessary nuclear@0: ImporterPimpl* pimpl = data->pImporter->Pimpl(); nuclear@0: pimpl->mFloatProperties = (*it).map.floats; nuclear@0: pimpl->mIntProperties = (*it).map.ints; nuclear@0: pimpl->mStringProperties = (*it).map.strings; nuclear@0: nuclear@0: if (!DefaultLogger::isNullLogger()) nuclear@0: { nuclear@0: DefaultLogger::get()->info("%%% BEGIN EXTERNAL FILE %%%"); nuclear@0: DefaultLogger::get()->info("File: " + (*it).file); nuclear@0: } nuclear@0: data->pImporter->ReadFile((*it).file,pp); nuclear@0: (*it).scene = data->pImporter->GetOrphanedScene(); nuclear@0: (*it).loaded = true; nuclear@0: nuclear@0: DefaultLogger::get()->info("%%% END EXTERNAL FILE %%%"); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: nuclear@0: nuclear@0: