/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 |