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