Coverage Report

Created: 2025-08-03 06:54

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