Coverage Report

Created: 2025-12-05 06:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/assimp/code/AssetLib/MD2/MD2Loader.cpp
Line
Count
Source
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
#ifndef ASSIMP_BUILD_NO_MD2_IMPORTER
43
44
/** @file Implementation of the MD2 importer class */
45
#include "MD2Loader.h"
46
#include <assimp/ByteSwapper.h>
47
#include "MD2NormalTable.h" // shouldn't be included by other units
48
#include <assimp/DefaultLogger.hpp>
49
#include <assimp/Importer.hpp>
50
#include <assimp/IOSystem.hpp>
51
#include <assimp/scene.h>
52
#include <assimp/importerdesc.h>
53
#include <assimp/StringUtils.h>
54
55
#include <memory>
56
57
using namespace Assimp;
58
using namespace Assimp::MD2;
59
60
// helper macro to determine the size of an array
61
#if (!defined ARRAYSIZE)
62
0
#   define ARRAYSIZE(_array) (int(sizeof(_array) / sizeof(_array[0])))
63
#endif
64
65
static constexpr aiImporterDesc desc = {
66
    "Quake II Mesh Importer",
67
    "",
68
    "",
69
    "",
70
    aiImporterFlags_SupportBinaryFlavour,
71
    0,
72
    0,
73
    0,
74
    0,
75
    "md2"
76
};
77
78
// ------------------------------------------------------------------------------------------------
79
// Helper function to lookup a normal in Quake 2's pre-calculated table
80
void MD2::LookupNormalIndex(uint8_t iNormalIndex,aiVector3D& vOut)
81
0
{
82
    // make sure the normal index has a valid value
83
0
    if (iNormalIndex >= ARRAYSIZE(g_avNormals)) {
84
0
        ASSIMP_LOG_WARN("Index overflow in Quake II normal vector list");
85
0
        iNormalIndex = ARRAYSIZE(g_avNormals) - 1;
86
0
    }
87
0
    vOut = *((const aiVector3D*)(&g_avNormals[iNormalIndex]));
88
0
}
89
90
91
// ------------------------------------------------------------------------------------------------
92
// Constructor to be privately used by Importer
93
MD2Importer::MD2Importer()
94
    : configFrameID(),
95
    m_pcHeader(),
96
    mBuffer(),
97
    fileSize()
98
1.19k
{}
99
100
// ------------------------------------------------------------------------------------------------
101
// Returns whether the class can handle the format of the given file.
102
bool MD2Importer::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool /*checkSig*/) const
103
828
{
104
828
    static constexpr uint32_t tokens[] = { AI_MD2_MAGIC_NUMBER_LE };
105
828
    return CheckMagicToken(pIOHandler,pFile,tokens,AI_COUNT_OF(tokens));
106
828
}
107
108
// ------------------------------------------------------------------------------------------------
109
// Get a list of all extensions supported by this loader
110
const aiImporterDesc* MD2Importer::GetInfo () const
111
1.20k
{
112
1.20k
    return &desc;
113
1.20k
}
114
115
// ------------------------------------------------------------------------------------------------
116
// Setup configuration properties
117
void MD2Importer::SetupProperties(const Importer* pImp)
118
1
{
119
    // The
120
    // AI_CONFIG_IMPORT_MD2_KEYFRAME option overrides the
121
    // AI_CONFIG_IMPORT_GLOBAL_KEYFRAME option.
122
1
    configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MD2_KEYFRAME,-1);
123
1
    if(static_cast<unsigned int>(-1) == configFrameID){
124
1
        configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_GLOBAL_KEYFRAME,0);
125
1
    }
