Coverage Report

Created: 2026-05-04 07:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/assimp/code/AssetLib/Obj/ObjFileImporter.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
42
#ifndef ASSIMP_BUILD_NO_OBJ_IMPORTER
43
44
#include "ObjFileImporter.h"
45
#include "ObjFileData.h"
46
#include "ObjFileParser.h"
47
#include <assimp/DefaultIOSystem.h>
48
#include <assimp/IOStreamBuffer.h>
49
#include <assimp/ai_assert.h>
50
#include <assimp/importerdesc.h>
51
#include <assimp/scene.h>
52
#include <assimp/DefaultLogger.hpp>
53
#include <assimp/Importer.hpp>
54
#include <assimp/ObjMaterial.h>
55
#include <memory>
56
57
static constexpr aiImporterDesc desc = {
58
    "Wavefront Object Importer",
59
    "",
60
    "",
61
    "surfaces not supported",
62
    aiImporterFlags_SupportTextFlavour,
63
    0,
64
    0,
65
    0,
66
    0,
67
    "obj"
68
};
69
70
static constexpr unsigned int ObjMinSize = 16u;
71
72
namespace Assimp {
73
74
using namespace std;
75
76
// ------------------------------------------------------------------------------------------------
77
//  Default constructor
78
ObjFileImporter::ObjFileImporter() :
79
38.9k
        m_Buffer(),
80
38.9k
        m_pRootObject(nullptr),
81
38.9k
        m_strAbsPath(std::string(1, DefaultIOSystem().getOsSeparator())) {
82
    // empty
83
38.9k
}
84
85
// ------------------------------------------------------------------------------------------------
86
//  Destructor.
87
38.9k
ObjFileImporter::~ObjFileImporter() {
88
38.9k
    delete m_pRootObject;
89
38.9k
}
90
91
// ------------------------------------------------------------------------------------------------
92
//  Returns true if file is an obj file.
93
0
bool ObjFileImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
94
0
    static const char *tokens[] = { "mtllib", "usemtl", "v ", "vt ", "vn ", "o ", "g ", "s ", "f " };
95
0
    return BaseImporter::SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens), 200, false, true);
