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