126
1
}
127
// ------------------------------------------------------------------------------------------------
128
// Validate the file header
129
void MD2Importer::ValidateHeader( )
130
1
{
131
    // check magic number
132
1
    if (m_pcHeader->magic != AI_MD2_MAGIC_NUMBER_BE &&
133
0
        m_pcHeader->magic != AI_MD2_MAGIC_NUMBER_LE)
134
0
    {
135
0
        throw DeadlyImportError("Invalid MD2 magic word: expected IDP2, found ",
136
0
                                ai_str_toprintable((char *)&m_pcHeader->magic, 4));
137
0
    }
138
139
    // check file format version
140
1
    if (m_pcHeader->version != 8)
141
1
        ASSIMP_LOG_WARN( "Unsupported MD2 file version. Continuing happily ...");
142
143
    // check some values whether they are valid
144
1
    if (0 == m_pcHeader->numFrames)
145
0
        throw DeadlyImportError( "Invalid MD2 file: NUM_FRAMES is 0");
146
147
1
    if (m_pcHeader->offsetEnd > (uint32_t)fileSize)
148
1
        throw DeadlyImportError( "Invalid MD2 file: File is too small");
149
150
0
    if (m_pcHeader->numSkins > AI_MAX_ALLOC(MD2::Skin)) {
151
0
        throw DeadlyImportError("Invalid MD2 header: Too many skins, would overflow");
152
0
    }
153
154
0
    if (m_pcHeader->numVertices > AI_MAX_ALLOC(MD2::Vertex)) {
155
0
        throw DeadlyImportError("Invalid MD2 header: Too many vertices, would overflow");
156
0
    }
157
158
0
    if (m_pcHeader->numTexCoords > AI_MAX_ALLOC(MD2::TexCoord)) {
159
0
        throw DeadlyImportError("Invalid MD2 header: Too many texcoords, would overflow");
160
0
    }
161
162
0
    if (m_pcHeader->numTriangles > AI_MAX_ALLOC(MD2::Triangle)) {
163
0
        throw DeadlyImportError("Invalid MD2 header: Too many triangles, would overflow");
164
0
    }
165
166
0
    if (m_pcHeader->numFrames > AI_MAX_ALLOC(MD2::Frame)) {
167
0
        throw DeadlyImportError("Invalid MD2 header: Too many frames, would overflow");
168
0
    }
169
170
    // -1 because Frame already contains one
171
0
    unsigned int frameSize = sizeof (MD2::Frame) + (m_pcHeader->numVertices - 1) * sizeof(MD2::Vertex);
172
173
0
    if (m_pcHeader->offsetSkins     + m_pcHeader->numSkins * sizeof (MD2::Skin)         >= fileSize ||
174
0
        m_pcHeader->offsetTexCoords + m_pcHeader->numTexCoords * sizeof (MD2::TexCoord) >= fileSize ||
175
0
        m_pcHeader->offsetTriangles + m_pcHeader->numTriangles * sizeof (MD2::Triangle) >= fileSize ||
176
0
        m_pcHeader->offsetFrames    + m_pcHeader->numFrames * frameSize                 >= fileSize ||
177
0
        m_pcHeader->offsetEnd           > fileSize)
178
0
    {
179
0
        throw DeadlyImportError("Invalid MD2 header: Some offsets are outside the file");
180
0
    }
181
182
0
    if (m_pcHeader->numSkins > AI_MD2_MAX_SKINS)
183
0
        ASSIMP_LOG_WARN("The model contains more skins than Quake 2 supports");
184
0
    if ( m_pcHeader->numFrames > AI_MD2_MAX_FRAMES)
185
0
        ASSIMP_LOG_WARN("The model contains more frames than Quake 2 supports");
186
0
    if (m_pcHeader->numVertices > AI_MD2_MAX_VERTS)
187
0
        ASSIMP_LOG_WARN("The model contains more vertices than Quake 2 supports");
188
189
0
    if (m_pcHeader->numFrames <= configFrameID )
190
0
        throw DeadlyImportError("MD2: The requested frame (", configFrameID, ") does not exist in the file");
191
0
}
192
193
// ------------------------------------------------------------------------------------------------
194
// Imports the given file into the given scene structure.
195
void MD2Importer::InternReadFile( const std::string& pFile,
196
    aiScene* pScene, IOSystem* pIOHandler)