96
0
}
97
98
// ------------------------------------------------------------------------------------------------
99
63.0k
const aiImporterDesc *ObjFileImporter::GetInfo() const {
100
63.0k
    return &desc;
101
63.0k
}
102
103
// ------------------------------------------------------------------------------------------------
104
//  Obj-file import implementation
105
12.0k
void ObjFileImporter::InternReadFile(const std::string &file, aiScene *pScene, IOSystem *pIOHandler) {
106
12.0k
    if (m_pRootObject != nullptr) {
107
0
        delete m_pRootObject;
108
0
        m_pRootObject = nullptr;
109
0
    }
110
111
    // Read file into memory
112
12.0k
    static constexpr char mode[] = "rb";
113
12.0k
    auto streamCloser = [&](IOStream *pStream) {
114
12.0k
        pIOHandler->Close(pStream);
115
12.0k
    };
116
12.0k
    std::unique_ptr<IOStream, decltype(streamCloser)> fileStream(pIOHandler->Open(file, mode), streamCloser);
117
12.0k
    if (!fileStream) {
118
0
        throw DeadlyImportError("Failed to open file ", file, ".");
119
0
    }
120
121
    // Get the file-size and validate it, throwing an exception when fails
122
12.0k
    size_t fileSize = fileStream->FileSize();
123
12.0k
    if (fileSize < ObjMinSize) {
124
6
        throw DeadlyImportError("OBJ-file is too small.");
125
6
    }
126
127
12.0k
    IOStreamBuffer<char> streamedBuffer;
128
12.0k
    streamedBuffer.open(fileStream.get());
129
130
    // Allocate buffer and read file into it
131
    //TextFileToBuffer( fileStream.get(),m_Buffer);
132
133
    // Get the model name
134
12.0k
    std::string modelName, folderName;
135
12.0k
    std::string::size_type pos = file.find_last_of("\\/");
136
12.0k
    if (pos != std::string::npos) {
137
0
        modelName = file.substr(pos + 1, file.size() - pos - 1);
138
0
        folderName = file.substr(0, pos);
139
0
        if (!folderName.empty()) {
140
0
            pIOHandler->PushDirectory(folderName);
141
0
        }
142
12.0k
    } else {
143
12.0k
        modelName = file;
144
12.0k
    }
145
146
    // parse the file into a temporary representation
147
12.0k
    ObjFileParser parser(streamedBuffer, modelName, pIOHandler, m_progress, file);
148
149
    // And create the proper return structures out of it
150
12.0k
    CreateDataFromImport(parser.GetModel(), pScene);
151
152
12.0k
    streamedBuffer.close();
153
154
    // Clean up allocated storage for the next import
155
12.0k
    m_Buffer.clear();
156
157
    // Pop directory stack
158
12.0k
    if (pIOHandler->StackSize() > 0) {
159
0
        pIOHandler->PopDirectory();
160
0
    }
161
12.0k
}
162
163
// ------------------------------------------------------------------------------------------------
164
//  Create the data from parsed obj-file
165
11.1k
void ObjFileImporter::CreateDataFromImport(const ObjFile::Model *pModel, aiScene *pScene) {
166
11.1k
    if (pModel == nullptr) {
167
0
        return;
168
0
    }
169
170
    // Create the root node of the scene
171
11.1k
    pScene->mRootNode = new aiNode;
172
11.1k
    if (!pModel->mModelName.empty()) {
173
        // Set the name of the scene
174
11.1k
        pScene->mRootNode->mName.Set(pModel->mModelName);
175
11.1k
    } else {
176
        // This is a fatal error, so break down the application
177
0
        ai_assert(false);
178
0
    }
179
180
11.1k
    if (!pModel->mObjects.empty()) {
181
5.47k
        unsigned int meshCount = 0;
182
5.47k
        unsigned int childCount = 0;
183
184
182k
        for (auto object : pModel->mObjects) {
185
182k
            if (object) {
186
182k
                ++childCount;
187
182k
                meshCount += (unsigned int)object->m_Meshes.size();
188
182k
            }
189
182k
        }
190
191
        // Allocate space for the child nodes on the root node
192
5.47k
        pScene->mRootNode->mChildren = new aiNode *[childCount];
193
194
        // Create nodes for the whole scene
195
5.47k
        std::vector<std::unique_ptr<aiMesh>> MeshArray;
196
5.47k
        MeshArray.reserve(meshCount);
197
181k
        for (size_t index = 0; index < pModel->mObjects.size(); ++index) {
198
175k
            createNodes(pModel, pModel->mObjects[index], pScene->mRootNode, pScene, MeshArray);
199
175k
        }
200
201
5.47k
        ai_assert(pScene->mRootNode->mNumChildren == childCount);
202
203
        // Create mesh pointer buffer for this scene
204
5.47k
        if (pScene->mNumMeshes > 0) {
205
3.43k
            pScene->mMeshes = new aiMesh *[MeshArray.size()];
206
86.1k
            for (size_t index = 0; index < MeshArray.size(); ++index) {
207
82.6k
                pScene->mMeshes[index] = MeshArray[index].release();
208
82.6k
            }
209
3.43k
        }
210
211
        // Create all materials
212
5.47k
        createMaterials(pModel, pScene);
213
5.63k
    } else {
214
5.63k
        if (pModel->mVertices.empty()) {
215
5.24k
            return;
216
5.24k
        }
217
218
390
        std::unique_ptr<aiMesh> mesh(new aiMesh);
219
390
        mesh->mPrimitiveTypes = aiPrimitiveType_POINT;
220
390
        unsigned int n = (unsigned int)pModel->mVertices.size();
221
390
        mesh->mNumVertices = n;
222
223
390
        mesh->mVertices = new aiVector3D[n];
224
390
        memcpy(mesh->mVertices, pModel->mVertices.data(), n * sizeof(aiVector3D));
225
226
390
        if (!pModel->mNormals.empty()) {
227
11
            mesh->mNormals = new aiVector3D[n];
228
11
            if (pModel->mNormals.size() < n) {
229
2
                throw DeadlyImportError("OBJ: vertex normal index out of range");
230
2
            }
231
9
            memcpy(mesh->mNormals, pModel->mNormals.data(), n * sizeof(aiVector3D));
232
9
        }
233
234
388
        if (!pModel->mVertexColors.empty()) {
235
132
            mesh->mColors[0] = new aiColor4D[mesh->mNumVertices];
236
76.3k
            for (unsigned int i = 0; i < n; ++i) {
237
76.2k
                if (i < pModel->mVertexColors.size()) {
238
76.2k
                    const aiVector3D &color = pModel->mVertexColors[i];
239
76.2k
                    mesh->mColors[0][i] = aiColor4D(color.x, color.y, color.z, 1.0);
240
76.2k
                } else {
241
0
                    throw DeadlyImportError("OBJ: vertex color index out of range");
242
0
                }
243
76.2k
            }
244
132
        }
245
246
388
        pScene->mRootNode->mNumMeshes = 1;
247
388
        pScene->mRootNode->mMeshes = new unsigned int[1];
248
388
        pScene->mRootNode->mMeshes[0] = 0;
249
388
        pScene->mMeshes = new aiMesh *[1];
250
388
        pScene->mNumMeshes = 1;
251
388
        pScene->mMeshes[0] = mesh.release();
252
388
    }
253
11.1k
}
254
255
// ------------------------------------------------------------------------------------------------
256
//  Creates all nodes of the model
257
aiNode *ObjFileImporter::createNodes(const ObjFile::Model *pModel, const ObjFile::Object *pObject,
258
        aiNode *pParent, aiScene *pScene,
259
175k
        std::vector<std::unique_ptr<aiMesh>> &MeshArray) {
260
175k
    if (nullptr == pObject || pModel == nullptr) {
261
0
        return nullptr;
262
0
    }
263
264
    // Store older mesh size to be able to computes mesh offsets for new mesh instances
265
175k
    const size_t oldMeshSize = MeshArray.size();
266
175k
    aiNode *pNode = new aiNode;
267
268
175k
    pNode->mName = pObject->m_strObjName;
269
270
    // If we have a parent node, store it
271
175k
    ai_assert(nullptr != pParent);
272
175k
    appendChildToParentNode(pParent, pNode);
273
274
383k
    for (size_t i = 0; i < pObject->m_Meshes.size(); ++i) {
275
207k
        unsigned int meshId = pObject->m_Meshes[i];
276
207k
        std::unique_ptr<aiMesh> pMesh = createTopology(pModel, pObject, meshId);
277
207k
        if (pMesh != nullptr) {
278
83.3k
            if (pMesh->mNumFaces > 0) {
279
83.3k
                MeshArray.push_back(std::move(pMesh));
280
83.3k
            }
281
83.3k
        }
282
207k
    }
283
284
    // Create all nodes from the sub-objects stored in the current object
285
175k
    if (!pObject->m_SubObjects.empty()) {
286
0
        size_t numChilds = pObject->m_SubObjects.size();
287
0
        pNode->mNumChildren = static_cast<unsigned int>(numChilds);
288
0
        pNode->mChildren = new aiNode *[numChilds];
289
0
        pNode->mNumMeshes = 1;
290
0
        pNode->mMeshes = new unsigned int[1];
291
0
    }
292
293
    // Set mesh instances into scene- and node-instances
294
175k
    const size_t meshSizeDiff = MeshArray.size() - oldMeshSize;
295
175k
    if (meshSizeDiff > 0) {
296
55.0k
        pNode->mMeshes = new unsigned int[meshSizeDiff];
297
55.0k
        pNode->mNumMeshes = static_cast<unsigned int>(meshSizeDiff);
298
55.0k
        size_t index = 0;
299
138k
        for (size_t i = oldMeshSize; i < MeshArray.size(); ++i) {
300
83.2k
            pNode->mMeshes[index] = pScene->mNumMeshes;
301
83.2k
            pScene->mNumMeshes++;
302
83.2k
            ++index;
303
83.2k
        }
304
55.0k
    }
305
306
175k
    return pNode;
307
175k
}
308
309
// ------------------------------------------------------------------------------------------------
310
//  Create topology data
311
207k
std::unique_ptr<aiMesh> ObjFileImporter::createTopology(const ObjFile::Model *pModel, const ObjFile::Object *pData, unsigned int meshIndex) {
312
207k
    if (nullptr == pData || pModel == nullptr) {
313
0
        return nullptr;
314
0
    }
315
316
    // Create faces
317
207k
    ObjFile::Mesh *pObjMesh = pModel->mMeshes[meshIndex];
318
207k
    if (pObjMesh == nullptr) {
319
0
        return nullptr;
320
0
    }
321
322
207k
    if (pObjMesh->m_Faces.empty()) {
323
123k
        return nullptr;
324
123k
    }
325
326
83.9k
    std::unique_ptr<aiMesh> pMesh(new aiMesh);
327
83.9k
    if (!pObjMesh->m_name.empty()) {
328
82.2k
        pMesh->mName.Set(pObjMesh->m_name);
329
82.2k
    }
330
331
755k
    for (size_t index = 0; index < pObjMesh->m_Faces.size(); index++) {
332
671k
        const ObjFile::Face *inp = pObjMesh->m_Faces[index];
333
671k
        if (inp == nullptr) {
334
0
            continue;
335
0
        }
336
337
671k
        if (inp->mPrimitiveType == aiPrimitiveType_LINE) {
338
20.5k
            pMesh->mNumFaces += static_cast<unsigned int>(inp->m_vertices.size() - 1);
339
20.5k
            pMesh->mPrimitiveTypes |= aiPrimitiveType_LINE;
340
650k
        } else if (inp->mPrimitiveType == aiPrimitiveType_POINT) {
341
3.99k
            pMesh->mNumFaces += static_cast<unsigned int>(inp->m_vertices.size());
342
3.99k
            pMesh->mPrimitiveTypes |= aiPrimitiveType_POINT;
343
646k
        } else {
344
646k
            ++pMesh->mNumFaces;
345
646k
            if (inp->m_vertices.size() > 3) {
346
97.5k
                pMesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON;
347
549k
            } else {
348
549k
                pMesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE;
349
549k
            }
350
646k
        }
351
671k
    }
352
353
83.9k
    unsigned int uiIdxCount = 0u;
354
83.9k
    if (pMesh->mNumFaces > 0) {
355
83.8k
        pMesh->mFaces = new aiFace[pMesh->mNumFaces];
356
83.8k
        if (pObjMesh->m_uiMaterialIndex != ObjFile::Mesh::NoMaterial) {
357
76.4k
            pMesh->mMaterialIndex = pObjMesh->m_uiMaterialIndex;
358
76.4k
        }
359
360
83.8k
        unsigned int outIndex = 0u;
361
362
        // Copy all data from all stored meshes
363
671k
        for (auto &face : pObjMesh->m_Faces) {
364
671k
            const ObjFile::Face *inp = face;
365
671k
            if (inp->mPrimitiveType == aiPrimitiveType_LINE) {
366
388k
                for (size_t i = 0; i < inp->m_vertices.size() - 1; ++i) {
367
368k
                    aiFace &f = pMesh->mFaces[outIndex++];
368
368k
                    uiIdxCount += f.mNumIndices = 2;
369
368k
                    f.mIndices = new unsigned int[2];
370
368k
                }
371
20.5k
                continue;
372
650k
            } else if (inp->mPrimitiveType == aiPrimitiveType_POINT) {
373
36.8k
                for (size_t i = 0; i < inp->m_vertices.size(); ++i) {
374
32.8k
                    aiFace &f = pMesh->mFaces[outIndex++];
375
32.8k
                    uiIdxCount += f.mNumIndices = 1;
376
32.8k
                    f.mIndices = new unsigned int[1];
377
32.8k
                }
378
3.99k
                continue;
379
3.99k
            }
380
381
646k
            aiFace *pFace = &pMesh->mFaces[outIndex++];
382
646k
            const unsigned int uiNumIndices = (unsigned int)face->m_vertices.size();
383
646k
            uiIdxCount += pFace->mNumIndices = (unsigned int)uiNumIndices;
384
646k
            if (pFace->mNumIndices > 0) {
385
646k
                pFace->mIndices = new unsigned int[uiNumIndices];
386
646k
            }
387
646k
        }
388
83.8k
    }
389
390
    // Create mesh vertices
391
83.9k
    createVertexArray(pModel, pData, meshIndex, pMesh.get(), uiIdxCount);
392
393
83.9k
    return pMesh;
394
207k
}
395
396
// ------------------------------------------------------------------------------------------------
397
//  Creates a vertex array
398
void ObjFileImporter::createVertexArray(const ObjFile::Model *pModel,
399
        const ObjFile::Object *pCurrentObject,
400
        unsigned int uiMeshIndex,
401
        aiMesh *pMesh,
402
83.9k
        unsigned int numIndices) {
403
    // Checking preconditions
404
83.9k
    if (pCurrentObject == nullptr || pModel == nullptr || pMesh == nullptr) {
405
0
        return;
406
0
    }
407
408
    // Break, if no faces are stored in object
409
83.9k
    if (pCurrentObject->m_Meshes.empty()) {
410
0
        return;
411
0
    }
412
413
    // Get current mesh
414
83.9k
    ObjFile::Mesh *pObjMesh = pModel->mMeshes[uiMeshIndex];
415
83.9k
    if (nullptr == pObjMesh || pObjMesh->m_uiNumIndices < 1) {
416
0
        return;
417
0
    }
418
419
    // Copy vertices of this mesh instance
420
83.9k
    pMesh->mNumVertices = numIndices;
421
83.9k
    if (pMesh->mNumVertices == 0) {
422
25
        throw DeadlyImportError("OBJ: no vertices");
423
83.8k
    } else if (pMesh->mNumVertices > AI_MAX_VERTICES) {
424
0
        throw DeadlyImportError("OBJ: Too many vertices");
425
0
    }
426
83.8k
    pMesh->mVertices = new aiVector3D[pMesh->mNumVertices];
427
428
    // Allocate buffer for normal vectors
429
83.8k
    if (!pModel->mNormals.empty() && pObjMesh->m_hasNormals)
430
19.1k
        pMesh->mNormals = new aiVector3D[pMesh->mNumVertices];
431
432
    // Allocate buffer for vertex-color vectors
433
83.8k
    if (!pModel->mVertexColors.empty())
434
28.4k
        pMesh->mColors[0] = new aiColor4D[pMesh->mNumVertices];
435
436
    // Allocate buffer for texture coordinates
437
83.8k
    if (!pModel->mTextureCoord.empty() && pObjMesh->m_uiUVCoordinates[0]) {
438
9.29k
        pMesh->mNumUVComponents[0] = pModel->mTextureCoordDim;
439
9.29k
        pMesh->mTextureCoords[0] = new aiVector3D[pMesh->mNumVertices];
440
9.29k
    }
441
442
    // Copy vertices, normals and textures into aiMesh instance
443
83.8k
    bool normalsok = true, uvok = true;
444
83.8k
    unsigned int newIndex = 0, outIndex = 0;
445
658k
    for (auto sourceFace : pObjMesh->m_Faces) {
446
        // Copy all index arrays
447
6.37M
        for (size_t vertexIndex = 0, outVertexIndex = 0; vertexIndex < sourceFace->m_vertices.size(); vertexIndex++) {
448
5.71M
            const unsigned int vertex = sourceFace->m_vertices.at(vertexIndex);
449
5.71M
            if (vertex >= pModel->mVertices.size()) {
450
566
                throw DeadlyImportError("OBJ: vertex index out of range");
451
566
            }
452
453
5.71M
            if (pMesh->mNumVertices <= newIndex) {
454
5
                throw DeadlyImportError("OBJ: bad vertex index");
455
5
            }
456
457
5.71M
            pMesh->mVertices[newIndex] = pModel->mVertices[vertex];
458
459
            // Copy all normals
460
5.71M
            if (normalsok && !pModel->mNormals.empty() && vertexIndex < sourceFace->m_normals.size()) {
461
1.00M
                const unsigned int normal = sourceFace->m_normals.at(vertexIndex);
462
1.00M
                if (normal >= pModel->mNormals.size()) {
463
2.90k
                    normalsok = false;
464
1.00M
                } else {
465
1.00M
                    pMesh->mNormals[newIndex] = pModel->mNormals[normal];
466
1.00M
                }
467
1.00M
            }
468
469
            // Copy all vertex colors
470
5.71M
            if (vertex < pModel->mVertexColors.size()) {
471
3.13M
                const aiVector3D &color = pModel->mVertexColors[vertex];
472
3.13M
                pMesh->mColors[0][newIndex] = aiColor4D(color.x, color.y, color.z, 1.0);
473
3.13M
            }
474
475
            // Copy all texture coordinates
476
5.71M
            if (uvok && !pModel->mTextureCoord.empty() && vertexIndex < sourceFace->m_texturCoords.size()) {
477
229k
                const unsigned int tex = sourceFace->m_texturCoords.at(vertexIndex);
478
479
229k
                if (tex >= pModel->mTextureCoord.size()) {
480
1.39k
                    uvok = false;
481
227k
                } else {
482
227k
                    const aiVector3D &coord3d = pModel->mTextureCoord[tex];
483
227k
                    pMesh->mTextureCoords[0][newIndex] = aiVector3D(coord3d.x, coord3d.y, coord3d.z);
484
227k
                }
485
229k
            }
486
487
            // Get destination face
488
5.71M
            aiFace *pDestFace = &pMesh->mFaces[outIndex];
489
490
5.71M
            const bool last = (vertexIndex == sourceFace->m_vertices.size() - 1);
491
5.71M
            if (sourceFace->mPrimitiveType != aiPrimitiveType_LINE || !last) {
492
5.69M
                pDestFace->mIndices[outVertexIndex] = newIndex;
493
5.69M
                outVertexIndex++;
494
5.69M
            }
495
496
5.71M
            if (sourceFace->mPrimitiveType == aiPrimitiveType_POINT) {
497
27.4k
                outIndex++;
498
27.4k
                outVertexIndex = 0;
499
5.68M
            } else if (sourceFace->mPrimitiveType == aiPrimitiveType_LINE) {
500
79.7k
                outVertexIndex = 0;
501
502
79.7k
                if (!last)
503
59.9k
                    outIndex++;
504
505
79.7k
                if (vertexIndex) {
506
59.8k
                    if (!last) {
507
40.0k
                        if (pMesh->mNumVertices <= newIndex + 1) {
508
2
                            throw DeadlyImportError("OBJ: bad vertex index");
509
2
                        }
510
511
40.0k
                        pMesh->mVertices[newIndex + 1] = pMesh->mVertices[newIndex];
512
40.0k
                        if (!sourceFace->m_normals.empty() && !pModel->mNormals.empty()) {
513
298
                            pMesh->mNormals[newIndex + 1] = pMesh->mNormals[newIndex];
514
298
                        }
515
40.0k
                        if (!pModel->mTextureCoord.empty()) {
516
46.5k
                            for (size_t i = 0; i < pMesh->GetNumUVChannels(); i++) {
517
20.2k
                                pMesh->mTextureCoords[i][newIndex + 1] = pMesh->mTextureCoords[i][newIndex];
518
20.2k
                            }
519
26.2k
                        }
520
40.0k
                        ++newIndex;
521
40.0k
                    }
522
523
59.8k
                    pDestFace[-1].mIndices[1] = newIndex;
524
59.8k
                }
525
5.60M
            } else if (last) {
526
635k
                outIndex++;
527
635k
            }
528
5.71M
            ++newIndex;
529
5.71M
        }
530
658k
    }
531
532
83.3k
    if (!normalsok) {
533
2.89k
        delete[] pMesh->mNormals;
534
2.89k
        pMesh->mNormals = nullptr;
535
2.89k
    }
536
537
83.3k
    if (!uvok) {
538
1.38k
        delete[] pMesh->mTextureCoords[0];
539
1.38k
        pMesh->mTextureCoords[0] = nullptr;
540
1.38k
    }
541
83.3k
}
542
543
// ------------------------------------------------------------------------------------------------
544
//  Counts all stored meshes
545
0
void ObjFileImporter::countObjects(const std::vector<ObjFile::Object *> &rObjects, int &iNumMeshes) {
546
0
    iNumMeshes = 0;
547
0
    if (rObjects.empty())
548
0
        return;
549
550
0
    iNumMeshes += static_cast<unsigned int>(rObjects.size());
551
0
    for (auto object : rObjects) {
552
0
        if (!object->m_SubObjects.empty()) {
553
0
            countObjects(object->m_SubObjects, iNumMeshes);
554
0
        }
555
0
    }
556
0
}
557
558
// ------------------------------------------------------------------------------------------------
559
//   Add clamp mode property to material if necessary
560
135
void ObjFileImporter::addTextureMappingModeProperty(aiMaterial *mat, aiTextureType type, int clampMode, int index) {
561
135
    if (nullptr == mat) {
562
0
        return;
563
0
    }
564
565
135
    mat->AddProperty<int>(&clampMode, 1, AI_MATKEY_MAPPINGMODE_U(type, index));
566
135
    mat->AddProperty<int>(&clampMode, 1, AI_MATKEY_MAPPINGMODE_V(type, index));
567
135
}
568
569
// ------------------------------------------------------------------------------------------------
570
//  Creates the material
571
4.87k
void ObjFileImporter::createMaterials(const ObjFile::Model *pModel, aiScene *pScene) {
572
4.87k
    if (nullptr == pScene) {
573
0
        return;
574
0
    }
575
576
4.87k
    const unsigned int numMaterials = (unsigned int)pModel->mMaterialLib.size();
577
4.87k
    pScene->mNumMaterials = 0;
578
4.87k
    if (pModel->mMaterialLib.empty()) {
579
0
        ASSIMP_LOG_DEBUG("OBJ: no materials specified");
580
0
        return;
581
0
    }
582
583
4.87k
    pScene->mMaterials = new aiMaterial *[numMaterials];
584
26.5k
    for (unsigned int matIndex = 0; matIndex < numMaterials; matIndex++) {
585
        // Store material name
586
21.7k
        std::map<std::string, ObjFile::Material *>::const_iterator it;
587
21.7k
        it = pModel->mMaterialMap.find(pModel->mMaterialLib[matIndex]);
588
589
        // No material found, use the default material
590
21.7k
        if (pModel->mMaterialMap.end() == it) {
591
0
            continue;
592
0
        }
593
594
21.7k
        aiMaterial *mat = new aiMaterial;
595
21.7k
        ObjFile::Material *pCurrentMaterial = it->second;
596
21.7k
        mat->AddProperty(&pCurrentMaterial->MaterialName, AI_MATKEY_NAME);
597
598
        // convert illumination model
599
21.7k
        int sm = 0;
600
21.7k
        switch (pCurrentMaterial->illumination_model) {
601
1.19k
        case 0:
602
1.19k
            sm = aiShadingMode_NoShading;
603
1.19k
            break;
604
20.0k
        case 1:
605
20.0k
            sm = aiShadingMode_Gouraud;
606
20.0k
            break;
607
148
        case 2:
608
148
            sm = aiShadingMode_Phong;
609
148
            break;
610
342
        default:
611
342
            sm = aiShadingMode_Gouraud;
612
342
            ASSIMP_LOG_ERROR("OBJ: unexpected illumination model (0-2 recognized)");
613
21.7k
        }
614
615
21.7k
        mat->AddProperty<int>(&sm, 1, AI_MATKEY_SHADING_MODEL);
616
617
        // Preserve the original illum value
618
21.7k
        mat->AddProperty<int>(&pCurrentMaterial->illumination_model, 1, AI_MATKEY_OBJ_ILLUM);
619
620
        // Adding material colors
621
21.7k
        mat->AddProperty(&pCurrentMaterial->ambient, 1, AI_MATKEY_COLOR_AMBIENT);
622
21.7k
        mat->AddProperty(&pCurrentMaterial->diffuse, 1, AI_MATKEY_COLOR_DIFFUSE);
623
21.7k
        mat->AddProperty(&pCurrentMaterial->specular, 1, AI_MATKEY_COLOR_SPECULAR);
624
21.7k
        mat->AddProperty(&pCurrentMaterial->emissive, 1, AI_MATKEY_COLOR_EMISSIVE);
625
21.7k
        mat->AddProperty(&pCurrentMaterial->shineness, 1, AI_MATKEY_SHININESS);
626
21.7k
        mat->AddProperty(&pCurrentMaterial->alpha, 1, AI_MATKEY_OPACITY);
627
21.7k
        mat->AddProperty(&pCurrentMaterial->transparent, 1, AI_MATKEY_COLOR_TRANSPARENT);
628
21.7k
        if (pCurrentMaterial->roughness)
629
99
            mat->AddProperty(&pCurrentMaterial->roughness.Get(), 1, AI_MATKEY_ROUGHNESS_FACTOR);
630
21.7k
        if (pCurrentMaterial->metallic)
631
111
            mat->AddProperty(&pCurrentMaterial->metallic.Get(), 1, AI_MATKEY_METALLIC_FACTOR);
632
21.7k
        if (pCurrentMaterial->sheen)
633
415
            mat->AddProperty(&pCurrentMaterial->sheen.Get(), 1, AI_MATKEY_SHEEN_COLOR_FACTOR);
634
21.7k
        if (pCurrentMaterial->clearcoat_thickness)
635
145
            mat->AddProperty(&pCurrentMaterial->clearcoat_thickness.Get(), 1, AI_MATKEY_CLEARCOAT_FACTOR);
636
21.7k
        if (pCurrentMaterial->clearcoat_roughness)
637
53
            mat->AddProperty(&pCurrentMaterial->clearcoat_roughness.Get(), 1, AI_MATKEY_CLEARCOAT_ROUGHNESS_FACTOR);
638
21.7k
        mat->AddProperty(&pCurrentMaterial->anisotropy, 1, AI_MATKEY_ANISOTROPY_FACTOR);
639
640
        // Adding refraction index
641
21.7k
        mat->AddProperty(&pCurrentMaterial->ior, 1, AI_MATKEY_REFRACTI);
642
643
        // Adding textures
644
21.7k
        const int uvwIndex = 0;
645
646
21.7k
        if (0 != pCurrentMaterial->texture.length) {
647
549
            mat->AddProperty(&pCurrentMaterial->texture, AI_MATKEY_TEXTURE_DIFFUSE(0));
648
549
            mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_DIFFUSE(0));
649
549
            if (pCurrentMaterial->clamp[ObjFile::Material::TextureDiffuseType]) {
650
20
                addTextureMappingModeProperty(mat, aiTextureType_DIFFUSE);
651
20
            }
652
549
        }
653
654
21.7k
        if (0 != pCurrentMaterial->textureAmbient.length) {
655
156
            mat->AddProperty(&pCurrentMaterial->textureAmbient, AI_MATKEY_TEXTURE_AMBIENT(0));
656
156
            mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_AMBIENT(0));
657
156
            if (pCurrentMaterial->clamp[ObjFile::Material::TextureAmbientType]) {
658
38
                addTextureMappingModeProperty(mat, aiTextureType_AMBIENT);
659
38
            }
660
156
        }
