Coverage Report

Created: 2025-08-26 06:41

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