197
1
{
198
1
    std::unique_ptr<IOStream> file( pIOHandler->Open( pFile));
199
200
    // Check whether we can read from the file
201
1
    if (file == nullptr) {
202
0
        throw DeadlyImportError("Failed to open MD2 file ", pFile, "");
203
0
    }
204
205
    // check whether the md3 file is large enough to contain
206
    // at least the file header
207
1
    fileSize = (unsigned int)file->FileSize();
208
1
    if (fileSize < sizeof(MD2::Header)) {
209
0
        throw DeadlyImportError("MD2 File is too small");
210
0
    }
211
212
1
    std::vector<uint8_t> mBuffer2(fileSize);
213
1
    file->Read(&mBuffer2[0], 1, fileSize);
214
1
    mBuffer = &mBuffer2[0];
215
216
1
    m_pcHeader = (BE_NCONST MD2::Header*)mBuffer;
217
218
#ifdef AI_BUILD_BIG_ENDIAN
219
220
    ByteSwap::Swap4(&m_pcHeader->frameSize);
221
    ByteSwap::Swap4(&m_pcHeader->magic);
222
    ByteSwap::Swap4(&m_pcHeader->numFrames);
223
    ByteSwap::Swap4(&m_pcHeader->numGlCommands);
224
    ByteSwap::Swap4(&m_pcHeader->numSkins);
225
    ByteSwap::Swap4(&m_pcHeader->numTexCoords);
226
    ByteSwap::Swap4(&m_pcHeader->numTriangles);
227
    ByteSwap::Swap4(&m_pcHeader->numVertices);
228
    ByteSwap::Swap4(&m_pcHeader->offsetEnd);
229
    ByteSwap::Swap4(&m_pcHeader->offsetFrames);
230
    ByteSwap::Swap4(&m_pcHeader->offsetGlCommands);
231
    ByteSwap::Swap4(&m_pcHeader->offsetSkins);
232
    ByteSwap::Swap4(&m_pcHeader->offsetTexCoords);
233
    ByteSwap::Swap4(&m_pcHeader->offsetTriangles);
234
    ByteSwap::Swap4(&m_pcHeader->skinHeight);
235
    ByteSwap::Swap4(&m_pcHeader->skinWidth);
236
    ByteSwap::Swap4(&m_pcHeader->version);
237
238
#endif
239
240
1
    ValidateHeader();
241
242
    // there won't be more than one mesh inside the file
243
1
    pScene->mNumMaterials = 1;
244
1
    pScene->mRootNode = new aiNode();
245
1
    pScene->mRootNode->mNumMeshes = 1;
246
1
    pScene->mRootNode->mMeshes = new unsigned int[1];
247
1
    pScene->mRootNode->mMeshes[0] = 0;
248
1
    pScene->mMaterials = new aiMaterial*[1];
249
1
    pScene->mMaterials[0] = new aiMaterial();
250
1
    pScene->mNumMeshes = 1;
251
1
    pScene->mMeshes = new aiMesh*[1];
252
253
1
    aiMesh* pcMesh = pScene->mMeshes[0] = new aiMesh();
254
1
    pcMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
255
256
    // navigate to the begin of the current frame data
257
1
  BE_NCONST MD2::Frame* pcFrame = (BE_NCONST MD2::Frame*) ((uint8_t*)
258
1
    m_pcHeader + m_pcHeader->offsetFrames + (m_pcHeader->frameSize * configFrameID));
259
260
    // navigate to the begin of the triangle data
261
1
    MD2::Triangle* pcTriangles = (MD2::Triangle*) ((uint8_t*)
262
1
        m_pcHeader + m_pcHeader->offsetTriangles);
263
264
    // navigate to the begin of the tex coords data
265
1
    BE_NCONST MD2::TexCoord* pcTexCoords = (BE_NCONST MD2::TexCoord*) ((uint8_t*)
266
1
        m_pcHeader + m_pcHeader->offsetTexCoords);
267
268
    // navigate to the begin of the vertex data
269
1
    BE_NCONST MD2::Vertex* pcVerts = (BE_NCONST MD2::Vertex*) (pcFrame->vertices);
270
271
#ifdef AI_BUILD_BIG_ENDIAN
272
    for (uint32_t i = 0; i< m_pcHeader->numTriangles; ++i)
273
    {
274
        for (unsigned int p = 0; p < 3;++p)
275
        {
276
            ByteSwap::Swap2(& pcTriangles[i].textureIndices[p]);
277
            ByteSwap::Swap2(& pcTriangles[i].vertexIndices[p]);
278
        }
279
    }
280
    for (uint32_t i = 0; i < m_pcHeader->offsetTexCoords;++i)
281
    {
282
        ByteSwap::Swap2(& pcTexCoords[i].s);
283
        ByteSwap::Swap2(& pcTexCoords[i].t);
284
    }
285
    ByteSwap::Swap4( & pcFrame->scale[0] );
286
    ByteSwap::Swap4( & pcFrame->scale[1] );
287
    ByteSwap::Swap4( & pcFrame->scale[2] );
288
    ByteSwap::Swap4( & pcFrame->translate[0] );
289
    ByteSwap::Swap4( & pcFrame->translate[1] );
290
    ByteSwap::Swap4( & pcFrame->translate[2] );
291
#endif
292
293
1
    pcMesh->mNumFaces = m_pcHeader->numTriangles;
294
1
    pcMesh->mFaces = new aiFace[m_pcHeader->numTriangles];
295
296
    // allocate output storage
297
1
    pcMesh->mNumVertices = (unsigned int)pcMesh->mNumFaces*3;
298
1
    pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices];