661
662
21.7k
        if (0 != pCurrentMaterial->textureEmissive.length) {
663
1.11k
            mat->AddProperty(&pCurrentMaterial->textureEmissive, AI_MATKEY_TEXTURE_EMISSIVE(0));
664
1.11k
            mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_EMISSIVE(0));
665
1.11k
        }
666
667
21.7k
        if (0 != pCurrentMaterial->textureSpecular.length) {
668
165
            mat->AddProperty(&pCurrentMaterial->textureSpecular, AI_MATKEY_TEXTURE_SPECULAR(0));
669
165
            mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_SPECULAR(0));
670
165
            if (pCurrentMaterial->clamp[ObjFile::Material::TextureSpecularType]) {
671
10
                addTextureMappingModeProperty(mat, aiTextureType_SPECULAR);
672
10
            }
673
165
        }
674
675
21.7k
        if (0 != pCurrentMaterial->textureBump.length) {
676
115
            mat->AddProperty(&pCurrentMaterial->textureBump, AI_MATKEY_TEXTURE_HEIGHT(0));
677
115
            mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_HEIGHT(0));
678
115
            if (pCurrentMaterial->bump_multiplier != 1.0) {
679
47
                mat->AddProperty(&pCurrentMaterial->bump_multiplier, 1, AI_MATKEY_OBJ_BUMPMULT_HEIGHT(0));
680
47
            }
