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