Coverage Report

Created: 2025-08-26 06:41

/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
46
        m_DataIt(),
77
46
        m_DataItEnd(),
78
46
        m_pModel(nullptr),
79
46
        m_uiLine(0),
80
        m_buffer(),
81
46
        m_pIO(io),
82
46
        m_progress(progress),
83
46
        m_originalObjFileName(originalObjFileName) {
84
46
    std::fill_n(m_buffer, Buffersize, '\0');
85
86
    // Create the model instance to store all the data
87
46
    m_pModel.reset(new ObjFile::Model());
88
46
    m_pModel->mModelName = modelName;
89
90
    // create default material and store it
91
46
    m_pModel->mDefaultMaterial = new ObjFile::Material;
92
46
    m_pModel->mDefaultMaterial->MaterialName.Set(DEFAULT_MATERIAL);
93
46
    m_pModel->mMaterialLib.emplace_back(DEFAULT_MATERIAL);
94
46
    m_pModel->mMaterialMap[DEFAULT_MATERIAL] = m_pModel->mDefaultMaterial;
95
96
    // Start parsing the file
97
46
    parseFile(streamBuffer);
98
46
}
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
43
ObjFile::Model *ObjFileParser::GetModel() const {
106
43
    return m_pModel.get();
107
43
}
108
109
46
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
46
    const unsigned int bytesToProcess = static_cast<unsigned int>(streamBuffer.size());
113
46
    const unsigned int progressTotal = bytesToProcess;
114
46
    unsigned int processed = 0u;
115
46
    size_t lastFilePos = 0u;
116
117
46
    bool insideCstype = false;
118
46
    std::vector<char> buffer;