299
1
    pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices];
300
301
    // Not sure whether there are MD2 files without texture coordinates
302
    // NOTE: texture coordinates can be there without a texture,
303
    // but a texture can't be there without a valid UV channel
304
1
    aiMaterial* pcHelper = (aiMaterial*)pScene->mMaterials[0];
305
1
    const int iMode = (int)aiShadingMode_Gouraud;
306
1
    pcHelper->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL);
307
308
1
    if (m_pcHeader->numTexCoords && m_pcHeader->numSkins)
309
0
    {
310
        // navigate to the first texture associated with the mesh
311
0
        const MD2::Skin* pcSkins = (const MD2::Skin*) ((unsigned char*)m_pcHeader +
312
0
            m_pcHeader->offsetSkins);
313
314
0
        aiColor3D clr;
315
0
        clr.b = clr.g = clr.r = 1.0f;
316
0
        pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_DIFFUSE);
317
0
        pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_SPECULAR);
318
319
0
        clr.b = clr.g = clr.r = 0.05f;
320
0
        pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_AMBIENT);
321
322
0
        const ai_uint32 MaxNameLength = AI_MAXLEN - 1; // one byte reserved for \0
323
0
        ai_uint32 iLen = static_cast<ai_uint32>(::strlen(pcSkins->name));
324
0
        bool nameTooLong = iLen > MaxNameLength;
325
326
0
        if (pcSkins->name[0] && !nameTooLong)
327
0
        {
328
0
            aiString szString;
329
0
            ::memcpy(szString.data, pcSkins->name, iLen);
330
0
            szString.data[iLen] = '\0';
331
0
            szString.length = iLen;
332
333
0
            pcHelper->AddProperty(&szString,AI_MATKEY_TEXTURE_DIFFUSE(0));
334
0
        }
335
0
        else if (nameTooLong) {
336
0
            ASSIMP_LOG_WARN("Texture file name is too long. It will be skipped.");
337
0
        }
338
0
        else{
339
0
            ASSIMP_LOG_WARN("Texture file name has zero length. It will be skipped.");
340
0
        }
341
0
    }
342
1
    else    {
343
        // apply a default material
344
1
        aiColor3D clr;
345
1
        clr.b = clr.g = clr.r = 0.6f;
346
1
        pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_DIFFUSE);
347
1
        pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_SPECULAR);
348
349
1
        clr.b = clr.g = clr.r = 0.05f;
350
1
        pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_AMBIENT);
351
352
1
        aiString szName;
353
1
        szName.Set(AI_DEFAULT_MATERIAL_NAME);
354
1
        pcHelper->AddProperty(&szName,AI_MATKEY_NAME);
355
356
1
        aiString sz;
357
358
        // TODO: Try to guess the name of the texture file from the model file name
359
360
1
        sz.Set("$texture_dummy.bmp");
361
1
        pcHelper->AddProperty(&sz,AI_MATKEY_TEXTURE_DIFFUSE(0));
362
1
    }
363
364
365
    // now read all triangles of the first frame, apply scaling and translation
366
1
    unsigned int iCurrent = 0;
367
368
1
    float fDivisorU = 1.0f,fDivisorV = 1.0f;
369
1
    if (m_pcHeader->numTexCoords)   {
370
        // allocate storage for texture coordinates, too
371
0
        pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices];
