Coverage Report

Created: 2025-06-22 07:30

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