1 /*
2 ---------------------------------------------------------------------------
3 Open Asset Import Library (assimp)
4 ---------------------------------------------------------------------------
5 
6 Copyright (c) 2006-2016, assimp team
7 
8 All rights reserved.
9 
10 Redistribution and use of this software in source and binary forms,
11 with or without modification, are permitted provided that the following
12 conditions are met:
13 
14 * Redistributions of source code must retain the above
15   copyright notice, this list of conditions and the
16   following disclaimer.
17 
18 * Redistributions in binary form must reproduce the above
19   copyright notice, this list of conditions and the
20   following disclaimer in the documentation and/or other
21   materials provided with the distribution.
22 
23 * Neither the name of the assimp team, nor the names of its
24   contributors may be used to endorse or promote products
25   derived from this software without specific prior
26   written permission of the assimp team.
27 
28 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
29 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
31 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
32 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
33 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
34 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
35 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
36 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
37 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
38 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39 ---------------------------------------------------------------------------
40 */
41 
42 /** @file MD3Loader.cpp
43  *  @brief Implementation of the MD3 importer class
44  *
45  *  Sources:
46  *     http://www.gamers.org/dEngine/quake3/UQ3S
47  *     http://linux.ucla.edu/~phaethon/q3/formats/md3format.html
48  *     http://www.heppler.com/shader/shader/
49  */
50 
51 
52 #ifndef ASSIMP_BUILD_NO_MD3_IMPORTER
53 
54 #include "MD3Loader.h"
55 #include "SceneCombiner.h"
56 #include "GenericProperty.h"
57 #include "RemoveComments.h"
58 #include "ParsingUtils.h"
59 #include "Importer.h"
60 #include <assimp/DefaultLogger.hpp>
61 #include <memory>
62 #include <assimp/IOSystem.hpp>
63 #include <assimp/material.h>
64 #include <assimp/scene.h>
65 #include <cctype>
66 
67 
68 
69 using namespace Assimp;
70 
71 static const aiImporterDesc desc = {
72     "Quake III Mesh Importer",
73     "",
74     "",
75     "",
76     aiImporterFlags_SupportBinaryFlavour,
77     0,
78     0,
79     0,
80     0,
81     "md3"
82 };
83 
84 // ------------------------------------------------------------------------------------------------
85 // Convert a Q3 shader blend function to the appropriate enum value
StringToBlendFunc(const std::string & m)86 Q3Shader::BlendFunc StringToBlendFunc(const std::string& m)
87 {
88     if (m == "GL_ONE") {
89         return Q3Shader::BLEND_GL_ONE;
90     }
91     if (m == "GL_ZERO") {
92         return Q3Shader::BLEND_GL_ZERO;
93     }
94     if (m == "GL_SRC_ALPHA") {
95         return Q3Shader::BLEND_GL_SRC_ALPHA;
96     }
97     if (m == "GL_ONE_MINUS_SRC_ALPHA") {
98         return Q3Shader::BLEND_GL_ONE_MINUS_SRC_ALPHA;
99     }
100     if (m == "GL_ONE_MINUS_DST_COLOR") {
101         return Q3Shader::BLEND_GL_ONE_MINUS_DST_COLOR;
102     }
103     DefaultLogger::get()->error("Q3Shader: Unknown blend function: " + m);
104     return Q3Shader::BLEND_NONE;
105 }
106 
107 // ------------------------------------------------------------------------------------------------
108 // Load a Quake 3 shader
LoadShader(ShaderData & fill,const std::string & pFile,IOSystem * io)109 bool Q3Shader::LoadShader(ShaderData& fill, const std::string& pFile,IOSystem* io)
110 {
111     std::unique_ptr<IOStream> file( io->Open( pFile, "rt"));
112     if (!file.get())
113         return false; // if we can't access the file, don't worry and return
114 
115     DefaultLogger::get()->info("Loading Quake3 shader file " + pFile);
116 
117     // read file in memory
118     const size_t s = file->FileSize();
119     std::vector<char> _buff(s+1);
120     file->Read(&_buff[0],s,1);
121     _buff[s] = 0;
122 
123     // remove comments from it (C++ style)
124     CommentRemover::RemoveLineComments("//",&_buff[0]);
125     const char* buff = &_buff[0];
126 
127     Q3Shader::ShaderDataBlock* curData = NULL;
128     Q3Shader::ShaderMapBlock*  curMap  = NULL;
129 
130     // read line per line
131     for (;SkipSpacesAndLineEnd(&buff);SkipLine(&buff)) {
132 
133         if (*buff == '{') {
134             ++buff;
135 
136             // append to last section, if any
137             if (!curData) {
138                 DefaultLogger::get()->error("Q3Shader: Unexpected shader section token \'{\'");
139                 return true; // still no failure, the file is there
140             }
141 
142             // read this data section
143             for (;SkipSpacesAndLineEnd(&buff);SkipLine(&buff)) {
144                 if (*buff == '{') {
145                     ++buff;
146                     // add new map section
147                     curData->maps.push_back(Q3Shader::ShaderMapBlock());
148                     curMap = &curData->maps.back();
149 
150                     for (;SkipSpacesAndLineEnd(&buff);SkipLine(&buff)) {
151                         // 'map' - Specifies texture file name
152                         if (TokenMatchI(buff,"map",3) || TokenMatchI(buff,"clampmap",8)) {
153                             curMap->name = GetNextToken(buff);
154                         }
155                         // 'blendfunc' - Alpha blending mode
156                         else if (TokenMatchI(buff,"blendfunc",9)) {
157                             const std::string blend_src = GetNextToken(buff);
158                             if (blend_src == "add") {
159                                 curMap->blend_src  = Q3Shader::BLEND_GL_ONE;
160                                 curMap->blend_dest = Q3Shader::BLEND_GL_ONE;
161                             }
162                             else if (blend_src == "filter") {
163                                 curMap->blend_src  = Q3Shader::BLEND_GL_DST_COLOR;
164                                 curMap->blend_dest = Q3Shader::BLEND_GL_ZERO;
165                             }
166                             else if (blend_src == "blend") {
167                                 curMap->blend_src  = Q3Shader::BLEND_GL_SRC_ALPHA;
168                                 curMap->blend_dest = Q3Shader::BLEND_GL_ONE_MINUS_SRC_ALPHA;
169                             }
170                             else {
171                                 curMap->blend_src  = StringToBlendFunc(blend_src);
172                                 curMap->blend_dest = StringToBlendFunc(GetNextToken(buff));
173                             }
174                         }
175                         // 'alphafunc' - Alpha testing mode
176                         else if (TokenMatchI(buff,"alphafunc",9)) {
177                             const std::string at = GetNextToken(buff);
178                             if (at == "GT0") {
179                                 curMap->alpha_test = Q3Shader::AT_GT0;
180                             }
181                             else if (at == "LT128") {
182                                 curMap->alpha_test = Q3Shader::AT_LT128;
183                             }
184                             else if (at == "GE128") {
185                                 curMap->alpha_test = Q3Shader::AT_GE128;
186                             }
187                         }
188                         else if (*buff == '}') {
189                             ++buff;
190                             // close this map section
191                             curMap = NULL;
192                             break;
193                         }
194                     }
195 
196                 }
197                 else if (*buff == '}') {
198                     ++buff;
199                     curData = NULL;
200                     break;
201                 }
202 
203                 // 'cull' specifies culling behaviour for the model
204                 else if (TokenMatchI(buff,"cull",4)) {
205                     SkipSpaces(&buff);
206                     if (!ASSIMP_strincmp(buff,"back",4)) {
207                         curData->cull = Q3Shader::CULL_CCW;
208                     }
209                     else if (!ASSIMP_strincmp(buff,"front",5)) {
210                         curData->cull = Q3Shader::CULL_CW;
211                     }
212                     else if (!ASSIMP_strincmp(buff,"none",4) || !ASSIMP_strincmp(buff,"disable",7)) {
213                         curData->cull = Q3Shader::CULL_NONE;
214                     }
215                     else DefaultLogger::get()->error("Q3Shader: Unrecognized cull mode");
216                 }
217             }
218         }
219 
220         else {
221             // add new section
222             fill.blocks.push_back(Q3Shader::ShaderDataBlock());
223             curData = &fill.blocks.back();
224 
225             // get the name of this section
226             curData->name = GetNextToken(buff);
227         }
228     }
229     return true;
230 }
231 
232 // ------------------------------------------------------------------------------------------------
233 // Load a Quake 3 skin
LoadSkin(SkinData & fill,const std::string & pFile,IOSystem * io)234 bool Q3Shader::LoadSkin(SkinData& fill, const std::string& pFile,IOSystem* io)
235 {
236     std::unique_ptr<IOStream> file( io->Open( pFile, "rt"));
237     if (!file.get())
238         return false; // if we can't access the file, don't worry and return
239 
240     DefaultLogger::get()->info("Loading Quake3 skin file " + pFile);
241 
242     // read file in memory
243     const size_t s = file->FileSize();
244     std::vector<char> _buff(s+1);const char* buff = &_buff[0];
245     file->Read(&_buff[0],s,1);
246     _buff[s] = 0;
247 
248     // remove commas
249     std::replace(_buff.begin(),_buff.end(),',',' ');
250 
251     // read token by token and fill output table
252     for (;*buff;) {
253         SkipSpacesAndLineEnd(&buff);
254 
255         // get first identifier
256         std::string ss = GetNextToken(buff);
257 
258         // ignore tokens starting with tag_
259         if (!::strncmp(&ss[0],"tag_",std::min((size_t)4, ss.length())))
260             continue;
261 
262         fill.textures.push_back(SkinData::TextureEntry());
263         SkinData::TextureEntry& s = fill.textures.back();
264 
265         s.first  = ss;
266         s.second = GetNextToken(buff);
267     }
268     return true;
269 }
270 
271 // ------------------------------------------------------------------------------------------------
272 // Convert Q3Shader to material
ConvertShaderToMaterial(aiMaterial * out,const ShaderDataBlock & shader)273 void Q3Shader::ConvertShaderToMaterial(aiMaterial* out, const ShaderDataBlock& shader)
274 {
275     ai_assert(NULL != out);
276 
277     /*  IMPORTANT: This is not a real conversion. Actually we're just guessing and
278      *  hacking around to build an aiMaterial that looks nearly equal to the
279      *  original Quake 3 shader. We're missing some important features like
280      *  animatable material properties in our material system, but at least
281      *  multiple textures should be handled correctly.
282      */
283 
284     // Two-sided material?
285     if (shader.cull == Q3Shader::CULL_NONE) {
286         const int twosided = 1;
287         out->AddProperty(&twosided,1,AI_MATKEY_TWOSIDED);
288     }
289 
290     unsigned int cur_emissive = 0, cur_diffuse = 0, cur_lm =0;
291 
292     // Iterate through all textures
293     for (std::list< Q3Shader::ShaderMapBlock >::const_iterator it = shader.maps.begin(); it != shader.maps.end();++it) {
294 
295         // CONVERSION BEHAVIOUR:
296         //
297         //
298         // If the texture is additive
299         //  - if it is the first texture, assume additive blending for the whole material
300         //  - otherwise register it as emissive texture.
301         //
302         // If the texture is using standard blend (or if the blend mode is unknown)
303         //  - if first texture: assume default blending for material
304         //  - in any case: set it as diffuse texture
305         //
306         // If the texture is using 'filter' blending
307         //  - take as lightmap
308         //
309         // Textures with alpha funcs
310         //  - aiTextureFlags_UseAlpha is set (otherwise aiTextureFlags_NoAlpha is explicitly set)
311         aiString s((*it).name);
312         aiTextureType type; unsigned int index;
313 
314         if ((*it).blend_src == Q3Shader::BLEND_GL_ONE && (*it).blend_dest == Q3Shader::BLEND_GL_ONE) {
315             if (it == shader.maps.begin()) {
316                 const int additive = aiBlendMode_Additive;
317                 out->AddProperty(&additive,1,AI_MATKEY_BLEND_FUNC);
318 
319                 index = cur_diffuse++;
320                 type  = aiTextureType_DIFFUSE;
321             }
322             else {
323                 index = cur_emissive++;
324                 type  = aiTextureType_EMISSIVE;
325             }
326         }
327         else if ((*it).blend_src == Q3Shader::BLEND_GL_DST_COLOR && (*it).blend_dest == Q3Shader::BLEND_GL_ZERO) {
328             index = cur_lm++;
329             type  = aiTextureType_LIGHTMAP;
330         }
331         else {
332             const int blend = aiBlendMode_Default;
333             out->AddProperty(&blend,1,AI_MATKEY_BLEND_FUNC);
334 
335             index = cur_diffuse++;
336             type  = aiTextureType_DIFFUSE;
337         }
338 
339         // setup texture
340         out->AddProperty(&s,AI_MATKEY_TEXTURE(type,index));
341 
342         // setup texture flags
343         const int use_alpha = ((*it).alpha_test != Q3Shader::AT_NONE ? aiTextureFlags_UseAlpha : aiTextureFlags_IgnoreAlpha);
344         out->AddProperty(&use_alpha,1,AI_MATKEY_TEXFLAGS(type,index));
345     }
346     // If at least one emissive texture was set, set the emissive base color to 1 to ensure
347     // the texture is actually displayed.
348     if (0 != cur_emissive) {
349         aiColor3D one(1.f,1.f,1.f);
350         out->AddProperty(&one,1,AI_MATKEY_COLOR_EMISSIVE);
351     }
352 }
353 
354 // ------------------------------------------------------------------------------------------------
355 // Constructor to be privately used by Importer
MD3Importer()356 MD3Importer::MD3Importer()
357     : configFrameID  (0)
358     , configHandleMP (true)
359     , configSpeedFlag()
360     , pcHeader()
361     , mBuffer()
362     , fileSize()
363     , mScene()
364     , mIOHandler()
365 {}
366 
367 // ------------------------------------------------------------------------------------------------
368 // Destructor, private as well
~MD3Importer()369 MD3Importer::~MD3Importer()
370 {}
371 
372 // ------------------------------------------------------------------------------------------------
373 // Returns whether the class can handle the format of the given file.
CanRead(const std::string & pFile,IOSystem * pIOHandler,bool checkSig) const374 bool MD3Importer::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
375 {
376     const std::string extension = GetExtension(pFile);
377     if (extension == "md3")
378         return true;
379 
380     // if check for extension is not enough, check for the magic tokens
381     if (!extension.length() || checkSig) {
382         uint32_t tokens[1];
383         tokens[0] = AI_MD3_MAGIC_NUMBER_LE;
384         return CheckMagicToken(pIOHandler,pFile,tokens,1);
385     }
386     return false;
387 }
388 
389 // ------------------------------------------------------------------------------------------------
ValidateHeaderOffsets()390 void MD3Importer::ValidateHeaderOffsets()
391 {
392     // Check magic number
393     if (pcHeader->IDENT != AI_MD3_MAGIC_NUMBER_BE &&
394         pcHeader->IDENT != AI_MD3_MAGIC_NUMBER_LE)
395             throw DeadlyImportError( "Invalid MD3 file: Magic bytes not found");
396 
397     // Check file format version
398     if (pcHeader->VERSION > 15)
399         DefaultLogger::get()->warn( "Unsupported MD3 file version. Continuing happily ...");
400 
401     // Check some offset values whether they are valid
402     if (!pcHeader->NUM_SURFACES)
403         throw DeadlyImportError( "Invalid md3 file: NUM_SURFACES is 0");
404 
405     if (pcHeader->OFS_FRAMES >= fileSize || pcHeader->OFS_SURFACES >= fileSize ||
406         pcHeader->OFS_EOF > fileSize) {
407         throw DeadlyImportError("Invalid MD3 header: some offsets are outside the file");
408     }
409 
410 	if (pcHeader->NUM_SURFACES > AI_MAX_ALLOC(MD3::Surface)) {
411         throw DeadlyImportError("Invalid MD3 header: too many surfaces, would overflow");
412 	}
413 
414     if (pcHeader->OFS_SURFACES + pcHeader->NUM_SURFACES * sizeof(MD3::Surface) >= fileSize) {
415         throw DeadlyImportError("Invalid MD3 header: some surfaces are outside the file");
416     }
417 
418     if (pcHeader->NUM_FRAMES <= configFrameID )
419         throw DeadlyImportError("The requested frame is not existing the file");
420 }
421 
422 // ------------------------------------------------------------------------------------------------
ValidateSurfaceHeaderOffsets(const MD3::Surface * pcSurf)423 void MD3Importer::ValidateSurfaceHeaderOffsets(const MD3::Surface* pcSurf)
424 {
425     // Calculate the relative offset of the surface
426     const int32_t ofs = int32_t((const unsigned char*)pcSurf-this->mBuffer);
427 
428     // Check whether all data chunks are inside the valid range
429     if (pcSurf->OFS_TRIANGLES + ofs + pcSurf->NUM_TRIANGLES * sizeof(MD3::Triangle) > fileSize  ||
430         pcSurf->OFS_SHADERS + ofs + pcSurf->NUM_SHADER * sizeof(MD3::Shader) > fileSize         ||
431         pcSurf->OFS_ST + ofs + pcSurf->NUM_VERTICES * sizeof(MD3::TexCoord) > fileSize          ||
432         pcSurf->OFS_XYZNORMAL + ofs + pcSurf->NUM_VERTICES * sizeof(MD3::Vertex) > fileSize)    {
433 
434         throw DeadlyImportError("Invalid MD3 surface header: some offsets are outside the file");
435     }
436 
437     // Check whether all requirements for Q3 files are met. We don't
438     // care, but probably someone does.
439     if (pcSurf->NUM_TRIANGLES > AI_MD3_MAX_TRIANGLES) {
440         DefaultLogger::get()->warn("MD3: Quake III triangle limit exceeded");
441     }
442 
443     if (pcSurf->NUM_SHADER > AI_MD3_MAX_SHADERS) {
444         DefaultLogger::get()->warn("MD3: Quake III shader limit exceeded");
445     }
446 
447     if (pcSurf->NUM_VERTICES > AI_MD3_MAX_VERTS) {
448         DefaultLogger::get()->warn("MD3: Quake III vertex limit exceeded");
449     }
450 
451     if (pcSurf->NUM_FRAMES > AI_MD3_MAX_FRAMES) {
452         DefaultLogger::get()->warn("MD3: Quake III frame limit exceeded");
453     }
454 }
455 
456 // ------------------------------------------------------------------------------------------------
GetInfo() const457 const aiImporterDesc* MD3Importer::GetInfo () const
458 {
459     return &desc;
460 }
461 
462 // ------------------------------------------------------------------------------------------------
463 // Setup configuration properties
SetupProperties(const Importer * pImp)464 void MD3Importer::SetupProperties(const Importer* pImp)
465 {
466     // The
467     // AI_CONFIG_IMPORT_MD3_KEYFRAME option overrides the
468     // AI_CONFIG_IMPORT_GLOBAL_KEYFRAME option.
469     configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MD3_KEYFRAME,-1);
470     if(static_cast<unsigned int>(-1) == configFrameID) {
471         configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_GLOBAL_KEYFRAME,0);
472     }
473 
474     // AI_CONFIG_IMPORT_MD3_HANDLE_MULTIPART
475     configHandleMP = (0 != pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MD3_HANDLE_MULTIPART,1));
476 
477     // AI_CONFIG_IMPORT_MD3_SKIN_NAME
478     configSkinFile = (pImp->GetPropertyString(AI_CONFIG_IMPORT_MD3_SKIN_NAME,"default"));
479 
480     // AI_CONFIG_IMPORT_MD3_SHADER_SRC
481     configShaderFile = (pImp->GetPropertyString(AI_CONFIG_IMPORT_MD3_SHADER_SRC,""));
482 
483     // AI_CONFIG_FAVOUR_SPEED
484     configSpeedFlag = (0 != pImp->GetPropertyInteger(AI_CONFIG_FAVOUR_SPEED,0));
485 }
486 
487 // ------------------------------------------------------------------------------------------------
488 // Try to read the skin for a MD3 file
ReadSkin(Q3Shader::SkinData & fill) const489 void MD3Importer::ReadSkin(Q3Shader::SkinData& fill) const
490 {
491     // skip any postfixes (e.g. lower_1.md3)
492     std::string::size_type s = filename.find_last_of('_');
493     if (s == std::string::npos) {
494         s = filename.find_last_of('.');
495         if (s == std::string::npos) {
496             s = filename.size();
497         }
498     }
499     ai_assert(s != std::string::npos);
500 
501     const std::string skin_file = path + filename.substr(0,s) + "_" + configSkinFile + ".skin";
502     Q3Shader::LoadSkin(fill,skin_file,mIOHandler);
503 }
504 
505 // ------------------------------------------------------------------------------------------------
506 // Try to read the shader for a MD3 file
ReadShader(Q3Shader::ShaderData & fill) const507 void MD3Importer::ReadShader(Q3Shader::ShaderData& fill) const
508 {
509     // Determine Q3 model name from given path
510     const std::string::size_type s = path.find_last_of("\\/",path.length()-2);
511     const std::string model_file = path.substr(s+1,path.length()-(s+2));
512 
513     // If no specific dir or file is given, use our default search behaviour
514     if (!configShaderFile.length()) {
515         if(!Q3Shader::LoadShader(fill,path + "..\\..\\..\\scripts\\" + model_file + ".shader",mIOHandler)) {
516             Q3Shader::LoadShader(fill,path + "..\\..\\..\\scripts\\" + filename + ".shader",mIOHandler);
517         }
518     }
519     else {
520         // If the given string specifies a file, load this file.
521         // Otherwise it's a directory.
522         const std::string::size_type st = configShaderFile.find_last_of('.');
523         if (st == std::string::npos) {
524 
525             if(!Q3Shader::LoadShader(fill,configShaderFile + model_file + ".shader",mIOHandler)) {
526                 Q3Shader::LoadShader(fill,configShaderFile + filename + ".shader",mIOHandler);
527             }
528         }
529         else {
530             Q3Shader::LoadShader(fill,configShaderFile,mIOHandler);
531         }
532     }
533 }
534 
535 // ------------------------------------------------------------------------------------------------
536 // Tiny helper to remove a single node from its parent' list
RemoveSingleNodeFromList(aiNode * nd)537 void RemoveSingleNodeFromList(aiNode* nd)
538 {
539     if (!nd || nd->mNumChildren || !nd->mParent)return;
540     aiNode* par = nd->mParent;
541     for (unsigned int i = 0; i < par->mNumChildren;++i) {
542         if (par->mChildren[i] == nd) {
543             --par->mNumChildren;
544             for (;i < par->mNumChildren;++i) {
545                 par->mChildren[i] = par->mChildren[i+1];
546             }
547             delete nd;
548             break;
549         }
550     }
551 }
552 
553 // ------------------------------------------------------------------------------------------------
554 // Read a multi-part Q3 player model
ReadMultipartFile()555 bool MD3Importer::ReadMultipartFile()
556 {
557     // check whether the file name contains a common postfix, e.g lower_2.md3
558     std::string::size_type s = filename.find_last_of('_'), t = filename.find_last_of('.');
559 
560     if (t == std::string::npos)
561         t = filename.size();
562     if (s == std::string::npos)
563         s = t;
564 
565     const std::string mod_filename = filename.substr(0,s);
566     const std::string suffix = filename.substr(s,t-s);
567 
568     if (mod_filename == "lower" || mod_filename == "upper" || mod_filename == "head"){
569         const std::string lower = path + "lower" + suffix + ".md3";
570         const std::string upper = path + "upper" + suffix + ".md3";
571         const std::string head  = path + "head"  + suffix + ".md3";
572 
573         aiScene* scene_upper = NULL;
574         aiScene* scene_lower = NULL;
575         aiScene* scene_head = NULL;
576         std::string failure;
577 
578         aiNode* tag_torso, *tag_head;
579         std::vector<AttachmentInfo> attach;
580 
581         DefaultLogger::get()->info("Multi part MD3 player model: lower, upper and head parts are joined");
582 
583         // ensure we won't try to load ourselves recursively
584         BatchLoader::PropertyMap props;
585         SetGenericProperty( props.ints, AI_CONFIG_IMPORT_MD3_HANDLE_MULTIPART, 0);
586 
587         // now read these three files
588         BatchLoader batch(mIOHandler);
589         const unsigned int _lower = batch.AddLoadRequest(lower,0,&props);
590         const unsigned int _upper = batch.AddLoadRequest(upper,0,&props);
591         const unsigned int _head  = batch.AddLoadRequest(head,0,&props);
592         batch.LoadAll();
593 
594         // now construct a dummy scene to place these three parts in
595         aiScene* master   = new aiScene();
596         aiNode* nd = master->mRootNode = new aiNode();
597         nd->mName.Set("<MD3_Player>");
598 
599         // ... and get them. We need all of them.
600         scene_lower = batch.GetImport(_lower);
601         if (!scene_lower) {
602             DefaultLogger::get()->error("M3D: Failed to read multi part model, lower.md3 fails to load");
603             failure = "lower";
604             goto error_cleanup;
605         }
606 
607         scene_upper = batch.GetImport(_upper);
608         if (!scene_upper) {
609             DefaultLogger::get()->error("M3D: Failed to read multi part model, upper.md3 fails to load");
610             failure = "upper";
611             goto error_cleanup;
612         }
613 
614         scene_head  = batch.GetImport(_head);
615         if (!scene_head) {
616             DefaultLogger::get()->error("M3D: Failed to read multi part model, head.md3 fails to load");
617             failure = "head";
618             goto error_cleanup;
619         }
620 
621         // build attachment infos. search for typical Q3 tags
622 
623         // original root
624         scene_lower->mRootNode->mName.Set("lower");
625         attach.push_back(AttachmentInfo(scene_lower, nd));
626 
627         // tag_torso
628         tag_torso = scene_lower->mRootNode->FindNode("tag_torso");
629         if (!tag_torso) {
630             DefaultLogger::get()->error("M3D: Failed to find attachment tag for multi part model: tag_torso expected");
631             goto error_cleanup;
632         }
633         scene_upper->mRootNode->mName.Set("upper");
634         attach.push_back(AttachmentInfo(scene_upper,tag_torso));
635 
636         // tag_head
637         tag_head = scene_upper->mRootNode->FindNode("tag_head");
638         if (!tag_head) {
639             DefaultLogger::get()->error("M3D: Failed to find attachment tag for multi part model: tag_head expected");
640             goto error_cleanup;
641         }
642         scene_head->mRootNode->mName.Set("head");
643         attach.push_back(AttachmentInfo(scene_head,tag_head));
644 
645         // Remove tag_head and tag_torso from all other model parts ...
646         // this ensures (together with AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY)
647         // that tag_torso/tag_head is also the name of the (unique) output node
648         RemoveSingleNodeFromList (scene_upper->mRootNode->FindNode("tag_torso"));
649         RemoveSingleNodeFromList (scene_head-> mRootNode->FindNode("tag_head" ));
650 
651         // Undo the rotations which we applied to the coordinate systems. We're
652         // working in global Quake space here
653         scene_head->mRootNode->mTransformation  = aiMatrix4x4();
654         scene_lower->mRootNode->mTransformation = aiMatrix4x4();
655         scene_upper->mRootNode->mTransformation = aiMatrix4x4();
656 
657         // and merge the scenes
658         SceneCombiner::MergeScenes(&mScene,master, attach,
659             AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES          |
660             AI_INT_MERGE_SCENE_GEN_UNIQUE_MATNAMES       |
661             AI_INT_MERGE_SCENE_RESOLVE_CROSS_ATTACHMENTS |
662             (!configSpeedFlag ? AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY : 0));
663 
664         // Now rotate the whole scene 90 degrees around the x axis to convert to internal coordinate system
665         mScene->mRootNode->mTransformation = aiMatrix4x4(1.f,0.f,0.f,0.f,
666             0.f,0.f,1.f,0.f,0.f,-1.f,0.f,0.f,0.f,0.f,0.f,1.f);
667 
668         return true;
669 
670 error_cleanup:
671         delete scene_upper;
672         delete scene_lower;
673         delete scene_head;
674         delete master;
675 
676         if (failure == mod_filename) {
677             throw DeadlyImportError("MD3: failure to read multipart host file");
678         }
679     }
680     return false;
681 }
682 
683 // ------------------------------------------------------------------------------------------------
684 // Convert a MD3 path to a proper value
ConvertPath(const char * texture_name,const char * header_name,std::string & out) const685 void MD3Importer::ConvertPath(const char* texture_name, const char* header_name, std::string& out) const
686 {
687     // If the MD3's internal path itself and the given path are using
688     // the same directory, remove it completely to get right output paths.
689     const char* end1 = ::strrchr(header_name,'\\');
690     if (!end1)end1   = ::strrchr(header_name,'/');
691 
692     const char* end2 = ::strrchr(texture_name,'\\');
693     if (!end2)end2   = ::strrchr(texture_name,'/');
694 
695     // HACK: If the paths starts with "models", ignore the
696     // next two hierarchy levels, it specifies just the model name.
697     // Ignored by Q3, it might be not equal to the real model location.
698     if (end2)   {
699 
700         size_t len2;
701         const size_t len1 = (size_t)(end1 - header_name);
702         if (!ASSIMP_strincmp(texture_name,"models",6) && (texture_name[6] == '/' || texture_name[6] == '\\')) {
703             len2 = 6; // ignore the seventh - could be slash or backslash
704 
705             if (!header_name[0]) {
706                 // Use the file name only
707                 out = end2+1;
708                 return;
709             }
710         }
711         else len2 = std::min (len1, (size_t)(end2 - texture_name ));
712         if (!ASSIMP_strincmp(texture_name,header_name,len2)) {
713             // Use the file name only
714             out = end2+1;
715             return;
716         }
717     }
718     // Use the full path
719     out = texture_name;
720 }
721 
722 // ------------------------------------------------------------------------------------------------
723 // Imports the given file into the given scene structure.
InternReadFile(const std::string & pFile,aiScene * pScene,IOSystem * pIOHandler)724 void MD3Importer::InternReadFile( const std::string& pFile,
725     aiScene* pScene, IOSystem* pIOHandler)
726 {
727     mFile = pFile;
728     mScene = pScene;
729     mIOHandler = pIOHandler;
730 
731     // get base path and file name
732     // todo ... move to PathConverter
733     std::string::size_type s = mFile.find_last_of("/\\");
734     if (s == std::string::npos) {
735         s = 0;
736     }
737     else ++s;
738     filename = mFile.substr(s), path = mFile.substr(0,s);
739     for( std::string::iterator it = filename .begin(); it != filename.end(); ++it)
740         *it = tolower( *it);
741 
742     // Load multi-part model file, if necessary
743     if (configHandleMP) {
744         if (ReadMultipartFile())
745             return;
746     }
747 
748     std::unique_ptr<IOStream> file( pIOHandler->Open( pFile));
749 
750     // Check whether we can read from the file
751     if( file.get() == NULL)
752         throw DeadlyImportError( "Failed to open MD3 file " + pFile + ".");
753 
754     // Check whether the md3 file is large enough to contain the header
755     fileSize = (unsigned int)file->FileSize();
756     if( fileSize < sizeof(MD3::Header))
757         throw DeadlyImportError( "MD3 File is too small.");
758 
759     // Allocate storage and copy the contents of the file to a memory buffer
760     std::vector<unsigned char> mBuffer2 (fileSize);
761     file->Read( &mBuffer2[0], 1, fileSize);
762     mBuffer = &mBuffer2[0];
763 
764     pcHeader = (BE_NCONST MD3::Header*)mBuffer;
765 
766     // Ensure correct endianness
767 #ifdef AI_BUILD_BIG_ENDIAN
768 
769     AI_SWAP4(pcHeader->VERSION);
770     AI_SWAP4(pcHeader->FLAGS);
771     AI_SWAP4(pcHeader->IDENT);
772     AI_SWAP4(pcHeader->NUM_FRAMES);
773     AI_SWAP4(pcHeader->NUM_SKINS);
774     AI_SWAP4(pcHeader->NUM_SURFACES);
775     AI_SWAP4(pcHeader->NUM_TAGS);
776     AI_SWAP4(pcHeader->OFS_EOF);
777     AI_SWAP4(pcHeader->OFS_FRAMES);
778     AI_SWAP4(pcHeader->OFS_SURFACES);
779     AI_SWAP4(pcHeader->OFS_TAGS);
780 
781 #endif
782 
783     // Validate the file header
784     ValidateHeaderOffsets();
785 
786     // Navigate to the list of surfaces
787     BE_NCONST MD3::Surface* pcSurfaces = (BE_NCONST MD3::Surface*)(mBuffer + pcHeader->OFS_SURFACES);
788 
789     // Navigate to the list of tags
790     BE_NCONST MD3::Tag* pcTags = (BE_NCONST MD3::Tag*)(mBuffer + pcHeader->OFS_TAGS);
791 
792     // Allocate output storage
793     pScene->mNumMeshes = pcHeader->NUM_SURFACES;
794     if (pcHeader->NUM_SURFACES == 0) {
795         throw DeadlyImportError("MD3: No surfaces");
796     } else if (pcHeader->NUM_SURFACES > AI_MAX_ALLOC(aiMesh)) {
797         // We allocate pointers but check against the size of aiMesh
798         // since those pointers will eventually have to point to real objects
799         throw DeadlyImportError("MD3: Too many surfaces, would run out of memory");
800     }
801     pScene->mMeshes = new aiMesh*[pScene->mNumMeshes];
802 
803     pScene->mNumMaterials = pcHeader->NUM_SURFACES;
804     pScene->mMaterials = new aiMaterial*[pScene->mNumMeshes];
805 
806     // Set arrays to zero to ensue proper destruction if an exception is raised
807     ::memset(pScene->mMeshes,0,pScene->mNumMeshes*sizeof(aiMesh*));
808     ::memset(pScene->mMaterials,0,pScene->mNumMaterials*sizeof(aiMaterial*));
809 
810     // Now read possible skins from .skin file
811     Q3Shader::SkinData skins;
812     ReadSkin(skins);
813 
814     // And check whether we can locate a shader file for this model
815     Q3Shader::ShaderData shaders;
816     ReadShader(shaders);
817 
818     // Adjust all texture paths in the shader
819     const char* header_name = pcHeader->NAME;
820     if (!shaders.blocks.empty()) {
821         for (std::list< Q3Shader::ShaderDataBlock >::iterator dit = shaders.blocks.begin(); dit != shaders.blocks.end(); ++dit) {
822             ConvertPath((*dit).name.c_str(),header_name,(*dit).name);
823 
824             for (std::list< Q3Shader::ShaderMapBlock >::iterator mit = (*dit).maps.begin(); mit != (*dit).maps.end(); ++mit) {
825                 ConvertPath((*mit).name.c_str(),header_name,(*mit).name);
826             }
827         }
828     }
829 
830     // Read all surfaces from the file
831     unsigned int iNum = pcHeader->NUM_SURFACES;
832     unsigned int iNumMaterials = 0;
833     while (iNum-- > 0)  {
834 
835         // Ensure correct endianness
836 #ifdef AI_BUILD_BIG_ENDIAN
837 
838         AI_SWAP4(pcSurfaces->FLAGS);
839         AI_SWAP4(pcSurfaces->IDENT);
840         AI_SWAP4(pcSurfaces->NUM_FRAMES);
841         AI_SWAP4(pcSurfaces->NUM_SHADER);
842         AI_SWAP4(pcSurfaces->NUM_TRIANGLES);
843         AI_SWAP4(pcSurfaces->NUM_VERTICES);
844         AI_SWAP4(pcSurfaces->OFS_END);
845         AI_SWAP4(pcSurfaces->OFS_SHADERS);
846         AI_SWAP4(pcSurfaces->OFS_ST);
847         AI_SWAP4(pcSurfaces->OFS_TRIANGLES);
848         AI_SWAP4(pcSurfaces->OFS_XYZNORMAL);
849 
850 #endif
851 
852         // Validate the surface header
853         ValidateSurfaceHeaderOffsets(pcSurfaces);
854 
855         // Navigate to the vertex list of the surface
856         BE_NCONST MD3::Vertex* pcVertices = (BE_NCONST MD3::Vertex*)
857             (((uint8_t*)pcSurfaces) + pcSurfaces->OFS_XYZNORMAL);
858 
859         // Navigate to the triangle list of the surface
860         BE_NCONST MD3::Triangle* pcTriangles = (BE_NCONST MD3::Triangle*)
861             (((uint8_t*)pcSurfaces) + pcSurfaces->OFS_TRIANGLES);
862 
863         // Navigate to the texture coordinate list of the surface
864         BE_NCONST MD3::TexCoord* pcUVs = (BE_NCONST MD3::TexCoord*)
865             (((uint8_t*)pcSurfaces) + pcSurfaces->OFS_ST);
866 
867         // Navigate to the shader list of the surface
868         BE_NCONST MD3::Shader* pcShaders = (BE_NCONST MD3::Shader*)
869             (((uint8_t*)pcSurfaces) + pcSurfaces->OFS_SHADERS);
870 
871         // If the submesh is empty ignore it
872         if (0 == pcSurfaces->NUM_VERTICES || 0 == pcSurfaces->NUM_TRIANGLES)
873         {
874             pcSurfaces = (BE_NCONST MD3::Surface*)(((uint8_t*)pcSurfaces) + pcSurfaces->OFS_END);
875             pScene->mNumMeshes--;
876             continue;
877         }
878 
879         // Allocate output mesh
880         pScene->mMeshes[iNum] = new aiMesh();
881         aiMesh* pcMesh = pScene->mMeshes[iNum];
882 
883         std::string _texture_name;
884         const char* texture_name = NULL;
885 
886         // Check whether we have a texture record for this surface in the .skin file
887         std::list< Q3Shader::SkinData::TextureEntry >::iterator it = std::find(
888             skins.textures.begin(), skins.textures.end(), pcSurfaces->NAME );
889 
890         if (it != skins.textures.end()) {
891             texture_name = &*( _texture_name = (*it).second).begin();
892             DefaultLogger::get()->debug("MD3: Assigning skin texture " + (*it).second + " to surface " + pcSurfaces->NAME);
893             (*it).resolved = true; // mark entry as resolved
894         }
895 
896         // Get the first shader (= texture?) assigned to the surface
897         if (!texture_name && pcSurfaces->NUM_SHADER)    {
898             texture_name = pcShaders->NAME;
899         }
900 
901         std::string convertedPath;
902         if (texture_name) {
903             ConvertPath(texture_name,header_name,convertedPath);
904         }
905 
906         const Q3Shader::ShaderDataBlock* shader = NULL;
907 
908         // Now search the current shader for a record with this name (
909         // excluding texture file extension)
910         if (!shaders.blocks.empty()) {
911 
912             std::string::size_type s = convertedPath.find_last_of('.');
913             if (s == std::string::npos)
914                 s = convertedPath.length();
915 
916             const std::string without_ext = convertedPath.substr(0,s);
917             std::list< Q3Shader::ShaderDataBlock >::const_iterator dit = std::find(shaders.blocks.begin(),shaders.blocks.end(),without_ext);
918             if (dit != shaders.blocks.end()) {
919                 // Hurra, wir haben einen. Tolle Sache.
920                 shader = &*dit;
921                 DefaultLogger::get()->info("Found shader record for " +without_ext );
922             }
923             else DefaultLogger::get()->warn("Unable to find shader record for " +without_ext );
924         }
925 
926         aiMaterial* pcHelper = new aiMaterial();
927 
928         const int iMode = (int)aiShadingMode_Gouraud;
929         pcHelper->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL);
930 
931         // Add a small ambient color value - Quake 3 seems to have one
932         aiColor3D clr;
933         clr.b = clr.g = clr.r = 0.05f;
934         pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_AMBIENT);
935 
936         clr.b = clr.g = clr.r = 1.0f;
937         pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_DIFFUSE);
938         pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_SPECULAR);
939 
940         // use surface name + skin_name as material name
941         aiString name;
942         name.Set("MD3_[" + configSkinFile + "][" + pcSurfaces->NAME + "]");
943         pcHelper->AddProperty(&name,AI_MATKEY_NAME);
944 
945         if (!shader) {
946             // Setup dummy texture file name to ensure UV coordinates are kept during postprocessing
947             aiString szString;
948             if (convertedPath.length()) {
949                 szString.Set(convertedPath);
950             }
951             else    {
952                 DefaultLogger::get()->warn("Texture file name has zero length. Using default name");
953                 szString.Set("dummy_texture.bmp");
954             }
955             pcHelper->AddProperty(&szString,AI_MATKEY_TEXTURE_DIFFUSE(0));
956 
957             // prevent transparency by default
958             int no_alpha = aiTextureFlags_IgnoreAlpha;
959             pcHelper->AddProperty(&no_alpha,1,AI_MATKEY_TEXFLAGS_DIFFUSE(0));
960         }
961         else {
962             Q3Shader::ConvertShaderToMaterial(pcHelper,*shader);
963         }
964 
965         pScene->mMaterials[iNumMaterials] = (aiMaterial*)pcHelper;
966         pcMesh->mMaterialIndex = iNumMaterials++;
967 
968             // Ensure correct endianness
969 #ifdef AI_BUILD_BIG_ENDIAN
970 
971         for (uint32_t i = 0; i < pcSurfaces->NUM_VERTICES;++i)  {
972             AI_SWAP2( pcVertices[i].NORMAL );
973             AI_SWAP2( pcVertices[i].X );
974             AI_SWAP2( pcVertices[i].Y );
975             AI_SWAP2( pcVertices[i].Z );
976 
977             AI_SWAP4( pcUVs[i].U );
978             AI_SWAP4( pcUVs[i].U );
979         }
980         for (uint32_t i = 0; i < pcSurfaces->NUM_TRIANGLES;++i) {
981             AI_SWAP4(pcTriangles[i].INDEXES[0]);
982             AI_SWAP4(pcTriangles[i].INDEXES[1]);
983             AI_SWAP4(pcTriangles[i].INDEXES[2]);
984         }
985 
986 #endif
987 
988         // Fill mesh information
989         pcMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
990 
991         pcMesh->mNumVertices        = pcSurfaces->NUM_TRIANGLES*3;
992         pcMesh->mNumFaces           = pcSurfaces->NUM_TRIANGLES;
993         pcMesh->mFaces              = new aiFace[pcSurfaces->NUM_TRIANGLES];
994         pcMesh->mNormals            = new aiVector3D[pcMesh->mNumVertices];
995         pcMesh->mVertices           = new aiVector3D[pcMesh->mNumVertices];
996         pcMesh->mTextureCoords[0]   = new aiVector3D[pcMesh->mNumVertices];
997         pcMesh->mNumUVComponents[0] = 2;
998 
999         // Fill in all triangles
1000         unsigned int iCurrent = 0;
1001         for (unsigned int i = 0; i < (unsigned int)pcSurfaces->NUM_TRIANGLES;++i)   {
1002             pcMesh->mFaces[i].mIndices = new unsigned int[3];
1003             pcMesh->mFaces[i].mNumIndices = 3;
1004 
1005             //unsigned int iTemp = iCurrent;
1006             for (unsigned int c = 0; c < 3;++c,++iCurrent)  {
1007                 pcMesh->mFaces[i].mIndices[c] = iCurrent;
1008 
1009                 // Read vertices
1010                 aiVector3D& vec = pcMesh->mVertices[iCurrent];
1011                 uint32_t index = pcTriangles->INDEXES[c];
1012                 if (index >= pcSurfaces->NUM_VERTICES) {
1013                     throw DeadlyImportError( "MD3: Invalid vertex index");
1014                 }
1015                 vec.x = pcVertices[index].X*AI_MD3_XYZ_SCALE;
1016                 vec.y = pcVertices[index].Y*AI_MD3_XYZ_SCALE;
1017                 vec.z = pcVertices[index].Z*AI_MD3_XYZ_SCALE;
1018 
1019                 // Convert the normal vector to uncompressed float3 format
1020                 aiVector3D& nor = pcMesh->mNormals[iCurrent];
1021                 LatLngNormalToVec3(pcVertices[pcTriangles->INDEXES[c]].NORMAL,(float*)&nor);
1022 
1023                 // Read texture coordinates
1024                 pcMesh->mTextureCoords[0][iCurrent].x = pcUVs[ pcTriangles->INDEXES[c]].U;
1025                 pcMesh->mTextureCoords[0][iCurrent].y = 1.0f-pcUVs[ pcTriangles->INDEXES[c]].V;
1026             }
1027             // Flip face order if necessary
1028             if (!shader || shader->cull == Q3Shader::CULL_CW) {
1029                 std::swap(pcMesh->mFaces[i].mIndices[2],pcMesh->mFaces[i].mIndices[1]);
1030             }
1031             pcTriangles++;
1032         }
1033 
1034         // Go to the next surface
1035         pcSurfaces = (BE_NCONST MD3::Surface*)(((unsigned char*)pcSurfaces) + pcSurfaces->OFS_END);
1036     }
1037 
1038     // For debugging purposes: check whether we found matches for all entries in the skins file
1039     if (!DefaultLogger::isNullLogger()) {
1040         for (std::list< Q3Shader::SkinData::TextureEntry>::const_iterator it = skins.textures.begin();it != skins.textures.end(); ++it) {
1041             if (!(*it).resolved) {
1042                 DefaultLogger::get()->error("MD3: Failed to match skin " + (*it).first + " to surface " + (*it).second);
1043             }
1044         }
1045     }
1046 
1047     if (!pScene->mNumMeshes)
1048         throw DeadlyImportError( "MD3: File contains no valid mesh");
1049     pScene->mNumMaterials = iNumMaterials;
1050 
1051     // Now we need to generate an empty node graph
1052     pScene->mRootNode = new aiNode("<MD3Root>");
1053     pScene->mRootNode->mNumMeshes = pScene->mNumMeshes;
1054     pScene->mRootNode->mMeshes = new unsigned int[pScene->mNumMeshes];
1055 
1056     // Attach tiny children for all tags
1057     if (pcHeader->NUM_TAGS) {
1058         pScene->mRootNode->mNumChildren = pcHeader->NUM_TAGS;
1059         pScene->mRootNode->mChildren = new aiNode*[pcHeader->NUM_TAGS];
1060 
1061         for (unsigned int i = 0; i < pcHeader->NUM_TAGS; ++i, ++pcTags) {
1062 
1063             aiNode* nd = pScene->mRootNode->mChildren[i] = new aiNode();
1064             nd->mName.Set((const char*)pcTags->NAME);
1065             nd->mParent = pScene->mRootNode;
1066 
1067             AI_SWAP4(pcTags->origin.x);
1068             AI_SWAP4(pcTags->origin.y);
1069             AI_SWAP4(pcTags->origin.z);
1070 
1071             // Copy local origin, again flip z,y
1072             nd->mTransformation.a4 = pcTags->origin.x;
1073             nd->mTransformation.b4 = pcTags->origin.y;
1074             nd->mTransformation.c4 = pcTags->origin.z;
1075 
1076             // Copy rest of transformation (need to transpose to match row-order matrix)
1077             for (unsigned int a = 0; a < 3;++a) {
1078                 for (unsigned int m = 0; m < 3;++m) {
1079                     nd->mTransformation[m][a] = pcTags->orientation[a][m];
1080                     AI_SWAP4(nd->mTransformation[m][a]);
1081                 }
1082             }
1083         }
1084     }
1085 
1086     for (unsigned int i = 0; i < pScene->mNumMeshes;++i)
1087         pScene->mRootNode->mMeshes[i] = i;
1088 
1089     // Now rotate the whole scene 90 degrees around the x axis to convert to internal coordinate system
1090     pScene->mRootNode->mTransformation = aiMatrix4x4(1.f,0.f,0.f,0.f,
1091         0.f,0.f,1.f,0.f,0.f,-1.f,0.f,0.f,0.f,0.f,0.f,1.f);
1092 }
1093 
1094 #endif // !! ASSIMP_BUILD_NO_MD3_IMPORTER
1095