119
217k
    while (streamBuffer.getNextDataLine(buffer, '\\')) {
120
217k
        m_DataIt = buffer.begin();
121
217k
        m_DataItEnd = buffer.end();
122
217k
        mEnd = &buffer[buffer.size() - 1] + 1;
123
124
        // Handle progress reporting
125
217k
        const size_t filePos(streamBuffer.getFilePos());
126
217k
        if (lastFilePos < filePos) {
127
46
            processed = static_cast<unsigned int>(filePos);
128
46
            lastFilePos = filePos;
129
46
            m_progress->UpdateFileRead(processed, progressTotal);
130
46
        }
131
132
        // handle c-stype section end (http://paulbourke.net/dataformats/obj/)
133
217k
        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
217k
        switch (*m_DataIt) {
146
9.11k
        case 'v': // Parse a vertex texture coordinate
147
9.11k
        {
148
9.11k
            ++m_DataIt;
149
9.11k
            if (*m_DataIt == ' ' || *m_DataIt == '\t') {
150
8.04k
                size_t numComponents = getNumComponentsInDataDefinition();
151
8.04k
                if (numComponents == 3) {
152
                    // read in vertex definition
153
3.36k
                    getVector3(m_pModel->mVertices);
154
4.68k
                } else if (numComponents == 4) {
155
                    // read in vertex definition (homogeneous coords)
156
288
                    getHomogeneousVector3(m_pModel->mVertices);
157
4.39k
                } else if (numComponents == 6) {
158
                    // fill previous omitted vertex-colors by default
159
56
                    if (m_pModel->mVertexColors.size() < m_pModel->mVertices.size()) {
160
7
                        m_pModel->mVertexColors.resize(m_pModel->mVertices.size(), aiVector3D(0, 0, 0));
161
7
                    }
162
                    // read vertex and vertex-color
163
56
                    getTwoVectors3(m_pModel->mVertices, m_pModel->mVertexColors);
164
56
                }
165
                // append omitted vertex-colors as default for the end if any vertex-color exists
166
8.04k
                if (!m_pModel->mVertexColors.empty() && m_pModel->mVertexColors.size() < m_pModel->mVertices.size()) {
167
946
                    m_pModel->mVertexColors.resize(m_pModel->mVertices.size(), aiVector3D(0, 0, 0));
168
946
                }
169
8.04k
            } else if (*m_DataIt == 't') {
170
                // read in texture coordinate ( 2D or 3D )
171
53
                ++m_DataIt;
172
53
                size_t dim = getTexCoordVector(m_pModel->mTextureCoord);
173
53
                m_pModel->mTextureCoordDim = std::max(m_pModel->mTextureCoordDim, (unsigned int)dim);
174
1.02k
            } else if (*m_DataIt == 'n') {
175
                // Read in normal vector definition
176
157
                ++m_DataIt;
177
157
                getVector3(m_pModel->mNormals);
178
157
            }
179
9.11k
        } break;
180
181
1.86k
        case 'p': // Parse a face, line or point statement
182
3.37k
        case 'l':
183
4.46k
        case 'f': {
184
4.46k
            getFace(*m_DataIt == 'f' ? aiPrimitiveType_POLYGON : (*m_DataIt == 'l' ? aiPrimitiveType_LINE : aiPrimitiveType_POINT));
185
4.46k
        } break;
186
187
358
        case '#': // Parse a comment
188
358
        {
189
358
            getComment();
190
358
        } break;
191
192
3.97k
        case 'u': // Parse a material desc. setter
193
3.97k
        {
194
3.97k
            std::string name;
195
196
3.97k
            getNameNoSpace(m_DataIt, m_DataItEnd, name);
197
198
3.97k
            size_t nextSpace = name.find(' ');
199
3.97k
            if (nextSpace != std::string::npos)
200
0
                name = name.substr(0, nextSpace);
201
202
3.97k
            if (name == "usemtl") {
203
2.22k
                getMaterialDesc();
204
2.22k
            }
205
3.97k
        } break;
206
207
2.68k
        case 'm': // Parse a material library or merging group ('mg')
208
2.68k
        {
209
2.68k
            std::string name;
210
211
2.68k
            getNameNoSpace(m_DataIt, m_DataItEnd, name);
212
213
2.68k
            size_t nextSpace = name.find(' ');
214
2.68k
            if (nextSpace != std::string::npos)
215
0
                name = name.substr(0, nextSpace);
216
217
2.68k
            if (name == "mg")
218
4
                getGroupNumberAndResolution();
219
2.68k
            else if (name == "mtllib")
220
1.06k
                getMaterialLib();
221
1.61k
            else
222
1.61k
                goto pf_skip_line;
223
2.68k
        } break;
224
225
16.6k
        case 'g': // Parse group name
226
16.6k
        {
227
16.6k
            getGroupName();
228
16.6k
        } break;
229
230
303
        case 's': // Parse group number
231
303
        {
232
303
            getGroupNumber();
233
303
        } break;
234
235
1.86k
        case 'o': // Parse object name
236
1.86k
        {
237
1.86k
            getObjectName();
238
1.86k
        } break;
239
240
336
        case 'c': // handle cstype section start
241
336
        {
242
336
            std::string name;
243
336
            getNameNoSpace(m_DataIt, m_DataItEnd, name);
244
336
            insideCstype = name == "cstype";
245
336
            goto pf_skip_line;
246
2.68k
        }
247
248
177k
        default: {
249
179k
        pf_skip_line:
250
179k
            m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
251
179k
        } break;
252
217k
        }
253
217k
    }
