/src/assimp/code/AssetLib/Obj/ObjFileParser.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 | | #ifndef ASSIMP_BUILD_NO_OBJ_IMPORTER |
42 | | |
43 | | #include "ObjFileParser.h" |
44 | | #include "ObjFileData.h" |
45 | | #include "ObjFileMtlImporter.h" |
46 | | #include "ObjTools.h" |
47 | | #include <assimp/BaseImporter.h> |
48 | | #include <assimp/DefaultIOSystem.h> |
49 | | #include <assimp/ParsingUtils.h> |
50 | | #include <assimp/DefaultLogger.hpp> |
51 | | #include <assimp/Importer.hpp> |
52 | | #include <cstdlib> |
53 | | #include <memory> |
54 | | #include <utility> |
55 | | |
56 | | namespace Assimp { |
57 | | |
58 | | constexpr const char ObjFileParser::DEFAULT_MATERIAL[]; |
59 | | |
60 | | ObjFileParser::ObjFileParser() : |
61 | 0 | m_DataIt(), |
62 | 0 | m_DataItEnd(), |
63 | 0 | m_pModel(nullptr), |
64 | 0 | m_uiLine(0), |
65 | | m_buffer(), |
66 | 0 | mEnd(&m_buffer[Buffersize]), |
67 | 0 | m_pIO(nullptr), |
68 | 0 | m_progress(nullptr), |
69 | 0 | m_originalObjFileName() { |
70 | 0 | std::fill_n(m_buffer, Buffersize, '\0'); |
71 | 0 | } |
72 | | |
73 | | ObjFileParser::ObjFileParser(IOStreamBuffer<char> &streamBuffer, const std::string &modelName, |
74 | | IOSystem *io, ProgressHandler *progress, |
75 | | const std::string &originalObjFileName) : |
76 | 292 | m_DataIt(), |
77 | 292 | m_DataItEnd(), |
78 | 292 | m_pModel(nullptr), |
79 | 292 | m_uiLine(0), |
80 | | m_buffer(), |
81 | 292 | m_pIO(io), |
82 | 292 | m_progress(progress), |
83 | 292 | m_originalObjFileName(originalObjFileName) { |
84 | 292 | std::fill_n(m_buffer, Buffersize, '\0'); |
85 | | |
86 | | // Create the model instance to store all the data |
87 | 292 | m_pModel.reset(new ObjFile::Model()); |
88 | 292 | m_pModel->mModelName = modelName; |
89 | | |
90 | | // create default material and store it |
91 | 292 | m_pModel->mDefaultMaterial = new ObjFile::Material; |
92 | 292 | m_pModel->mDefaultMaterial->MaterialName.Set(DEFAULT_MATERIAL); |
93 | 292 | m_pModel->mMaterialLib.emplace_back(DEFAULT_MATERIAL); |
94 | 292 | m_pModel->mMaterialMap[DEFAULT_MATERIAL] = m_pModel->mDefaultMaterial; |
95 | | |
96 | | // Start parsing the file |
97 | 292 | parseFile(streamBuffer); |
98 | 292 | } |
99 | | |
100 | 0 | void ObjFileParser::setBuffer(std::vector<char> &buffer) { |
101 | 0 | m_DataIt = buffer.begin(); |
102 | 0 | m_DataItEnd = buffer.end(); |
103 | 0 | } |
104 | | |
105 | 224 | ObjFile::Model *ObjFileParser::GetModel() const { |
106 | 224 | return m_pModel.get(); |
107 | 224 | } |
108 | | |
109 | 292 | void ObjFileParser::parseFile(IOStreamBuffer<char> &streamBuffer) { |
110 | | // only update every 100KB or it'll be too slow |
111 | | //const unsigned int updateProgressEveryBytes = 100 * 1024; |
112 | 292 | const unsigned int bytesToProcess = static_cast<unsigned int>(streamBuffer.size()); |
113 | 292 | const unsigned int progressTotal = bytesToProcess; |
114 | 292 | unsigned int processed = 0u; |
115 | 292 | size_t lastFilePos = 0u; |
116 | | |
117 | 292 | bool insideCstype = false; |
118 | 292 | std::vector<char> buffer; |
119 | 907k | while (streamBuffer.getNextDataLine(buffer, '\\')) { |
120 | 907k | m_DataIt = buffer.begin(); |
121 | 907k | m_DataItEnd = buffer.end(); |
122 | 907k | mEnd = &buffer[buffer.size() - 1] + 1; |
123 | | |
124 | | // Handle progress reporting |
125 | 907k | const size_t filePos(streamBuffer.getFilePos()); |
126 | 907k | if (lastFilePos < filePos) { |
127 | 292 | processed = static_cast<unsigned int>(filePos); |
128 | 292 | lastFilePos = filePos; |
129 | 292 | m_progress->UpdateFileRead(processed, progressTotal); |
130 | 292 | } |
131 | | |
132 | | // handle c-stype section end (http://paulbourke.net/dataformats/obj/) |
133 | 907k | if (insideCstype) { |
134 | 0 | switch (*m_DataIt) { |
135 | 0 | case 'e': { |
136 | 0 | std::string name; |
137 | 0 | getNameNoSpace(m_DataIt, m_DataItEnd, name); |
138 | 0 | insideCstype = name != "end"; |
139 | 0 | } break; |
140 | 0 | } |
141 | 0 | goto pf_skip_line; |
142 | 0 | } |
143 | | |
144 | | // parse line |
145 | 907k | switch (*m_DataIt) { |
146 | 39.9k | case 'v': // Parse a vertex texture coordinate |
147 | 39.9k | { |
148 | 39.9k | ++m_DataIt; |
149 | 39.9k | if (*m_DataIt == ' ' || *m_DataIt == '\t') { |
150 | 34.7k | size_t numComponents = getNumComponentsInDataDefinition(); |
151 | 34.7k | if (numComponents == 3) { |
152 | | // read in vertex definition |
153 | 20.9k | getVector3(m_pModel->mVertices); |
154 | 20.9k | } else if (numComponents == 4) { |
155 | | // read in vertex definition (homogeneous coords) |
156 | 738 | getHomogeneousVector3(m_pModel->mVertices); |
157 | 13.0k | } else if (numComponents == 6) { |
158 | | // fill previous omitted vertex-colors by default |
159 | 206 | if (m_pModel->mVertexColors.size() < m_pModel->mVertices.size()) { |
160 | 26 | m_pModel->mVertexColors.resize(m_pModel->mVertices.size(), aiVector3D(0, 0, 0)); |
161 | 26 | } |
162 | | // read vertex and vertex-color |
163 | 206 | getTwoVectors3(m_pModel->mVertices, m_pModel->mVertexColors); |
164 | 206 | } |
165 | | // append omitted vertex-colors as default for the end if any vertex-color exists |
166 | 34.7k | if (!m_pModel->mVertexColors.empty() && m_pModel->mVertexColors.size() < m_pModel->mVertices.size()) { |
167 | 1.99k | m_pModel->mVertexColors.resize(m_pModel->mVertices.size(), aiVector3D(0, 0, 0)); |
168 | 1.99k | } |
169 | 34.7k | } else if (*m_DataIt == 't') { |
170 | | // read in texture coordinate ( 2D or 3D ) |
171 | 606 | ++m_DataIt; |
172 | 606 | size_t dim = getTexCoordVector(m_pModel->mTextureCoord); |
173 | 606 | m_pModel->mTextureCoordDim = std::max(m_pModel->mTextureCoordDim, (unsigned int)dim); |
174 | 4.53k | } else if (*m_DataIt == 'n') { |
175 | | // Read in normal vector definition |
176 | 753 | ++m_DataIt; |
177 | 753 | getVector3(m_pModel->mNormals); |
178 | 753 | } |
179 | 39.9k | } break; |
180 | | |
181 | 4.37k | case 'p': // Parse a face, line or point statement |
182 | 6.44k | case 'l': |
183 | 17.9k | case 'f': { |
184 | 17.9k | getFace(*m_DataIt == 'f' ? aiPrimitiveType_POLYGON : (*m_DataIt == 'l' ? aiPrimitiveType_LINE : aiPrimitiveType_POINT)); |
185 | 17.9k | } break; |
186 | | |
187 | 397 | case '#': // Parse a comment |
188 | 397 | { |
189 | 397 | getComment(); |
190 | 397 | } break; |
191 | | |
192 | 35.3k | case 'u': // Parse a material desc. setter |
193 | 35.3k | { |
194 | 35.3k | std::string name; |
195 | | |
196 | 35.3k | getNameNoSpace(m_DataIt, m_DataItEnd, name); |
197 | | |
198 | 35.3k | size_t nextSpace = name.find(' '); |
199 | 35.3k | if (nextSpace != std::string::npos) |
200 | 0 | name = name.substr(0, nextSpace); |
201 | | |
202 | 35.3k | if (name == "usemtl") { |
203 | 20.2k | getMaterialDesc(); |
204 | 20.2k | } |
205 | 35.3k | } break; |
206 | | |
207 | 35.7k | case 'm': // Parse a material library or merging group ('mg') |
208 | 35.7k | { |
209 | 35.7k | std::string name; |
210 | | |
211 | 35.7k | getNameNoSpace(m_DataIt, m_DataItEnd, name); |
212 | | |
213 | 35.7k | size_t nextSpace = name.find(' '); |
214 | 35.7k | if (nextSpace != std::string::npos) |
215 | 0 | name = name.substr(0, nextSpace); |
216 | | |
217 | 35.7k | if (name == "mg") |
218 | 28 | getGroupNumberAndResolution(); |
219 | 35.6k | else if (name == "mtllib") |
220 | 15.4k | getMaterialLib(); |
221 | 20.2k | else |
222 | 20.2k | goto pf_skip_line; |
223 | 35.7k | } break; |
224 | | |
225 | 15.4k | case 'g': // Parse group name |
226 | 8.04k | { |
227 | 8.04k | getGroupName(); |
228 | 8.04k | } break; |
229 | | |
230 | 3.05k | case 's': // Parse group number |
231 | 3.05k | { |
232 | 3.05k | getGroupNumber(); |
233 | 3.05k | } break; |
234 | | |
235 | 4.71k | case 'o': // Parse object name |
236 | 4.71k | { |
237 | 4.71k | getObjectName(); |
238 | 4.71k | } break; |
239 | | |
240 | 2.52k | case 'c': // handle cstype section start |
241 | 2.52k | { |
242 | 2.52k | std::string name; |
243 | 2.52k | getNameNoSpace(m_DataIt, m_DataItEnd, name); |
244 | 2.52k | insideCstype = name == "cstype"; |
245 | 2.52k | goto pf_skip_line; |
246 | 35.7k | } |
247 | | |
248 | 760k | default: { |
249 | 782k | pf_skip_line: |
250 | 782k | m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); |
251 | 782k | } break; |
252 | 907k | } |
253 | 907k | } |
254 | 292 | } |
255 | | |
256 | 70.6k | void ObjFileParser::copyNextWord(char *pBuffer, size_t length) { |
257 | 70.6k | size_t index = 0; |
258 | 70.6k | m_DataIt = getNextWord<DataArrayIt>(m_DataIt, m_DataItEnd); |
259 | 70.6k | if (*m_DataIt == '\\') { |
260 | 0 | ++m_DataIt; |
261 | 0 | ++m_DataIt; |
262 | 0 | m_DataIt = getNextWord<DataArrayIt>(m_DataIt, m_DataItEnd); |
263 | 0 | } |
264 | 463k | while (m_DataIt != m_DataItEnd && !IsSpaceOrNewLine(*m_DataIt)) { |
265 | 392k | pBuffer[index] = *m_DataIt; |
266 | 392k | index++; |
267 | 392k | if (index == length - 1) { |
268 | 2 | break; |
269 | 2 | } |
270 | 392k | ++m_DataIt; |
271 | 392k | } |
272 | | |
273 | 70.6k | ai_assert(index < length); |
274 | 70.6k | pBuffer[index] = '\0'; |
275 | 70.6k | } |
276 | | |
277 | 96.5k | static bool isDataDefinitionEnd(const char *tmp) { |
278 | 96.5k | if (*tmp == '\\') { |
279 | 301 | tmp++; |
280 | 301 | if (IsLineEnd(*tmp)) { |
281 | 0 | return true; |
282 | 0 | } |
283 | 301 | } |
284 | 96.5k | return false; |
285 | 96.5k | } |
286 | | |
287 | 27.1k | static bool isNanOrInf(const char *in) { |
288 | | // Look for "nan" or "inf", case insensitive |
289 | 27.1k | return ((in[0] == 'N' || in[0] == 'n') && ASSIMP_strincmp(in, "nan", 3) == 0) || |
290 | 27.1k | ((in[0] == 'I' || in[0] == 'i') && ASSIMP_strincmp(in, "inf", 3) == 0); |
291 | 27.1k | } |
292 | | |
293 | 35.3k | size_t ObjFileParser::getNumComponentsInDataDefinition() { |
294 | 35.3k | size_t numComponents(0); |
295 | 35.3k | const char *tmp(&m_DataIt[0]); |
296 | 35.3k | bool end_of_definition = false; |
297 | 96.5k | while (!end_of_definition) { |
298 | 96.5k | if (isDataDefinitionEnd(tmp)) { |
299 | 0 | tmp += 2; |
300 | 96.5k | } else if (IsLineEnd(*tmp)) { |
301 | 0 | end_of_definition = true; |
302 | 0 | } |
303 | 96.5k | if (!SkipSpaces(&tmp, mEnd) || *tmp == '#') { |
304 | 39 | break; |
305 | 39 | } |
306 | 96.5k | const bool isNum(IsNumeric(*tmp) || isNanOrInf(tmp)); |
307 | 96.5k | SkipToken(tmp, mEnd); |
308 | 96.5k | if (isNum) { |
309 | 86.2k | ++numComponents; |
310 | 86.2k | } |
311 | 96.5k | if (!SkipSpaces(&tmp, mEnd) || *tmp == '#') { |
312 | 35.3k | break; |
313 | 35.3k | } |
314 | 96.5k | } |
315 | | |
316 | 35.3k | return numComponents; |
317 | 35.3k | } |
318 | | |
319 | 606 | size_t ObjFileParser::getTexCoordVector(std::vector<aiVector3D> &point3d_array) { |
320 | 606 | size_t numComponents = getNumComponentsInDataDefinition(); |
321 | 606 | ai_real x, y, z; |
322 | 606 | if (2 == numComponents) { |
323 | 541 | copyNextWord(m_buffer, Buffersize); |
324 | 541 | x = (ai_real)fast_atof(m_buffer); |
325 | | |
326 | 541 | copyNextWord(m_buffer, Buffersize); |
327 | 541 | y = (ai_real)fast_atof(m_buffer); |
328 | 541 | z = 0.0; |
329 | 541 | } else if (3 == numComponents) { |
330 | 64 | copyNextWord(m_buffer, Buffersize); |
331 | 64 | x = (ai_real)fast_atof(m_buffer); |
332 | | |
333 | 64 | copyNextWord(m_buffer, Buffersize); |
334 | 64 | y = (ai_real)fast_atof(m_buffer); |
335 | | |
336 | 64 | copyNextWord(m_buffer, Buffersize); |
337 | 64 | z = (ai_real)fast_atof(m_buffer); |
338 | 64 | } else { |
339 | 1 | throw DeadlyImportError("OBJ: Invalid number of components"); |
340 | 1 | } |
341 | | |
342 | | // Coerce nan and inf to 0 as is the OBJ default value |
343 | 605 | if (!std::isfinite(x)) |
344 | 7 | x = 0; |
345 | | |
346 | 605 | if (!std::isfinite(y)) |
347 | 7 | y = 0; |
348 | | |
349 | 605 | if (!std::isfinite(z)) |
350 | 0 | z = 0; |
351 | | |
352 | 605 | point3d_array.emplace_back(x, y, z); |
353 | 605 | m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); |
354 | 605 | return numComponents; |
355 | 606 | } |
356 | | |
357 | 21.7k | void ObjFileParser::getVector3(std::vector<aiVector3D> &point3d_array) { |
358 | 21.7k | ai_real x, y, z; |
359 | 21.7k | copyNextWord(m_buffer, Buffersize); |
360 | 21.7k | x = (ai_real)fast_atof(m_buffer); |
361 | | |
362 | 21.7k | copyNextWord(m_buffer, Buffersize); |
363 | 21.7k | y = (ai_real)fast_atof(m_buffer); |
364 | | |
365 | 21.7k | copyNextWord(m_buffer, Buffersize); |
366 | 21.7k | z = (ai_real)fast_atof(m_buffer); |
367 | | |
368 | 21.7k | point3d_array.emplace_back(x, y, z); |
369 | 21.7k | m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); |
370 | 21.7k | } |
371 | | |
372 | 738 | void ObjFileParser::getHomogeneousVector3(std::vector<aiVector3D> &point3d_array) { |
373 | 738 | ai_real x, y, z, w; |
374 | 738 | copyNextWord(m_buffer, Buffersize); |
375 | 738 | x = (ai_real)fast_atof(m_buffer); |
376 | | |
377 | 738 | copyNextWord(m_buffer, Buffersize); |
378 | 738 | y = (ai_real)fast_atof(m_buffer); |
379 | | |
380 | 738 | copyNextWord(m_buffer, Buffersize); |
381 | 738 | z = (ai_real)fast_atof(m_buffer); |
382 | | |
383 | 738 | copyNextWord(m_buffer, Buffersize); |
384 | 738 | w = (ai_real)fast_atof(m_buffer); |
385 | | |
386 | 738 | if (w == 0) |
387 | 2 | throw DeadlyImportError("OBJ: Invalid component in homogeneous vector (Division by zero)"); |
388 | | |
389 | 736 | point3d_array.emplace_back(x / w, y / w, z / w); |
390 | 736 | m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); |
391 | 736 | } |
392 | | |
393 | 206 | void ObjFileParser::getTwoVectors3(std::vector<aiVector3D> &point3d_array_a, std::vector<aiVector3D> &point3d_array_b) { |
394 | 206 | ai_real x, y, z; |
395 | 206 | copyNextWord(m_buffer, Buffersize); |
396 | 206 | x = (ai_real)fast_atof(m_buffer); |
397 | | |
398 | 206 | copyNextWord(m_buffer, Buffersize); |
399 | 206 | y = (ai_real)fast_atof(m_buffer); |
400 | | |
401 | 206 | copyNextWord(m_buffer, Buffersize); |
402 | 206 | z = (ai_real)fast_atof(m_buffer); |
403 | | |
404 | 206 | point3d_array_a.emplace_back(x, y, z); |
405 | | |
406 | 206 | copyNextWord(m_buffer, Buffersize); |
407 | 206 | x = (ai_real)fast_atof(m_buffer); |
408 | | |
409 | 206 | copyNextWord(m_buffer, Buffersize); |
410 | 206 | y = (ai_real)fast_atof(m_buffer); |
411 | | |
412 | 206 | copyNextWord(m_buffer, Buffersize); |
413 | 206 | z = (ai_real)fast_atof(m_buffer); |
414 | | |
415 | 206 | point3d_array_b.emplace_back(x, y, z); |
416 | | |
417 | 206 | m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); |
418 | 206 | } |
419 | | |
420 | 0 | void ObjFileParser::getVector2(std::vector<aiVector2D> &point2d_array) { |
421 | 0 | ai_real x, y; |
422 | 0 | copyNextWord(m_buffer, Buffersize); |
423 | 0 | x = (ai_real)fast_atof(m_buffer); |
424 | |
|
425 | 0 | copyNextWord(m_buffer, Buffersize); |
426 | 0 | y = (ai_real)fast_atof(m_buffer); |
427 | |
|
428 | 0 | point2d_array.emplace_back(x, y); |
429 | |
|
430 | 0 | m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); |
431 | 0 | } |
432 | | |
433 | | static constexpr char DefaultObjName[] = "defaultobject"; |
434 | | |
435 | 17.9k | void ObjFileParser::getFace(aiPrimitiveType type) { |
436 | 17.9k | m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd); |
437 | 17.9k | if (m_DataIt == m_DataItEnd || *m_DataIt == '\0') { |
438 | 0 | return; |
439 | 0 | } |
440 | | |
441 | 17.9k | ObjFile::Face *face = new ObjFile::Face(type); |
442 | 17.9k | bool hasNormal = false; |
443 | | |
444 | 17.9k | const int vSize = static_cast<unsigned int>(m_pModel->mVertices.size()); |
445 | 17.9k | const int vtSize = static_cast<unsigned int>(m_pModel->mTextureCoord.size()); |
446 | 17.9k | const int vnSize = static_cast<unsigned int>(m_pModel->mNormals.size()); |
447 | | |
448 | 17.9k | const bool vt = (!m_pModel->mTextureCoord.empty()); |
449 | 17.9k | const bool vn = (!m_pModel->mNormals.empty()); |
450 | 17.9k | int iPos = 0; |
451 | 248k | while (m_DataIt < m_DataItEnd) { |
452 | 248k | int iStep = 1; |
453 | | |
454 | 248k | if (IsLineEnd(*m_DataIt) || *m_DataIt == '#') { |
455 | 17.9k | break; |
456 | 17.9k | } |
457 | | |
458 | 230k | if (*m_DataIt == '/') { |
459 | 8.02k | if (type == aiPrimitiveType_POINT) { |
460 | 45 | ASSIMP_LOG_ERROR("Obj: Separator unexpected in point statement"); |
461 | 45 | } |
462 | 8.02k | iPos++; |
463 | 222k | } else if (IsSpaceOrNewLine(*m_DataIt)) { |
464 | 56.8k | iPos = 0; |
465 | 165k | } else { |
466 | | //OBJ USES 1 Base ARRAYS!!!! |
467 | 165k | int iVal; |
468 | 165k | auto end = m_DataIt; |
469 | | // find either the buffer end or the '\0' |
470 | 19.1M | while (end < m_DataItEnd && *end != '\0') |
471 | 19.0M | ++end; |
472 | | // avoid temporary string allocation if there is a zero |
473 | 165k | if (end != m_DataItEnd) { |
474 | 165k | iVal = ::atoi(&(*m_DataIt)); |
475 | 165k | } else { |
476 | | // otherwise make a zero terminated copy, which is safe to pass to atoi |
477 | 0 | std::string number(&(*m_DataIt), m_DataItEnd - m_DataIt); |
478 | 0 | iVal = ::atoi(number.c_str()); |
479 | 0 | } |
480 | | |
481 | | // increment iStep position based off of the sign and # of digits |
482 | 165k | int tmp = iVal; |
483 | 165k | if (iVal < 0) { |
484 | 678 | ++iStep; |
485 | 678 | } |
486 | 229k | while ((tmp = tmp / 10) != 0) { |
487 | 64.2k | ++iStep; |
488 | 64.2k | } |
489 | | |
490 | 165k | if (iPos == 1 && !vt && vn) { |
491 | 5.46k | iPos = 2; // skip texture coords for normals if there are no tex coords |
492 | 5.46k | } |
493 | | |
494 | 165k | if (iVal > 0) { |
495 | | // Store parsed index |
496 | 164k | if (0 == iPos) { |
497 | 157k | face->m_vertices.push_back(iVal - 1); |
498 | 157k | } else if (1 == iPos) { |
499 | 1.48k | face->m_texturCoords.push_back(iVal - 1); |
500 | 5.45k | } else if (2 == iPos) { |
501 | 5.45k | face->m_normals.push_back(iVal - 1); |
502 | 5.45k | hasNormal = true; |
503 | 5.45k | } else { |
504 | 0 | reportErrorTokenInFace(); |
505 | 0 | } |
506 | 164k | } else if (iVal < 0) { |
507 | | // Store relatively index |
508 | 678 | if (0 == iPos) { |
509 | 671 | face->m_vertices.push_back(vSize + iVal); |
510 | 671 | } else if (1 == iPos) { |
511 | 0 | face->m_texturCoords.push_back(vtSize + iVal); |
512 | 7 | } else if (2 == iPos) { |
513 | 7 | face->m_normals.push_back(vnSize + iVal); |
514 | 7 | hasNormal = true; |
515 | 7 | } else { |
516 | 0 | reportErrorTokenInFace(); |
517 | 0 | } |
518 | 678 | } else { |
519 | | //On error, std::atoi will return 0 which is not a valid value |
520 | 32 | delete face; |
521 | 32 | throw DeadlyImportError("OBJ: Invalid face index."); |
522 | 32 | } |
523 | 165k | } |
524 | 230k | m_DataIt += iStep; |
525 | 230k | } |
526 | | |
527 | 17.9k | if (face->m_vertices.empty()) { |
528 | 5.74k | ASSIMP_LOG_ERROR("Obj: Ignoring empty face"); |
529 | | // skip line and clean up |
530 | 5.74k | m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); |
531 | 5.74k | delete face; |
532 | 5.74k | return; |
533 | 5.74k | } |
534 | | |
535 | | // Set active material, if one set |
536 | 12.1k | if (nullptr != m_pModel->mCurrentMaterial) { |
537 | 10.2k | face->m_pMaterial = m_pModel->mCurrentMaterial; |
538 | 10.2k | } else { |
539 | 1.94k | face->m_pMaterial = m_pModel->mDefaultMaterial; |
540 | 1.94k | } |
541 | | |
542 | | // Create a default object, if nothing is there |
543 | 12.1k | if (nullptr == m_pModel->mCurrentObject) { |
544 | 121 | createObject(DefaultObjName); |
545 | 121 | } |
546 | | |
547 | | // Assign face to mesh |
548 | 12.1k | if (nullptr == m_pModel->mCurrentMesh) { |
549 | 0 | createMesh(DefaultObjName); |
550 | 0 | } |
551 | | |
552 | | // Store the face |
553 | 12.1k | m_pModel->mCurrentMesh->m_Faces.emplace_back(face); |
554 | 12.1k | m_pModel->mCurrentMesh->m_uiNumIndices += static_cast<unsigned int>(face->m_vertices.size()); |
555 | 12.1k | m_pModel->mCurrentMesh->m_uiUVCoordinates[0] += static_cast<unsigned int>(face->m_texturCoords.size()); |
556 | 12.1k | if (!m_pModel->mCurrentMesh->m_hasNormals && hasNormal) { |
557 | 3.46k | m_pModel->mCurrentMesh->m_hasNormals = true; |
558 | 3.46k | } |
559 | | // Skip the rest of the line |
560 | 12.1k | m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); |
561 | 12.1k | } |
562 | | |
563 | 20.2k | void ObjFileParser::getMaterialDesc() { |
564 | | // Get next data for material data |
565 | 20.2k | m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd); |
566 | 20.2k | if (m_DataIt == m_DataItEnd) { |
567 | 0 | return; |
568 | 0 | } |
569 | | |
570 | 20.2k | char *pStart = &(*m_DataIt); |
571 | 263k | while (m_DataIt != m_DataItEnd && !IsLineEnd(*m_DataIt)) { |
572 | 242k | ++m_DataIt; |
573 | 242k | } |
574 | | |
575 | | // In some cases we should ignore this 'usemtl' command, this variable helps us to do so |
576 | 20.2k | bool skip = false; |
577 | | |
578 | | // Get name |
579 | 20.2k | std::string strName(pStart, &(*m_DataIt)); |
580 | 20.2k | strName = ai_trim(strName); |
581 | 20.2k | if (strName.empty()) { |
582 | 976 | skip = true; |
583 | 976 | } |
584 | | |
585 | | // If the current mesh has the same material, we will ignore that 'usemtl' command |
586 | | // There is no need to create another object or even mesh here |
587 | 20.2k | if (!skip) { |
588 | 19.2k | if (m_pModel->mCurrentMaterial && m_pModel->mCurrentMaterial->MaterialName == aiString(strName)) { |
589 | 6.03k | skip = true; |
590 | 6.03k | } |
591 | 19.2k | } |
592 | | |
593 | 20.2k | if (!skip) { |
594 | | // Search for material |
595 | 13.2k | std::map<std::string, ObjFile::Material *>::iterator it = m_pModel->mMaterialMap.find(strName); |
596 | 13.2k | if (it == m_pModel->mMaterialMap.end()) { |
597 | | // Not found, so we don't know anything about the material except for its name. |
598 | | // This may be the case if the material library is missing. We don't want to lose all |
599 | | // materials if that happens, so create a new named material instead of discarding it |
600 | | // completely. |
601 | 1.10k | ASSIMP_LOG_ERROR("OBJ: failed to locate material ", strName, ", creating new material"); |
602 | 1.10k | m_pModel->mCurrentMaterial = new ObjFile::Material(); |
603 | 1.10k | m_pModel->mCurrentMaterial->MaterialName.Set(strName); |
604 | 1.10k | m_pModel->mMaterialLib.push_back(strName); |
605 | 1.10k | m_pModel->mMaterialMap[strName] = m_pModel->mCurrentMaterial; |
606 | 12.1k | } else { |
607 | | // Found, using detected material |
608 | 12.1k | m_pModel->mCurrentMaterial = (*it).second; |
609 | 12.1k | } |
610 | | |
611 | 13.2k | if (needsNewMesh(strName)) { |
612 | 5.67k | auto newMeshName = m_pModel->mActiveGroup.empty() ? strName : m_pModel->mActiveGroup; |
613 | 5.67k | createMesh(newMeshName); |
614 | 5.67k | } |
615 | | |
616 | 13.2k | m_pModel->mCurrentMesh->m_uiMaterialIndex = getMaterialIndex(strName); |
617 | 13.2k | } |
618 | | |
619 | | // Skip rest of line |
620 | 20.2k | m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); |
621 | 20.2k | } |
622 | | |
623 | | // ------------------------------------------------------------------- |
624 | | // Get a comment, values will be skipped |
625 | 397 | void ObjFileParser::getComment() { |
626 | 397 | m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); |
627 | 397 | } |
628 | | |
629 | | // ------------------------------------------------------------------- |
630 | | // Get material library from file. |
631 | 15.4k | void ObjFileParser::getMaterialLib() { |
632 | | // Translate tuple |
633 | 15.4k | m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd); |
634 | 15.4k | if (m_DataIt == m_DataItEnd) { |
635 | 0 | return; |
636 | 0 | } |
637 | | |
638 | 15.4k | char *pStart = &(*m_DataIt); |
639 | 392k | while (m_DataIt != m_DataItEnd && !IsLineEnd(*m_DataIt)) { |
640 | 377k | ++m_DataIt; |
641 | 377k | } |
642 | | |
643 | | // Check for existence |
644 | 15.4k | const std::string strMatName(pStart, &(*m_DataIt)); |
645 | 15.4k | std::string absName; |
646 | | |
647 | | // Check if directive is valid. |
648 | 15.4k | if (0 == strMatName.length()) { |
649 | 322 | ASSIMP_LOG_WARN("OBJ: no name for material library specified."); |
650 | 322 | return; |
651 | 322 | } |
652 | | |
653 | 15.1k | if (m_pIO->StackSize() > 0) { |
654 | 1.19k | std::string path = m_pIO->CurrentDirectory(); |
655 | 1.19k | if ('/' != *path.rbegin()) { |
656 | 131 | path += '/'; |
657 | 131 | } |
658 | 1.19k | absName += path; |
659 | 1.19k | absName += strMatName; |
660 | 13.9k | } else { |
661 | 13.9k | absName = strMatName; |
662 | 13.9k | } |
663 | | |
664 | 15.1k | std::unique_ptr<IOStream> pFile(m_pIO->Open(absName)); |
665 | 15.1k | if (nullptr == pFile) { |
666 | 7.35k | ASSIMP_LOG_ERROR("OBJ: Unable to locate material file ", strMatName); |
667 | 7.35k | std::string strMatFallbackName = m_originalObjFileName.substr(0, m_originalObjFileName.length() - 3) + "mtl"; |
668 | 7.35k | ASSIMP_LOG_INFO("OBJ: Opening fallback material file ", strMatFallbackName); |
669 | 7.35k | pFile.reset(m_pIO->Open(strMatFallbackName)); |
670 | 7.35k | if (!pFile) { |
671 | 7.19k | ASSIMP_LOG_ERROR("OBJ: Unable to locate fallback material file ", strMatFallbackName); |
672 | 7.19k | m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); |
673 | 7.19k | return; |
674 | 7.19k | } |
675 | 7.35k | } |
676 | | |
677 | | // Import material library data from file. |
678 | | // Some exporters (e.g. Silo) will happily write out empty |
679 | | // material files if the model doesn't use any materials, so we |
680 | | // allow that. |
681 | 7.91k | std::vector<char> buffer; |
682 | 7.91k | BaseImporter::TextFileToBuffer(pFile.get(), buffer, BaseImporter::ALLOW_EMPTY); |
683 | | //m_pIO->Close(pFile); |
684 | | |
685 | | // Importing the material library |
686 | 7.91k | ObjFileMtlImporter mtlImporter(buffer, strMatName, m_pModel.get()); |
687 | 7.91k | } |
688 | | |
689 | | // ------------------------------------------------------------------- |
690 | | // Set a new material definition as the current material. |
691 | 0 | void ObjFileParser::getNewMaterial() { |
692 | 0 | m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd); |
693 | 0 | m_DataIt = getNextWord<DataArrayIt>(m_DataIt, m_DataItEnd); |
694 | 0 | if (m_DataIt == m_DataItEnd) { |
695 | 0 | return; |
696 | 0 | } |
697 | | |
698 | 0 | char *pStart = &(*m_DataIt); |
699 | 0 | std::string strMat(pStart, *m_DataIt); |
700 | 0 | while (m_DataIt != m_DataItEnd && IsSpaceOrNewLine(*m_DataIt)) { |
701 | 0 | ++m_DataIt; |
702 | 0 | } |
703 | 0 | std::map<std::string, ObjFile::Material *>::iterator it = m_pModel->mMaterialMap.find(strMat); |
704 | 0 | if (it == m_pModel->mMaterialMap.end()) { |
705 | | // Show a warning, if material was not found |
706 | 0 | ASSIMP_LOG_WARN("OBJ: Unsupported material requested: ", strMat); |
707 | 0 | m_pModel->mCurrentMaterial = m_pModel->mDefaultMaterial; |
708 | 0 | } else { |
709 | | // Set new material |
710 | 0 | if (needsNewMesh(strMat)) { |
711 | 0 | createMesh(strMat); |
712 | 0 | } |
713 | 0 | m_pModel->mCurrentMesh->m_uiMaterialIndex = getMaterialIndex(strMat); |
714 | 0 | } |
715 | |
|
716 | 0 | m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); |
717 | 0 | } |
718 | | |
719 | | // ------------------------------------------------------------------- |
720 | 29.5k | int ObjFileParser::getMaterialIndex(const std::string &strMaterialName) { |
721 | 29.5k | int mat_index = -1; |
722 | 29.5k | if (strMaterialName.empty()) { |
723 | 57 | return mat_index; |
724 | 57 | } |
725 | 114k | for (size_t index = 0; index < m_pModel->mMaterialLib.size(); ++index) { |
726 | 113k | if (strMaterialName == m_pModel->mMaterialLib[index]) { |
727 | 29.3k | mat_index = (int)index; |
728 | 29.3k | break; |
729 | 29.3k | } |
730 | 113k | } |
731 | 29.4k | return mat_index; |
732 | 29.5k | } |
733 | | |
734 | | // ------------------------------------------------------------------- |
735 | | // Getter for a group name. |
736 | 8.04k | void ObjFileParser::getGroupName() { |
737 | 8.04k | std::string groupName; |
738 | | |
739 | | // here we skip 'g ' from line |
740 | 8.04k | m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd); |
741 | 8.04k | m_DataIt = getName<DataArrayIt>(m_DataIt, m_DataItEnd, groupName); |
742 | 8.04k | if (isEndOfBuffer(m_DataIt, m_DataItEnd)) { |
743 | 0 | return; |
744 | 0 | } |
745 | | |
746 | | // Change active group, if necessary |
747 | 8.04k | if (m_pModel->mActiveGroup != groupName) { |
748 | | // Search for already existing entry |
749 | 4.95k | ObjFile::Model::ConstGroupMapIt it = m_pModel->mGroups.find(groupName); |
750 | | |
751 | | // We are mapping groups into the object structure |
752 | 4.95k | createObject(groupName); |
753 | | |
754 | | // New group name, creating a new entry |
755 | 4.95k | if (it == m_pModel->mGroups.end()) { |
756 | 647 | std::vector<unsigned int> *pFaceIDArray = new std::vector<unsigned int>; |
757 | 647 | m_pModel->mGroups[groupName] = pFaceIDArray; |
758 | 647 | m_pModel->mGroupFaceIDs = (pFaceIDArray); |
759 | 4.31k | } else { |
760 | 4.31k | m_pModel->mGroupFaceIDs = (*it).second; |
761 | 4.31k | } |
762 | 4.95k | m_pModel->mActiveGroup = groupName; |
763 | 4.95k | } |
764 | 8.04k | m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); |
765 | 8.04k | } |
766 | | |
767 | | // ------------------------------------------------------------------- |
768 | | // Not supported |
769 | 3.05k | void ObjFileParser::getGroupNumber() { |
770 | | // Not used |
771 | | |
772 | 3.05k | m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); |
773 | 3.05k | } |
774 | | |
775 | | // ------------------------------------------------------------------- |
776 | | // Not supported |
777 | 28 | void ObjFileParser::getGroupNumberAndResolution() { |
778 | | // Not used |
779 | | |
780 | 28 | m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); |
781 | 28 | } |
782 | | |
783 | | // ------------------------------------------------------------------- |
784 | | // Stores values for a new object instance, name will be used to |
785 | | // identify it. |
786 | 4.71k | void ObjFileParser::getObjectName() { |
787 | 4.71k | m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd); |
788 | 4.71k | if (m_DataIt == m_DataItEnd) { |
789 | 0 | return; |
790 | 0 | } |
791 | 4.71k | char *pStart = &(*m_DataIt); |
792 | 47.7k | while (m_DataIt != m_DataItEnd && !IsSpaceOrNewLine(*m_DataIt)) { |
793 | 43.0k | ++m_DataIt; |
794 | 43.0k | } |
795 | | |
796 | 4.71k | std::string strObjectName(pStart, &(*m_DataIt)); |
797 | 4.71k | if (!strObjectName.empty()) { |
798 | | // Reset current object |
799 | 3.46k | m_pModel->mCurrentObject = nullptr; |
800 | | |
801 | | // Search for actual object |
802 | 3.46k | for (std::vector<ObjFile::Object *>::const_iterator it = m_pModel->mObjects.begin(); |
803 | 15.9k | it != m_pModel->mObjects.end(); |
804 | 15.5k | ++it) { |
805 | 15.5k | if ((*it)->m_strObjName == strObjectName) { |
806 | 3.13k | m_pModel->mCurrentObject = *it; |
807 | 3.13k | break; |
808 | 3.13k | } |
809 | 15.5k | } |
810 | | |
811 | | // Allocate a new object, if current one was not found before |
812 | 3.46k | if (nullptr == m_pModel->mCurrentObject) { |
813 | 332 | createObject(strObjectName); |
814 | 332 | } |
815 | 3.46k | } |
816 | 4.71k | m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); |
817 | 4.71k | } |
818 | | // ------------------------------------------------------------------- |
819 | | // Creates a new object instance |
820 | 5.41k | void ObjFileParser::createObject(const std::string &objName) { |
821 | 5.41k | ai_assert(nullptr != m_pModel); |
822 | | |
823 | 5.41k | m_pModel->mCurrentObject = new ObjFile::Object; |
824 | 5.41k | m_pModel->mCurrentObject->m_strObjName = objName; |
825 | 5.41k | m_pModel->mObjects.push_back(m_pModel->mCurrentObject); |
826 | | |
827 | 5.41k | createMesh(objName); |
828 | | |
829 | 5.41k | if (m_pModel->mCurrentMaterial) { |
830 | 3.21k | m_pModel->mCurrentMesh->m_uiMaterialIndex = |
831 | 3.21k | getMaterialIndex(m_pModel->mCurrentMaterial->MaterialName.data); |
832 | 3.21k | m_pModel->mCurrentMesh->m_pMaterial = m_pModel->mCurrentMaterial; |
833 | 3.21k | } |
834 | 5.41k | } |
835 | | // ------------------------------------------------------------------- |
836 | | // Creates a new mesh |
837 | 11.0k | void ObjFileParser::createMesh(const std::string &meshName) { |
838 | 11.0k | ai_assert(nullptr != m_pModel); |
839 | | |
840 | 11.0k | m_pModel->mCurrentMesh = new ObjFile::Mesh(meshName); |
841 | 11.0k | m_pModel->mMeshes.push_back(m_pModel->mCurrentMesh); |
842 | 11.0k | unsigned int meshId = static_cast<unsigned int>(m_pModel->mMeshes.size() - 1); |
843 | 11.0k | if (nullptr != m_pModel->mCurrentObject) { |
844 | 10.9k | m_pModel->mCurrentObject->m_Meshes.push_back(meshId); |
845 | 10.9k | } else { |
846 | 149 | ASSIMP_LOG_ERROR("OBJ: No object detected to attach a new mesh instance."); |
847 | 149 | } |
848 | 11.0k | } |
849 | | |
850 | | // ------------------------------------------------------------------- |
851 | | // Returns true, if a new mesh must be created. |
852 | 13.2k | bool ObjFileParser::needsNewMesh(const std::string &materialName) { |
853 | | // If no mesh data yet |
854 | 13.2k | if (m_pModel->mCurrentMesh == nullptr) { |
855 | 149 | return true; |
856 | 149 | } |
857 | 13.0k | bool newMat = false; |
858 | 13.0k | int matIdx = getMaterialIndex(materialName); |
859 | 13.0k | int curMatIdx = m_pModel->mCurrentMesh->m_uiMaterialIndex; |
860 | 13.0k | if (curMatIdx != int(ObjFile::Mesh::NoMaterial) && curMatIdx != matIdx |
861 | | // no need create a new mesh if no faces in current |
862 | | // lets say 'usemtl' goes straight after 'g' |
863 | 13.0k | && !m_pModel->mCurrentMesh->m_Faces.empty()) { |
864 | | // New material -> only one material per mesh, so we need to create a new |
865 | | // material |
866 | 5.52k | newMat = true; |
867 | 5.52k | } |
868 | 13.0k | return newMat; |
869 | 13.2k | } |
870 | | |
871 | | // ------------------------------------------------------------------- |
872 | | // Shows an error in parsing process. |
873 | 0 | void ObjFileParser::reportErrorTokenInFace() { |
874 | 0 | m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); |
875 | 0 | ASSIMP_LOG_ERROR("OBJ: Not supported token in face description detected"); |
876 | 0 | } |
877 | | |
878 | | // ------------------------------------------------------------------- |
879 | | |
880 | | } // Namespace Assimp |
881 | | |
882 | | #endif // !! ASSIMP_BUILD_NO_OBJ_IMPORTER |