681
115
            if (pCurrentMaterial->clamp[ObjFile::Material::TextureBumpType]) {
682
10
                addTextureMappingModeProperty(mat, aiTextureType_HEIGHT);
683
10
            }
684
115
        }
685
686
21.7k
        if (0 != pCurrentMaterial->textureNormal.length) {
687
851
            mat->AddProperty(&pCurrentMaterial->textureNormal, AI_MATKEY_TEXTURE_NORMALS(0));
688
851
            mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_NORMALS(0));
689
851
            if (pCurrentMaterial->bump_multiplier != 1.0) {
690
125
                mat->AddProperty(&pCurrentMaterial->bump_multiplier, 1, AI_MATKEY_OBJ_BUMPMULT_NORMALS(0));
691
125
            }
692
851
            if (pCurrentMaterial->clamp[ObjFile::Material::TextureNormalType]) {
693
38
                addTextureMappingModeProperty(mat, aiTextureType_NORMALS);
694
38
            }
695
851
        }
696
697
21.7k
        if (0 != pCurrentMaterial->textureReflection[0].length) {
698
355
            ObjFile::Material::TextureType type = 0 != pCurrentMaterial->textureReflection[1].length ?
699
240
                                                          ObjFile::Material::TextureReflectionCubeTopType :
700
355
                                                          ObjFile::Material::TextureReflectionSphereType;
701
702
355
            unsigned count = type == ObjFile::Material::TextureReflectionSphereType ? 1 : 6;
703
1.91k
            for (unsigned i = 0; i < count; i++) {
704
1.55k
                mat->AddProperty(&pCurrentMaterial->textureReflection[i], AI_MATKEY_TEXTURE_REFLECTION(i));
705
1.55k
                mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_REFLECTION(i));
706
707
1.55k
                if (pCurrentMaterial->clamp[type])
708
0
                    addTextureMappingModeProperty(mat, aiTextureType_REFLECTION, 1, i);
709
1.55k
            }