372
0
        pcMesh->mNumUVComponents[0] = 2;
373
374
        // check whether the skin width or height are zero (this would
375
        // cause a division through zero)
376
0
        if (!m_pcHeader->skinWidth) {
377
0
            ASSIMP_LOG_ERROR("MD2: No valid skin width given");
378
0
        }
379
0
        else fDivisorU = (float)m_pcHeader->skinWidth;
380
0
        if (!m_pcHeader->skinHeight){
381
0
            ASSIMP_LOG_ERROR("MD2: No valid skin height given");
382
0
        }
383
0
        else fDivisorV = (float)m_pcHeader->skinHeight;
384
0
    }
385
386
1
    for (unsigned int i = 0; i < (unsigned int)m_pcHeader->numTriangles;++i)    {
387
        // Allocate the face
388
0
        pScene->mMeshes[0]->mFaces[i].mIndices = new unsigned int[3];
389
0
        pScene->mMeshes[0]->mFaces[i].mNumIndices = 3;
390
391
        // copy texture coordinates
392
        // check whether they are different from the previous value at this index.
393
        // In this case, create a full separate set of vertices/normals/texcoords
394
0
        for (unsigned int c = 0; c < 3;++c,++iCurrent)  {
395
396
            // validate vertex indices
397
0
            unsigned int iIndex = (unsigned int)pcTriangles[i].vertexIndices[c];
398
0
            if (iIndex >= m_pcHeader->numVertices)  {
399
0
                ASSIMP_LOG_ERROR("MD2: Vertex index is outside the allowed range");
400
0
                iIndex = m_pcHeader->numVertices-1;
401
0
            }
402
403
            // read x,y, and z component of the vertex
404
0
            aiVector3D& vec = pcMesh->mVertices[iCurrent];
405
406
0
            vec.x = (float)pcVerts[iIndex].vertex[0] * pcFrame->scale[0];
407
0
            vec.x += pcFrame->translate[0];
408
409
0
            vec.y = (float)pcVerts[iIndex].vertex[1] * pcFrame->scale[1];
410
0
            vec.y += pcFrame->translate[1];
411
412
0
            vec.z = (float)pcVerts[iIndex].vertex[2] * pcFrame->scale[2];
413
0
            vec.z += pcFrame->translate[2];
414
415
            // read the normal vector from the precalculated normal table
416
0
            aiVector3D& vNormal = pcMesh->mNormals[iCurrent];
417
0
            LookupNormalIndex(pcVerts[iIndex].lightNormalIndex,vNormal);
418
419
0
            if (m_pcHeader->numTexCoords)   {
420
                // validate texture coordinates
421
0
                iIndex = pcTriangles[i].textureIndices[c];
422
0
                if (iIndex >= m_pcHeader->numTexCoords) {
423
0
                    ASSIMP_LOG_ERROR("MD2: UV index is outside the allowed range");
424
0
                    iIndex = m_pcHeader->numTexCoords-1;
425
0
                }
426
427
0
                aiVector3D& pcOut = pcMesh->mTextureCoords[0][iCurrent];
428
429
                // the texture coordinates are absolute values but we
430
                // need relative values between 0 and 1
431
0
                pcOut.x = pcTexCoords[iIndex].s / fDivisorU;
432
0
                pcOut.y = 1.f-pcTexCoords[iIndex].t / fDivisorV;
433
0
            }
434
0
            pScene->mMeshes[0]->mFaces[i].mIndices[c] = iCurrent;
435
0
        }
436
        // flip the face order
437
0
        std::swap( pScene->mMeshes[0]->mFaces[i].mIndices[0], pScene->mMeshes[0]->mFaces[i].mIndices[2] );
438
0
    }
439
    // Now rotate the whole scene 90 degrees around the x axis to convert to internal coordinate system
440
1
    pScene->mRootNode->mTransformation = aiMatrix4x4(
441
1
            1.f, 0.f, 0.f, 0.f,
442
1
            0.f, 0.f, 1.f, 0.f,
443
1
            0.f, -1.f, 0.f, 0.f,
444
1
            0.f, 0.f, 0.f, 1.f);
445
1
}
446
447
#endif // !! ASSIMP_BUILD_NO_MD2_IMPORTER