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

source code of qt3d/src/3rdparty/assimp/src/code/Common/BaseImporter.cpp