254
46
}
255
256
12.1k
void ObjFileParser::copyNextWord(char *pBuffer, size_t length) {
257
12.1k
    size_t index = 0;
258
12.1k
    m_DataIt = getNextWord<DataArrayIt>(m_DataIt, m_DataItEnd);
259
12.1k
    if (*m_DataIt == '\\') {
260
0
        ++m_DataIt;
261
0
        ++m_DataIt;
262
0
        m_DataIt = getNextWord<DataArrayIt>(m_DataIt, m_DataItEnd);
263
0
    }
264
67.5k
    while (m_DataIt != m_DataItEnd && !IsSpaceOrNewLine(*m_DataIt)) {
265
55.3k
        pBuffer[index] = *m_DataIt;
266
55.3k
        index++;
267
55.3k
        if (index == length - 1) {
268
0
            break;
269
0
        }
270
55.3k
        ++m_DataIt;
271
55.3k
    }
272
273
12.1k
    ai_assert(index < length);
274
12.1k
    pBuffer[index] = '\0';
275
12.1k
}
276
277
24.1k
static bool isDataDefinitionEnd(const char *tmp) {
278
24.1k
    if (*tmp == '\\') {
279
12
        tmp++;
280
12
        if (IsLineEnd(*tmp)) {
281
7
            return true;
282
7
        }
283
12
    }
284
24.1k
    return false;
285
24.1k
}
286
287
5.24k
static bool isNanOrInf(const char *in) {
288
    // Look for "nan" or "inf", case insensitive
289
5.24k
    return ((in[0] == 'N' || in[0] == 'n') && ASSIMP_strincmp(in, "nan", 3) == 0) ||
290
5.24k
           ((in[0] == 'I' || in[0] == 'i') && ASSIMP_strincmp(in, "inf", 3) == 0);
291
5.24k
}
292
293
8.09k
size_t ObjFileParser::getNumComponentsInDataDefinition() {
294
8.09k
    size_t numComponents(0);
295
8.09k
    const char *tmp(&m_DataIt[0]);
296
8.09k
    bool end_of_definition = false;
297
24.1k
    while (!end_of_definition) {
298
24.1k
        if (isDataDefinitionEnd(tmp)) {
299
7
            tmp += 2;
300
24.1k
        } else if (IsLineEnd(*tmp)) {
301
0
            end_of_definition = true;
302
0
        }
303
24.1k
        if (!SkipSpaces(&tmp, mEnd) || *tmp == '#') {
304
123
            break;
305
123
        }
306
24.0k
        const bool isNum(IsNumeric(*tmp) || isNanOrInf(tmp));
307
24.0k
        SkipToken(tmp, mEnd);
308
24.0k
        if (isNum) {
309
18.7k
            ++numComponents;
310
18.7k
        }
311
24.0k
        if (!SkipSpaces(&tmp, mEnd) || *tmp == '#') {
312
7.97k
            break;
313
7.97k
        }
314
24.0k
    }
315
316
8.09k
    return numComponents;
317
8.09k
}
318
319
53
size_t ObjFileParser::getTexCoordVector(std::vector<aiVector3D> &point3d_array) {
320
53
    size_t numComponents = getNumComponentsInDataDefinition();
321
53
    ai_real x, y, z;
322
53
    if (2 == numComponents) {
323
53
        copyNextWord(m_buffer, Buffersize);
324
53
        x = (ai_real)fast_atof(m_buffer);
325
326
53
        copyNextWord(m_buffer, Buffersize);
327
53
        y = (ai_real)fast_atof(m_buffer);
328
53
        z = 0.0;
329
53
    } else if (3 == numComponents) {
330
0
        copyNextWord(m_buffer, Buffersize);
331
0
        x = (ai_real)fast_atof(m_buffer);
332
333
0
        copyNextWord(m_buffer, Buffersize);
334
0
        y = (ai_real)fast_atof(m_buffer);
335
336
0
        copyNextWord(m_buffer, Buffersize);
337
0
        z = (ai_real)fast_atof(m_buffer);
338
0
    } else {
339
0
        throw DeadlyImportError("OBJ: Invalid number of components");
340
0
    }
341
342
    // Coerce nan and inf to 0 as is the OBJ default value
343
53
    if (!std::isfinite(x))
344
0
        x = 0;
345
346
53
    if (!std::isfinite(y))
347
0
        y = 0;
348
349
53
    if (!std::isfinite(z))
350
0
        z = 0;
351
352
53
    point3d_array.emplace_back(x, y, z);
353
53
    m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
354
53
    return numComponents;
355
53
}
356
357
3.51k
void ObjFileParser::getVector3(std::vector<aiVector3D> &point3d_array) {
358
3.51k
    ai_real x, y, z;
359
3.51k
    copyNextWord(m_buffer, Buffersize);
360
3.51k
    x = (ai_real)fast_atof(m_buffer);
361
362
3.51k
    copyNextWord(m_buffer, Buffersize);
363
3.51k
    y = (ai_real)fast_atof(m_buffer);
364
365
3.51k
    copyNextWord(m_buffer, Buffersize);
366
3.51k
    z = (ai_real)fast_atof(m_buffer);
367
368
3.51k
    point3d_array.emplace_back(x, y, z);
369
3.51k
    m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
370
3.51k
}
371
372
288
void ObjFileParser::getHomogeneousVector3(std::vector<aiVector3D> &point3d_array) {
373
288
    ai_real x, y, z, w;
374
288
    copyNextWord(m_buffer, Buffersize);
375
288
    x = (ai_real)fast_atof(m_buffer);
376
377
288
    copyNextWord(m_buffer, Buffersize);
378
288
    y = (ai_real)fast_atof(m_buffer);
379
380
288
    copyNextWord(m_buffer, Buffersize);
381
288
    z = (ai_real)fast_atof(m_buffer);
382
383
288
    copyNextWord(m_buffer, Buffersize);
384
288
    w = (ai_real)fast_atof(m_buffer);
385
386
288
    if (w == 0)
387
0
        throw DeadlyImportError("OBJ: Invalid component in homogeneous vector (Division by zero)");
388
389
288
    point3d_array.emplace_back(x / w, y / w, z / w);
390
288
    m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
391
288
}
392
393
56
void ObjFileParser::getTwoVectors3(std::vector<aiVector3D> &point3d_array_a, std::vector<aiVector3D> &point3d_array_b) {
394
56
    ai_real x, y, z;
395
56
    copyNextWord(m_buffer, Buffersize);
396
56
    x = (ai_real)fast_atof(m_buffer);
397
398
56
    copyNextWord(m_buffer, Buffersize);
399
56
    y = (ai_real)fast_atof(m_buffer);
400
401
56
    copyNextWord(m_buffer, Buffersize);
402
56
    z = (ai_real)fast_atof(m_buffer);
403
404
56
    point3d_array_a.emplace_back(x, y, z);
405
406
56
    copyNextWord(m_buffer, Buffersize);
407
56
    x = (ai_real)fast_atof(m_buffer);
408
409
56
    copyNextWord(m_buffer, Buffersize);
410
56
    y = (ai_real)fast_atof(m_buffer);
411
412
56
    copyNextWord(m_buffer, Buffersize);
413
56
    z = (ai_real)fast_atof(m_buffer);
414
415
56
    point3d_array_b.emplace_back(x, y, z);
416
417
56
    m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
418
56
}
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
4.46k
void ObjFileParser::getFace(aiPrimitiveType type) {
436
4.46k
    m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
437
4.46k
    if (m_DataIt == m_DataItEnd || *m_DataIt == '\0') {
438
0
        return;
439
0
    }
440
441
4.46k
    ObjFile::Face *face = new ObjFile::Face(type);
442
4.46k
    bool hasNormal = false;
443
444
4.46k
    const int vSize = static_cast<unsigned int>(m_pModel->mVertices.size());
445
4.46k
    const int vtSize = static_cast<unsigned int>(m_pModel->mTextureCoord.size());
446
4.46k
    const int vnSize = static_cast<unsigned int>(m_pModel->mNormals.size());
447
448
4.46k
    const bool vt = (!m_pModel->mTextureCoord.empty());
449
4.46k
    const bool vn = (!m_pModel->mNormals.empty());
450
4.46k
    int iPos = 0;
451
28.9k
    while (m_DataIt < m_DataItEnd) {
452
28.9k
        int iStep = 1;
453
454
28.9k
        if (IsLineEnd(*m_DataIt) || *m_DataIt == '#') {
455
4.45k
            break;
456
4.45k
        }
457
458
24.5k
        if (*m_DataIt == '/') {
459
425
            if (type == aiPrimitiveType_POINT) {
460
0
                ASSIMP_LOG_ERROR("Obj: Separator unexpected in point statement");
461
0
            }
462
425
            iPos++;
463
24.1k
        } else if (IsSpaceOrNewLine(*m_DataIt)) {
464
7.16k
            iPos = 0;
465
16.9k
        } else {
466
            //OBJ USES 1 Base ARRAYS!!!!
467
16.9k
            int iVal;
468
16.9k
            auto end = m_DataIt;
469
            // find either the buffer end or the '\0'
470
2.71M
            while (end < m_DataItEnd && *end != '\0')
471
2.70M
                ++end;
472
            // avoid temporary string allocation if there is a zero
473
16.9k
            if (end != m_DataItEnd) {
474
16.9k
                iVal = ::atoi(&(*m_DataIt));
475
16.9k
            } 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
16.9k
            int tmp = iVal;
483
16.9k
            if (iVal < 0) {
484
318
                ++iStep;
485
318
            }
486
20.9k
            while ((tmp = tmp / 10) != 0) {
487
3.96k
                ++iStep;
488
3.96k
            }
489
490
16.9k
            if (iPos == 1 && !vt && vn) {
491
219
                iPos = 2; // skip texture coords for normals if there are no tex coords
492
219
            }
493
494
16.9k
            if (iVal > 0) {
495
                // Store parsed index
496
16.6k
                if (0 == iPos) {
497
16.2k
                    face->m_vertices.push_back(iVal - 1);
498
16.2k
                } else if (1 == iPos) {
499
124
                    face->m_texturCoords.push_back(iVal - 1);
500
219
                } else if (2 == iPos) {
501
219
                    face->m_normals.push_back(iVal - 1);
502
219
                    hasNormal = true;
503
219
                } else {
504
0
                    reportErrorTokenInFace();
505
0
                }
506
16.6k
            } else if (iVal < 0) {
507
                // Store relatively index
508
318
                if (0 == iPos) {
509
280
                    face->m_vertices.push_back(vSize + iVal);
510
280
                } else if (1 == iPos) {
511
36
                    face->m_texturCoords.push_back(vtSize + iVal);
512
36
                } else if (2 == iPos) {
513
2
                    face->m_normals.push_back(vnSize + iVal);
514
2
                    hasNormal = true;
515
2
                } else {
516
0
                    reportErrorTokenInFace();
517
0
                }
518
318
            } else {
519
                //On error, std::atoi will return 0 which is not a valid value
520
2
                delete face;
521
2
                throw DeadlyImportError("OBJ: Invalid face index.");
522
2
            }
523
16.9k
        }
524
24.5k
        m_DataIt += iStep;
525
24.5k
    }
526
527
4.45k
    if (face->m_vertices.empty()) {
528
985
        ASSIMP_LOG_ERROR("Obj: Ignoring empty face");
529
        // skip line and clean up
530
985
        m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
531
985
        delete face;
532
985
        return;
533
985
    }
534
535
    // Set active material, if one set
536
3.47k
    if (nullptr != m_pModel->mCurrentMaterial) {
537
2.83k
        face->m_pMaterial = m_pModel->mCurrentMaterial;
538
2.83k
    } else {
539
643
        face->m_pMaterial = m_pModel->mDefaultMaterial;
540
643
    }
541
542
    // Create a default object, if nothing is there
543
3.47k
    if (nullptr == m_pModel->mCurrentObject) {
544
17
        createObject(DefaultObjName);
545
17
    }
546
547
    // Assign face to mesh
548
3.47k
    if (nullptr == m_pModel->mCurrentMesh) {
549
0
        createMesh(DefaultObjName);
550
0
    }
551
552
    // Store the face
553
3.47k
    m_pModel->mCurrentMesh->m_Faces.emplace_back(face);
554
3.47k
    m_pModel->mCurrentMesh->m_uiNumIndices += static_cast<unsigned int>(face->m_vertices.size());
555
3.47k
    m_pModel->mCurrentMesh->m_uiUVCoordinates[0] += static_cast<unsigned int>(face->m_texturCoords.size());
556
3.47k
    if (!m_pModel->mCurrentMesh->m_hasNormals && hasNormal) {
557
181
        m_pModel->mCurrentMesh->m_hasNormals = true;
558
181
    }
559
    // Skip the rest of the line
560
3.47k
    m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
561
3.47k
}
562
563
2.22k
void ObjFileParser::getMaterialDesc() {
564
    // Get next data for material data
565
2.22k
    m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
566
2.22k
    if (m_DataIt == m_DataItEnd) {
567
0
        return;
568
0
    }
569
570
2.22k
    char *pStart = &(*m_DataIt);
571
96.8k
    while (m_DataIt != m_DataItEnd && !IsLineEnd(*m_DataIt)) {
572
94.6k
        ++m_DataIt;
573
94.6k
    }
574
575
    // In some cases we should ignore this 'usemtl' command, this variable helps us to do so
576
2.22k
    bool skip = false;
577
578
    // Get name
579
2.22k
    std::string strName(pStart, &(*m_DataIt));
580
2.22k
    strName = ai_trim(strName);
581
2.22k
    if (strName.empty()) {
582
3
        skip = true;
583
3
    }
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
2.22k
    if (!skip) {
588
2.22k
        if (m_pModel->mCurrentMaterial && m_pModel->mCurrentMaterial->MaterialName == aiString(strName)) {
589
309
            skip = true;
590
309
        }
591
2.22k
    }
592
593
2.22k
    if (!skip) {
594
        // Search for material
595
1.91k
        std::map<std::string, ObjFile::Material *>::iterator it = m_pModel->mMaterialMap.find(strName);
596
1.91k
        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
288
            ASSIMP_LOG_ERROR("OBJ: failed to locate material ", strName, ", creating new material");
602
288
            m_pModel->mCurrentMaterial = new ObjFile::Material();
603
288
            m_pModel->mCurrentMaterial->MaterialName.Set(strName);
604
288
            m_pModel->mMaterialLib.push_back(strName);
605
288
            m_pModel->mMaterialMap[strName] = m_pModel->mCurrentMaterial;
606
1.62k
        } else {
607
            // Found, using detected material
608
1.62k
            m_pModel->mCurrentMaterial = (*it).second;
609
1.62k
        }
610
611
1.91k
        if (needsNewMesh(strName)) {
612
610
            auto newMeshName = m_pModel->mActiveGroup.empty() ? strName : m_pModel->mActiveGroup;
613
610
            createMesh(newMeshName);
614
610
        }
615
616
1.91k
        m_pModel->mCurrentMesh->m_uiMaterialIndex = getMaterialIndex(strName);
617
1.91k
    }