710
355
        }
711
712
21.7k
        if (0 != pCurrentMaterial->textureDisp.length) {
713
101
            mat->AddProperty(&pCurrentMaterial->textureDisp, AI_MATKEY_TEXTURE_DISPLACEMENT(0));
714
101
            mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_DISPLACEMENT(0));
715
101
            if (pCurrentMaterial->clamp[ObjFile::Material::TextureDispType]) {
716
0
                addTextureMappingModeProperty(mat, aiTextureType_DISPLACEMENT);
717
0
            }
718
101
        }
719
720
21.7k
        if (0 != pCurrentMaterial->textureOpacity.length) {
721
373
            mat->AddProperty(&pCurrentMaterial->textureOpacity, AI_MATKEY_TEXTURE_OPACITY(0));
722
373
            mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_OPACITY(0));
723
373
            if (pCurrentMaterial->clamp[ObjFile::Material::TextureOpacityType]) {
724
19
                addTextureMappingModeProperty(mat, aiTextureType_OPACITY);
725
19
            }
726
373
        }
727
728
21.7k
        if (0 != pCurrentMaterial->textureSpecularity.length) {
729
119
            mat->AddProperty(&pCurrentMaterial->textureSpecularity, AI_MATKEY_TEXTURE_SHININESS(0));
730
119
            mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_SHININESS(0));
