1 /*
2 ---------------------------------------------------------------------------
3 Open Asset Import Library (assimp)
4 ---------------------------------------------------------------------------
5
6 Copyright (c) 2006-2019, assimp team
7
8
9
10 All rights reserved.
11
12 Redistribution and use of this software in source and binary forms,
13 with or without modification, are permitted provided that the following
14 conditions 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
30 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
31 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
32 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
33 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
34 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
36 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
37 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
38 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
39 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
40 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41 ---------------------------------------------------------------------------
42 */
43 #ifndef ASSIMP_BUILD_NO_OBJ_IMPORTER
44
45 #include "ObjFileParser.h"
46 #include "ObjFileMtlImporter.h"
47 #include "ObjTools.h"
48 #include "ObjFileData.h"
49 #include <assimp/ParsingUtils.h>
50 #include <assimp/BaseImporter.h>
51 #include <assimp/DefaultIOSystem.h>
52 #include <assimp/DefaultLogger.hpp>
53 #include <assimp/material.h>
54 #include <assimp/Importer.hpp>
55 #include <cstdlib>
56
57 namespace Assimp {
58
59 const std::string ObjFileParser::DEFAULT_MATERIAL = AI_DEFAULT_MATERIAL_NAME;
60
ObjFileParser()61 ObjFileParser::ObjFileParser()
62 : m_DataIt()
63 , m_DataItEnd()
64 , m_pModel( nullptr )
65 , m_uiLine( 0 )
66 , m_pIO( nullptr )
67 , m_progress( nullptr )
68 , m_originalObjFileName() {
69 // empty
70 }
71
ObjFileParser(IOStreamBuffer<char> & streamBuffer,const std::string & modelName,IOSystem * io,ProgressHandler * progress,const std::string & originalObjFileName)72 ObjFileParser::ObjFileParser( IOStreamBuffer<char> &streamBuffer, const std::string &modelName,
73 IOSystem *io, ProgressHandler* progress,
74 const std::string &originalObjFileName) :
75 m_DataIt(),
76 m_DataItEnd(),
77 m_pModel(nullptr),
78 m_uiLine(0),
79 m_pIO( io ),
80 m_progress(progress),
81 m_originalObjFileName(originalObjFileName)
82 {
83 std::fill_n(m_buffer,Buffersize,0);
84
85 // Create the model instance to store all the data
86 m_pModel.reset(new ObjFile::Model());
87 m_pModel->m_ModelName = modelName;
88
89 // create default material and store it
90 m_pModel->m_pDefaultMaterial = new ObjFile::Material;
91 m_pModel->m_pDefaultMaterial->MaterialName.Set( DEFAULT_MATERIAL );
92 m_pModel->m_MaterialLib.push_back( DEFAULT_MATERIAL );
93 m_pModel->m_MaterialMap[ DEFAULT_MATERIAL ] = m_pModel->m_pDefaultMaterial;
94
95 // Start parsing the file
96 parseFile( streamBuffer );
97 }
98
~ObjFileParser()99 ObjFileParser::~ObjFileParser() {
100 }
101
setBuffer(std::vector<char> & buffer)102 void ObjFileParser::setBuffer( std::vector<char> &buffer ) {
103 m_DataIt = buffer.begin();
104 m_DataItEnd = buffer.end();
105 }
106
GetModel() const107 ObjFile::Model *ObjFileParser::GetModel() const {
108 return m_pModel.get();
109 }
110
parseFile(IOStreamBuffer<char> & streamBuffer)111 void ObjFileParser::parseFile( IOStreamBuffer<char> &streamBuffer ) {
112 // only update every 100KB or it'll be too slow
113 //const unsigned int updateProgressEveryBytes = 100 * 1024;
114 unsigned int progressCounter = 0;
115 const unsigned int bytesToProcess = static_cast<unsigned int>(streamBuffer.size());
116 const unsigned int progressTotal = bytesToProcess;
117 unsigned int processed = 0;
118 size_t lastFilePos( 0 );
119
120 std::vector<char> buffer;
121 while ( streamBuffer.getNextDataLine( buffer, '\\' ) ) {
122 m_DataIt = buffer.begin();
123 m_DataItEnd = buffer.end();
124
125 // Handle progress reporting
126 const size_t filePos( streamBuffer.getFilePos() );
127 if ( lastFilePos < filePos ) {
128 processed = static_cast<unsigned int>(filePos);
129 lastFilePos = filePos;
130 progressCounter++;
131 m_progress->UpdateFileRead( processed, progressTotal );
132 }
133
134 // parse line
135 switch (*m_DataIt) {
136 case 'v': // Parse a vertex texture coordinate
137 {
138 ++m_DataIt;
139 if (*m_DataIt == ' ' || *m_DataIt == '\t') {
140 size_t numComponents = getNumComponentsInDataDefinition();
141 if (numComponents == 3) {
142 // read in vertex definition
143 getVector3(m_pModel->m_Vertices);
144 } else if (numComponents == 4) {
145 // read in vertex definition (homogeneous coords)
146 getHomogeneousVector3(m_pModel->m_Vertices);
147 } else if (numComponents == 6) {
148 // read vertex and vertex-color
149 getTwoVectors3(m_pModel->m_Vertices, m_pModel->m_VertexColors);
150 }
151 } else if (*m_DataIt == 't') {
152 // read in texture coordinate ( 2D or 3D )
153 ++m_DataIt;
154 size_t dim = getTexCoordVector(m_pModel->m_TextureCoord);
155 m_pModel->m_TextureCoordDim = std::max(m_pModel->m_TextureCoordDim, (unsigned int)dim);
156 } else if (*m_DataIt == 'n') {
157 // Read in normal vector definition
158 ++m_DataIt;
159 getVector3( m_pModel->m_Normals );
160 }
161 }
162 break;
163
164 case 'p': // Parse a face, line or point statement
165 case 'l':
166 case 'f':
167 {
168 getFace(*m_DataIt == 'f' ? aiPrimitiveType_POLYGON : (*m_DataIt == 'l'
169 ? aiPrimitiveType_LINE : aiPrimitiveType_POINT));
170 }
171 break;
172
173 case '#': // Parse a comment
174 {
175 getComment();
176 }
177 break;
178
179 case 'u': // Parse a material desc. setter
180 {
181 std::string name;
182
183 getNameNoSpace(m_DataIt, m_DataItEnd, name);
184
185 size_t nextSpace = name.find(" ");
186 if (nextSpace != std::string::npos)
187 name = name.substr(0, nextSpace);
188
189 if(name == "usemtl")
190 {
191 getMaterialDesc();
192 }
193 }
194 break;
195
196 case 'm': // Parse a material library or merging group ('mg')
197 {
198 std::string name;
199
200 getNameNoSpace(m_DataIt, m_DataItEnd, name);
201
202 size_t nextSpace = name.find(" ");
203 if (nextSpace != std::string::npos)
204 name = name.substr(0, nextSpace);
205
206 if (name == "mg")
207 getGroupNumberAndResolution();
208 else if(name == "mtllib")
209 getMaterialLib();
210 else
211 goto pf_skip_line;
212 }
213 break;
214
215 case 'g': // Parse group name
216 {
217 getGroupName();
218 }
219 break;
220
221 case 's': // Parse group number
222 {
223 getGroupNumber();
224 }
225 break;
226
227 case 'o': // Parse object name
228 {
229 getObjectName();
230 }
231 break;
232
233 default:
234 {
235 pf_skip_line:
236 m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
237 }
238 break;
239 }
240 }
241 }
242
copyNextWord(char * pBuffer,size_t length)243 void ObjFileParser::copyNextWord(char *pBuffer, size_t length) {
244 size_t index = 0;
245 m_DataIt = getNextWord<DataArrayIt>(m_DataIt, m_DataItEnd);
246 if ( *m_DataIt == '\\' ) {
247 ++m_DataIt;
248 ++m_DataIt;
249 m_DataIt = getNextWord<DataArrayIt>( m_DataIt, m_DataItEnd );
250 }
251 while( m_DataIt != m_DataItEnd && !IsSpaceOrNewLine( *m_DataIt ) ) {
252 pBuffer[index] = *m_DataIt;
253 index++;
254 if( index == length - 1 ) {
255 break;
256 }
257 ++m_DataIt;
258 }
259
260 ai_assert(index < length);
261 pBuffer[index] = '\0';
262 }
263
isDataDefinitionEnd(const char * tmp)264 static bool isDataDefinitionEnd( const char *tmp ) {
265 if ( *tmp == '\\' ) {
266 tmp++;
267 if ( IsLineEnd( *tmp ) ) {
268 tmp++;
269 return true;
270 }
271 }
272 return false;
273 }
274
isNanOrInf(const char * in)275 static bool isNanOrInf(const char * in) {
276 // Look for "nan" or "inf", case insensitive
277 if ((in[0] == 'N' || in[0] == 'n') && ASSIMP_strincmp(in, "nan", 3) == 0) {
278 return true;
279 }
280 else if ((in[0] == 'I' || in[0] == 'i') && ASSIMP_strincmp(in, "inf", 3) == 0) {
281 return true;
282 }
283 return false;
284 }
285
getNumComponentsInDataDefinition()286 size_t ObjFileParser::getNumComponentsInDataDefinition() {
287 size_t numComponents( 0 );
288 const char* tmp( &m_DataIt[0] );
289 bool end_of_definition = false;
290 while ( !end_of_definition ) {
291 if ( isDataDefinitionEnd( tmp ) ) {
292 tmp += 2;
293 } else if ( IsLineEnd( *tmp ) ) {
294 end_of_definition = true;
295 }
296 if ( !SkipSpaces( &tmp ) ) {
297 break;
298 }
299 const bool isNum( IsNumeric( *tmp ) || isNanOrInf(tmp));
300 SkipToken( tmp );
301 if ( isNum ) {
302 ++numComponents;
303 }
304 if ( !SkipSpaces( &tmp ) ) {
305 break;
306 }
307 }
308 return numComponents;
309 }
310
getTexCoordVector(std::vector<aiVector3D> & point3d_array)311 size_t ObjFileParser::getTexCoordVector( std::vector<aiVector3D> &point3d_array ) {
312 size_t numComponents = getNumComponentsInDataDefinition();
313 ai_real x, y, z;
314 if( 2 == numComponents ) {
315 copyNextWord( m_buffer, Buffersize );
316 x = ( ai_real ) fast_atof( m_buffer );
317
318 copyNextWord( m_buffer, Buffersize );
319 y = ( ai_real ) fast_atof( m_buffer );
320 z = 0.0;
321 } else if( 3 == numComponents ) {
322 copyNextWord( m_buffer, Buffersize );
323 x = ( ai_real ) fast_atof( m_buffer );
324
325 copyNextWord( m_buffer, Buffersize );
326 y = ( ai_real ) fast_atof( m_buffer );
327
328 copyNextWord( m_buffer, Buffersize );
329 z = ( ai_real ) fast_atof( m_buffer );
330 } else {
331 throw DeadlyImportError( "OBJ: Invalid number of components" );
332 }
333
334 // Coerce nan and inf to 0 as is the OBJ default value
335 if (!std::isfinite(x))
336 x = 0;
337
338 if (!std::isfinite(y))
339 y = 0;
340
341 if (!std::isfinite(z))
342 z = 0;
343
344 point3d_array.push_back( aiVector3D( x, y, z ) );
345 m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
346 return numComponents;
347 }
348
getVector3(std::vector<aiVector3D> & point3d_array)349 void ObjFileParser::getVector3( std::vector<aiVector3D> &point3d_array ) {
350 ai_real x, y, z;
351 copyNextWord(m_buffer, Buffersize);
352 x = (ai_real) fast_atof(m_buffer);
353
354 copyNextWord(m_buffer, Buffersize);
355 y = (ai_real) fast_atof(m_buffer);
356
357 copyNextWord( m_buffer, Buffersize );
358 z = ( ai_real ) fast_atof( m_buffer );
359
360 point3d_array.push_back( aiVector3D( x, y, z ) );
361 m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
362 }
363
getHomogeneousVector3(std::vector<aiVector3D> & point3d_array)364 void ObjFileParser::getHomogeneousVector3( std::vector<aiVector3D> &point3d_array ) {
365 ai_real x, y, z, w;
366 copyNextWord(m_buffer, Buffersize);
367 x = (ai_real) fast_atof(m_buffer);
368
369 copyNextWord(m_buffer, Buffersize);
370 y = (ai_real) fast_atof(m_buffer);
371
372 copyNextWord( m_buffer, Buffersize );
373 z = ( ai_real ) fast_atof( m_buffer );
374
375 copyNextWord( m_buffer, Buffersize );
376 w = ( ai_real ) fast_atof( m_buffer );
377
378 if (w == 0)
379 throw DeadlyImportError("OBJ: Invalid component in homogeneous vector (Division by zero)");
380
381 point3d_array.push_back( aiVector3D( x/w, y/w, z/w ) );
382 m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
383 }
384
getTwoVectors3(std::vector<aiVector3D> & point3d_array_a,std::vector<aiVector3D> & point3d_array_b)385 void ObjFileParser::getTwoVectors3( std::vector<aiVector3D> &point3d_array_a, std::vector<aiVector3D> &point3d_array_b ) {
386 ai_real x, y, z;
387 copyNextWord(m_buffer, Buffersize);
388 x = (ai_real) fast_atof(m_buffer);
389
390 copyNextWord(m_buffer, Buffersize);
391 y = (ai_real) fast_atof(m_buffer);
392
393 copyNextWord( m_buffer, Buffersize );
394 z = ( ai_real ) fast_atof( m_buffer );
395
396 point3d_array_a.push_back( aiVector3D( x, y, z ) );
397
398 copyNextWord(m_buffer, Buffersize);
399 x = (ai_real) fast_atof(m_buffer);
400
401 copyNextWord(m_buffer, Buffersize);
402 y = (ai_real) fast_atof(m_buffer);
403
404 copyNextWord( m_buffer, Buffersize );
405 z = ( ai_real ) fast_atof( m_buffer );
406
407 point3d_array_b.push_back( aiVector3D( x, y, z ) );
408
409 m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
410 }
411
getVector2(std::vector<aiVector2D> & point2d_array)412 void ObjFileParser::getVector2( std::vector<aiVector2D> &point2d_array ) {
413 ai_real x, y;
414 copyNextWord(m_buffer, Buffersize);
415 x = (ai_real) fast_atof(m_buffer);
416
417 copyNextWord(m_buffer, Buffersize);
418 y = (ai_real) fast_atof(m_buffer);
419
420 point2d_array.push_back(aiVector2D(x, y));
421
422 m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
423 }
424
425 static const std::string DefaultObjName = "defaultobject";
426
getFace(aiPrimitiveType type)427 void ObjFileParser::getFace( aiPrimitiveType type ) {
428 m_DataIt = getNextToken<DataArrayIt>( m_DataIt, m_DataItEnd );
429 if ( m_DataIt == m_DataItEnd || *m_DataIt == '\0' ) {
430 return;
431 }
432
433 ObjFile::Face *face = new ObjFile::Face( type );
434 bool hasNormal = false;
435
436 const int vSize = static_cast<unsigned int>(m_pModel->m_Vertices.size());
437 const int vtSize = static_cast<unsigned int>(m_pModel->m_TextureCoord.size());
438 const int vnSize = static_cast<unsigned int>(m_pModel->m_Normals.size());
439
440 const bool vt = (!m_pModel->m_TextureCoord.empty());
441 const bool vn = (!m_pModel->m_Normals.empty());
442 int iStep = 0, iPos = 0;
443 while ( m_DataIt != m_DataItEnd ) {
444 iStep = 1;
445
446 if ( IsLineEnd( *m_DataIt ) ) {
447 break;
448 }
449
450 if ( *m_DataIt =='/' ) {
451 if (type == aiPrimitiveType_POINT) {
452 ASSIMP_LOG_ERROR("Obj: Separator unexpected in point statement");
453 }
454 iPos++;
455 } else if( IsSpaceOrNewLine( *m_DataIt ) ) {
456 iPos = 0;
457 } else {
458 //OBJ USES 1 Base ARRAYS!!!!
459 const int iVal( ::atoi( & ( *m_DataIt ) ) );
460
461 // increment iStep position based off of the sign and # of digits
462 int tmp = iVal;
463 if ( iVal < 0 ) {
464 ++iStep;
465 }
466 while ( ( tmp = tmp / 10 ) != 0 ) {
467 ++iStep;
468 }
469
470 if (iPos == 1 && !vt && vn)
471 iPos = 2; // skip texture coords for normals if there are no tex coords
472
473 if ( iVal > 0 ) {
474 // Store parsed index
475 if ( 0 == iPos ) {
476 face->m_vertices.push_back( iVal - 1 );
477 } else if ( 1 == iPos ) {
478 face->m_texturCoords.push_back( iVal - 1 );
479 } else if ( 2 == iPos ) {
480 face->m_normals.push_back( iVal - 1 );
481 hasNormal = true;
482 } else {
483 reportErrorTokenInFace();
484 }
485 } else if ( iVal < 0 ) {
486 // Store relatively index
487 if ( 0 == iPos ) {
488 face->m_vertices.push_back( vSize + iVal );
489 } else if ( 1 == iPos ) {
490 face->m_texturCoords.push_back( vtSize + iVal );
491 } else if ( 2 == iPos ) {
492 face->m_normals.push_back( vnSize + iVal );
493 hasNormal = true;
494 } else {
495 reportErrorTokenInFace();
496 }
497 } else {
498 //On error, std::atoi will return 0 which is not a valid value
499 delete face;
500 throw DeadlyImportError("OBJ: Invalid face indice");
501 }
502
503 }
504 m_DataIt += iStep;
505 }
506
507 if ( face->m_vertices.empty() ) {
508 ASSIMP_LOG_ERROR("Obj: Ignoring empty face");
509 // skip line and clean up
510 m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
511 delete face;
512 return;
513 }
514
515 // Set active material, if one set
516 if( NULL != m_pModel->m_pCurrentMaterial ) {
517 face->m_pMaterial = m_pModel->m_pCurrentMaterial;
518 } else {
519 face->m_pMaterial = m_pModel->m_pDefaultMaterial;
520 }
521
522 // Create a default object, if nothing is there
523 if( NULL == m_pModel->m_pCurrent ) {
524 createObject( DefaultObjName );
525 }
526
527 // Assign face to mesh
528 if ( NULL == m_pModel->m_pCurrentMesh ) {
529 createMesh( DefaultObjName );
530 }
531
532 // Store the face
533 m_pModel->m_pCurrentMesh->m_Faces.push_back( face );
534 m_pModel->m_pCurrentMesh->m_uiNumIndices += (unsigned int) face->m_vertices.size();
535 m_pModel->m_pCurrentMesh->m_uiUVCoordinates[ 0 ] += (unsigned int) face->m_texturCoords.size();
536 if( !m_pModel->m_pCurrentMesh->m_hasNormals && hasNormal ) {
537 m_pModel->m_pCurrentMesh->m_hasNormals = true;
538 }
539 // Skip the rest of the line
540 m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
541 }
542
getMaterialDesc()543 void ObjFileParser::getMaterialDesc() {
544 // Get next data for material data
545 m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
546 if (m_DataIt == m_DataItEnd) {
547 return;
548 }
549
550 char *pStart = &(*m_DataIt);
551 while( m_DataIt != m_DataItEnd && !IsLineEnd( *m_DataIt ) ) {
552 ++m_DataIt;
553 }
554
555 // In some cases we should ignore this 'usemtl' command, this variable helps us to do so
556 bool skip = false;
557
558 // Get name
559 std::string strName(pStart, &(*m_DataIt));
560 strName = trim_whitespaces(strName);
561 if (strName.empty())
562 skip = true;
563
564 // If the current mesh has the same material, we simply ignore that 'usemtl' command
565 // There is no need to create another object or even mesh here
566 if ( m_pModel->m_pCurrentMaterial && m_pModel->m_pCurrentMaterial->MaterialName == aiString( strName ) ) {
567 skip = true;
568 }
569
570 if (!skip) {
571 // Search for material
572 std::map<std::string, ObjFile::Material*>::iterator it = m_pModel->m_MaterialMap.find(strName);
573 if (it == m_pModel->m_MaterialMap.end()) {
574 // Not found, so we don't know anything about the material except for its name.
575 // This may be the case if the material library is missing. We don't want to lose all
576 // materials if that happens, so create a new named material instead of discarding it
577 // completely.
578 ASSIMP_LOG_ERROR("OBJ: failed to locate material " + strName + ", creating new material");
579 m_pModel->m_pCurrentMaterial = new ObjFile::Material();
580 m_pModel->m_pCurrentMaterial->MaterialName.Set(strName);
581 m_pModel->m_MaterialLib.push_back(strName);
582 m_pModel->m_MaterialMap[strName] = m_pModel->m_pCurrentMaterial;
583 } else {
584 // Found, using detected material
585 m_pModel->m_pCurrentMaterial = (*it).second;
586 }
587
588 if ( needsNewMesh( strName ) ) {
589 createMesh( strName );
590 }
591
592 m_pModel->m_pCurrentMesh->m_uiMaterialIndex = getMaterialIndex(strName);
593 }
594
595 // Skip rest of line
596 m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
597 }
598
599 // -------------------------------------------------------------------
600 // Get a comment, values will be skipped
getComment()601 void ObjFileParser::getComment() {
602 m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
603 }
604
605 // -------------------------------------------------------------------
606 // Get material library from file.
getMaterialLib()607 void ObjFileParser::getMaterialLib() {
608 // Translate tuple
609 m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
610 if( m_DataIt == m_DataItEnd ) {
611 return;
612 }
613
614 char *pStart = &(*m_DataIt);
615 while( m_DataIt != m_DataItEnd && !IsLineEnd( *m_DataIt ) ) {
616 ++m_DataIt;
617 }
618
619 // Check for existence
620 const std::string strMatName(pStart, &(*m_DataIt));
621 std::string absName;
622
623 // Check if directive is valid.
624 if ( 0 == strMatName.length() ) {
625 ASSIMP_LOG_WARN( "OBJ: no name for material library specified." );
626 return;
627 }
628
629 if ( m_pIO->StackSize() > 0 ) {
630 std::string path = m_pIO->CurrentDirectory();
631 if ( '/' != *path.rbegin() ) {
632 path += '/';
633 }
634 absName += path;
635 absName += strMatName;
636 } else {
637 absName = strMatName;
638 }
639
640 IOStream *pFile = m_pIO->Open( absName );
641 if ( nullptr == pFile ) {
642 ASSIMP_LOG_ERROR("OBJ: Unable to locate material file " + strMatName);
643 std::string strMatFallbackName = m_originalObjFileName.substr(0, m_originalObjFileName.length() - 3) + "mtl";
644 ASSIMP_LOG_INFO("OBJ: Opening fallback material file " + strMatFallbackName);
645 pFile = m_pIO->Open(strMatFallbackName);
646 if (!pFile) {
647 ASSIMP_LOG_ERROR("OBJ: Unable to locate fallback material file " + strMatFallbackName);
648 m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
649 return;
650 }
651 }
652
653 // Import material library data from file.
654 // Some exporters (e.g. Silo) will happily write out empty
655 // material files if the model doesn't use any materials, so we
656 // allow that.
657 std::vector<char> buffer;
658 BaseImporter::TextFileToBuffer( pFile, buffer, BaseImporter::ALLOW_EMPTY );
659 m_pIO->Close( pFile );
660
661 // Importing the material library
662 ObjFileMtlImporter mtlImporter( buffer, strMatName, m_pModel.get() );
663 }
664
665 // -------------------------------------------------------------------
666 // Set a new material definition as the current material.
getNewMaterial()667 void ObjFileParser::getNewMaterial() {
668 m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
669 m_DataIt = getNextWord<DataArrayIt>(m_DataIt, m_DataItEnd);
670 if( m_DataIt == m_DataItEnd ) {
671 return;
672 }
673
674 char *pStart = &(*m_DataIt);
675 std::string strMat( pStart, *m_DataIt );
676 while( m_DataIt != m_DataItEnd && IsSpaceOrNewLine( *m_DataIt ) ) {
677 ++m_DataIt;
678 }
679 std::map<std::string, ObjFile::Material*>::iterator it = m_pModel->m_MaterialMap.find( strMat );
680 if ( it == m_pModel->m_MaterialMap.end() ) {
681 // Show a warning, if material was not found
682 ASSIMP_LOG_WARN("OBJ: Unsupported material requested: " + strMat);
683 m_pModel->m_pCurrentMaterial = m_pModel->m_pDefaultMaterial;
684 } else {
685 // Set new material
686 if ( needsNewMesh( strMat ) ) {
687 createMesh( strMat );
688 }
689 m_pModel->m_pCurrentMesh->m_uiMaterialIndex = getMaterialIndex( strMat );
690 }
691
692 m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
693 }
694
695 // -------------------------------------------------------------------
getMaterialIndex(const std::string & strMaterialName)696 int ObjFileParser::getMaterialIndex( const std::string &strMaterialName )
697 {
698 int mat_index = -1;
699 if( strMaterialName.empty() ) {
700 return mat_index;
701 }
702 for (size_t index = 0; index < m_pModel->m_MaterialLib.size(); ++index)
703 {
704 if ( strMaterialName == m_pModel->m_MaterialLib[ index ])
705 {
706 mat_index = (int)index;
707 break;
708 }
709 }
710 return mat_index;
711 }
712
713 // -------------------------------------------------------------------
714 // Getter for a group name.
getGroupName()715 void ObjFileParser::getGroupName() {
716 std::string groupName;
717
718 // here we skip 'g ' from line
719 m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
720 m_DataIt = getName<DataArrayIt>(m_DataIt, m_DataItEnd, groupName);
721 if( isEndOfBuffer( m_DataIt, m_DataItEnd ) ) {
722 return;
723 }
724
725 // Change active group, if necessary
726 if ( m_pModel->m_strActiveGroup != groupName ) {
727 // Search for already existing entry
728 ObjFile::Model::ConstGroupMapIt it = m_pModel->m_Groups.find(groupName);
729
730 // We are mapping groups into the object structure
731 createObject( groupName );
732
733 // New group name, creating a new entry
734 if (it == m_pModel->m_Groups.end())
735 {
736 std::vector<unsigned int> *pFaceIDArray = new std::vector<unsigned int>;
737 m_pModel->m_Groups[ groupName ] = pFaceIDArray;
738 m_pModel->m_pGroupFaceIDs = (pFaceIDArray);
739 }
740 else
741 {
742 m_pModel->m_pGroupFaceIDs = (*it).second;
743 }
744 m_pModel->m_strActiveGroup = groupName;
745 }
746 m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
747 }
748
749 // -------------------------------------------------------------------
750 // Not supported
getGroupNumber()751 void ObjFileParser::getGroupNumber()
752 {
753 // Not used
754
755 m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
756 }
757
758 // -------------------------------------------------------------------
759 // Not supported
getGroupNumberAndResolution()760 void ObjFileParser::getGroupNumberAndResolution()
761 {
762 // Not used
763
764 m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
765 }
766
767 // -------------------------------------------------------------------
768 // Stores values for a new object instance, name will be used to
769 // identify it.
getObjectName()770 void ObjFileParser::getObjectName()
771 {
772 m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
773 if( m_DataIt == m_DataItEnd ) {
774 return;
775 }
776 char *pStart = &(*m_DataIt);
777 while( m_DataIt != m_DataItEnd && !IsSpaceOrNewLine( *m_DataIt ) ) {
778 ++m_DataIt;
779 }
780
781 std::string strObjectName(pStart, &(*m_DataIt));
782 if (!strObjectName.empty())
783 {
784 // Reset current object
785 m_pModel->m_pCurrent = NULL;
786
787 // Search for actual object
788 for (std::vector<ObjFile::Object*>::const_iterator it = m_pModel->m_Objects.begin();
789 it != m_pModel->m_Objects.end();
790 ++it)
791 {
792 if ((*it)->m_strObjName == strObjectName)
793 {
794 m_pModel->m_pCurrent = *it;
795 break;
796 }
797 }
798
799 // Allocate a new object, if current one was not found before
800 if( NULL == m_pModel->m_pCurrent ) {
801 createObject( strObjectName );
802 }
803 }
804 m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
805 }
806 // -------------------------------------------------------------------
807 // Creates a new object instance
createObject(const std::string & objName)808 void ObjFileParser::createObject(const std::string &objName)
809 {
810 ai_assert( NULL != m_pModel );
811
812 m_pModel->m_pCurrent = new ObjFile::Object;
813 m_pModel->m_pCurrent->m_strObjName = objName;
814 m_pModel->m_Objects.push_back( m_pModel->m_pCurrent );
815
816 createMesh( objName );
817
818 if( m_pModel->m_pCurrentMaterial )
819 {
820 m_pModel->m_pCurrentMesh->m_uiMaterialIndex =
821 getMaterialIndex( m_pModel->m_pCurrentMaterial->MaterialName.data );
822 m_pModel->m_pCurrentMesh->m_pMaterial = m_pModel->m_pCurrentMaterial;
823 }
824 }
825 // -------------------------------------------------------------------
826 // Creates a new mesh
createMesh(const std::string & meshName)827 void ObjFileParser::createMesh( const std::string &meshName )
828 {
829 ai_assert( NULL != m_pModel );
830 m_pModel->m_pCurrentMesh = new ObjFile::Mesh( meshName );
831 m_pModel->m_Meshes.push_back( m_pModel->m_pCurrentMesh );
832 unsigned int meshId = static_cast<unsigned int>(m_pModel->m_Meshes.size()-1);
833 if ( NULL != m_pModel->m_pCurrent )
834 {
835 m_pModel->m_pCurrent->m_Meshes.push_back( meshId );
836 }
837 else
838 {
839 ASSIMP_LOG_ERROR("OBJ: No object detected to attach a new mesh instance.");
840 }
841 }
842
843 // -------------------------------------------------------------------
844 // Returns true, if a new mesh must be created.
needsNewMesh(const std::string & materialName)845 bool ObjFileParser::needsNewMesh( const std::string &materialName )
846 {
847 // If no mesh data yet
848 if(m_pModel->m_pCurrentMesh == 0)
849 {
850 return true;
851 }
852 bool newMat = false;
853 int matIdx = getMaterialIndex( materialName );
854 int curMatIdx = m_pModel->m_pCurrentMesh->m_uiMaterialIndex;
855 if ( curMatIdx != int(ObjFile::Mesh::NoMaterial)
856 && curMatIdx != matIdx
857 // no need create a new mesh if no faces in current
858 // lets say 'usemtl' goes straight after 'g'
859 && m_pModel->m_pCurrentMesh->m_Faces.size() > 0 )
860 {
861 // New material -> only one material per mesh, so we need to create a new
862 // material
863 newMat = true;
864 }
865 return newMat;
866 }
867
868 // -------------------------------------------------------------------
869 // Shows an error in parsing process.
reportErrorTokenInFace()870 void ObjFileParser::reportErrorTokenInFace()
871 {
872 m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
873 ASSIMP_LOG_ERROR("OBJ: Not supported token in face description detected");
874 }
875
876 // -------------------------------------------------------------------
877
878 } // Namespace Assimp
879
880 #endif // !! ASSIMP_BUILD_NO_OBJ_IMPORTER
881