Coverage Report

Created: 2025-08-28 06:38

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