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