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