731
119
            if (pCurrentMaterial->clamp[ObjFile::Material::TextureSpecularityType]) {
732
0
                addTextureMappingModeProperty(mat, aiTextureType_SHININESS);
733
0
            }
734
119
        }
735
736
21.7k
        if (0 != pCurrentMaterial->textureRoughness.length) {
737
196
            mat->AddProperty(&pCurrentMaterial->textureRoughness, _AI_MATKEY_TEXTURE_BASE, aiTextureType_DIFFUSE_ROUGHNESS, 0);
738
196
            mat->AddProperty(&uvwIndex, 1, _AI_MATKEY_UVWSRC_BASE, aiTextureType_DIFFUSE_ROUGHNESS, 0 );
739
196
            if (pCurrentMaterial->clamp[ObjFile::Material::TextureRoughnessType]) {
740
0
                addTextureMappingModeProperty(mat, aiTextureType_DIFFUSE_ROUGHNESS);
741
0
            }
742
196
        }
743
744
21.7k
        if (0 != pCurrentMaterial->textureMetallic.length) {
745
310
            mat->AddProperty(&pCurrentMaterial->textureMetallic, _AI_MATKEY_TEXTURE_BASE, aiTextureType_METALNESS, 0);
746
310
            mat->AddProperty(&uvwIndex, 1, _AI_MATKEY_UVWSRC_BASE, aiTextureType_METALNESS, 0 );
747
310
            if (pCurrentMaterial->clamp[ObjFile::Material::TextureMetallicType]) {
748
0
                addTextureMappingModeProperty(mat, aiTextureType_METALNESS);
749
0
            }
750
310
        }
