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