Coverage Report

Created: 2026-02-05 07:01

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