751
752
21.7k
        if (0 != pCurrentMaterial->textureSheen.length) {
753
305
            mat->AddProperty(&pCurrentMaterial->textureSheen, _AI_MATKEY_TEXTURE_BASE, aiTextureType_SHEEN, 0);
754
305
            mat->AddProperty(&uvwIndex, 1, _AI_MATKEY_UVWSRC_BASE, aiTextureType_SHEEN, 0 );
755
305
            if (pCurrentMaterial->clamp[ObjFile::Material::TextureSheenType]) {
756
0
                addTextureMappingModeProperty(mat, aiTextureType_SHEEN);
757
0
            }
758
305
        }
759
760
21.7k
        if (0 != pCurrentMaterial->textureRMA.length) {
761
            // NOTE: glTF importer places Rough/Metal/AO texture in Unknown so doing the same here for consistency.
762
0
            mat->AddProperty(&pCurrentMaterial->textureRMA, _AI_MATKEY_TEXTURE_BASE, aiTextureType_UNKNOWN, 0);
763
0
            mat->AddProperty(&uvwIndex, 1, _AI_MATKEY_UVWSRC_BASE, aiTextureType_UNKNOWN, 0 );
764
0
            if (pCurrentMaterial->clamp[ObjFile::Material::TextureRMAType]) {
765
0
                addTextureMappingModeProperty(mat, aiTextureType_UNKNOWN);
766
0
            }
767
0
        }
768
769
        // Store material property info in material array in scene
770
21.7k
        pScene->mMaterials[pScene->mNumMaterials] = mat;
771
21.7k
        pScene->mNumMaterials++;
772
21.7k
    }
773
774
    // Test number of created materials.
775
4.87k
    ai_assert(pScene->mNumMaterials == numMaterials);
776
4.87k
}
777
778
// ------------------------------------------------------------------------------------------------
779
//  Appends this node to the parent node
780
175k
void ObjFileImporter::appendChildToParentNode(aiNode *pParent, aiNode *pChild) {
781
    // Checking preconditions
782
175k
    if (pParent == nullptr || pChild == nullptr) {
783
0
        ai_assert(nullptr != pParent);
784
0
        ai_assert(nullptr != pChild);
785
0
        return;
786
0
    }
787
788
    // Assign parent to child
789
175k
    pChild->mParent = pParent;
790
791
    // Copy node instances into parent node
792
175k
    pParent->mNumChildren++;
793
175k
    pParent->mChildren[pParent->mNumChildren - 1] = pChild;
794
175k
}
795
796
// ------------------------------------------------------------------------------------------------
797
798
} // Namespace Assimp
799
800
#endif // !! ASSIMP_BUILD_NO_OBJ_IMPORTER