1/*
2---------------------------------------------------------------------------
3Open Asset Import Library (assimp)
4---------------------------------------------------------------------------
5
6Copyright (c) 2006-2017, assimp team
7
8
9All rights reserved.
10
11Redistribution and use of this software in source and binary forms,
12with or without modification, are permitted provided that the following
13conditions are met:
14
15* Redistributions of source code must retain the above
16 copyright notice, this list of conditions and the
17 following disclaimer.
18
19* Redistributions in binary form must reproduce the above
20 copyright notice, this list of conditions and the
21 following disclaimer in the documentation and/or other
22 materials provided with the distribution.
23
24* Neither the name of the assimp team, nor the names of its
25 contributors may be used to endorse or promote products
26 derived from this software without specific prior
27 written permission of the assimp team.
28
29THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
30"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
31LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
32A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
33OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
34SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
36DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
37THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
38(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
39OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40---------------------------------------------------------------------------
41*/
42
43/** @file BaseImporter.cpp
44 * @brief Implementation of BaseImporter
45 */
46
47#include "BaseImporter.h"
48#include "FileSystemFilter.h"
49#include "Importer.h"
50#include "ByteSwapper.h"
51#include <assimp/scene.h>
52#include <assimp/Importer.hpp>
53#include <assimp/postprocess.h>
54#include <assimp/importerdesc.h>
55#include <ios>
56#include <list>
57#include <memory>
58#include <sstream>
59#include <cctype>
60
61using namespace Assimp;
62
63// ------------------------------------------------------------------------------------------------
64// Constructor to be privately used by Importer
65BaseImporter::BaseImporter()
66: m_progress()
67{
68 // nothing to do here
69}
70
71// ------------------------------------------------------------------------------------------------
72// Destructor, private as well
73BaseImporter::~BaseImporter()
74{
75 // nothing to do here
76}
77
78// ------------------------------------------------------------------------------------------------
79// Imports the given file and returns the imported data.
80aiScene* BaseImporter::ReadFile(const Importer* pImp, const std::string& pFile, IOSystem* pIOHandler)
81{
82 m_progress = pImp->GetProgressHandler();
83 ai_assert(m_progress);
84
85 // Gather configuration properties for this run
86 SetupProperties( pImp );
87
88 // Construct a file system filter to improve our success ratio at reading external files
89 FileSystemFilter filter(pFile,pIOHandler);
90
91 // create a scene object to hold the data
92 std::unique_ptr<aiScene> sc(new aiScene());
93
94 // dispatch importing
95 try
96 {
97 InternReadFile( pFile, sc.get(), &filter);
98
99 } catch( const std::exception& err ) {
100 // extract error description
101 m_ErrorText = err.what();
102 DefaultLogger::get()->error(m_ErrorText);
103 return NULL;
104 }
105
106 // return what we gathered from the import.
107 return sc.release();
108}
109
110// ------------------------------------------------------------------------------------------------
111void BaseImporter::SetupProperties(const Importer* /*pImp*/)
112{
113 // the default implementation does nothing
114}
115
116// ------------------------------------------------------------------------------------------------
117void BaseImporter::GetExtensionList(std::set<std::string>& extensions)
118{
119 const aiImporterDesc* desc = GetInfo();
120 ai_assert(desc != NULL);
121
122 const char* ext = desc->mFileExtensions;
123 ai_assert(ext != NULL);
124
125 const char* last = ext;
126 do {
127 if (!*ext || *ext == ' ') {
128 extensions.insert(std::string(last,ext-last));
129 ai_assert(ext-last > 0);
130 last = ext;
131 while(*last == ' ') {
132 ++last;
133 }
134 }
135 }
136 while(*ext++);
137}
138
139// ------------------------------------------------------------------------------------------------
140/*static*/ bool BaseImporter::SearchFileHeaderForToken( IOSystem* pIOHandler,
141 const std::string& pFile,
142 const char** tokens,
143 unsigned int numTokens,
144 unsigned int searchBytes /* = 200 */,
145 bool tokensSol /* false */)
146{
147 ai_assert( NULL != tokens );
148 ai_assert( 0 != numTokens );
149 ai_assert( 0 != searchBytes);
150
151 if (!pIOHandler)
152 return false;
153
154 std::unique_ptr<IOStream> pStream (pIOHandler->Open(pFile));
155 if (pStream.get() ) {
156 // read 200 characters from the file
157 std::unique_ptr<char[]> _buffer (new char[searchBytes+1 /* for the '\0' */]);
158 char* buffer = _buffer.get();
159 if( NULL == buffer ) {
160 return false;
161 }
162
163 const size_t read = pStream->Read(buffer,1,searchBytes);
164 if( !read ) {
165 return false;
166 }
167
168 for( size_t i = 0; i < read; ++i ) {
169 buffer[ i ] = ::tolower( buffer[ i ] );
170 }
171
172 // It is not a proper handling of unicode files here ...
173 // ehm ... but it works in most cases.
174 char* cur = buffer,*cur2 = buffer,*end = &buffer[read];
175 while (cur != end) {
176 if( *cur ) {
177 *cur2++ = *cur;
178 }
179 ++cur;
180 }
181 *cur2 = '\0';
182
183 for (unsigned int i = 0; i < numTokens;++i) {
184 ai_assert(NULL != tokens[i]);
185 const char* r = strstr(buffer,tokens[i]);
186 if( !r ) {
187 continue;
188 }
189 // We got a match, either we don't care where it is, or it happens to
190 // be in the beginning of the file / line
191 if (!tokensSol || r == buffer || r[-1] == '\r' || r[-1] == '\n') {
192 DefaultLogger::get()->debug(std::string("Found positive match for header keyword: ") + tokens[i]);
193 return true;
194 }
195 }
196 }
197
198 return false;
199}
200
201// ------------------------------------------------------------------------------------------------
202// Simple check for file extension
203/*static*/ bool BaseImporter::SimpleExtensionCheck (const std::string& pFile,
204 const char* ext0,
205 const char* ext1,
206 const char* ext2)
207{
208 std::string::size_type pos = pFile.find_last_of('.');
209
210 // no file extension - can't read
211 if( pos == std::string::npos)
212 return false;
213
214 const char* ext_real = & pFile[ pos+1 ];
215 if( !ASSIMP_stricmp(ext_real,ext0) )
216 return true;
217
218 // check for other, optional, file extensions
219 if (ext1 && !ASSIMP_stricmp(ext_real,ext1))
220 return true;
221
222 if (ext2 && !ASSIMP_stricmp(ext_real,ext2))
223 return true;
224
225 return false;
226}
227
228// ------------------------------------------------------------------------------------------------
229// Get file extension from path
230/*static*/ std::string BaseImporter::GetExtension (const std::string& pFile)
231{
232 std::string::size_type pos = pFile.find_last_of('.');
233
234 // no file extension at all
235 if( pos == std::string::npos)
236 return "";
237
238 std::string ret = pFile.substr(pos+1);
239 std::transform(ret.begin(),ret.end(),ret.begin(),::tolower); // thanks to Andy Maloney for the hint
240 return ret;
241}
242
243// ------------------------------------------------------------------------------------------------
244// Check for magic bytes at the beginning of the file.
245/* static */ bool BaseImporter::CheckMagicToken(IOSystem* pIOHandler, const std::string& pFile,
246 const void* _magic, unsigned int num, unsigned int offset, unsigned int size)
247{
248 ai_assert(size <= 16 && _magic);
249
250 if (!pIOHandler) {
251 return false;
252 }
253 union {
254 const char* magic;
255 const uint16_t* magic_u16;
256 const uint32_t* magic_u32;
257 };
258 magic = reinterpret_cast<const char*>(_magic);
259 std::unique_ptr<IOStream> pStream (pIOHandler->Open(pFile));
260 if (pStream.get() ) {
261
262 // skip to offset
263 pStream->Seek(offset,aiOrigin_SET);
264
265 // read 'size' characters from the file
266 union {
267 char data[16];
268 uint16_t data_u16[8];
269 uint32_t data_u32[4];
270 };
271 if(size != pStream->Read(data,1,size)) {
272 return false;
273 }
274
275 for (unsigned int i = 0; i < num; ++i) {
276 // also check against big endian versions of tokens with size 2,4
277 // that's just for convenience, the chance that we cause conflicts
278 // is quite low and it can save some lines and prevent nasty bugs
279 if (2 == size) {
280 uint16_t rev = *magic_u16;
281 ByteSwap::Swap(&rev);
282 if (data_u16[0] == *magic_u16 || data_u16[0] == rev) {
283 return true;
284 }
285 }
286 else if (4 == size) {
287 uint32_t rev = *magic_u32;
288 ByteSwap::Swap(&rev);
289 if (data_u32[0] == *magic_u32 || data_u32[0] == rev) {
290 return true;
291 }
292 }
293 else {
294 // any length ... just compare
295 if(!memcmp(magic,data,size)) {
296 return true;
297 }
298 }
299 magic += size;
300 }
301 }
302 return false;
303}
304
305#include "../contrib/utf8cpp/source/utf8.h"
306
307// ------------------------------------------------------------------------------------------------
308// Convert to UTF8 data
309void BaseImporter::ConvertToUTF8(std::vector<char>& data)
310{
311 //ConversionResult result;
312 if(data.size() < 8) {
313 throw DeadlyImportError("File is too small");
314 }
315
316 // UTF 8 with BOM
317 if((uint8_t)data[0] == 0xEF && (uint8_t)data[1] == 0xBB && (uint8_t)data[2] == 0xBF) {
318 DefaultLogger::get()->debug("Found UTF-8 BOM ...");
319
320 std::copy(data.begin()+3,data.end(),data.begin());
321 data.resize(data.size()-3);
322 return;
323 }
324
325
326 // UTF 32 BE with BOM
327 if(*((uint32_t*)&data.front()) == 0xFFFE0000) {
328
329 // swap the endianness ..
330 for(uint32_t* p = (uint32_t*)&data.front(), *end = (uint32_t*)&data.back(); p <= end; ++p) {
331 AI_SWAP4P(p);
332 }
333 }
334
335 // UTF 32 LE with BOM
336 if(*((uint32_t*)&data.front()) == 0x0000FFFE) {
337 DefaultLogger::get()->debug("Found UTF-32 BOM ...");
338
339 std::vector<char> output;
340 int *ptr = (int*)&data[ 0 ];
341 int *end = ptr + ( data.size() / sizeof(int) ) +1;
342 utf8::utf32to8( ptr, end, back_inserter(output));
343 return;
344 }
345
346 // UTF 16 BE with BOM
347 if(*((uint16_t*)&data.front()) == 0xFFFE) {
348
349 // swap the endianness ..
350 for(uint16_t* p = (uint16_t*)&data.front(), *end = (uint16_t*)&data.back(); p <= end; ++p) {
351 ByteSwap::Swap2(p);
352 }
353 }
354
355 // UTF 16 LE with BOM
356 if(*((uint16_t*)&data.front()) == 0xFEFF) {
357 DefaultLogger::get()->debug("Found UTF-16 BOM ...");
358
359 std::vector<unsigned char> output;
360 utf8::utf16to8(data.begin(), data.end(), back_inserter(output));
361 return;
362 }
363}
364
365// ------------------------------------------------------------------------------------------------
366// Convert to UTF8 data to ISO-8859-1
367void BaseImporter::ConvertUTF8toISO8859_1(std::string& data)
368{
369 size_t size = data.size();
370 size_t i = 0, j = 0;
371
372 while(i < size) {
373 if ((unsigned char) data[i] < (size_t) 0x80) {
374 data[j] = data[i];
375 } else if(i < size - 1) {
376 if((unsigned char) data[i] == 0xC2) {
377 data[j] = data[++i];
378 } else if((unsigned char) data[i] == 0xC3) {
379 data[j] = ((unsigned char) data[++i] + 0x40);
380 } else {
381 std::stringstream stream;
382
383 stream << "UTF8 code " << std::hex << data[i] << data[i + 1] << " can not be converted into ISA-8859-1.";
384
385 DefaultLogger::get()->error(stream.str());
386
387 data[j++] = data[i++];
388 data[j] = data[i];
389 }
390 } else {
391 DefaultLogger::get()->error("UTF8 code but only one character remaining");
392
393 data[j] = data[i];
394 }
395
396 i++; j++;
397 }
398
399 data.resize(j);
400}
401
402// ------------------------------------------------------------------------------------------------
403void BaseImporter::TextFileToBuffer(IOStream* stream,
404 std::vector<char>& data,
405 TextFileMode mode)
406{
407 ai_assert(NULL != stream);
408
409 const size_t fileSize = stream->FileSize();
410 if (mode == FORBID_EMPTY) {
411 if(!fileSize) {
412 throw DeadlyImportError("File is empty");
413 }
414 }
415
416 data.reserve(fileSize+1);
417 data.resize(fileSize);
418 if(fileSize > 0) {
419 if(fileSize != stream->Read( &data[0], 1, fileSize)) {
420 throw DeadlyImportError("File read error");
421 }
422
423 ConvertToUTF8(data);
424 }
425
426 // append a binary zero to simplify string parsing
427 data.push_back(0);
428}
429
430// ------------------------------------------------------------------------------------------------
431namespace Assimp {
432 // Represents an import request
433 struct LoadRequest {
434 LoadRequest(const std::string& _file, unsigned int _flags,const BatchLoader::PropertyMap* _map, unsigned int _id)
435 : file(_file)
436 , flags(_flags)
437 , refCnt(1)
438 , scene(NULL)
439 , loaded(false)
440 , id(_id) {
441 if ( _map ) {
442 map = *_map;
443 }
444 }
445
446 bool operator== ( const std::string& f ) const {
447 return file == f;
448 }
449
450 const std::string file;
451 unsigned int flags;
452 unsigned int refCnt;
453 aiScene *scene;
454 bool loaded;
455 BatchLoader::PropertyMap map;
456 unsigned int id;
457 };
458}
459
460// ------------------------------------------------------------------------------------------------
461// BatchLoader::pimpl data structure
462struct Assimp::BatchData {
463 BatchData( IOSystem* pIO, bool validate )
464 : pIOSystem( pIO )
465 , pImporter( nullptr )
466 , next_id(0xffff)
467 , validate( validate ) {
468 ai_assert( NULL != pIO );
469
470 pImporter = new Importer();
471 pImporter->SetIOHandler( pIO );
472 }
473
474 ~BatchData() {
475 pImporter->SetIOHandler( NULL ); /* get pointer back into our possession */
476 delete pImporter;
477 }
478
479 // IO system to be used for all imports
480 IOSystem* pIOSystem;
481
482 // Importer used to load all meshes
483 Importer* pImporter;
484
485 // List of all imports
486 std::list<LoadRequest> requests;
487
488 // Base path
489 std::string pathBase;
490
491 // Id for next item
492 unsigned int next_id;
493
494 // Validation enabled state
495 bool validate;
496};
497
498typedef std::list<LoadRequest>::iterator LoadReqIt;
499
500// ------------------------------------------------------------------------------------------------
501BatchLoader::BatchLoader(IOSystem* pIO, bool validate )
502{
503 ai_assert(NULL != pIO);
504
505 m_data = new BatchData( pIO, validate );
506}
507
508// ------------------------------------------------------------------------------------------------
509BatchLoader::~BatchLoader()
510{
511 // delete all scenes what have not been polled by the user
512 for ( LoadReqIt it = m_data->requests.begin();it != m_data->requests.end(); ++it) {
513 delete (*it).scene;
514 }
515 delete m_data;
516}
517
518// ------------------------------------------------------------------------------------------------
519void BatchLoader::setValidation( bool enabled ) {
520 m_data->validate = enabled;
521}
522
523// ------------------------------------------------------------------------------------------------
524bool BatchLoader::getValidation() const {
525 return m_data->validate;
526}
527
528// ------------------------------------------------------------------------------------------------
529unsigned int BatchLoader::AddLoadRequest(const std::string& file,
530 unsigned int steps /*= 0*/, const PropertyMap* map /*= NULL*/)
531{
532 ai_assert(!file.empty());
533
534 // check whether we have this loading request already
535 for ( LoadReqIt it = m_data->requests.begin();it != m_data->requests.end(); ++it) {
536 // Call IOSystem's path comparison function here
537 if ( m_data->pIOSystem->ComparePaths((*it).file,file)) {
538 if (map) {
539 if ( !( ( *it ).map == *map ) ) {
540 continue;
541 }
542 }
543 else if ( !( *it ).map.empty() ) {
544 continue;
545 }
546
547 (*it).refCnt++;
548 return (*it).id;
549 }
550 }
551
552 // no, we don't have it. So add it to the queue ...
553 m_data->requests.push_back(LoadRequest(file,steps,map, m_data->next_id));
554 return m_data->next_id++;
555}
556
557// ------------------------------------------------------------------------------------------------
558aiScene* BatchLoader::GetImport( unsigned int which )
559{
560 for ( LoadReqIt it = m_data->requests.begin();it != m_data->requests.end(); ++it) {
561 if ((*it).id == which && (*it).loaded) {
562 aiScene* sc = (*it).scene;
563 if (!(--(*it).refCnt)) {
564 m_data->requests.erase(it);
565 }
566 return sc;
567 }
568 }
569 return NULL;
570}
571
572// ------------------------------------------------------------------------------------------------
573void BatchLoader::LoadAll()
574{
575 // no threaded implementation for the moment
576 for ( LoadReqIt it = m_data->requests.begin();it != m_data->requests.end(); ++it) {
577 // force validation in debug builds
578 unsigned int pp = (*it).flags;
579 if ( m_data->validate ) {
580 pp |= aiProcess_ValidateDataStructure;
581 }
582
583 // setup config properties if necessary
584 ImporterPimpl* pimpl = m_data->pImporter->Pimpl();
585 pimpl->mFloatProperties = (*it).map.floats;
586 pimpl->mIntProperties = (*it).map.ints;
587 pimpl->mStringProperties = (*it).map.strings;
588 pimpl->mMatrixProperties = (*it).map.matrices;
589
590 if (!DefaultLogger::isNullLogger())
591 {
592 DefaultLogger::get()->info("%%% BEGIN EXTERNAL FILE %%%");
593 DefaultLogger::get()->info("File: " + (*it).file);
594 }
595 m_data->pImporter->ReadFile((*it).file,pp);
596 (*it).scene = m_data->pImporter->GetOrphanedScene();
597 (*it).loaded = true;
598
599 DefaultLogger::get()->info("%%% END EXTERNAL FILE %%%");
600 }
601}
602