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