/src/assimp/code/AssetLib/X/XFileParser.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 | | /** @file Implementation of the XFile parser helper class */ |
43 | | |
44 | | #ifndef ASSIMP_BUILD_NO_X_IMPORTER |
45 | | |
46 | | #include "XFileParser.h" |
47 | | #include "XFileHelper.h" |
48 | | #include <assimp/ByteSwapper.h> |
49 | | #include <assimp/Exceptional.h> |
50 | | #include <assimp/StringUtils.h> |
51 | | #include <assimp/TinyFormatter.h> |
52 | | #include <assimp/fast_atof.h> |
53 | | #include <assimp/DefaultLogger.hpp> |
54 | | |
55 | | using namespace Assimp; |
56 | | using namespace Assimp::XFile; |
57 | | using namespace Assimp::Formatter; |
58 | | |
59 | | #ifndef ASSIMP_BUILD_NO_COMPRESSED_X |
60 | | |
61 | | #include "Common/Compression.h" |
62 | | |
63 | | // Magic identifier for MSZIP compressed data |
64 | | constexpr unsigned int MSZIP_MAGIC = 0x4B43; |
65 | | constexpr size_t MSZIP_BLOCK = 32786l; |
66 | | |
67 | | #endif // !! ASSIMP_BUILD_NO_COMPRESSED_X |
68 | | |
69 | | // ------------------------------------------------------------------------------------------------ |
70 | | // Throws an exception with a line number and the given text. |
71 | | template<typename... T> |
72 | 0 | AI_WONT_RETURN void XFileParser::ThrowException(T&&... args) { |
73 | 0 | if (mIsBinaryFormat) { |
74 | 0 | throw DeadlyImportError(args...); |
75 | 0 | } else { |
76 | 0 | throw DeadlyImportError("Line ", mLineNumber, ": ", args...); |
77 | 0 | } |
78 | 0 | } Unexecuted instantiation: void Assimp::XFileParser::ThrowException<char const (&) [28], char const&, char const&, char const&, char const&, char const (&) [2]>(char const (&) [28], char const&, char const&, char const&, char const&, char const (&) [2]) Unexecuted instantiation: void Assimp::XFileParser::ThrowException<char const (&) [20], unsigned int&, char const (&) [29]>(char const (&) [20], unsigned int&, char const (&) [29]) Unexecuted instantiation: void Assimp::XFileParser::ThrowException<char const (&) [65]>(char const (&) [65]) Unexecuted instantiation: void Assimp::XFileParser::ThrowException<char const (&) [51]>(char const (&) [51]) Unexecuted instantiation: void Assimp::XFileParser::ThrowException<char const (&) [52]>(char const (&) [52]) Unexecuted instantiation: void Assimp::XFileParser::ThrowException<char const (&) [37]>(char const (&) [37]) Unexecuted instantiation: void Assimp::XFileParser::ThrowException<char const (&) [48]>(char const (&) [48]) Unexecuted instantiation: void Assimp::XFileParser::ThrowException<char const (&) [19]>(char const (&) [19]) Unexecuted instantiation: void Assimp::XFileParser::ThrowException<char const (&) [47]>(char const (&) [47]) Unexecuted instantiation: void Assimp::XFileParser::ThrowException<char const (&) [33]>(char const (&) [33]) Unexecuted instantiation: void Assimp::XFileParser::ThrowException<char const (&) [57]>(char const (&) [57]) Unexecuted instantiation: void Assimp::XFileParser::ThrowException<char const (&) [60]>(char const (&) [60]) Unexecuted instantiation: void Assimp::XFileParser::ThrowException<char const (&) [56]>(char const (&) [56]) Unexecuted instantiation: void Assimp::XFileParser::ThrowException<char const (&) [18], unsigned int&, char const (&) [15]>(char const (&) [18], unsigned int&, char const (&) [15]) Unexecuted instantiation: void Assimp::XFileParser::ThrowException<char const (&) [54]>(char const (&) [54]) Unexecuted instantiation: void Assimp::XFileParser::ThrowException<char const (&) [24]>(char const (&) [24]) Unexecuted instantiation: void Assimp::XFileParser::ThrowException<char const (&) [20]>(char const (&) [20]) Unexecuted instantiation: void Assimp::XFileParser::ThrowException<char const (&) [43]>(char const (&) [43]) Unexecuted instantiation: void Assimp::XFileParser::ThrowException<char const (&) [44]>(char const (&) [44]) Unexecuted instantiation: void Assimp::XFileParser::ThrowException<char const (&) [25]>(char const (&) [25]) Unexecuted instantiation: void Assimp::XFileParser::ThrowException<char const (&) [62]>(char const (&) [62]) Unexecuted instantiation: void Assimp::XFileParser::ThrowException<char const (&) [17]>(char const (&) [17]) |
79 | | |
80 | | // ------------------------------------------------------------------------------------------------ |
81 | | // Constructor. Creates a data structure out of the XFile given in the memory block. |
82 | | XFileParser::XFileParser(const std::vector<char> &pBuffer) : |
83 | 4 | mMajorVersion(0), mMinorVersion(0), mIsBinaryFormat(false), mBinaryNumCount(0), mP(nullptr), mEnd(nullptr), mLineNumber(0), mScene(nullptr) { |
84 | | // vector to store uncompressed file for INFLATE'd X files |
85 | 4 | std::vector<char> uncompressed; |
86 | | |
87 | | // set up memory pointers |
88 | 4 | mP = &pBuffer.front(); |
89 | 4 | mEnd = mP + pBuffer.size() - 1; |
90 | | |
91 | | // check header |
92 | 4 | if (0 != strncmp(mP, "xof ", 4)) { |
93 | 0 | throw DeadlyImportError("Header mismatch, file is not an XFile."); |
94 | 0 | } |
95 | | |
96 | | // read version. It comes in a four byte format such as "0302" |
97 | 4 | mMajorVersion = (unsigned int)(mP[4] - 48) * 10 + (unsigned int)(mP[5] - 48); |
98 | 4 | mMinorVersion = (unsigned int)(mP[6] - 48) * 10 + (unsigned int)(mP[7] - 48); |
99 | | |
100 | 4 | bool compressed = false; |
101 | | |
102 | | // txt - pure ASCII text format |
103 | 4 | if (strncmp(mP + 8, "txt ", 4) == 0) |
104 | 3 | mIsBinaryFormat = false; |
105 | | |
106 | | // bin - Binary format |
107 | 1 | else if (strncmp(mP + 8, "bin ", 4) == 0) |
108 | 1 | mIsBinaryFormat = true; |
109 | | |
110 | | // tzip - Inflate compressed text format |
111 | 0 | else if (strncmp(mP + 8, "tzip", 4) == 0) { |
112 | 0 | mIsBinaryFormat = false; |
113 | 0 | compressed = true; |
114 | 0 | } |
115 | | // bzip - Inflate compressed binary format |
116 | 0 | else if (strncmp(mP + 8, "bzip", 4) == 0) { |
117 | 0 | mIsBinaryFormat = true; |
118 | 0 | compressed = true; |
119 | 0 | } else |
120 | 0 | ThrowException("Unsupported x-file format '", mP[8], mP[9], mP[10], mP[11], "'"); |
121 | | |
122 | | // float size |
123 | 4 | mBinaryFloatSize = (unsigned int)(mP[12] - 48) * 1000 + (unsigned int)(mP[13] - 48) * 100 + (unsigned int)(mP[14] - 48) * 10 + (unsigned int)(mP[15] - 48); |
124 | | |
125 | 4 | if (mBinaryFloatSize != 32 && mBinaryFloatSize != 64) |
126 | 0 | ThrowException("Unknown float size ", mBinaryFloatSize, " specified in x-file header."); |
127 | | |
128 | | // The x format specifies size in bits, but we work in bytes |
129 | 4 | mBinaryFloatSize /= 8; |
130 | | |
131 | 4 | mP += 16; |
132 | | |
133 | | // If this is a compressed X file, apply the inflate algorithm to it |
134 | 4 | if (compressed) { |
135 | | #ifdef ASSIMP_BUILD_NO_COMPRESSED_X |
136 | | throw DeadlyImportError("Assimp was built without compressed X support"); |
137 | | #else |
138 | | /* /////////////////////////////////////////////////////////////////////// |
139 | | * COMPRESSED X FILE FORMAT |
140 | | * /////////////////////////////////////////////////////////////////////// |
141 | | * [xhead] |
142 | | * 2 major |
143 | | * 2 minor |
144 | | * 4 type // bzip,tzip |
145 | | * [mszip_master_head] |
146 | | * 4 unkn // checksum? |
147 | | * 2 unkn // flags? (seems to be constant) |
148 | | * [mszip_head] |
149 | | * 2 ofs // offset to next section |
150 | | * 2 magic // 'CK' |
151 | | * ... ofs bytes of data |
152 | | * ... next mszip_head |
153 | | * |
154 | | * http://www.kdedevelopers.org/node/3181 has been very helpful. |
155 | | * /////////////////////////////////////////////////////////////////////// |
156 | | */ |
157 | | |
158 | | // skip unknown data (checksum, flags?) |
159 | 0 | mP += 6; |
160 | | |
161 | | // First find out how much storage we'll need. Count sections. |
162 | 0 | const char *P1 = mP; |
163 | 0 | unsigned int est_out = 0; |
164 | |
|
165 | 0 | while (P1 + 3 < mEnd) { |
166 | | // read next offset |
167 | 0 | uint16_t ofs = *((uint16_t *)P1); |
168 | 0 | AI_SWAP2(ofs); |
169 | 0 | P1 += 2; |
170 | |
|
171 | 0 | if (ofs >= MSZIP_BLOCK) |
172 | 0 | throw DeadlyImportError("X: Invalid offset to next MSZIP compressed block"); |
173 | | |
174 | | // check magic word |
175 | 0 | uint16_t magic = *((uint16_t *)P1); |
176 | 0 | AI_SWAP2(magic); |
177 | 0 | P1 += 2; |
178 | |
|
179 | 0 | if (magic != MSZIP_MAGIC) |
180 | 0 | throw DeadlyImportError("X: Unsupported compressed format, expected MSZIP header"); |
181 | | |
182 | | // and advance to the next offset |
183 | 0 | P1 += ofs; |
184 | 0 | est_out += MSZIP_BLOCK; // one decompressed block is 327861 in size |
185 | 0 | } |
186 | | |
187 | | // Allocate storage and terminating zero and do the actual uncompressing |
188 | 0 | Compression compression; |
189 | 0 | uncompressed.resize(est_out + 1); |
190 | 0 | char *out = &uncompressed.front(); |
191 | 0 | if (compression.open(mIsBinaryFormat ? Compression::Format::Binary : Compression::Format::ASCII, |
192 | 0 | Compression::FlushMode::SyncFlush, -Compression::MaxWBits)) { |
193 | 0 | while (mP + 3 < mEnd) { |
194 | 0 | uint16_t ofs = *((uint16_t *)mP); |
195 | 0 | AI_SWAP2(ofs); |
196 | 0 | mP += 4; |
197 | |
|
198 | 0 | if (mP + ofs > mEnd + 2) { |
199 | 0 | throw DeadlyImportError("X: Unexpected EOF in compressed chunk"); |
200 | 0 | } |
201 | 0 | out += compression.decompressBlock(mP, ofs, out, MSZIP_BLOCK); |
202 | 0 | mP += ofs; |
203 | 0 | } |
204 | 0 | compression.close(); |
205 | 0 | } |
206 | | |
207 | | // ok, update pointers to point to the uncompressed file data |
208 | 0 | mP = &uncompressed[0]; |
209 | 0 | mEnd = out; |
210 | | |
211 | | // FIXME: we don't need the compressed data anymore, could release |
212 | | // it already for better memory usage. Consider breaking const-co. |
213 | 0 | ASSIMP_LOG_INFO("Successfully decompressed MSZIP-compressed file"); |
214 | 0 | #endif // !! ASSIMP_BUILD_NO_COMPRESSED_X |
215 | 4 | } else { |
216 | | // start reading here |
217 | 4 | ReadUntilEndOfLine(); |
218 | 4 | } |
219 | | |
220 | 4 | mScene = new Scene; |
221 | 4 | ParseFile(); |
222 | | |
223 | | // filter the imported hierarchy for some degenerated cases |
224 | 4 | if (mScene->mRootNode) { |
225 | 4 | FilterHierarchy(mScene->mRootNode); |
226 | 4 | } |
227 | 4 | } |
228 | | |
229 | | // ------------------------------------------------------------------------------------------------ |
230 | | // Destructor. Destroys all imported data along with it |
231 | 4 | XFileParser::~XFileParser() { |
232 | | // kill everything we created |
233 | 4 | delete mScene; |
234 | 4 | } |
235 | | |
236 | | // ------------------------------------------------------------------------------------------------ |
237 | 4 | void XFileParser::ParseFile() { |
238 | 4 | bool running = true; |
239 | 73 | while (running) { |
240 | | // read name of next object |
241 | 73 | std::string objectName = GetNextToken(); |
242 | 73 | if (objectName.length() == 0) { |
243 | 4 | break; |
244 | 4 | } |
245 | | |
246 | | // parse specific object |
247 | 69 | if (objectName == "template") { |
248 | 40 | ParseDataObjectTemplate(); |
249 | 40 | } else if (objectName == "Frame") { |
250 | 10 | ParseDataObjectFrame(nullptr); |
251 | 19 | } else if (objectName == "Mesh") { |
252 | | // some meshes have no frames at all |
253 | 0 | Mesh *mesh = new Mesh; |
254 | 0 | ParseDataObjectMesh(mesh); |
255 | 0 | mScene->mGlobalMeshes.push_back(mesh); |
256 | 19 | } else if (objectName == "AnimTicksPerSecond") |
257 | 5 | ParseDataObjectAnimTicksPerSecond(); |
258 | 14 | else if (objectName == "AnimationSet") |
259 | 5 | ParseDataObjectAnimationSet(); |
260 | 9 | else if (objectName == "Material") { |
261 | | // Material outside of a mesh or node |
262 | 0 | Material material; |
263 | 0 | ParseDataObjectMaterial(&material); |
264 | 0 | mScene->mGlobalMaterials.push_back(material); |
265 | 9 | } else if (objectName == "}") { |
266 | | // whatever? |
267 | 0 | ASSIMP_LOG_WARN("} found in dataObject"); |
268 | 9 | } else { |
269 | | // unknown format |
270 | 9 | ASSIMP_LOG_WARN("Unknown data object in animation of .x file"); |
271 | 9 | ParseUnknownDataObject(); |
272 | 9 | } |
273 | 69 | } |
274 | 4 | } |
275 | | |
276 | | // ------------------------------------------------------------------------------------------------ |
277 | 40 | void XFileParser::ParseDataObjectTemplate() { |
278 | | // parse a template data object. Currently not stored. |
279 | 40 | std::string name; |
280 | 40 | readHeadOfDataObject(&name); |
281 | | |
282 | | // read GUID |
283 | 40 | std::string guid = GetNextToken(); |
284 | | |
285 | | // read and ignore data members |
286 | 40 | bool running = true; |
287 | 312 | while (running) { |
288 | 312 | std::string s = GetNextToken(); |
289 | | |
290 | 312 | if (s == "}") { |
291 | 40 | break; |
292 | 40 | } |
293 | | |
294 | 272 | if (s.length() == 0) { |
295 | 0 | ThrowException("Unexpected end of file reached while parsing template definition"); |
296 | 0 | } |
297 | 272 | } |
298 | 40 | } |
299 | | |
300 | | // ------------------------------------------------------------------------------------------------ |
301 | 101 | void XFileParser::ParseDataObjectFrame(Node *pParent) { |
302 | | // A coordinate frame, or "frame of reference." The Frame template |
303 | | // is open and can contain any object. The Direct3D extensions (D3DX) |
304 | | // mesh-loading functions recognize Mesh, FrameTransformMatrix, and |
305 | | // Frame template instances as child objects when loading a Frame |
306 | | // instance. |
307 | 101 | std::string name; |
308 | 101 | readHeadOfDataObject(&name); |
309 | | |
310 | | // create a named node and place it at its parent, if given |
311 | 101 | Node *node = new Node(pParent); |
312 | 101 | node->mName = name; |
313 | 101 | if (pParent) { |
314 | 91 | pParent->mChildren.push_back(node); |
315 | 91 | } else { |
316 | | // there might be multiple root nodes |
317 | 10 | if (mScene->mRootNode != nullptr) { |
318 | | // place a dummy root if not there |
319 | 6 | if (mScene->mRootNode->mName != "$dummy_root") { |
320 | 3 | Node *exroot = mScene->mRootNode; |
321 | 3 | mScene->mRootNode = new Node(nullptr); |
322 | 3 | mScene->mRootNode->mName = "$dummy_root"; |
323 | 3 | mScene->mRootNode->mChildren.push_back(exroot); |
324 | 3 | exroot->mParent = mScene->mRootNode; |
325 | 3 | } |
326 | | // put the new node as its child instead |
327 | 6 | mScene->mRootNode->mChildren.push_back(node); |
328 | 6 | node->mParent = mScene->mRootNode; |
329 | 6 | } else { |
330 | | // it's the first node imported. place it as root |
331 | 4 | mScene->mRootNode = node; |
332 | 4 | } |
333 | 10 | } |
334 | | |
335 | | // Now inside a frame. |
336 | | // read tokens until closing brace is reached. |
337 | 101 | bool running = true; |
338 | 395 | while (running) { |
339 | 395 | std::string objectName = GetNextToken(); |
340 | 395 | if (objectName.size() == 0) |
341 | 0 | ThrowException("Unexpected end of file reached while parsing frame"); |
342 | | |
343 | 395 | if (objectName == "}") |
344 | 101 | break; // frame finished |
345 | 294 | else if (objectName == "Frame") |
346 | 91 | ParseDataObjectFrame(node); // child frame |
347 | 203 | else if (objectName == "FrameTransformMatrix") |
348 | 101 | ParseDataObjectTransformationMatrix(node->mTrafoMatrix); |
349 | 102 | else if (objectName == "Mesh") { |
350 | 6 | Mesh *mesh = new Mesh(name); |
351 | 6 | node->mMeshes.push_back(mesh); |
352 | 6 | ParseDataObjectMesh(mesh); |
353 | 96 | } else { |
354 | 96 | ASSIMP_LOG_WARN("Unknown data object in frame in x file"); |
355 | 96 | ParseUnknownDataObject(); |
356 | 96 | } |
357 | 395 | } |
358 | 101 | } |
359 | | |
360 | | // ------------------------------------------------------------------------------------------------ |
361 | 101 | void XFileParser::ParseDataObjectTransformationMatrix(aiMatrix4x4 &pMatrix) { |
362 | | // read header, we're not interested if it has a name |
363 | 101 | readHeadOfDataObject(); |
364 | | |
365 | | // read its components |
366 | 101 | pMatrix.a1 = ReadFloat(); |
367 | 101 | pMatrix.b1 = ReadFloat(); |
368 | 101 | pMatrix.c1 = ReadFloat(); |
369 | 101 | pMatrix.d1 = ReadFloat(); |
370 | 101 | pMatrix.a2 = ReadFloat(); |
371 | 101 | pMatrix.b2 = ReadFloat(); |
372 | 101 | pMatrix.c2 = ReadFloat(); |
373 | 101 | pMatrix.d2 = ReadFloat(); |
374 | 101 | pMatrix.a3 = ReadFloat(); |
375 | 101 | pMatrix.b3 = ReadFloat(); |
376 | 101 | pMatrix.c3 = ReadFloat(); |
377 | 101 | pMatrix.d3 = ReadFloat(); |
378 | 101 | pMatrix.a4 = ReadFloat(); |
379 | 101 | pMatrix.b4 = ReadFloat(); |
380 | 101 | pMatrix.c4 = ReadFloat(); |
381 | 101 | pMatrix.d4 = ReadFloat(); |
382 | | |
383 | | // trailing symbols |
384 | 101 | CheckForSemicolon(); |
385 | 101 | CheckForClosingBrace(); |
386 | 101 | } |
387 | | |
388 | | // ------------------------------------------------------------------------------------------------ |
389 | 6 | void XFileParser::ParseDataObjectMesh(Mesh *pMesh) { |
390 | 6 | std::string name; |
391 | 6 | readHeadOfDataObject(&name); |
392 | | |
393 | | // read vertex count |
394 | 6 | unsigned int numVertices = ReadInt(); |
395 | 6 | pMesh->mPositions.resize(numVertices); |
396 | | |
397 | | // read vertices |
398 | 12.0k | for (unsigned int a = 0; a < numVertices; a++) |
399 | 12.0k | pMesh->mPositions[a] = ReadVector3(); |
400 | | |
401 | | // read position faces |
402 | 6 | unsigned int numPosFaces = ReadInt(); |
403 | 6 | pMesh->mPosFaces.resize(numPosFaces); |
404 | 16.3k | for (unsigned int a = 0; a < numPosFaces; ++a) { |
405 | | // read indices |
406 | 16.3k | unsigned int numIndices = ReadInt(); |
407 | 16.3k | Face &face = pMesh->mPosFaces[a]; |
408 | 65.4k | for (unsigned int b = 0; b < numIndices; ++b) { |
409 | 49.0k | const int idx(ReadInt()); |
410 | 49.0k | if (static_cast<unsigned int>(idx) <= numVertices) { |
411 | 49.0k | face.mIndices.push_back(idx); |
412 | 49.0k | } |
413 | 49.0k | } |
414 | 16.3k | TestForSeparator(); |
415 | 16.3k | } |
416 | | |
417 | | // here, other data objects may follow |
418 | 6 | bool running = true; |
419 | 122 | while (running) { |
420 | 122 | std::string objectName = GetNextToken(); |
421 | | |
422 | 122 | if (objectName.empty()) |
423 | 0 | ThrowException("Unexpected end of file while parsing mesh structure"); |
424 | 122 | else if (objectName == "}") |
425 | 6 | break; // mesh finished |
426 | 116 | else if (objectName == "MeshNormals") |
427 | 6 | ParseDataObjectMeshNormals(pMesh); |
428 | 110 | else if (objectName == "MeshTextureCoords") |
429 | 6 | ParseDataObjectMeshTextureCoords(pMesh); |
430 | 104 | else if (objectName == "MeshVertexColors") |
431 | 0 | ParseDataObjectMeshVertexColors(pMesh); |
432 | 104 | else if (objectName == "MeshMaterialList") |
433 | 2 | ParseDataObjectMeshMaterialList(pMesh); |
434 | 102 | else if (objectName == "VertexDuplicationIndices") |
435 | 1 | ParseUnknownDataObject(); // we'll ignore vertex duplication indices |
436 | 101 | else if (objectName == "XSkinMeshHeader") |
437 | 5 | ParseDataObjectSkinMeshHeader(pMesh); |
438 | 96 | else if (objectName == "SkinWeights") |
439 | 95 | ParseDataObjectSkinWeights(pMesh); |
440 | 1 | else { |
441 | 1 | ASSIMP_LOG_WARN("Unknown data object in mesh in x file"); |
442 | 1 | ParseUnknownDataObject(); |
443 | 1 | } |
444 | 122 | } |
445 | 6 | } |
446 | | |
447 | | // ------------------------------------------------------------------------------------------------ |
448 | 95 | void XFileParser::ParseDataObjectSkinWeights(Mesh *pMesh) { |
449 | 95 | if (nullptr == pMesh) { |
450 | 0 | return; |
451 | 0 | } |
452 | 95 | readHeadOfDataObject(); |
453 | | |
454 | 95 | std::string transformNodeName; |
455 | 95 | GetNextTokenAsString(transformNodeName); |
456 | | |
457 | 95 | pMesh->mBones.emplace_back(); |
458 | 95 | Bone &bone = pMesh->mBones.back(); |
459 | 95 | bone.mName = transformNodeName; |
460 | | |
461 | | // read vertex weights |
462 | 95 | unsigned int numWeights = ReadInt(); |
463 | 95 | bone.mWeights.reserve(numWeights); |
464 | | |
465 | 17.0k | for (unsigned int a = 0; a < numWeights; a++) { |
466 | 16.9k | BoneWeight weight = {}; |
467 | 16.9k | weight.mVertex = ReadInt(); |
468 | 16.9k | bone.mWeights.push_back(weight); |
469 | 16.9k | } |
470 | | |
471 | | // read vertex weights |
472 | 17.0k | for (unsigned int a = 0; a < numWeights; a++) |
473 | 16.9k | bone.mWeights[a].mWeight = ReadFloat(); |
474 | | |
475 | | // read matrix offset |
476 | 95 | bone.mOffsetMatrix.a1 = ReadFloat(); |
477 | 95 | bone.mOffsetMatrix.b1 = ReadFloat(); |
478 | 95 | bone.mOffsetMatrix.c1 = ReadFloat(); |
479 | 95 | bone.mOffsetMatrix.d1 = ReadFloat(); |
480 | 95 | bone.mOffsetMatrix.a2 = ReadFloat(); |
481 | 95 | bone.mOffsetMatrix.b2 = ReadFloat(); |
482 | 95 | bone.mOffsetMatrix.c2 = ReadFloat(); |
483 | 95 | bone.mOffsetMatrix.d2 = ReadFloat(); |
484 | 95 | bone.mOffsetMatrix.a3 = ReadFloat(); |
485 | 95 | bone.mOffsetMatrix.b3 = ReadFloat(); |
486 | 95 | bone.mOffsetMatrix.c3 = ReadFloat(); |
487 | 95 | bone.mOffsetMatrix.d3 = ReadFloat(); |
488 | 95 | bone.mOffsetMatrix.a4 = ReadFloat(); |
489 | 95 | bone.mOffsetMatrix.b4 = ReadFloat(); |
490 | 95 | bone.mOffsetMatrix.c4 = ReadFloat(); |
491 | 95 | bone.mOffsetMatrix.d4 = ReadFloat(); |
492 | | |
493 | 95 | CheckForSemicolon(); |
494 | 95 | CheckForClosingBrace(); |
495 | 95 | } |
496 | | |
497 | | // ------------------------------------------------------------------------------------------------ |
498 | 5 | void XFileParser::ParseDataObjectSkinMeshHeader(Mesh * /*pMesh*/) { |
499 | 5 | readHeadOfDataObject(); |
500 | | |
501 | | /*unsigned int maxSkinWeightsPerVertex =*/ReadInt(); |
502 | 5 | /*unsigned int maxSkinWeightsPerFace =*/ReadInt(); |
503 | 5 | /*unsigned int numBonesInMesh = */ ReadInt(); |
504 | | |
505 | 5 | CheckForClosingBrace(); |
506 | 5 | } |
507 | | |
508 | | // ------------------------------------------------------------------------------------------------ |
509 | 6 | void XFileParser::ParseDataObjectMeshNormals(Mesh *pMesh) { |
510 | 6 | readHeadOfDataObject(); |
511 | | |
512 | | // read count |
513 | 6 | unsigned int numNormals = ReadInt(); |
514 | 6 | pMesh->mNormals.resize(numNormals); |
515 | | |
516 | | // read normal vectors |
517 | 12.0k | for (unsigned int a = 0; a < numNormals; ++a) { |
518 | 12.0k | pMesh->mNormals[a] = ReadVector3(); |
519 | 12.0k | } |
520 | | |
521 | | // read normal indices |
522 | 6 | unsigned int numFaces = ReadInt(); |
523 | 6 | if (numFaces != pMesh->mPosFaces.size()) { |
524 | 0 | ThrowException("Normal face count does not match vertex face count."); |
525 | 0 | } |
526 | | |
527 | | // do not crah when no face definitions are there |
528 | 6 | if (numFaces > 0) { |
529 | | // normal face creation |
530 | 6 | pMesh->mNormFaces.resize(numFaces); |
531 | 16.3k | for (unsigned int a = 0; a < numFaces; ++a) { |
532 | 16.3k | unsigned int numIndices = ReadInt(); |
533 | 16.3k | pMesh->mNormFaces[a] = Face(); |
534 | 16.3k | Face &face = pMesh->mNormFaces[a]; |
535 | 65.4k | for (unsigned int b = 0; b < numIndices; ++b) { |
536 | 49.0k | face.mIndices.push_back(ReadInt()); |
537 | 49.0k | } |
538 | | |
539 | 16.3k | TestForSeparator(); |
540 | 16.3k | } |
541 | 6 | } |
542 | | |
543 | 6 | CheckForClosingBrace(); |
544 | 6 | } |
545 | | |
546 | | // ------------------------------------------------------------------------------------------------ |
547 | 6 | void XFileParser::ParseDataObjectMeshTextureCoords(Mesh *pMesh) { |
548 | 6 | readHeadOfDataObject(); |
549 | 6 | if (pMesh->mNumTextures + 1 > AI_MAX_NUMBER_OF_TEXTURECOORDS) |
550 | 0 | ThrowException("Too many sets of texture coordinates"); |
551 | | |
552 | 6 | std::vector<aiVector2D> &coords = pMesh->mTexCoords[pMesh->mNumTextures++]; |
553 | | |
554 | 6 | unsigned int numCoords = ReadInt(); |
555 | 6 | if (numCoords != pMesh->mPositions.size()) |
556 | 0 | ThrowException("Texture coord count does not match vertex count"); |
557 | | |
558 | 6 | coords.resize(numCoords); |
559 | 12.0k | for (unsigned int a = 0; a < numCoords; a++) |
560 | 12.0k | coords[a] = ReadVector2(); |
561 | | |
562 | 6 | CheckForClosingBrace(); |
563 | 6 | } |
564 | | |
565 | | // ------------------------------------------------------------------------------------------------ |
566 | 0 | void XFileParser::ParseDataObjectMeshVertexColors(Mesh *pMesh) { |
567 | 0 | readHeadOfDataObject(); |
568 | 0 | if (pMesh->mNumColorSets + 1 > AI_MAX_NUMBER_OF_COLOR_SETS) |
569 | 0 | ThrowException("Too many colorsets"); |
570 | 0 | std::vector<aiColor4D> &colors = pMesh->mColors[pMesh->mNumColorSets++]; |
571 | |
|
572 | 0 | unsigned int numColors = ReadInt(); |
573 | 0 | if (numColors != pMesh->mPositions.size()) |
574 | 0 | ThrowException("Vertex color count does not match vertex count"); |
575 | |
|
576 | 0 | colors.resize(numColors, aiColor4D(0, 0, 0, 1)); |
577 | 0 | for (unsigned int a = 0; a < numColors; a++) { |
578 | 0 | unsigned int index = ReadInt(); |
579 | 0 | if (index >= pMesh->mPositions.size()) |
580 | 0 | ThrowException("Vertex color index out of bounds"); |
581 | |
|
582 | 0 | colors[index] = ReadRGBA(); |
583 | | // HACK: (thom) Maxon Cinema XPort plugin puts a third separator here, kwxPort puts a comma. |
584 | | // Ignore gracefully. |
585 | 0 | if (!mIsBinaryFormat) { |
586 | 0 | FindNextNoneWhiteSpace(); |
587 | 0 | if (*mP == ';' || *mP == ',') |
588 | 0 | mP++; |
589 | 0 | } |
590 | 0 | } |
591 | |
|
592 | 0 | CheckForClosingBrace(); |
593 | 0 | } |
594 | | |
595 | | // ------------------------------------------------------------------------------------------------ |
596 | 2 | void XFileParser::ParseDataObjectMeshMaterialList(Mesh *pMesh) { |
597 | 2 | readHeadOfDataObject(); |
598 | | |
599 | | // read material count |
600 | | /*unsigned int numMaterials =*/ReadInt(); |
601 | | // read non triangulated face material index count |
602 | 2 | unsigned int numMatIndices = ReadInt(); |
603 | | |
604 | | // some models have a material index count of 1... to be able to read them we |
605 | | // replicate this single material index on every face |
606 | 2 | if (numMatIndices != pMesh->mPosFaces.size() && numMatIndices != 1) |
607 | 0 | ThrowException("Per-Face material index count does not match face count."); |
608 | | |
609 | | // read per-face material indices |
610 | 7.49k | for (unsigned int a = 0; a < numMatIndices; a++) |
611 | 7.49k | pMesh->mFaceMaterials.push_back(ReadInt()); |
612 | | |
613 | | // in version 03.02, the face indices end with two semicolons. |
614 | | // commented out version check, as version 03.03 exported from blender also has 2 semicolons |
615 | 2 | if (!mIsBinaryFormat) // && MajorVersion == 3 && MinorVersion <= 2) |
616 | 1 | { |
617 | 1 | if (mP < mEnd && *mP == ';') |
618 | 0 | ++mP; |
619 | 1 | } |
620 | | |
621 | | // if there was only a single material index, replicate it on all faces |
622 | 2 | while (pMesh->mFaceMaterials.size() < pMesh->mPosFaces.size()) |
623 | 0 | pMesh->mFaceMaterials.push_back(pMesh->mFaceMaterials.front()); |
624 | | |
625 | | // read following data objects |
626 | 2 | bool running = true; |
627 | 4 | while (running) { |
628 | 4 | std::string objectName = GetNextToken(); |
629 | 4 | if (objectName.size() == 0) |
630 | 0 | ThrowException("Unexpected end of file while parsing mesh material list."); |
631 | 4 | else if (objectName == "}") |
632 | 2 | break; // material list finished |
633 | 2 | else if (objectName == "{") { |
634 | | // template materials |
635 | 0 | std::string matName = GetNextToken(); |
636 | 0 | Material material; |
637 | 0 | material.mIsReference = true; |
638 | 0 | material.mName = matName; |
639 | 0 | pMesh->mMaterials.push_back(material); |
640 | |
|
641 | 0 | CheckForClosingBrace(); // skip } |
642 | 2 | } else if (objectName == "Material") { |
643 | 2 | pMesh->mMaterials.emplace_back(); |
644 | 2 | ParseDataObjectMaterial(&pMesh->mMaterials.back()); |
645 | 2 | } else if (objectName == ";") { |
646 | | // ignore |
647 | 0 | } else { |
648 | 0 | ASSIMP_LOG_WARN("Unknown data object in material list in x file"); |
649 | 0 | ParseUnknownDataObject(); |
650 | 0 | } |
651 | 4 | } |
652 | 2 | } |
653 | | |
654 | | // ------------------------------------------------------------------------------------------------ |
655 | 2 | void XFileParser::ParseDataObjectMaterial(Material *pMaterial) { |
656 | 2 | std::string matName; |
657 | 2 | readHeadOfDataObject(&matName); |
658 | 2 | if (matName.empty()) |
659 | 1 | matName = std::string("material") + ai_to_string(mLineNumber); |
660 | 2 | pMaterial->mName = matName; |
661 | 2 | pMaterial->mIsReference = false; |
662 | | |
663 | | // read material values |
664 | 2 | pMaterial->mDiffuse = ReadRGBA(); |
665 | 2 | pMaterial->mSpecularExponent = ReadFloat(); |
666 | 2 | pMaterial->mSpecular = ReadRGB(); |
667 | 2 | pMaterial->mEmissive = ReadRGB(); |
668 | | |
669 | | // read other data objects |
670 | 2 | bool running = true; |
671 | 3 | while (running) { |
672 | 3 | std::string objectName = GetNextToken(); |
673 | 3 | if (objectName.size() == 0) |
674 | 0 | ThrowException("Unexpected end of file while parsing mesh material"); |
675 | 3 | else if (objectName == "}") |
676 | 2 | break; // material finished |
677 | 1 | else if (objectName == "TextureFilename" || objectName == "TextureFileName") { |
678 | | // some exporters write "TextureFileName" instead. |
679 | 1 | std::string texname; |
680 | 1 | ParseDataObjectTextureFilename(texname); |
681 | 1 | pMaterial->mTextures.emplace_back(texname); |
682 | 1 | } else if (objectName == "NormalmapFilename" || objectName == "NormalmapFileName") { |
683 | | // one exporter writes out the normal map in a separate filename tag |
684 | 0 | std::string texname; |
685 | 0 | ParseDataObjectTextureFilename(texname); |
686 | 0 | pMaterial->mTextures.emplace_back(texname, true); |
687 | 0 | } else { |
688 | 0 | ASSIMP_LOG_WARN("Unknown data object in material in x file"); |
689 | 0 | ParseUnknownDataObject(); |
690 | 0 | } |
691 | 3 | } |
692 | 2 | } |
693 | | |
694 | | // ------------------------------------------------------------------------------------------------ |
695 | 5 | void XFileParser::ParseDataObjectAnimTicksPerSecond() { |
696 | 5 | readHeadOfDataObject(); |
697 | 5 | mScene->mAnimTicksPerSecond = ReadInt(); |
698 | 5 | CheckForClosingBrace(); |
699 | 5 | } |
700 | | |
701 | | // ------------------------------------------------------------------------------------------------ |
702 | 5 | void XFileParser::ParseDataObjectAnimationSet() { |
703 | 5 | std::string animName; |
704 | 5 | readHeadOfDataObject(&animName); |
705 | | |
706 | 5 | Animation *anim = new Animation; |
707 | 5 | mScene->mAnims.push_back(anim); |
708 | 5 | anim->mName = animName; |
709 | | |
710 | 5 | bool running = true; |
711 | 183 | while (running) { |
712 | 183 | std::string objectName = GetNextToken(); |
713 | 183 | if (objectName.length() == 0) |
714 | 0 | ThrowException("Unexpected end of file while parsing animation set."); |
715 | 183 | else if (objectName == "}") |
716 | 5 | break; // animation set finished |
717 | 178 | else if (objectName == "Animation") |
718 | 178 | ParseDataObjectAnimation(anim); |
719 | 0 | else { |
720 | 0 | ASSIMP_LOG_WARN("Unknown data object in animation set in x file"); |
721 | 0 | ParseUnknownDataObject(); |
722 | 0 | } |
723 | 183 | } |
724 | 5 | } |
725 | | |
726 | | // ------------------------------------------------------------------------------------------------ |
727 | 178 | void XFileParser::ParseDataObjectAnimation(Animation *pAnim) { |
728 | 178 | readHeadOfDataObject(); |
729 | 178 | AnimBone *banim = new AnimBone; |
730 | 178 | pAnim->mAnims.push_back(banim); |
731 | | |
732 | 178 | bool running = true; |
733 | 1.06k | while (running) { |
734 | 1.06k | std::string objectName = GetNextToken(); |
735 | | |
736 | 1.06k | if (objectName.length() == 0) |
737 | 0 | ThrowException("Unexpected end of file while parsing animation."); |
738 | 1.06k | else if (objectName == "}") |
739 | 178 | break; // animation finished |
740 | 886 | else if (objectName == "AnimationKey") |
741 | 534 | ParseDataObjectAnimationKey(banim); |
742 | 352 | else if (objectName == "AnimationOptions") |
743 | 174 | ParseUnknownDataObject(); // not interested |
744 | 178 | else if (objectName == "{") { |
745 | | // read frame name |
746 | 178 | banim->mBoneName = GetNextToken(); |
747 | 178 | CheckForClosingBrace(); |
748 | 178 | } else { |
749 | 0 | ASSIMP_LOG_WARN("Unknown data object in animation in x file"); |
750 | 0 | ParseUnknownDataObject(); |
751 | 0 | } |
752 | 1.06k | } |
753 | 178 | } |
754 | | |
755 | | // ------------------------------------------------------------------------------------------------ |
756 | 534 | void XFileParser::ParseDataObjectAnimationKey(AnimBone *pAnimBone) { |
757 | 534 | readHeadOfDataObject(); |
758 | | |
759 | | // read key type |
760 | 534 | unsigned int keyType = ReadInt(); |
761 | | |
762 | | // read number of keys |
763 | 534 | unsigned int numKeys = ReadInt(); |
764 | | |
765 | 7.22k | for (unsigned int a = 0; a < numKeys; a++) { |
766 | | // read time |
767 | 6.68k | unsigned int time = ReadInt(); |
768 | | |
769 | | // read keys |
770 | 6.68k | switch (keyType) { |
771 | 6.01k | case 0: // rotation quaternion |
772 | 6.01k | { |
773 | | // read count |
774 | 6.01k | if (ReadInt() != 4) |
775 | 0 | ThrowException("Invalid number of arguments for quaternion key in animation"); |
776 | | |
777 | 6.01k | aiQuatKey key; |
778 | 6.01k | key.mTime = double(time); |
779 | 6.01k | key.mValue.w = ReadFloat(); |
780 | 6.01k | key.mValue.x = ReadFloat(); |
781 | 6.01k | key.mValue.y = ReadFloat(); |
782 | 6.01k | key.mValue.z = ReadFloat(); |
783 | 6.01k | pAnimBone->mRotKeys.push_back(key); |
784 | | |
785 | 6.01k | CheckForSemicolon(); |
786 | 6.01k | break; |
787 | 0 | } |
788 | | |
789 | 270 | case 1: // scale vector |
790 | 668 | case 2: // position vector |
791 | 668 | { |
792 | | // read count |
793 | 668 | if (ReadInt() != 3) |
794 | 0 | ThrowException("Invalid number of arguments for vector key in animation"); |
795 | | |
796 | 668 | aiVectorKey key; |
797 | 668 | key.mTime = double(time); |
798 | 668 | key.mValue = ReadVector3(); |
799 | | |
800 | 668 | if (keyType == 2) |
801 | 398 | pAnimBone->mPosKeys.push_back(key); |
802 | 270 | else |
803 | 270 | pAnimBone->mScaleKeys.push_back(key); |
804 | | |
805 | 668 | break; |
806 | 270 | } |
807 | | |
808 | 0 | case 3: // combined transformation matrix |
809 | 0 | case 4: // denoted both as 3 or as 4 |
810 | 0 | { |
811 | | // read count |
812 | 0 | if (ReadInt() != 16) |
813 | 0 | ThrowException("Invalid number of arguments for matrix key in animation"); |
814 | | |
815 | | // read matrix |
816 | 0 | MatrixKey key; |
817 | 0 | key.mTime = double(time); |
818 | 0 | key.mMatrix.a1 = ReadFloat(); |
819 | 0 | key.mMatrix.b1 = ReadFloat(); |
820 | 0 | key.mMatrix.c1 = ReadFloat(); |
821 | 0 | key.mMatrix.d1 = ReadFloat(); |
822 | 0 | key.mMatrix.a2 = ReadFloat(); |
823 | 0 | key.mMatrix.b2 = ReadFloat(); |
824 | 0 | key.mMatrix.c2 = ReadFloat(); |
825 | 0 | key.mMatrix.d2 = ReadFloat(); |
826 | 0 | key.mMatrix.a3 = ReadFloat(); |
827 | 0 | key.mMatrix.b3 = ReadFloat(); |
828 | 0 | key.mMatrix.c3 = ReadFloat(); |
829 | 0 | key.mMatrix.d3 = ReadFloat(); |
830 | 0 | key.mMatrix.a4 = ReadFloat(); |
831 | 0 | key.mMatrix.b4 = ReadFloat(); |
832 | 0 | key.mMatrix.c4 = ReadFloat(); |
833 | 0 | key.mMatrix.d4 = ReadFloat(); |
834 | 0 | pAnimBone->mTrafoKeys.push_back(key); |
835 | |
|
836 | 0 | CheckForSemicolon(); |
837 | 0 | break; |
838 | 0 | } |
839 | | |
840 | 0 | default: |
841 | 0 | ThrowException("Unknown key type ", keyType, " in animation."); |
842 | 6.68k | } // end switch |
843 | | |
844 | | // key separator |
845 | 6.68k | CheckForSeparator(); |
846 | 6.68k | } |
847 | | |
848 | 534 | CheckForClosingBrace(); |
849 | 534 | } |
850 | | |
851 | | // ------------------------------------------------------------------------------------------------ |
852 | 1 | void XFileParser::ParseDataObjectTextureFilename(std::string &pName) { |
853 | 1 | readHeadOfDataObject(); |
854 | 1 | GetNextTokenAsString(pName); |
855 | 1 | CheckForClosingBrace(); |
856 | | |
857 | | // FIX: some files (e.g. AnimationTest.x) have "" as texture file name |
858 | 1 | if (!pName.length()) { |
859 | 1 | ASSIMP_LOG_WARN("Length of texture file name is zero. Skipping this texture."); |
860 | 1 | } |
861 | | |
862 | | // some exporters write double backslash paths out. We simply replace them if we find them |
863 | 1 | while (pName.find("\\\\") != std::string::npos) |
864 | 0 | pName.replace(pName.find("\\\\"), 2, "\\"); |
865 | 1 | } |
866 | | |
867 | | // ------------------------------------------------------------------------------------------------ |
868 | 281 | void XFileParser::ParseUnknownDataObject() { |
869 | | // find opening delimiter |
870 | 281 | bool running = true; |
871 | 377 | while (running) { |
872 | 377 | std::string t = GetNextToken(); |
873 | 377 | if (t.length() == 0) |
874 | 0 | ThrowException("Unexpected end of file while parsing unknown segment."); |
875 | | |
876 | 377 | if (t == "{") |
877 | 281 | break; |
878 | 377 | } |
879 | | |
880 | 281 | unsigned int counter = 1; |
881 | | |
882 | | // parse until closing delimiter |
883 | 28.5k | while (counter > 0) { |
884 | 28.2k | std::string t = GetNextToken(); |
885 | | |
886 | 28.2k | if (t.length() == 0) |
887 | 0 | ThrowException("Unexpected end of file while parsing unknown segment."); |
888 | | |
889 | 28.2k | if (t == "{") |
890 | 0 | ++counter; |
891 | 28.2k | else if (t == "}") |
892 | 281 | --counter; |
893 | 28.2k | } |
894 | 281 | } |
895 | | |
896 | | // ------------------------------------------------------------------------------------------------ |
897 | | //! checks for closing curly brace |
898 | 931 | void XFileParser::CheckForClosingBrace() { |
899 | 931 | if (GetNextToken() != "}") |
900 | 0 | ThrowException("Closing brace expected."); |
901 | 931 | } |
902 | | |
903 | | // ------------------------------------------------------------------------------------------------ |
904 | | //! checks for one following semicolon |
905 | 6.21k | void XFileParser::CheckForSemicolon() { |
906 | 6.21k | if (mIsBinaryFormat) |
907 | 1 | return; |
908 | | |
909 | 6.21k | if (GetNextToken() != ";") |
910 | 0 | ThrowException("Semicolon expected."); |
911 | 6.21k | } |
912 | | |
913 | | // ------------------------------------------------------------------------------------------------ |
914 | | //! checks for a separator char, either a ',' or a ';' |
915 | 226k | void XFileParser::CheckForSeparator() { |
916 | 226k | if (mIsBinaryFormat) |
917 | 0 | return; |
918 | | |
919 | 226k | std::string token = GetNextToken(); |
920 | 226k | if (token != "," && token != ";") |
921 | 0 | ThrowException("Separator character (';' or ',') expected."); |
922 | 226k | } |
923 | | |
924 | | // ------------------------------------------------------------------------------------------------ |
925 | | // tests and possibly consumes a separator char, but does nothing if there was no separator |
926 | 69.5k | void XFileParser::TestForSeparator() { |
927 | 69.5k | if (mIsBinaryFormat) |
928 | 25.7k | return; |
929 | | |
930 | 43.8k | FindNextNoneWhiteSpace(); |
931 | 43.8k | if (mP >= mEnd) |
932 | 0 | return; |
933 | | |
934 | | // test and skip |
935 | 43.8k | if (*mP == ';' || *mP == ',') |
936 | 43.8k | mP++; |
937 | 43.8k | } |
938 | | |
939 | | // ------------------------------------------------------------------------------------------------ |
940 | 1.08k | void XFileParser::readHeadOfDataObject(std::string *poName) { |
941 | 1.08k | std::string nameOrBrace = GetNextToken(); |
942 | 1.08k | if (nameOrBrace != "{") { |
943 | 1.04k | if (poName) |
944 | 153 | *poName = nameOrBrace; |
945 | | |
946 | 1.04k | if (GetNextToken() != "{") { |
947 | 0 | delete mScene; |
948 | 0 | ThrowException("Opening brace expected."); |
949 | 0 | } |
950 | 1.04k | } |
951 | 1.08k | } |
952 | | |
953 | | // ------------------------------------------------------------------------------------------------ |
954 | 266k | std::string XFileParser::GetNextToken() { |
955 | 266k | std::string s; |
956 | | |
957 | | // process binary-formatted file |
958 | 266k | if (mIsBinaryFormat) { |
959 | | // in binary mode it will only return NAME and STRING token |
960 | | // and (correctly) skip over other tokens. |
961 | 29 | if (mEnd - mP < 2) { |
962 | 1 | return s; |
963 | 1 | } |
964 | 28 | unsigned int tok = ReadBinWord(); |
965 | 28 | unsigned int len; |
966 | | |
967 | | // standalone tokens |
968 | 28 | switch (tok) { |
969 | 11 | case 1: { |
970 | | // name token |
971 | 11 | if (mEnd - mP < 4) { |
972 | 0 | return s; |
973 | 0 | } |
974 | 11 | len = ReadBinDWord(); |
975 | 11 | const int bounds = int(mEnd - mP); |
976 | 11 | const int iLen = int(len); |
977 | 11 | if (iLen < 0) { |
978 | 0 | return s; |
979 | 0 | } |
980 | 11 | if (bounds < iLen) { |
981 | 0 | return s; |
982 | 0 | } |
983 | 11 | s = std::string(mP, len); |
984 | 11 | mP += len; |
985 | 11 | } |
986 | 0 | return s; |
987 | | |
988 | 0 | case 2: |
989 | | // string token |
990 | 0 | if (mEnd - mP < 4) return s; |
991 | 0 | len = ReadBinDWord(); |
992 | 0 | if (mEnd - mP < int(len)) return s; |
993 | 0 | s = std::string(mP, len); |
994 | 0 | mP += (len + 2); |
995 | 0 | return s; |
996 | 0 | case 3: |
997 | | // integer token |
998 | 0 | mP += 4; |
999 | 0 | return "<integer>"; |
1000 | 0 | case 5: |
1001 | | // GUID token |
1002 | 0 | mP += 16; |
1003 | 0 | return "<guid>"; |
1004 | 1 | case 6: |
1005 | 1 | if (mEnd - mP < 4) return s; |
1006 | 1 | len = ReadBinDWord(); |
1007 | 1 | mP += (len * 4); |
1008 | 1 | return "<int_list>"; |
1009 | 0 | case 7: |
1010 | 0 | if (mEnd - mP < 4) return s; |
1011 | 0 | len = ReadBinDWord(); |
1012 | 0 | mP += (len * mBinaryFloatSize); |
1013 | 0 | return "<flt_list>"; |
1014 | 8 | case 0x0a: |
1015 | 8 | return "{"; |
1016 | 8 | case 0x0b: |
1017 | 8 | return "}"; |
1018 | 0 | case 0x0c: |
1019 | 0 | return "("; |
1020 | 0 | case 0x0d: |
1021 | 0 | return ")"; |
1022 | 0 | case 0x0e: |
1023 | 0 | return "["; |
1024 | 0 | case 0x0f: |
1025 | 0 | return "]"; |
1026 | 0 | case 0x10: |
1027 | 0 | return "<"; |
1028 | 0 | case 0x11: |
1029 | 0 | return ">"; |
1030 | 0 | case 0x12: |
1031 | 0 | return "."; |
1032 | 0 | case 0x13: |
1033 | 0 | return ","; |
1034 | 0 | case 0x14: |
1035 | 0 | return ";"; |
1036 | 0 | case 0x1f: |
1037 | 0 | return "template"; |
1038 | 0 | case 0x28: |
1039 | 0 | return "WORD"; |
1040 | 0 | case 0x29: |
1041 | 0 | return "DWORD"; |
1042 | 0 | case 0x2a: |
1043 | 0 | return "FLOAT"; |
1044 | 0 | case 0x2b: |
1045 | 0 | return "DOUBLE"; |
1046 | 0 | case 0x2c: |
1047 | 0 | return "CHAR"; |
1048 | 0 | case 0x2d: |
1049 | 0 | return "UCHAR"; |
1050 | 0 | case 0x2e: |
1051 | 0 | return "SWORD"; |
1052 | 0 | case 0x2f: |
1053 | 0 | return "SDWORD"; |
1054 | 0 | case 0x30: |
1055 | 0 | return "void"; |
1056 | 0 | case 0x31: |
1057 | 0 | return "string"; |
1058 | 0 | case 0x32: |
1059 | 0 | return "unicode"; |
1060 | 0 | case 0x33: |
1061 | 0 | return "cstring"; |
1062 | 0 | case 0x34: |
1063 | 0 | return "array"; |
1064 | 28 | } |
1065 | 28 | } |
1066 | | // process text-formatted file |
1067 | 266k | else { |
1068 | 266k | FindNextNoneWhiteSpace(); |
1069 | 266k | if (mP >= mEnd) |
1070 | 3 | return s; |
1071 | | |
1072 | 418k | while ((mP < mEnd) && !isspace((unsigned char)*mP)) { |
1073 | | // either keep token delimiters when already holding a token, or return if first valid char |
1074 | 415k | if (*mP == ';' || *mP == '}' || *mP == '{' || *mP == ',') { |
1075 | 263k | if (!s.size()) |
1076 | 249k | s.append(mP++, 1); |
1077 | 263k | break; // stop for delimiter |
1078 | 263k | } |
1079 | 152k | s.append(mP++, 1); |
1080 | 152k | } |
1081 | 266k | } |
1082 | 266k | return s; |
1083 | 266k | } |
1084 | | |
1085 | | // ------------------------------------------------------------------------------------------------ |
1086 | 530k | void XFileParser::FindNextNoneWhiteSpace() { |
1087 | 530k | if (mIsBinaryFormat) |
1088 | 0 | return; |
1089 | | |
1090 | 530k | bool running = true; |
1091 | 530k | while (running) { |
1092 | 1.22M | while (mP < mEnd && isspace((unsigned char)*mP)) { |
1093 | 693k | if (*mP == '\n') |
1094 | 103k | mLineNumber++; |
1095 | 693k | ++mP; |
1096 | 693k | } |
1097 | | |
1098 | 530k | if (mP >= mEnd) |
1099 | 3 | return; |
1100 | | |
1101 | | // check if this is a comment |
1102 | 530k | if ((mP[0] == '/' && mP[1] == '/') || mP[0] == '#') |
1103 | 0 | ReadUntilEndOfLine(); |
1104 | 530k | else |
1105 | 530k | break; |
1106 | 530k | } |
1107 | 530k | } |
1108 | | |
1109 | | // ------------------------------------------------------------------------------------------------ |
1110 | 96 | void XFileParser::GetNextTokenAsString(std::string &poString) { |
1111 | 96 | if (mIsBinaryFormat) { |
1112 | 0 | poString = GetNextToken(); |
1113 | 0 | return; |
1114 | 0 | } |
1115 | | |
1116 | 96 | FindNextNoneWhiteSpace(); |
1117 | 96 | if (mP >= mEnd) { |
1118 | 0 | delete mScene; |
1119 | 0 | ThrowException("Unexpected end of file while parsing string"); |
1120 | 0 | } |
1121 | | |
1122 | 96 | if (*mP != '"') { |
1123 | 0 | delete mScene; |
1124 | 0 | ThrowException("Expected quotation mark."); |
1125 | 0 | } |
1126 | 96 | ++mP; |
1127 | | |
1128 | 1.18k | while (mP < mEnd && *mP != '"') |
1129 | 1.08k | poString.append(mP++, 1); |
1130 | | |
1131 | 96 | if (mP >= mEnd - 1) { |
1132 | 0 | delete mScene; |
1133 | 0 | ThrowException("Unexpected end of file while parsing string"); |
1134 | 0 | } |
1135 | | |
1136 | 96 | if (mP[1] != ';' || mP[0] != '"') { |
1137 | 0 | delete mScene; |
1138 | 0 | ThrowException("Expected quotation mark and semicolon at the end of a string."); |
1139 | 0 | } |
1140 | 96 | mP += 2; |
1141 | 96 | } |
1142 | | |
1143 | | // ------------------------------------------------------------------------------------------------ |
1144 | 4 | void XFileParser::ReadUntilEndOfLine() { |
1145 | 4 | if (mIsBinaryFormat) |
1146 | 1 | return; |
1147 | | |
1148 | 3 | while (mP < mEnd) { |
1149 | 3 | if (*mP == '\n' || *mP == '\r') { |
1150 | 3 | ++mP; |
1151 | 3 | mLineNumber++; |
1152 | 3 | return; |
1153 | 3 | } |
1154 | | |
1155 | 0 | ++mP; |
1156 | 0 | } |
1157 | 3 | } |
1158 | | |
1159 | | // ------------------------------------------------------------------------------------------------ |
1160 | 39 | unsigned short XFileParser::ReadBinWord() { |
1161 | 39 | ai_assert(mEnd - mP >= 2); |
1162 | 39 | const unsigned char *q = (const unsigned char *)mP; |
1163 | 39 | unsigned short tmp = q[0] | (q[1] << 8); |
1164 | 39 | mP += 2; |
1165 | 39 | return tmp; |
1166 | 39 | } |
1167 | | |
1168 | | // ------------------------------------------------------------------------------------------------ |
1169 | 59.9k | unsigned int XFileParser::ReadBinDWord() { |
1170 | 59.9k | ai_assert(mEnd - mP >= 4); |
1171 | | |
1172 | 59.9k | const unsigned char *q = (const unsigned char *)mP; |
1173 | 59.9k | unsigned int tmp = q[0] | (q[1] << 8) | (q[2] << 16) | (q[3] << 24); |
1174 | 59.9k | mP += 4; |
1175 | 59.9k | return tmp; |
1176 | 59.9k | } |
1177 | | |
1178 | | // ------------------------------------------------------------------------------------------------ |
1179 | 169k | unsigned int XFileParser::ReadInt() { |
1180 | 169k | if (mIsBinaryFormat) { |
1181 | 59.9k | if (mBinaryNumCount == 0 && mEnd - mP >= 2) { |
1182 | 6 | unsigned short tmp = ReadBinWord(); // 0x06 or 0x03 |
1183 | 6 | if (tmp == 0x06 && mEnd - mP >= 4) // array of ints follows |
1184 | 6 | mBinaryNumCount = ReadBinDWord(); |
1185 | 0 | else // single int follows |
1186 | 0 | mBinaryNumCount = 1; |
1187 | 6 | } |
1188 | | |
1189 | 59.9k | --mBinaryNumCount; |
1190 | 59.9k | const size_t len(mEnd - mP); |
1191 | 59.9k | if (len >= 4) { |
1192 | 59.9k | return ReadBinDWord(); |
1193 | 59.9k | } else { |
1194 | 0 | mP = mEnd; |
1195 | 0 | return 0; |
1196 | 0 | } |
1197 | 109k | } else { |
1198 | 109k | FindNextNoneWhiteSpace(); |
1199 | | |
1200 | | // TODO: consider using strtol10 instead??? |
1201 | | |
1202 | | // check preceding minus sign |
1203 | 109k | bool isNegative = false; |
1204 | 109k | if (*mP == '-') { |
1205 | 0 | isNegative = true; |
1206 | 0 | mP++; |
1207 | 0 | } |
1208 | | |
1209 | | // at least one digit expected |
1210 | 109k | if (!isdigit((unsigned char)*mP)) |
1211 | 0 | ThrowException("Number expected."); |
1212 | | |
1213 | | // read digits |
1214 | 109k | unsigned int number = 0; |
1215 | 412k | while (mP < mEnd) { |
1216 | 412k | if (!isdigit((unsigned char)*mP)) |
1217 | 109k | break; |
1218 | 302k | number = number * 10 + (*mP - 48); |
1219 | 302k | mP++; |
1220 | 302k | } |
1221 | | |
1222 | 109k | CheckForSeparator(); |
1223 | | |
1224 | 109k | return isNegative ? ((unsigned int)-int(number)) : number; |
1225 | 109k | } |
1226 | 169k | } |
1227 | | |
1228 | | // ------------------------------------------------------------------------------------------------ |
1229 | 142k | ai_real XFileParser::ReadFloat() { |
1230 | 142k | if (mIsBinaryFormat) { |
1231 | 33.0k | if (mBinaryNumCount == 0 && mEnd - mP >= 2) { |
1232 | 5 | unsigned short tmp = ReadBinWord(); // 0x07 or 0x42 |
1233 | 5 | if (tmp == 0x07 && mEnd - mP >= 4) // array of floats following |
1234 | 5 | mBinaryNumCount = ReadBinDWord(); |
1235 | 0 | else // single float following |
1236 | 0 | mBinaryNumCount = 1; |
1237 | 5 | } |
1238 | | |
1239 | 33.0k | --mBinaryNumCount; |
1240 | 33.0k | if (mBinaryFloatSize == 8) { |
1241 | 0 | if (mEnd - mP >= 8) { |
1242 | 0 | double res; |
1243 | 0 | ::memcpy(&res, mP, 8); |
1244 | 0 | mP += 8; |
1245 | 0 | const ai_real result(static_cast<ai_real>(res)); |
1246 | 0 | return result; |
1247 | 0 | } else { |
1248 | 0 | mP = mEnd; |
1249 | 0 | return 0; |
1250 | 0 | } |
1251 | 33.0k | } else { |
1252 | 33.0k | if (mEnd - mP >= 4) { |
1253 | 33.0k | ai_real result; |
1254 | 33.0k | ::memcpy(&result, mP, 4); |
1255 | 33.0k | mP += 4; |
1256 | 33.0k | return result; |
1257 | 33.0k | } else { |
1258 | 0 | mP = mEnd; |
1259 | 0 | return 0; |
1260 | 0 | } |
1261 | 33.0k | } |
1262 | 33.0k | } |
1263 | | |
1264 | | // text version |
1265 | 109k | FindNextNoneWhiteSpace(); |
1266 | | // check for various special strings to allow reading files from faulty exporters |
1267 | | // I mean you, Blender! |
1268 | | // Reading is safe because of the terminating zero |
1269 | 109k | if (strncmp(mP, "-1.#IND00", 9) == 0 || strncmp(mP, "1.#IND00", 8) == 0) { |
1270 | 0 | mP += 9; |
1271 | 0 | CheckForSeparator(); |
1272 | 0 | return 0.0; |
1273 | 109k | } else if (strncmp(mP, "1.#QNAN0", 8) == 0) { |
1274 | 0 | mP += 8; |
1275 | 0 | CheckForSeparator(); |
1276 | 0 | return 0.0; |
1277 | 0 | } |
1278 | | |
1279 | 109k | ai_real result = 0.0; |
1280 | 109k | mP = fast_atoreal_move(mP, result); |
1281 | | |
1282 | 109k | CheckForSeparator(); |
1283 | | |
1284 | 109k | return result; |
1285 | 109k | } |
1286 | | |
1287 | | // ------------------------------------------------------------------------------------------------ |
1288 | 12.0k | aiVector2D XFileParser::ReadVector2() { |
1289 | 12.0k | aiVector2D vector; |
1290 | 12.0k | vector.x = ReadFloat(); |
1291 | 12.0k | vector.y = ReadFloat(); |
1292 | 12.0k | TestForSeparator(); |
1293 | | |
1294 | 12.0k | return vector; |
1295 | 12.0k | } |
1296 | | |
1297 | | // ------------------------------------------------------------------------------------------------ |
1298 | 24.8k | aiVector3D XFileParser::ReadVector3() { |
1299 | 24.8k | aiVector3D vector; |
1300 | 24.8k | vector.x = ReadFloat(); |
1301 | 24.8k | vector.y = ReadFloat(); |
1302 | 24.8k | vector.z = ReadFloat(); |
1303 | 24.8k | TestForSeparator(); |
1304 | | |
1305 | 24.8k | return vector; |
1306 | 24.8k | } |
1307 | | |
1308 | | // ------------------------------------------------------------------------------------------------ |
1309 | 2 | aiColor4D XFileParser::ReadRGBA() { |
1310 | 2 | aiColor4D color; |
1311 | 2 | color.r = ReadFloat(); |
1312 | 2 | color.g = ReadFloat(); |
1313 | 2 | color.b = ReadFloat(); |
1314 | 2 | color.a = ReadFloat(); |
1315 | 2 | TestForSeparator(); |
1316 | | |
1317 | 2 | return color; |
1318 | 2 | } |
1319 | | |
1320 | | // ------------------------------------------------------------------------------------------------ |
1321 | 4 | aiColor3D XFileParser::ReadRGB() { |
1322 | 4 | aiColor3D color; |
1323 | 4 | color.r = ReadFloat(); |
1324 | 4 | color.g = ReadFloat(); |
1325 | 4 | color.b = ReadFloat(); |
1326 | 4 | TestForSeparator(); |
1327 | | |
1328 | 4 | return color; |
1329 | 4 | } |
1330 | | |
1331 | | // ------------------------------------------------------------------------------------------------ |
1332 | | // Filters the imported hierarchy for some degenerated cases that some exporters produce. |
1333 | 104 | void XFileParser::FilterHierarchy(XFile::Node *pNode) { |
1334 | | // if the node has just a single unnamed child containing a mesh, remove |
1335 | | // the anonymous node between. The 3DSMax kwXport plugin seems to produce this |
1336 | | // mess in some cases |
1337 | 104 | if (pNode->mChildren.size() == 1 && pNode->mMeshes.empty()) { |
1338 | 54 | XFile::Node *child = pNode->mChildren.front(); |
1339 | 54 | if (child->mName.length() == 0 && child->mMeshes.size() > 0) { |
1340 | | // transfer its meshes to us |
1341 | 0 | for (unsigned int a = 0; a < child->mMeshes.size(); a++) |
1342 | 0 | pNode->mMeshes.push_back(child->mMeshes[a]); |
1343 | 0 | child->mMeshes.clear(); |
1344 | | |
1345 | | // transfer the transform as well |
1346 | 0 | pNode->mTrafoMatrix = pNode->mTrafoMatrix * child->mTrafoMatrix; |
1347 | | |
1348 | | // then kill it |
1349 | 0 | delete child; |
1350 | 0 | pNode->mChildren.clear(); |
1351 | 0 | } |
1352 | 54 | } |
1353 | | |
1354 | | // recurse |
1355 | 204 | for (unsigned int a = 0; a < pNode->mChildren.size(); a++) |
1356 | 100 | FilterHierarchy(pNode->mChildren[a]); |
1357 | 104 | } |
1358 | | |
1359 | | #endif // !! ASSIMP_BUILD_NO_X_IMPORTER |