| /* |
| --------------------------------------------------------------------------- |
| Open Asset Import Library (assimp) |
| --------------------------------------------------------------------------- |
| |
| Copyright (c) 2006-2017, 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 |
| conditions are met: |
| |
| * Redistributions of source code must retain the above |
| copyright notice, this list of conditions and the |
| following disclaimer. |
| |
| * Redistributions in binary form must reproduce the above |
| copyright notice, this list of conditions and the |
| following disclaimer in the documentation and/or other |
| materials provided with the distribution. |
| |
| * Neither the name of the assimp team, nor the names of its |
| contributors may be used to endorse or promote products |
| derived from this software without specific prior |
| written permission of the assimp team. |
| |
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| --------------------------------------------------------------------------- |
| */ |
| |
| /** @file BaseImporter.cpp |
| * @brief Implementation of BaseImporter |
| */ |
| |
| #include "BaseImporter.h" |
| #include "FileSystemFilter.h" |
| #include "Importer.h" |
| #include "ByteSwapper.h" |
| #include <assimp/scene.h> |
| #include <assimp/Importer.hpp> |
| #include <assimp/postprocess.h> |
| #include <assimp/importerdesc.h> |
| #include <ios> |
| #include <list> |
| #include <memory> |
| #include <sstream> |
| #include <cctype> |
| |
| using namespace Assimp; |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Constructor to be privately used by Importer |
| BaseImporter::BaseImporter() |
| : m_progress() |
| { |
| // nothing to do here |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Destructor, private as well |
| BaseImporter::~BaseImporter() |
| { |
| // nothing to do here |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Imports the given file and returns the imported data. |
| aiScene* BaseImporter::ReadFile(const Importer* pImp, const std::string& pFile, IOSystem* pIOHandler) |
| { |
| m_progress = pImp->GetProgressHandler(); |
| ai_assert(m_progress); |
| |
| // Gather configuration properties for this run |
| SetupProperties( pImp ); |
| |
| // Construct a file system filter to improve our success ratio at reading external files |
| FileSystemFilter filter(pFile,pIOHandler); |
| |
| // create a scene object to hold the data |
| std::unique_ptr<aiScene> sc(new aiScene()); |
| |
| // dispatch importing |
| try |
| { |
| InternReadFile( pFile, sc.get(), &filter); |
| |
| } catch( const std::exception& err ) { |
| // extract error description |
| m_ErrorText = err.what(); |
| DefaultLogger::get()->error(m_ErrorText); |
| return NULL; |
| } |
| |
| // return what we gathered from the import. |
| return sc.release(); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| void BaseImporter::SetupProperties(const Importer* /*pImp*/) |
| { |
| // the default implementation does nothing |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| void BaseImporter::GetExtensionList(std::set<std::string>& extensions) |
| { |
| const aiImporterDesc* desc = GetInfo(); |
| ai_assert(desc != NULL); |
| |
| const char* ext = desc->mFileExtensions; |
| ai_assert(ext != NULL); |
| |
| const char* last = ext; |
| do { |
| if (!*ext || *ext == ' ') { |
| extensions.insert(std::string(last,ext-last)); |
| ai_assert(ext-last > 0); |
| last = ext; |
| while(*last == ' ') { |
| ++last; |
| } |
| } |
| } |
| while(*ext++); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| /*static*/ bool BaseImporter::SearchFileHeaderForToken( IOSystem* pIOHandler, |
| const std::string& pFile, |
| const char** tokens, |
| unsigned int numTokens, |
| unsigned int searchBytes /* = 200 */, |
| bool tokensSol /* false */) |
| { |
| ai_assert( NULL != tokens ); |
| ai_assert( 0 != numTokens ); |
| ai_assert( 0 != searchBytes); |
| |
| if (!pIOHandler) |
| return false; |
| |
| std::unique_ptr<IOStream> pStream (pIOHandler->Open(pFile)); |
| if (pStream.get() ) { |
| // read 200 characters from the file |
| std::unique_ptr<char[]> _buffer (new char[searchBytes+1 /* for the '\0' */]); |
| char* buffer = _buffer.get(); |
| if( NULL == buffer ) { |
| return false; |
| } |
| |
| const size_t read = pStream->Read(buffer,1,searchBytes); |
| if( !read ) { |
| return false; |
| } |
| |
| for( size_t i = 0; i < read; ++i ) { |
| buffer[ i ] = ::tolower( buffer[ i ] ); |
| } |
| |
| // It is not a proper handling of unicode files here ... |
| // ehm ... but it works in most cases. |
| char* cur = buffer,*cur2 = buffer,*end = &buffer[read]; |
| while (cur != end) { |
| if( *cur ) { |
| *cur2++ = *cur; |
| } |
| ++cur; |
| } |
| *cur2 = '\0'; |
| |
| for (unsigned int i = 0; i < numTokens;++i) { |
| ai_assert(NULL != tokens[i]); |
| const char* r = strstr(buffer,tokens[i]); |
| if( !r ) { |
| continue; |
| } |
| // We got a match, either we don't care where it is, or it happens to |
| // be in the beginning of the file / line |
| if (!tokensSol || r == buffer || r[-1] == '\r' || r[-1] == '\n') { |
| DefaultLogger::get()->debug(std::string("Found positive match for header keyword: ") + tokens[i]); |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Simple check for file extension |
| /*static*/ bool BaseImporter::SimpleExtensionCheck (const std::string& pFile, |
| const char* ext0, |
| const char* ext1, |
| const char* ext2) |
| { |
| std::string::size_type pos = pFile.find_last_of('.'); |
| |
| // no file extension - can't read |
| if( pos == std::string::npos) |
| return false; |
| |
| const char* ext_real = & pFile[ pos+1 ]; |
| if( !ASSIMP_stricmp(ext_real,ext0) ) |
| return true; |
| |
| // check for other, optional, file extensions |
| if (ext1 && !ASSIMP_stricmp(ext_real,ext1)) |
| return true; |
| |
| if (ext2 && !ASSIMP_stricmp(ext_real,ext2)) |
| return true; |
| |
| return false; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Get file extension from path |
| /*static*/ std::string BaseImporter::GetExtension (const std::string& pFile) |
| { |
| std::string::size_type pos = pFile.find_last_of('.'); |
| |
| // no file extension at all |
| if( pos == std::string::npos) |
| return ""; |
| |
| std::string ret = pFile.substr(pos+1); |
| std::transform(ret.begin(),ret.end(),ret.begin(),::tolower); // thanks to Andy Maloney for the hint |
| return ret; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Check for magic bytes at the beginning of the file. |
| /* static */ bool BaseImporter::CheckMagicToken(IOSystem* pIOHandler, const std::string& pFile, |
| const void* _magic, unsigned int num, unsigned int offset, unsigned int size) |
| { |
| ai_assert(size <= 16 && _magic); |
| |
| if (!pIOHandler) { |
| return false; |
| } |
| union { |
| const char* magic; |
| const uint16_t* magic_u16; |
| const uint32_t* magic_u32; |
| }; |
| magic = reinterpret_cast<const char*>(_magic); |
| std::unique_ptr<IOStream> pStream (pIOHandler->Open(pFile)); |
| if (pStream.get() ) { |
| |
| // skip to offset |
| pStream->Seek(offset,aiOrigin_SET); |
| |
| // read 'size' characters from the file |
| union { |
| char data[16]; |
| uint16_t data_u16[8]; |
| uint32_t data_u32[4]; |
| }; |
| if(size != pStream->Read(data,1,size)) { |
| return false; |
| } |
| |
| for (unsigned int i = 0; i < num; ++i) { |
| // also check against big endian versions of tokens with size 2,4 |
| // that's just for convenience, the chance that we cause conflicts |
| // is quite low and it can save some lines and prevent nasty bugs |
| if (2 == size) { |
| uint16_t rev = *magic_u16; |
| ByteSwap::Swap(&rev); |
| if (data_u16[0] == *magic_u16 || data_u16[0] == rev) { |
| return true; |
| } |
| } |
| else if (4 == size) { |
| uint32_t rev = *magic_u32; |
| ByteSwap::Swap(&rev); |
| if (data_u32[0] == *magic_u32 || data_u32[0] == rev) { |
| return true; |
| } |
| } |
| else { |
| // any length ... just compare |
| if(!memcmp(magic,data,size)) { |
| return true; |
| } |
| } |
| magic += size; |
| } |
| } |
| return false; |
| } |
| |
| #include "../contrib/utf8cpp/source/utf8.h" |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Convert to UTF8 data |
| void BaseImporter::ConvertToUTF8(std::vector<char>& data) |
| { |
| //ConversionResult result; |
| if(data.size() < 8) { |
| throw DeadlyImportError("File is too small"); |
| } |
| |
| // UTF 8 with BOM |
| if((uint8_t)data[0] == 0xEF && (uint8_t)data[1] == 0xBB && (uint8_t)data[2] == 0xBF) { |
| DefaultLogger::get()->debug("Found UTF-8 BOM ..."); |
| |
| std::copy(data.begin()+3,data.end(),data.begin()); |
| data.resize(data.size()-3); |
| return; |
| } |
| |
| |
| // UTF 32 BE with BOM |
| if(*((uint32_t*)&data.front()) == 0xFFFE0000) { |
| |
| // swap the endianness .. |
| for(uint32_t* p = (uint32_t*)&data.front(), *end = (uint32_t*)&data.back(); p <= end; ++p) { |
| AI_SWAP4P(p); |
| } |
| } |
| |
| // UTF 32 LE with BOM |
| if(*((uint32_t*)&data.front()) == 0x0000FFFE) { |
| DefaultLogger::get()->debug("Found UTF-32 BOM ..."); |
| |
| std::vector<char> output; |
| int *ptr = (int*)&data[ 0 ]; |
| int *end = ptr + ( data.size() / sizeof(int) ) +1; |
| utf8::utf32to8( ptr, end, back_inserter(output)); |
| return; |
| } |
| |
| // UTF 16 BE with BOM |
| if(*((uint16_t*)&data.front()) == 0xFFFE) { |
| |
| // swap the endianness .. |
| for(uint16_t* p = (uint16_t*)&data.front(), *end = (uint16_t*)&data.back(); p <= end; ++p) { |
| ByteSwap::Swap2(p); |
| } |
| } |
| |
| // UTF 16 LE with BOM |
| if(*((uint16_t*)&data.front()) == 0xFEFF) { |
| DefaultLogger::get()->debug("Found UTF-16 BOM ..."); |
| |
| std::vector<unsigned char> output; |
| utf8::utf16to8(data.begin(), data.end(), back_inserter(output)); |
| return; |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Convert to UTF8 data to ISO-8859-1 |
| void BaseImporter::ConvertUTF8toISO8859_1(std::string& data) |
| { |
| size_t size = data.size(); |
| size_t i = 0, j = 0; |
| |
| while(i < size) { |
| if ((unsigned char) data[i] < (size_t) 0x80) { |
| data[j] = data[i]; |
| } else if(i < size - 1) { |
| if((unsigned char) data[i] == 0xC2) { |
| data[j] = data[++i]; |
| } else if((unsigned char) data[i] == 0xC3) { |
| data[j] = ((unsigned char) data[++i] + 0x40); |
| } else { |
| std::stringstream stream; |
| |
| stream << "UTF8 code " << std::hex << data[i] << data[i + 1] << " can not be converted into ISA-8859-1."; |
| |
| DefaultLogger::get()->error(stream.str()); |
| |
| data[j++] = data[i++]; |
| data[j] = data[i]; |
| } |
| } else { |
| DefaultLogger::get()->error("UTF8 code but only one character remaining"); |
| |
| data[j] = data[i]; |
| } |
| |
| i++; j++; |
| } |
| |
| data.resize(j); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| void BaseImporter::TextFileToBuffer(IOStream* stream, |
| std::vector<char>& data, |
| TextFileMode mode) |
| { |
| ai_assert(NULL != stream); |
| |
| const size_t fileSize = stream->FileSize(); |
| if (mode == FORBID_EMPTY) { |
| if(!fileSize) { |
| throw DeadlyImportError("File is empty"); |
| } |
| } |
| |
| data.reserve(fileSize+1); |
| data.resize(fileSize); |
| if(fileSize > 0) { |
| if(fileSize != stream->Read( &data[0], 1, fileSize)) { |
| throw DeadlyImportError("File read error"); |
| } |
| |
| ConvertToUTF8(data); |
| } |
| |
| // append a binary zero to simplify string parsing |
| data.push_back(0); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| namespace Assimp { |
| // Represents an import request |
| struct LoadRequest { |
| LoadRequest(const std::string& _file, unsigned int _flags,const BatchLoader::PropertyMap* _map, unsigned int _id) |
| : file(_file) |
| , flags(_flags) |
| , refCnt(1) |
| , scene(NULL) |
| , loaded(false) |
| , id(_id) { |
| if ( _map ) { |
| map = *_map; |
| } |
| } |
| |
| bool operator== ( const std::string& f ) const { |
| return file == f; |
| } |
| |
| const std::string file; |
| unsigned int flags; |
| unsigned int refCnt; |
| aiScene *scene; |
| bool loaded; |
| BatchLoader::PropertyMap map; |
| unsigned int id; |
| }; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // BatchLoader::pimpl data structure |
| struct Assimp::BatchData { |
| BatchData( IOSystem* pIO, bool validate ) |
| : pIOSystem( pIO ) |
| , pImporter( nullptr ) |
| , next_id(0xffff) |
| , validate( validate ) { |
| ai_assert( NULL != pIO ); |
| |
| pImporter = new Importer(); |
| pImporter->SetIOHandler( pIO ); |
| } |
| |
| ~BatchData() { |
| pImporter->SetIOHandler( NULL ); /* get pointer back into our possession */ |
| delete pImporter; |
| } |
| |
| // IO system to be used for all imports |
| IOSystem* pIOSystem; |
| |
| // Importer used to load all meshes |
| Importer* pImporter; |
| |
| // List of all imports |
| std::list<LoadRequest> requests; |
| |
| // Base path |
| std::string pathBase; |
| |
| // Id for next item |
| unsigned int next_id; |
| |
| // Validation enabled state |
| bool validate; |
| }; |
| |
| typedef std::list<LoadRequest>::iterator LoadReqIt; |
| |
| // ------------------------------------------------------------------------------------------------ |
| BatchLoader::BatchLoader(IOSystem* pIO, bool validate ) |
| { |
| ai_assert(NULL != pIO); |
| |
| m_data = new BatchData( pIO, validate ); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| BatchLoader::~BatchLoader() |
| { |
| // delete all scenes what have not been polled by the user |
| for ( LoadReqIt it = m_data->requests.begin();it != m_data->requests.end(); ++it) { |
| delete (*it).scene; |
| } |
| delete m_data; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| void BatchLoader::setValidation( bool enabled ) { |
| m_data->validate = enabled; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| bool BatchLoader::getValidation() const { |
| return m_data->validate; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| unsigned int BatchLoader::AddLoadRequest(const std::string& file, |
| unsigned int steps /*= 0*/, const PropertyMap* map /*= NULL*/) |
| { |
| ai_assert(!file.empty()); |
| |
| // check whether we have this loading request already |
| for ( LoadReqIt it = m_data->requests.begin();it != m_data->requests.end(); ++it) { |
| // Call IOSystem's path comparison function here |
| if ( m_data->pIOSystem->ComparePaths((*it).file,file)) { |
| if (map) { |
| if ( !( ( *it ).map == *map ) ) { |
| continue; |
| } |
| } |
| else if ( !( *it ).map.empty() ) { |
| continue; |
| } |
| |
| (*it).refCnt++; |
| return (*it).id; |
| } |
| } |
| |
| // no, we don't have it. So add it to the queue ... |
| m_data->requests.push_back(LoadRequest(file,steps,map, m_data->next_id)); |
| return m_data->next_id++; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| aiScene* BatchLoader::GetImport( unsigned int which ) |
| { |
| for ( LoadReqIt it = m_data->requests.begin();it != m_data->requests.end(); ++it) { |
| if ((*it).id == which && (*it).loaded) { |
| aiScene* sc = (*it).scene; |
| if (!(--(*it).refCnt)) { |
| m_data->requests.erase(it); |
| } |
| return sc; |
| } |
| } |
| return NULL; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| void BatchLoader::LoadAll() |
| { |
| // no threaded implementation for the moment |
| for ( LoadReqIt it = m_data->requests.begin();it != m_data->requests.end(); ++it) { |
| // force validation in debug builds |
| unsigned int pp = (*it).flags; |
| if ( m_data->validate ) { |
| pp |= aiProcess_ValidateDataStructure; |
| } |
| |
| // setup config properties if necessary |
| ImporterPimpl* pimpl = m_data->pImporter->Pimpl(); |
| pimpl->mFloatProperties = (*it).map.floats; |
| pimpl->mIntProperties = (*it).map.ints; |
| pimpl->mStringProperties = (*it).map.strings; |
| pimpl->mMatrixProperties = (*it).map.matrices; |
| |
| if (!DefaultLogger::isNullLogger()) |
| { |
| DefaultLogger::get()->info("%%% BEGIN EXTERNAL FILE %%%"); |
| DefaultLogger::get()->info("File: " + (*it).file); |
| } |
| m_data->pImporter->ReadFile((*it).file,pp); |
| (*it).scene = m_data->pImporter->GetOrphanedScene(); |
| (*it).loaded = true; |
| |
| DefaultLogger::get()->info("%%% END EXTERNAL FILE %%%"); |
| } |
| } |