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