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