618
619
    // Skip rest of line
620
2.22k
    m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
621
2.22k
}
622
623
// -------------------------------------------------------------------
624
//  Get a comment, values will be skipped
625
358
void ObjFileParser::getComment() {
626
358
    m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
627
358
}
628
629
// -------------------------------------------------------------------
630
//  Get material library from file.
631
1.06k
void ObjFileParser::getMaterialLib() {
632
    // Translate tuple
633
1.06k
    m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
634
1.06k
    if (m_DataIt == m_DataItEnd) {
635
0
        return;
636
0
    }
637
638
1.06k
    char *pStart = &(*m_DataIt);
639
43.6k
    while (m_DataIt != m_DataItEnd && !IsLineEnd(*m_DataIt)) {
640
42.6k
        ++m_DataIt;
641
42.6k
    }
642
643
    // Check for existence
644
1.06k
    const std::string strMatName(pStart, &(*m_DataIt));
645
1.06k
    std::string absName;
646
647
    // Check if directive is valid.
648
1.06k
    if (0 == strMatName.length()) {
649
23
        ASSIMP_LOG_WARN("OBJ: no name for material library specified.");
650
23
        return;
651
23
    }
652
653
1.04k
    if (m_pIO->StackSize() > 0) {
654
0
        std::string path = m_pIO->CurrentDirectory();
655
0
        if ('/' != *path.rbegin()) {
656
0
            path += '/';
657
0
        }
658
0
        absName += path;
659
0
        absName += strMatName;
660
1.04k
    } else {
661
1.04k
        absName = strMatName;
662
1.04k
    }
663
  
664
1.04k
  std::unique_ptr<IOStream> pFile(m_pIO->Open(absName));
665
1.04k
    if (nullptr == pFile) {
666
550
        ASSIMP_LOG_ERROR("OBJ: Unable to locate material file ", strMatName);
667
550
        std::string strMatFallbackName = m_originalObjFileName.substr(0, m_originalObjFileName.length() - 3) + "mtl";
668
550
        ASSIMP_LOG_INFO("OBJ: Opening fallback material file ", strMatFallbackName);
669
550
        pFile.reset(m_pIO->Open(strMatFallbackName));
670
550
        if (!pFile) {
671
550
            ASSIMP_LOG_ERROR("OBJ: Unable to locate fallback material file ", strMatFallbackName);
672
550
            m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
673
550
            return;
674
550
        }
675
550
    }
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
494
    std::vector<char> buffer;
682
494
    BaseImporter::TextFileToBuffer(pFile.get(), buffer, BaseImporter::ALLOW_EMPTY);
683
    //m_pIO->Close(pFile);
684
685
    // Importing the material library
686
494
    ObjFileMtlImporter mtlImporter(buffer, strMatName, m_pModel.get());
687
494
}
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
8.01k
int ObjFileParser::getMaterialIndex(const std::string &strMaterialName) {
721
8.01k
    int mat_index = -1;
722
8.01k
    if (strMaterialName.empty()) {
723
0
        return mat_index;
724
0
    }
725
52.4k
    for (size_t index = 0; index < m_pModel->mMaterialLib.size(); ++index) {
726
52.4k
        if (strMaterialName == m_pModel->mMaterialLib[index]) {
727
7.99k
            mat_index = (int)index;
728
7.99k
            break;
729
7.99k
        }
730
52.4k
    }
731
8.01k
    return mat_index;
732
8.01k
}
733
734
// -------------------------------------------------------------------
735
//  Getter for a group name.
736
16.6k
void ObjFileParser::getGroupName() {
737
16.6k
    std::string groupName;
738
739
    // here we skip 'g ' from line
740
16.6k
    m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
741
16.6k
    m_DataIt = getName<DataArrayIt>(m_DataIt, m_DataItEnd, groupName);
742
16.6k
    if (isEndOfBuffer(m_DataIt, m_DataItEnd)) {
743
0
        return;
744
0
    }
745
746
    // Change active group, if necessary
747
16.6k
    if (m_pModel->mActiveGroup != groupName) {
748
        // Search for already existing entry
749
14.0k
        ObjFile::Model::ConstGroupMapIt it = m_pModel->mGroups.find(groupName);
750
751
        // We are mapping groups into the object structure
752
14.0k
        createObject(groupName);
753
754
        // New group name, creating a new entry
755
14.0k
        if (it == m_pModel->mGroups.end()) {
756
400
            std::vector<unsigned int> *pFaceIDArray = new std::vector<unsigned int>;
757
400
            m_pModel->mGroups[groupName] = pFaceIDArray;
758
400
            m_pModel->mGroupFaceIDs = (pFaceIDArray);
759
13.6k
        } else {
760
13.6k
            m_pModel->mGroupFaceIDs = (*it).second;
761
13.6k
        }
762
14.0k
        m_pModel->mActiveGroup = groupName;
763
14.0k
    }
764
16.6k
    m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
765
16.6k
}
766
767
// -------------------------------------------------------------------
768
//  Not supported
769
303
void ObjFileParser::getGroupNumber() {
770
    // Not used
771
772
303
    m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
773
303
}
774
775
// -------------------------------------------------------------------
776
//  Not supported
777
4
void ObjFileParser::getGroupNumberAndResolution() {
778
    // Not used
779
780
4
    m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
781
4
}
782
783
// -------------------------------------------------------------------
784
//  Stores values for a new object instance, name will be used to
785
//  identify it.
786
1.86k
void ObjFileParser::getObjectName() {
787
1.86k
    m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
788
1.86k
    if (m_DataIt == m_DataItEnd) {
789
0
        return;
790
0
    }
791
1.86k
    char *pStart = &(*m_DataIt);
792
11.0k
    while (m_DataIt != m_DataItEnd && !IsSpaceOrNewLine(*m_DataIt)) {
793
9.20k
        ++m_DataIt;
794
9.20k
    }
795
796
1.86k
    std::string strObjectName(pStart, &(*m_DataIt));
797
1.86k
    if (!strObjectName.empty()) {
798
        // Reset current object
799
1.20k
        m_pModel->mCurrentObject = nullptr;
800
801
        // Search for actual object
802
1.20k
        for (std::vector<ObjFile::Object *>::const_iterator it = m_pModel->mObjects.begin();
803
13.9k
                it != m_pModel->mObjects.end();
804
13.8k
                ++it) {
805
13.8k
            if ((*it)->m_strObjName == strObjectName) {
806
1.09k
                m_pModel->mCurrentObject = *it;
807
1.09k
                break;
808
1.09k
            }
809
13.8k
        }
810
811
        // Allocate a new object, if current one was not found before
812
1.20k
        if (nullptr == m_pModel->mCurrentObject) {
813
107
            createObject(strObjectName);
814
107
        }
815
1.20k
    }
816
1.86k
    m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
817
1.86k
}
818
// -------------------------------------------------------------------
819
//  Creates a new object instance
820
14.1k
void ObjFileParser::createObject(const std::string &objName) {
821
14.1k
    ai_assert(nullptr != m_pModel);
822
823
14.1k
    m_pModel->mCurrentObject = new ObjFile::Object;
824
14.1k
    m_pModel->mCurrentObject->m_strObjName = objName;
825
14.1k
    m_pModel->mObjects.push_back(m_pModel->mCurrentObject);
826
827
14.1k
    createMesh(objName);
828
829
14.1k
    if (m_pModel->mCurrentMaterial) {
830
4.20k
        m_pModel->mCurrentMesh->m_uiMaterialIndex =
831
4.20k
                getMaterialIndex(m_pModel->mCurrentMaterial->MaterialName.data);
832
4.20k
        m_pModel->mCurrentMesh->m_pMaterial = m_pModel->mCurrentMaterial;
833
4.20k
    }
834
14.1k
}
835
// -------------------------------------------------------------------
836
//  Creates a new mesh
837
14.7k
void ObjFileParser::createMesh(const std::string &meshName) {
838
14.7k
    ai_assert(nullptr != m_pModel);
839
840
14.7k
    m_pModel->mCurrentMesh = new ObjFile::Mesh(meshName);
841
14.7k
    m_pModel->mMeshes.push_back(m_pModel->mCurrentMesh);
842
14.7k
    unsigned int meshId = static_cast<unsigned int>(m_pModel->mMeshes.size() - 1);
843
14.7k
    if (nullptr != m_pModel->mCurrentObject) {
844
14.7k
        m_pModel->mCurrentObject->m_Meshes.push_back(meshId);
845
14.7k
    } else {
846
21
        ASSIMP_LOG_ERROR("OBJ: No object detected to attach a new mesh instance.");
847
21
    }
848
14.7k
}
849
850
// -------------------------------------------------------------------
851
//  Returns true, if a new mesh must be created.
852
1.91k
bool ObjFileParser::needsNewMesh(const std::string &materialName) {
853
    // If no mesh data yet
854
1.91k
    if (m_pModel->mCurrentMesh == nullptr) {
855
21
        return true;
856
21
    }
857
1.89k
    bool newMat = false;
858
1.89k
    int matIdx = getMaterialIndex(materialName);
859
1.89k
    int curMatIdx = m_pModel->mCurrentMesh->m_uiMaterialIndex;
860
1.89k
    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
1.89k
            && !m_pModel->mCurrentMesh->m_Faces.empty()) {
864
        // New material -> only one material per mesh, so we need to create a new
865
        // material
866
589
        newMat = true;
867
589
    }
868
1.89k
    return newMat;
869
1.91k
}
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