vrshoot

view libs/assimp/MD3Loader.cpp @ 1:e7ca128b8713

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