Coverage Report

Created: 2026-04-01 06:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/assimp/code/AssetLib/MDL/MDLLoader.cpp
Line
Count
Source
1
/*
2
---------------------------------------------------------------------------
3
Open Asset Import Library (assimp)
4
---------------------------------------------------------------------------
5
6
Copyright (c) 2006-2026, 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 MDLLoader.cpp
43
 *  @brief Implementation of the main parts of the MDL importer class
44
 *  *TODO* Cleanup and further testing of some parts necessary
45
 */
46
47
// internal headers
48
49
#ifndef ASSIMP_BUILD_NO_MDL_IMPORTER
50
51
#include "MDLLoader.h"
52
#include "AssetLib/MD2/MD2FileData.h"
53
#include "HalfLife/HL1MDLLoader.h"
54
#include "HalfLife/HL1FileData.h"
55
#include "MDLDefaultColorMap.h"
56
57
#include <assimp/StringUtils.h>
58
#include <assimp/importerdesc.h>
59
#include <assimp/qnan.h>
60
#include <assimp/scene.h>
61
#include <assimp/DefaultLogger.hpp>
62
#include <assimp/IOSystem.hpp>
63
#include <assimp/Importer.hpp>
64
65
#include <memory>
66
67
using namespace Assimp;
68
69
static constexpr aiImporterDesc desc = {
70
    "Quake Mesh / 3D GameStudio Mesh Importer",
71
    "",
72
    "",
73
    "",
74
    aiImporterFlags_SupportBinaryFlavour,
75
    0,
76
    0,
77
    7,
78
    0,
79
    "mdl"
80
};
81
82
// ------------------------------------------------------------------------------------------------
83
// Ugly stuff ... nevermind
84
#define _AI_MDL7_ACCESS(_data, _index, _limit, _type) \
85
43.2k
    (*((const _type *)(((const char *)_data) + _index * _limit)))
86
87
#define _AI_MDL7_ACCESS_PTR(_data, _index, _limit, _type) \
88
0
    ((BE_NCONST _type *)(((const char *)_data) + _index * _limit))
89
90
#define _AI_MDL7_ACCESS_VERT(_data, _index, _limit) \
91
43.2k
    _AI_MDL7_ACCESS(_data, _index, _limit, MDL::Vertex_MDL7)
92
93
// ------------------------------------------------------------------------------------------------
94
// Constructor to be privately used by Importer
95
MDLImporter::MDLImporter() :
96
1.24k
        configFrameID(), mBuffer(), iGSFileVersion(), mIOHandler(nullptr), pScene(), iFileSize() {
97
    // empty
98
1.24k
}
99
100
// ------------------------------------------------------------------------------------------------
101
// Returns whether the class can handle the format of the given file.
102
464
bool MDLImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
103
464
    static constexpr uint32_t tokens[] = {
104
464
        AI_MDL_MAGIC_NUMBER_LE_HL2a,
105
464
        AI_MDL_MAGIC_NUMBER_LE_HL2b,
106
464
        AI_MDL_MAGIC_NUMBER_LE_GS7,
107
464
        AI_MDL_MAGIC_NUMBER_LE_GS5b,
108
464
        AI_MDL_MAGIC_NUMBER_LE_GS5a,
109
464
        AI_MDL_MAGIC_NUMBER_LE_GS4,
110
464
        AI_MDL_MAGIC_NUMBER_LE_GS3,
111
464
        AI_MDL_MAGIC_NUMBER_LE
112
464
    };
113
464
    return CheckMagicToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
114
464
}
115
116
// ------------------------------------------------------------------------------------------------
117
// Setup configuration properties
118
11
void MDLImporter::SetupProperties(const Importer *pImp) {
119
11
    configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MDL_KEYFRAME, -1);
120
121
    // The
122
    // AI_CONFIG_IMPORT_MDL_KEYFRAME option overrides the
123
    // AI_CONFIG_IMPORT_GLOBAL_KEYFRAME option.
124
11
    if (static_cast<unsigned int>(-1) == configFrameID) {
125
11
        configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_GLOBAL_KEYFRAME, 0);
126
11
    }
127
128
    // AI_CONFIG_IMPORT_MDL_COLORMAP - palette file
129
11
    configPalette = pImp->GetPropertyString(AI_CONFIG_IMPORT_MDL_COLORMAP, "colormap.lmp");
130
131
    // Read configuration specific to MDL (Half-Life 1).
132
11
    mHL1ImportSettings.read_animations = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_ANIMATIONS, true);
133
11
    if (mHL1ImportSettings.read_animations) {
134
11
        mHL1ImportSettings.read_animation_events = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_ANIMATION_EVENTS, true);
135
11
        mHL1ImportSettings.read_blend_controllers = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_BLEND_CONTROLLERS, true);
136
11
        mHL1ImportSettings.read_sequence_transitions = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_SEQUENCE_TRANSITIONS, true);
137
11
    }
138
11
    mHL1ImportSettings.read_attachments = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_ATTACHMENTS, true);
139
11
    mHL1ImportSettings.read_bone_controllers = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_BONE_CONTROLLERS, true);
140
11
    mHL1ImportSettings.read_hitboxes = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_HITBOXES, true);
141
11
    mHL1ImportSettings.read_misc_global_info = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_MISC_GLOBAL_INFO, true);
142
11
    mHL1ImportSettings.transform_coord_system = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_TRANSFORM_COORD_SYSTEM);
143
11
}
144
145
// ------------------------------------------------------------------------------------------------
146
// Get a list of all supported extensions
147
645
const aiImporterDesc *MDLImporter::GetInfo() const {
148
645
    return &desc;
149
645
}
150
151
// ------------------------------------------------------------------------------------------------
152
0
static void transformCoordinateSystem(const aiScene *pScene) {
153
0
    if (pScene == nullptr) {
154
0
        return;
155
0
    }
156
157
0
    pScene->mRootNode->mTransformation = aiMatrix4x4(
158
0
        0.f, -1.f, 0.f, 0.f,
159
0
        0.f, 0.f, 1.f, 0.f,
160
0
        -1.f, 0.f, 0.f, 0.f,
161
0
        0.f, 0.f, 0.f, 1.f
162
0
    );
163
0
}
164
165
// ------------------------------------------------------------------------------------------------
166
// Imports the given file into the given scene structure.
167
void MDLImporter::InternReadFile(const std::string &pFile,
168
11
        aiScene *_pScene, IOSystem *pIOHandler) {
169
11
    pScene = _pScene;
170
11
    mIOHandler = pIOHandler;
171
11
    std::unique_ptr<IOStream> file(pIOHandler->Open(pFile));
172
173
    // Check whether we can read from the file
174
11
    if (file == nullptr) {
175
0
        throw DeadlyImportError("Failed to open MDL file ", pFile, ".");
176
0
    }
177
178
    // This should work for all other types of MDL files, too ...
179
    // the HL1 sequence group header is one of the smallest, afaik
180
11
    iFileSize = (unsigned int)file->FileSize();
181
11
    if (iFileSize < sizeof(MDL::HalfLife::SequenceHeader_HL1)) {
182
0
        throw DeadlyImportError("MDL File is too small.");
183
0
    }
184
185
    // delete the file buffer and cleanup.
186
11
    auto DeleteBufferAndCleanup = [&]() {
187
11
        if (mBuffer) {
188
11
            delete[] mBuffer;
189
11
            mBuffer = nullptr;
190
11
        }
191
11
        AI_DEBUG_INVALIDATE_PTR(mIOHandler);
192
11
        AI_DEBUG_INVALIDATE_PTR(pScene);
193
11
    };
194
195
11
    try {
196
        // Allocate storage and copy the contents of the file to a memory buffer
197
11
        mBuffer = new unsigned char[iFileSize + 1];
198
11
        file->Read((void *)mBuffer, 1, iFileSize);
199
200
        // Append a binary zero to the end of the buffer.
201
        // this is just for safety that string parsing routines
202
        // find the end of the buffer ...
203
11
        mBuffer[iFileSize] = '\0';
204
11
        const uint32_t iMagicWord = *((uint32_t *)mBuffer);
205
206
        // Determine the file subtype and call the appropriate member function
207
11
        bool is_half_life = false;
208
209
        // Original Quake1 format
210
11
        if (AI_MDL_MAGIC_NUMBER_BE == iMagicWord || AI_MDL_MAGIC_NUMBER_LE == iMagicWord) {
211
0
            ASSIMP_LOG_DEBUG("MDL subtype: Quake 1, magic word is IDPO");
212
0
            iGSFileVersion = 0;
213
0
            InternReadFile_Quake1();
214
0
        }
215
        // GameStudio A<old> MDL2 format - used by some test models that come with 3DGS
216
11
        else if (AI_MDL_MAGIC_NUMBER_BE_GS3 == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_GS3 == iMagicWord) {
217
0
            ASSIMP_LOG_DEBUG("MDL subtype: 3D GameStudio A2, magic word is MDL2");
218
0
            iGSFileVersion = 2;
219
0
            InternReadFile_Quake1();
220
0
        }
221
        // GameStudio A4 MDL3 format
222
11
        else if (AI_MDL_MAGIC_NUMBER_BE_GS4 == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_GS4 == iMagicWord) {
223
0
            ASSIMP_LOG_DEBUG("MDL subtype: 3D GameStudio A4, magic word is MDL3");
224
0
            iGSFileVersion = 3;
225
0
            InternReadFile_3DGS_MDL345();
226
0
        }
227
        // GameStudio A5+ MDL4 format
228
11
        else if (AI_MDL_MAGIC_NUMBER_BE_GS5a == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_GS5a == iMagicWord) {
229
0
            ASSIMP_LOG_DEBUG("MDL subtype: 3D GameStudio A4, magic word is MDL4");
230
0
            iGSFileVersion = 4;
231
0
            InternReadFile_3DGS_MDL345();
232
0
        }
233
        // GameStudio A5+ MDL5 format
234
11
        else if (AI_MDL_MAGIC_NUMBER_BE_GS5b == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_GS5b == iMagicWord) {
235
3
            ASSIMP_LOG_DEBUG("MDL subtype: 3D GameStudio A5, magic word is MDL5");
236
3
            iGSFileVersion = 5;
237
3
            InternReadFile_3DGS_MDL345();
238
3
        }
239
        // GameStudio A7 MDL7 format
240
8
        else if (AI_MDL_MAGIC_NUMBER_BE_GS7 == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_GS7 == iMagicWord) {
241
4
            ASSIMP_LOG_DEBUG("MDL subtype: 3D GameStudio A7, magic word is MDL7");
242
4
            iGSFileVersion = 7;
243
4
            InternReadFile_3DGS_MDL7();
244
4
        }
245
        // IDST/IDSQ Format (CS:S/HL^2, etc ...)
246
4
        else if (AI_MDL_MAGIC_NUMBER_BE_HL2a == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_HL2a == iMagicWord ||
247
4
                 AI_MDL_MAGIC_NUMBER_BE_HL2b == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_HL2b == iMagicWord) {
248
4
            iGSFileVersion = 0;
249
4
            is_half_life = true;
250
251
4
            HalfLife::HalfLifeMDLBaseHeader *pHeader = (HalfLife::HalfLifeMDLBaseHeader *)mBuffer;
252
4
            if (pHeader->version == AI_MDL_HL1_VERSION) {
253
4
                ASSIMP_LOG_DEBUG("MDL subtype: Half-Life 1/Goldsrc Engine, magic word is IDST/IDSQ");
254
4
                InternReadFile_HL1(pFile, iMagicWord);
255
4
            } else {
256
0
                ASSIMP_LOG_DEBUG("MDL subtype: Source(tm) Engine, magic word is IDST/IDSQ");
257
0
                InternReadFile_HL2();
258
0
            }
259
4
        } else {
260
            // print the magic word to the log file
261
0
            throw DeadlyImportError("Unknown MDL subformat ", pFile,
262
0
                                    ". Magic word (", ai_str_toprintable((const char *)&iMagicWord, sizeof(iMagicWord)), ") is not known");
263
0
        }
264
265
11
        if (is_half_life && mHL1ImportSettings.transform_coord_system) {
266
            // Now rotate the whole scene 90 degrees around the z and x axes to convert to internal coordinate system
267
0
            transformCoordinateSystem(pScene);
268
11
        } else {
269
            // Now rotate the whole scene 90 degrees around the x axis to convert to internal coordinate system
270
11
            pScene->mRootNode->mTransformation = aiMatrix4x4(
271
11
                1.f,  0.f, 0.f, 0.f,
272
11
                0.f,  0.f, 1.f, 0.f,
273
11
                0.f, -1.f, 0.f, 0.f,
274
11
                0.f,  0.f, 0.f, 1.f);
275
11
        }
276
277
11
        DeleteBufferAndCleanup();
278
11
    } catch (...) {
279
4
        DeleteBufferAndCleanup();
280
4
        throw;
281
4
    }
282
11
}
283
284
// ------------------------------------------------------------------------------------------------
285
// Check whether we're still inside the valid file range
286
76
bool MDLImporter::IsPosValid(const void *szPos) const {
287
76
    return szPos && (const unsigned char *)szPos <= this->mBuffer + this->iFileSize && szPos >= this->mBuffer;
288
76
}
289
290
// ------------------------------------------------------------------------------------------------
291
// Check whether we're still inside the valid file range
292
0
void MDLImporter::SizeCheck(const void *szPos) {
293
0
    if (!IsPosValid(szPos)) {
294
0
        throw DeadlyImportError("Invalid MDL file. The file is too small "
295
0
                                "or contains invalid data.");
296
0
    }
297
0
}
298
299
// ------------------------------------------------------------------------------------------------
300
// Just for debugging purposes
301
76
void MDLImporter::SizeCheck(const void *szPos, const char *szFile, unsigned int iLine) {
302
76
    ai_assert(nullptr != szFile);
303
76
    if (!IsPosValid(szPos)) {
304
        // remove a directory if there is one
305
0
        const char *szFilePtr = ::strrchr(szFile, '\\');
306
0
        if (!szFilePtr) {
307
0
            szFilePtr = ::strrchr(szFile, '/');
308
0
            if (nullptr == szFilePtr) {
309
0
                szFilePtr = szFile;
310
0
            }
311
0
        }
312
0
        if (szFilePtr) {
313
0
            ++szFilePtr;
314
0
        }
315
316
0
        char szBuffer[1024];
317
0
        ::snprintf(szBuffer, sizeof(szBuffer), "Invalid MDL file. The file is too small "
318
0
                            "or contains invalid data (File: %s Line: %u)",
319
0
                szFilePtr, iLine);
320
321
0
        throw DeadlyImportError(szBuffer);
322
0
    }
323
76
}
324
325
// ------------------------------------------------------------------------------------------------
326
// Validate a quake file header
327
3
void MDLImporter::ValidateHeader_Quake1(const MDL::Header *pcHeader) {
328
    // some values may not be nullptr
329
3
    if (pcHeader->num_frames <= 0)
330
0
        throw DeadlyImportError("[Quake 1 MDL] There are no frames in the file");
331
332
3
    if (pcHeader->num_verts <= 0)
333
0
        throw DeadlyImportError("[Quake 1 MDL] There are no vertices in the file");
334
335
3
    if (pcHeader->num_tris <= 0)
336
0
        throw DeadlyImportError("[Quake 1 MDL] There are no triangles in the file");
337
338
    // check whether the maxima are exceeded ...however, this applies for Quake 1 MDLs only
339
3
    if (!this->iGSFileVersion) {
340
0
        if (pcHeader->num_verts > AI_MDL_MAX_VERTS)
341
0
            ASSIMP_LOG_WARN("Quake 1 MDL model has more than AI_MDL_MAX_VERTS vertices");
342
343
0
        if (pcHeader->num_tris > AI_MDL_MAX_TRIANGLES)
344
0
            ASSIMP_LOG_WARN("Quake 1 MDL model has more than AI_MDL_MAX_TRIANGLES triangles");
345
346
0
        if (pcHeader->num_frames > AI_MDL_MAX_FRAMES)
347
0
            ASSIMP_LOG_WARN("Quake 1 MDL model has more than AI_MDL_MAX_FRAMES frames");
348
349
        // (this does not apply for 3DGS MDLs)
350
0
        if (!this->iGSFileVersion && pcHeader->version != AI_MDL_VERSION)
351
0
            ASSIMP_LOG_WARN("Quake 1 MDL model has an unknown version: AI_MDL_VERSION (=6) is "
352
0
                            "the expected file format version");
353
0
        if (pcHeader->num_skins && (!pcHeader->skinwidth || !pcHeader->skinheight))
354
0
            ASSIMP_LOG_WARN("Skin width or height are 0");
355
0
    }
356
3
}
357
358
#ifdef AI_BUILD_BIG_ENDIAN
359
// ------------------------------------------------------------------------------------------------
360
void FlipQuakeHeader(BE_NCONST MDL::Header *pcHeader) {
361
    AI_SWAP4(pcHeader->ident);
362
    AI_SWAP4(pcHeader->version);
363
    AI_SWAP4(pcHeader->boundingradius);
364
    AI_SWAP4(pcHeader->flags);
365
    AI_SWAP4(pcHeader->num_frames);
366
    AI_SWAP4(pcHeader->num_skins);
367
    AI_SWAP4(pcHeader->num_tris);
368
    AI_SWAP4(pcHeader->num_verts);
369
    for (unsigned int i = 0; i < 3; ++i) {
370
        AI_SWAP4(pcHeader->scale[i]);
371
        AI_SWAP4(pcHeader->translate[i]);
372
    }
373
    AI_SWAP4(pcHeader->size);
374
    AI_SWAP4(pcHeader->skinheight);
375
    AI_SWAP4(pcHeader->skinwidth);
376
    AI_SWAP4(pcHeader->synctype);
377
}
378
#endif
379
380
// ------------------------------------------------------------------------------------------------
381
// Read a Quake 1 file
382
0
void MDLImporter::InternReadFile_Quake1() {
383
0
    ai_assert(nullptr != pScene);
384
385
0
    BE_NCONST MDL::Header *pcHeader = (BE_NCONST MDL::Header *)this->mBuffer;
386
387
#ifdef AI_BUILD_BIG_ENDIAN
388
    FlipQuakeHeader(pcHeader);
389
#endif
390
391
0
    ValidateHeader_Quake1(pcHeader);
392
393
    // current cursor position in the file
394
0
    const unsigned char *szCurrent = (const unsigned char *)(pcHeader + 1);
395
396
    // need to read all textures
397
0
    for (unsigned int i = 0; i < (unsigned int)pcHeader->num_skins; ++i) {
398
0
        union {
399
0
            BE_NCONST MDL::Skin *pcSkin;
400
0
            BE_NCONST MDL::GroupSkin *pcGroupSkin;
401
0
        };
402
0
        if (szCurrent + sizeof(MDL::Skin) > this->mBuffer + this->iFileSize) {
403
0
            throw DeadlyImportError("[Quake 1 MDL] Unexpected EOF");
404
0
        }
405
0
        pcSkin = (BE_NCONST MDL::Skin *)szCurrent;
406
407
0
        AI_SWAP4(pcSkin->group);
408
409
        // Quake 1 group-skins
410
0
        if (1 == pcSkin->group) {
411
0
            AI_SWAP4(pcGroupSkin->nb);
412
413
            // need to skip multiple images
414
0
            const unsigned int iNumImages = (unsigned int)pcGroupSkin->nb;
415
0
            szCurrent += sizeof(uint32_t) * 2;
416
417
0
            if (0 != iNumImages) {
418
0
                if (!i) {
419
                    // however, create only one output image (the first)
420
0
                    this->CreateTextureARGB8_3DGS_MDL3(szCurrent + iNumImages * sizeof(float));
421
0
                }
422
                // go to the end of the skin section / the beginning of the next skin
423
0
                bool overflow = false;
424
0
                if (pcHeader->skinwidth != 0 && pcHeader->skinheight != 0) {
425
0
                    if ((pcHeader->skinheight > INT_MAX / pcHeader->skinwidth) || (pcHeader->skinwidth > INT_MAX / pcHeader->skinheight)){
426
0
                        overflow = true;
427
0
                    }
428
0
                    if (!overflow) {
429
0
                        szCurrent += pcHeader->skinheight * pcHeader->skinwidth +sizeof(float) * iNumImages;
430
0
                    }
431
0
                }
432
0
            }
433
0
        } else {
434
0
            szCurrent += sizeof(uint32_t);
435
0
            unsigned int iSkip = i ? UINT_MAX : 0;
436
0
            CreateTexture_3DGS_MDL4(szCurrent, pcSkin->group, &iSkip);
437
0
            szCurrent += iSkip;
438
0
        }
439
0
    }
440
    // get a pointer to the texture coordinates
441
0
    BE_NCONST MDL::TexCoord *pcTexCoords = (BE_NCONST MDL::TexCoord *)szCurrent;
442
0
    szCurrent += sizeof(MDL::TexCoord) * pcHeader->num_verts;
443
444
    // get a pointer to the triangles
445
0
    BE_NCONST MDL::Triangle *pcTriangles = (BE_NCONST MDL::Triangle *)szCurrent;
446
0
    szCurrent += sizeof(MDL::Triangle) * pcHeader->num_tris;
447
0
    VALIDATE_FILE_SIZE(szCurrent);
448
449
    // now get a pointer to the first frame in the file
450
0
    BE_NCONST MDL::Frame *pcFrames = (BE_NCONST MDL::Frame *)szCurrent;
451
0
    MDL::SimpleFrame *pcFirstFrame;
452
453
0
    VALIDATE_FILE_SIZE((const unsigned char *)(pcFrames + 1));
454
0
    if (0 == pcFrames->type) {
455
        // get address of single frame
456
0
        pcFirstFrame = (MDL::SimpleFrame *)&pcFrames->frame;
457
0
    } else {
458
        // get the first frame in the group
459
0
        BE_NCONST MDL::GroupFrame *pcFrames2 = (BE_NCONST MDL::GroupFrame *)szCurrent;
460
0
        VALIDATE_FILE_SIZE((const unsigned char *)(pcFrames2 + 1));
461
0
        pcFirstFrame = (MDL::SimpleFrame *)( szCurrent + sizeof(MDL::GroupFrame::type) + sizeof(MDL::GroupFrame::numframes)
462
0
        + sizeof(MDL::GroupFrame::min) + sizeof(MDL::GroupFrame::max) + sizeof(*MDL::GroupFrame::times) * pcFrames2->numframes );
463
0
    }
464
0
    BE_NCONST MDL::Vertex *pcVertices = (BE_NCONST MDL::Vertex *)((pcFirstFrame->name) + sizeof(pcFirstFrame->name));
465
0
    VALIDATE_FILE_SIZE((const unsigned char *)(pcVertices + pcHeader->num_verts));
466
467
#ifdef AI_BUILD_BIG_ENDIAN
468
    for (int i = 0; i < pcHeader->num_verts; ++i) {
469
        AI_SWAP4(pcTexCoords[i].onseam);
470
        AI_SWAP4(pcTexCoords[i].s);
471
        AI_SWAP4(pcTexCoords[i].t);
472
    }
473
474
    for (int i = 0; i < pcHeader->num_tris; ++i) {
475
        AI_SWAP4(pcTriangles[i].facesfront);
476
        AI_SWAP4(pcTriangles[i].vertex[0]);
477
        AI_SWAP4(pcTriangles[i].vertex[1]);
478
        AI_SWAP4(pcTriangles[i].vertex[2]);
479
    }
480
#endif
481
482
    // setup materials
483
0
    SetupMaterialProperties_3DGS_MDL5_Quake1();
484
485
    // allocate enough storage to hold all vertices and triangles
486
0
    aiMesh *pcMesh = new aiMesh();
487
488
0
    pcMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
489
0
    pcMesh->mNumVertices = pcHeader->num_tris * 3;
490
0
    pcMesh->mNumFaces = pcHeader->num_tris;
491
0
    pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices];
492
0
    pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices];
493
0
    pcMesh->mFaces = new aiFace[pcMesh->mNumFaces];
494
0
    pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices];
495
0
    pcMesh->mNumUVComponents[0] = 2;
496
497
    // there won't be more than one mesh inside the file
498
0
    pScene->mRootNode = new aiNode();
499
0
    pScene->mRootNode->mNumMeshes = 1;
500
0
    pScene->mRootNode->mMeshes = new unsigned int[1];
501
0
    pScene->mRootNode->mMeshes[0] = 0;
502
0
    pScene->mNumMeshes = 1;
503
0
    pScene->mMeshes = new aiMesh *[1];
504
0
    pScene->mMeshes[0] = pcMesh;
505
506
    // now iterate through all triangles
507
0
    unsigned int iCurrent = 0;
508
0
    for (unsigned int i = 0; i < (unsigned int)pcHeader->num_tris; ++i) {
509
0
        pcMesh->mFaces[i].mIndices = new unsigned int[3];
510
0
        pcMesh->mFaces[i].mNumIndices = 3;
511
512
0
        unsigned int iTemp = iCurrent;
513
0
        for (unsigned int c = 0; c < 3; ++c, ++iCurrent) {
514
0
            pcMesh->mFaces[i].mIndices[c] = iCurrent;
515
516
            // read vertices
517
0
            unsigned int iIndex = pcTriangles->vertex[c];
518
0
            if (iIndex >= (unsigned int)pcHeader->num_verts) {
519
0
                iIndex = pcHeader->num_verts - 1;
520
0
                ASSIMP_LOG_WARN("Index overflow in Q1-MDL vertex list.");
521
0
            }
522
523
0
            aiVector3D &vec = pcMesh->mVertices[iCurrent];
524
0
            vec.x = (float)pcVertices[iIndex].v[0] * pcHeader->scale[0];
525
0
            vec.x += pcHeader->translate[0];
526
527
0
            vec.y = (float)pcVertices[iIndex].v[1] * pcHeader->scale[1];
528
0
            vec.y += pcHeader->translate[1];
529
            //vec.y *= -1.0f;
530
531
0
            vec.z = (float)pcVertices[iIndex].v[2] * pcHeader->scale[2];
532
0
            vec.z += pcHeader->translate[2];
533
534
            // read the normal vector from the precalculated normal table
535
0
            MD2::LookupNormalIndex(pcVertices[iIndex].normalIndex, pcMesh->mNormals[iCurrent]);
536
            //pcMesh->mNormals[iCurrent].y *= -1.0f;
537
538
            // read texture coordinates
539
0
            float s = (float)pcTexCoords[iIndex].s;
540
0
            float t = (float)pcTexCoords[iIndex].t;
541
542
            // translate texture coordinates
543
0
            if (0 == pcTriangles->facesfront && 0 != pcTexCoords[iIndex].onseam) {
544
0
                s += pcHeader->skinwidth * 0.5f;
545
0
            }
546
547
            // Scale s and t to range from 0.0 to 1.0
548
0
            pcMesh->mTextureCoords[0][iCurrent].x = (s + 0.5f) / pcHeader->skinwidth;
549
0
            pcMesh->mTextureCoords[0][iCurrent].y = 1.0f - (t + 0.5f) / pcHeader->skinheight;
550
0
        }
551
0
        pcMesh->mFaces[i].mIndices[0] = iTemp + 2;
552
0
        pcMesh->mFaces[i].mIndices[1] = iTemp + 1;
553
0
        pcMesh->mFaces[i].mIndices[2] = iTemp + 0;
554
0
        pcTriangles++;
555
0
    }
556
0
    return;
557
0
}
558
559
// ------------------------------------------------------------------------------------------------
560
// Setup material properties for Quake and older GameStudio files
561
3
void MDLImporter::SetupMaterialProperties_3DGS_MDL5_Quake1() {
562
3
    const MDL::Header *const pcHeader = (const MDL::Header *)this->mBuffer;
563
564
    // allocate ONE material
565
3
    pScene->mMaterials = new aiMaterial *[1];
566
3
    pScene->mMaterials[0] = new aiMaterial();
567
3
    pScene->mNumMaterials = 1;
568
569
    // setup the material's properties
570
3
    const int iMode = (int)aiShadingMode_Gouraud;
571
3
    aiMaterial *const pcHelper = (aiMaterial *)pScene->mMaterials[0];
572
3
    pcHelper->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL);
573
574
3
    aiColor4D clr;
575
3
    if (0 != pcHeader->num_skins && pScene->mNumTextures) {
576
        // can we replace the texture with a single color?
577
0
        clr = this->ReplaceTextureWithColor(pScene->mTextures[0]);
578
0
        if (is_not_qnan(clr.r)) {
579
0
            delete pScene->mTextures[0];
580
0
            delete[] pScene->mTextures;
581
582
0
            pScene->mTextures = nullptr;
583
0
            pScene->mNumTextures = 0;
584
0
        } else {
585
0
            clr.b = clr.a = clr.g = clr.r = 1.0f;
586
0
            aiString szString;
587
0
            ::memcpy(szString.data, AI_MAKE_EMBEDDED_TEXNAME(0), 3);
588
0
            szString.length = 2;
589
0
            pcHelper->AddProperty(&szString, AI_MATKEY_TEXTURE_DIFFUSE(0));
590
0
        }
591
0
    }
592
593
3
    pcHelper->AddProperty<aiColor4D>(&clr, 1, AI_MATKEY_COLOR_DIFFUSE);
594
3
    pcHelper->AddProperty<aiColor4D>(&clr, 1, AI_MATKEY_COLOR_SPECULAR);
595
596
3
    clr.r *= 0.05f;
597
3
    clr.g *= 0.05f;
598
3
    clr.b *= 0.05f;
599
3
    clr.a = 1.0f;
600
3
    pcHelper->AddProperty<aiColor4D>(&clr, 1, AI_MATKEY_COLOR_AMBIENT);
601
3
}
602
603
// ------------------------------------------------------------------------------------------------
604
// Read a MDL 3,4,5 file
605
3
void MDLImporter::InternReadFile_3DGS_MDL345() {
606
3
    ai_assert(nullptr != pScene);
607
3
    if (pScene == nullptr) {
608
0
        throw DeadlyImportError("INvalid scene pointer detected.");
609
0
    }
610
611
    // the header of MDL 3/4/5 is nearly identical to the original Quake1 header
612
3
    BE_NCONST MDL::Header *pcHeader = (BE_NCONST MDL::Header *)this->mBuffer;
613
#ifdef AI_BUILD_BIG_ENDIAN
614
    FlipQuakeHeader(pcHeader);
615
#endif
616
3
    ValidateHeader_Quake1(pcHeader);
617
618
3
    if (pcHeader->synctype < 0) {
619
0
        throw DeadlyImportError("Invalid synctype value in MDL header; possible corrupt file.");
620
0
    }
621
622
    // current cursor position in the file
623
3
    const unsigned char *szCurrent = (const unsigned char *)(pcHeader + 1);
624
3
    const unsigned char *szEnd = mBuffer + iFileSize;
625
626
    // need to read all textures
627
3
    for (unsigned int i = 0; i < (unsigned int)pcHeader->num_skins; ++i) {
628
0
        if (szCurrent + sizeof(uint32_t) > szEnd) {
629
0
            throw DeadlyImportError("Texture data past end of file.");
630
0
        }
631
0
        BE_NCONST MDL::Skin *pcSkin = (BE_NCONST MDL::Skin *)szCurrent;
632
0
        AI_SWAP4(pcSkin->group);
633
        // create one output image
634
0
        unsigned int iSkip = i ? UINT_MAX : 0;
635
0
        if (5 <= iGSFileVersion) {
636
            // MDL5 format could contain MIPmaps
637
0
            CreateTexture_3DGS_MDL5((unsigned char *)pcSkin + sizeof(uint32_t),
638
0
                    pcSkin->group, &iSkip);
639
0
        } else {
640
0
            CreateTexture_3DGS_MDL4((unsigned char *)pcSkin + sizeof(uint32_t),
641
0
                    pcSkin->group, &iSkip);
642
0
        }
643
        // need to skip one image
644
0
        szCurrent += iSkip + sizeof(uint32_t);
645
0
    }
646
    // get a pointer to the texture coordinates
647
3
    BE_NCONST MDL::TexCoord_MDL3 *pcTexCoords = (BE_NCONST MDL::TexCoord_MDL3 *)szCurrent;
648
3
    szCurrent += sizeof(MDL::TexCoord_MDL3) * pcHeader->synctype;
649
650
    // NOTE: for MDLn formats "synctype" corresponds to the number of UV coords
651
652
    // get a pointer to the triangles
653
3
    BE_NCONST MDL::Triangle_MDL3 *pcTriangles = (BE_NCONST MDL::Triangle_MDL3 *)szCurrent;
654
3
    szCurrent += sizeof(MDL::Triangle_MDL3) * pcHeader->num_tris;
655
656
#ifdef AI_BUILD_BIG_ENDIAN
657
658
    for (int i = 0; i < pcHeader->synctype; ++i) {
659
        AI_SWAP2(pcTexCoords[i].u);
660
        AI_SWAP2(pcTexCoords[i].v);
661
    }
662
663
    for (int i = 0; i < pcHeader->num_tris; ++i) {
664
        AI_SWAP2(pcTriangles[i].index_xyz[0]);
665
        AI_SWAP2(pcTriangles[i].index_xyz[1]);
666
        AI_SWAP2(pcTriangles[i].index_xyz[2]);
667
        AI_SWAP2(pcTriangles[i].index_uv[0]);
668
        AI_SWAP2(pcTriangles[i].index_uv[1]);
669
        AI_SWAP2(pcTriangles[i].index_uv[2]);
670
    }
671
672
#endif
673
674
3
    VALIDATE_FILE_SIZE(szCurrent);
675
676
    // setup materials
677
3
    SetupMaterialProperties_3DGS_MDL5_Quake1();
678
679
    // allocate enough storage to hold all vertices and triangles
680
3
    aiMesh *pcMesh = new aiMesh();
681
3
    pcMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
682
683
3
    pcMesh->mNumVertices = pcHeader->num_tris * 3;
684
3
    pcMesh->mNumFaces = pcHeader->num_tris;
685
3
    pcMesh->mFaces = new aiFace[pcMesh->mNumFaces];
686
687
    // there won't be more than one mesh inside the file
688
3
    pScene->mRootNode = new aiNode();
689
3
    pScene->mRootNode->mNumMeshes = 1;
690
3
    pScene->mRootNode->mMeshes = new unsigned int[1];
691
3
    pScene->mRootNode->mMeshes[0] = 0;
692
3
    pScene->mNumMeshes = 1;
693
3
    pScene->mMeshes = new aiMesh *[1];
694
3
    pScene->mMeshes[0] = pcMesh;
695
696
    // allocate output storage
697
3
    pcMesh->mNumVertices = (unsigned int)pcHeader->num_tris * 3;
698
3
    pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices];
699
3
    pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices];
700
701
3
    if (pcHeader->synctype) {
702
3
        pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices];
703
3
        pcMesh->mNumUVComponents[0] = 2;
704
3
    }
705
706
    // now get a pointer to the first frame in the file
707
3
    BE_NCONST MDL::Frame *pcFrames = (BE_NCONST MDL::Frame *)szCurrent;
708
3
    VALIDATE_FILE_SIZE((const unsigned char *)(pcFrames + 1));
709
3
    AI_SWAP4(pcFrames->type);
710
711
    // byte packed vertices
712
    // FIXME: these two snippets below are almost identical ... join them?
713
    /////////////////////////////////////////////////////////////////////////////////////
714
3
    if (0 == pcFrames->type || 3 >= this->iGSFileVersion) {
715
716
0
        const MDL::SimpleFrame *pcFirstFrame = (const MDL::SimpleFrame *)(szCurrent + sizeof(uint32_t));
717
0
        const MDL::Vertex *pcVertices = (const MDL::Vertex *)((pcFirstFrame->name) + sizeof(pcFirstFrame->name));
718
719
0
        VALIDATE_FILE_SIZE(pcVertices + pcHeader->num_verts);
720
721
        // now iterate through all triangles
722
0
        unsigned int iCurrent = 0;
723
0
        for (unsigned int i = 0; i < (unsigned int)pcHeader->num_tris; ++i) {
724
0
            pcMesh->mFaces[i].mIndices = new unsigned int[3];
725
0
            pcMesh->mFaces[i].mNumIndices = 3;
726
727
0
            unsigned int iTemp = iCurrent;
728
0
            for (unsigned int c = 0; c < 3; ++c, ++iCurrent) {
729
                // read vertices
730
0
                unsigned int iIndex = pcTriangles->index_xyz[c];
731
0
                if (iIndex >= (unsigned int)pcHeader->num_verts) {
732
0
                    iIndex = pcHeader->num_verts - 1;
733
0
                    ASSIMP_LOG_WARN("Index overflow in MDLn vertex list");
734
0
                }
735
736
0
                aiVector3D &vec = pcMesh->mVertices[iCurrent];
737
0
                vec.x = (float)pcVertices[iIndex].v[0] * pcHeader->scale[0];
738
0
                vec.x += pcHeader->translate[0];
739
740
0
                vec.y = (float)pcVertices[iIndex].v[1] * pcHeader->scale[1];
741
0
                vec.y += pcHeader->translate[1];
742
                // vec.y *= -1.0f;
743
744
0
                vec.z = (float)pcVertices[iIndex].v[2] * pcHeader->scale[2];
745
0
                vec.z += pcHeader->translate[2];
746
747
                // read the normal vector from the precalculated normal table
748
0
                MD2::LookupNormalIndex(pcVertices[iIndex].normalIndex, pcMesh->mNormals[iCurrent]);
749
                // pcMesh->mNormals[iCurrent].y *= -1.0f;
750
751
                // read texture coordinates
752
0
                if (pcHeader->synctype) {
753
0
                    ImportUVCoordinate_3DGS_MDL345(pcMesh->mTextureCoords[0][iCurrent],
754
0
                            pcTexCoords, pcTriangles->index_uv[c]);
755
0
                }
756
0
            }
757
0
            pcMesh->mFaces[i].mIndices[0] = iTemp + 2;
758
0
            pcMesh->mFaces[i].mIndices[1] = iTemp + 1;
759
0
            pcMesh->mFaces[i].mIndices[2] = iTemp + 0;
760
0
            pcTriangles++;
761
0
        }
762
763
0
    }
764
    // short packed vertices
765
    /////////////////////////////////////////////////////////////////////////////////////
766
3
    else {
767
        // now get a pointer to the first frame in the file
768
3
        const MDL::SimpleFrame_MDLn_SP *pcFirstFrame = (const MDL::SimpleFrame_MDLn_SP *)(szCurrent + sizeof(uint32_t));
769
770
        // get a pointer to the vertices
771
3
        const MDL::Vertex_MDL4 *pcVertices = (const MDL::Vertex_MDL4 *)((pcFirstFrame->name) +
772
3
                                                                        sizeof(pcFirstFrame->name));
773
774
3
        VALIDATE_FILE_SIZE(pcVertices + pcHeader->num_verts);
775
776
        // now iterate through all triangles
777
3
        unsigned int iCurrent = 0;
778
2.88k
        for (unsigned int i = 0; i < (unsigned int)pcHeader->num_tris; ++i) {
779
2.88k
            pcMesh->mFaces[i].mIndices = new unsigned int[3];
780
2.88k
            pcMesh->mFaces[i].mNumIndices = 3;
781
782
2.88k
            unsigned int iTemp = iCurrent;
783
11.5k
            for (unsigned int c = 0; c < 3; ++c, ++iCurrent) {
784
                // read vertices
785
8.64k
                unsigned int iIndex = pcTriangles->index_xyz[c];
786
8.64k
                if (iIndex >= (unsigned int)pcHeader->num_verts) {
787
3
                    iIndex = pcHeader->num_verts - 1;
788
3
                    ASSIMP_LOG_WARN("Index overflow in MDLn vertex list");
789
3
                }
790
791
8.64k
                aiVector3D &vec = pcMesh->mVertices[iCurrent];
792
8.64k
                vec.x = (float)pcVertices[iIndex].v[0] * pcHeader->scale[0];
793
8.64k
                vec.x += pcHeader->translate[0];
794
795
8.64k
                vec.y = (float)pcVertices[iIndex].v[1] * pcHeader->scale[1];
796
8.64k
                vec.y += pcHeader->translate[1];
797
                // vec.y *= -1.0f;
798
799
8.64k
                vec.z = (float)pcVertices[iIndex].v[2] * pcHeader->scale[2];
800
8.64k
                vec.z += pcHeader->translate[2];
801
802
                // read the normal vector from the precalculated normal table
803
8.64k
                MD2::LookupNormalIndex(pcVertices[iIndex].normalIndex, pcMesh->mNormals[iCurrent]);
804
                // pcMesh->mNormals[iCurrent].y *= -1.0f;
805
806
                // read texture coordinates
807
8.64k
                if (pcHeader->synctype) {
808
8.64k
                    ImportUVCoordinate_3DGS_MDL345(pcMesh->mTextureCoords[0][iCurrent],
809
8.64k
                            pcTexCoords, pcTriangles->index_uv[c]);
810
8.64k
                }
811
8.64k
            }
812
2.88k
            pcMesh->mFaces[i].mIndices[0] = iTemp + 2;
813
2.88k
            pcMesh->mFaces[i].mIndices[1] = iTemp + 1;
814
2.88k
            pcMesh->mFaces[i].mIndices[2] = iTemp + 0;
815
2.88k
            pcTriangles++;
816
2.88k
        }
817
3
    }
818
819
    // For MDL5 we will need to build valid texture coordinates
820
    // basing upon the file loaded (only support one file as skin)
821
3
    if (0x5 == iGSFileVersion)
822
3
        CalculateUVCoordinates_MDL5();
823
3
    return;
824
3
}
825
826
// ------------------------------------------------------------------------------------------------
827
// Get a single UV coordinate for Quake and older GameStudio files
828
void MDLImporter::ImportUVCoordinate_3DGS_MDL345(
829
        aiVector3D &vOut,
830
        const MDL::TexCoord_MDL3 *pcSrc,
831
8.64k
        unsigned int iIndex) {
832
8.64k
    ai_assert(nullptr != pcSrc);
833
8.64k
    const MDL::Header *const pcHeader = (const MDL::Header *)this->mBuffer;
834
835
    // validate UV indices
836
8.64k
    if (iIndex >= (unsigned int)pcHeader->synctype) {
837
718
        iIndex = pcHeader->synctype - 1;
838
718
        ASSIMP_LOG_WARN("Index overflow in MDLn UV coord list");
839
718
    }
840
841
8.64k
    float s = (float)pcSrc[iIndex].u;
842
8.64k
    float t = (float)pcSrc[iIndex].v;
843
844
    // Scale s and t to range from 0.0 to 1.0
845
8.64k
    if (0x5 != iGSFileVersion) {
846
0
        s = (s + 0.5f) / pcHeader->skinwidth;
847
0
        t = 1.0f - (t + 0.5f) / pcHeader->skinheight;
848
0
    }
849
850
8.64k
    vOut.x = s;
851
8.64k
    vOut.y = t;
852
8.64k
    vOut.z = 0.0f;
853
8.64k
}
854
855
// ------------------------------------------------------------------------------------------------
856
// Compute UV coordinates for a MDL5 file
857
3
void MDLImporter::CalculateUVCoordinates_MDL5() {
858
3
    const MDL::Header *const pcHeader = (const MDL::Header *)this->mBuffer;
859
3
    if (pcHeader->num_skins && this->pScene->mNumTextures) {
860
0
        const aiTexture *pcTex = this->pScene->mTextures[0];
861
862
        // if the file is loaded in DDS format: get the size of the
863
        // texture from the header of the DDS file
864
        // skip three DWORDs and read first height, then the width
865
0
        unsigned int iWidth, iHeight;
866
0
        if (!pcTex->mHeight) {
867
0
            const uint32_t *piPtr = (uint32_t *)pcTex->pcData;
868
869
0
            piPtr += 3;
870
0
            iHeight = (unsigned int)*piPtr++;
871
0
            iWidth = (unsigned int)*piPtr;
872
0
            if (!iHeight || !iWidth) {
873
0
                ASSIMP_LOG_WARN("Either the width or the height of the "
874
0
                                "embedded DDS texture is zero. Unable to compute final texture "
875
0
                                "coordinates. The texture coordinates remain in their original "
876
0
                                "0-x/0-y (x,y = texture size) range.");
877
0
                iWidth = 1;
878
0
                iHeight = 1;
879
0
            }
880
0
        } else {
881
0
            iWidth = pcTex->mWidth;
882
0
            iHeight = pcTex->mHeight;
883
0
        }
884
885
0
        if (1 != iWidth || 1 != iHeight) {
886
0
            const float fWidth = (float)iWidth;
887
0
            const float fHeight = (float)iHeight;
888
0
            aiMesh *pcMesh = this->pScene->mMeshes[0];
889
0
            for (unsigned int i = 0; i < pcMesh->mNumVertices; ++i) {
890
0
                if (!pcMesh->HasTextureCoords(0)) {
891
0
                    continue;
892
0
                }
893
0
                pcMesh->mTextureCoords[0][i].x /= fWidth;
894
0
                pcMesh->mTextureCoords[0][i].y /= fHeight;
895
0
                pcMesh->mTextureCoords[0][i].y = 1.0f - pcMesh->mTextureCoords[0][i].y; // DX to OGL
896
0
            }
897
0
        }
898
0
    }
899
3
}
900
901
// ------------------------------------------------------------------------------------------------
902
// Validate the header of a MDL7 file
903
4
void MDLImporter::ValidateHeader_3DGS_MDL7(const MDL::Header_MDL7 *pcHeader) {
904
4
    ai_assert(nullptr != pcHeader);
905
906
    // There are some fixed sizes ...
907
4
    if (sizeof(MDL::ColorValue_MDL7) != pcHeader->colorvalue_stc_size) {
908
0
        throw DeadlyImportError(
909
0
                "[3DGS MDL7] sizeof(MDL::ColorValue_MDL7) != pcHeader->colorvalue_stc_size");
910
0
    }
911
4
    if (sizeof(MDL::TexCoord_MDL7) != pcHeader->skinpoint_stc_size) {
912
0
        throw DeadlyImportError(
913
0
                "[3DGS MDL7] sizeof(MDL::TexCoord_MDL7) != pcHeader->skinpoint_stc_size");
914
0
    }
915
4
    if (sizeof(MDL::Skin_MDL7) != pcHeader->skin_stc_size) {
916
0
        throw DeadlyImportError(
917
0
                "sizeof(MDL::Skin_MDL7) != pcHeader->skin_stc_size");
918
0
    }
919
920
    // if there are no groups ... how should we load such a file?
921
4
    if (!pcHeader->groups_num) {
922
0
        throw DeadlyImportError("[3DGS MDL7] No frames found");
923
0
    }
924
4
}
925
926
// ------------------------------------------------------------------------------------------------
927
// resolve bone animation matrices
928
0
void MDLImporter::CalcAbsBoneMatrices_3DGS_MDL7(MDL::IntBone_MDL7 **apcOutBones) {
929
0
    const MDL::Header_MDL7 *pcHeader = (const MDL::Header_MDL7 *)this->mBuffer;
930
0
    const MDL::Bone_MDL7 *pcBones = (const MDL::Bone_MDL7 *)(pcHeader + 1);
931
0
    ai_assert(nullptr != apcOutBones);
932
933
    // first find the bone that has NO parent, calculate the
934
    // animation matrix for it, then go on and search for the next parent
935
    // index (0) and so on until we can't find a new node.
936
0
    uint16_t iParent = 0xffff;
937
0
    uint32_t iIterations = 0;
938
0
    while (iIterations++ < pcHeader->bones_num) {
939
0
        for (uint32_t iBone = 0; iBone < pcHeader->bones_num; ++iBone) {
940
0
            BE_NCONST MDL::Bone_MDL7 *pcBone = _AI_MDL7_ACCESS_PTR(pcBones, iBone,
941
0
                    pcHeader->bone_stc_size, MDL::Bone_MDL7);
942
943
0
            AI_SWAP2(pcBone->parent_index);
944
0
            AI_SWAP4(pcBone->x);
945
0
            AI_SWAP4(pcBone->y);
946
0
            AI_SWAP4(pcBone->z);
947
948
0
            if (iParent == pcBone->parent_index) {
949
                // MDL7 readme
950
                ////////////////////////////////////////////////////////////////
951
                /*
952
                The animation matrix is then calculated the following way:
953
954
                vector3 bPos = <absolute bone position>
955
                matrix44 laM;   // local animation matrix
956
                sphrvector key_rotate = <bone rotation>
957
958
                matrix44 m1,m2;
959
                create_trans_matrix(m1, -bPos.x, -bPos.y, -bPos.z);
960
                create_trans_matrix(m2, -bPos.x, -bPos.y, -bPos.z);
961
962
                create_rotation_matrix(laM,key_rotate);
963
964
                laM = sm1 * laM;
965
                laM = laM * sm2;
966
                */
967
                /////////////////////////////////////////////////////////////////
968
969
0
                MDL::IntBone_MDL7 *const pcOutBone = apcOutBones[iBone];
970
971
                // store the parent index of the bone
972
0
                pcOutBone->iParent = pcBone->parent_index;
973
0
                if (0xffff != iParent) {
974
0
                    const MDL::IntBone_MDL7 *pcParentBone = apcOutBones[iParent];
975
0
                    pcOutBone->mOffsetMatrix.a4 = -pcParentBone->vPosition.x;
976
0
                    pcOutBone->mOffsetMatrix.b4 = -pcParentBone->vPosition.y;
977
0
                    pcOutBone->mOffsetMatrix.c4 = -pcParentBone->vPosition.z;
978
0
                }
979
0
                pcOutBone->vPosition.x = pcBone->x;
980
0
                pcOutBone->vPosition.y = pcBone->y;
981
0
                pcOutBone->vPosition.z = pcBone->z;
982
0
                pcOutBone->mOffsetMatrix.a4 -= pcBone->x;
983
0
                pcOutBone->mOffsetMatrix.b4 -= pcBone->y;
984
0
                pcOutBone->mOffsetMatrix.c4 -= pcBone->z;
985
986
0
                if (AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_NOT_THERE == pcHeader->bone_stc_size) {
987
                    // no real name for our poor bone is specified :-(
988
0
                    pcOutBone->mName.length = ai_snprintf(pcOutBone->mName.data, AI_MAXLEN,
989
0
                            "UnnamedBone_%i", iBone);
990
0
                } else {
991
                    // Make sure we won't run over the buffer's end if there is no
992
                    // terminal 0 character (however the documentation says there
993
                    // should be one)
994
0
                    uint32_t iMaxLen = pcHeader->bone_stc_size - 16;
995
0
                    for (uint32_t qq = 0; qq < iMaxLen; ++qq) {
996
0
                        if (!pcBone->name[qq]) {
997
0
                            iMaxLen = qq;
998
0
                            break;
999
0
                        }
1000
0
                    }
1001
1002
                    // store the name of the bone
1003
0
                    pcOutBone->mName.length = static_cast<ai_uint32>(iMaxLen);
1004
0
                    ::memcpy(pcOutBone->mName.data, pcBone->name, pcOutBone->mName.length);
1005
0
                    pcOutBone->mName.data[pcOutBone->mName.length] = '\0';
1006
0
                }
1007
0
            }
1008
0
        }
1009
0
        ++iParent;
1010
0
    }
1011
0
}
1012
1013
// ------------------------------------------------------------------------------------------------
1014
// read bones from a MDL7 file
1015
4
MDL::IntBone_MDL7 **MDLImporter::LoadBones_3DGS_MDL7() {
1016
4
    const MDL::Header_MDL7 *pcHeader = (const MDL::Header_MDL7 *)this->mBuffer;
1017
4
    if (pcHeader->bones_num) {
1018
        // validate the size of the bone data structure in the file
1019
0
        if (AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_20_CHARS != pcHeader->bone_stc_size &&
1020
0
                AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_32_CHARS != pcHeader->bone_stc_size &&
1021
0
                AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_NOT_THERE != pcHeader->bone_stc_size) {
1022
0
            ASSIMP_LOG_WARN("Unknown size of bone data structure");
1023
0
            return nullptr;
1024
0
        }
1025
1026
0
        MDL::IntBone_MDL7 **apcBonesOut = new MDL::IntBone_MDL7 *[pcHeader->bones_num];
1027
0
        for (uint32_t crank = 0; crank < pcHeader->bones_num; ++crank)
1028
0
            apcBonesOut[crank] = new MDL::IntBone_MDL7();
1029
1030
        // and calculate absolute bone offset matrices ...
1031
0
        CalcAbsBoneMatrices_3DGS_MDL7(apcBonesOut);
1032
0
        return apcBonesOut;
1033
0
    }
1034
4
    return nullptr;
1035
4
}
1036
1037
// ------------------------------------------------------------------------------------------------
1038
// read faces from a MDL7 file
1039
void MDLImporter::ReadFaces_3DGS_MDL7(const MDL::IntGroupInfo_MDL7 &groupInfo,
1040
20
        MDL::IntGroupData_MDL7 &groupData) {
1041
20
    const MDL::Header_MDL7 *pcHeader = (const MDL::Header_MDL7 *)this->mBuffer;
1042
20
    MDL::Triangle_MDL7 *pcGroupTris = groupInfo.pcGroupTris;
1043
1044
    // iterate through all triangles and build valid display lists
1045
20
    unsigned int iOutIndex = 0;
1046
2.42k
    for (unsigned int iTriangle = 0; iTriangle < (unsigned int)groupInfo.pcGroup->numtris; ++iTriangle) {
1047
2.40k
        AI_SWAP2(pcGroupTris->v_index[0]);
1048
2.40k
        AI_SWAP2(pcGroupTris->v_index[1]);
1049
2.40k
        AI_SWAP2(pcGroupTris->v_index[2]);
1050
1051
        // iterate through all indices of the current triangle
1052
9.60k
        for (unsigned int c = 0; c < 3; ++c, ++iOutIndex) {
1053
1054
            // validate the vertex index
1055
7.20k
            unsigned int iIndex = pcGroupTris->v_index[c];
1056
7.20k
            if (iIndex > (unsigned int)groupInfo.pcGroup->numverts) {
1057
                // (we might need to read this section a second time - to process frame vertices correctly)
1058
0
                pcGroupTris->v_index[c] = (uint16_t)(iIndex = groupInfo.pcGroup->numverts - 1);
1059
0
                ASSIMP_LOG_WARN("Index overflow in MDL7 vertex list");
1060
0
            }
1061
1062
            // write the output face index
1063
7.20k
            groupData.pcFaces[iTriangle].mIndices[2 - c] = iOutIndex;
1064
1065
7.20k
            aiVector3D &vPosition = groupData.vPositions[iOutIndex];
1066
7.20k
            vPosition.x = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts, iIndex, pcHeader->mainvertex_stc_size).x;
1067
7.20k
            vPosition.y = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts, iIndex, pcHeader->mainvertex_stc_size).y;
1068
7.20k
            vPosition.z = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts, iIndex, pcHeader->mainvertex_stc_size).z;
1069
1070
            // if we have bones, save the index
1071
7.20k
            if (!groupData.aiBones.empty()) {
1072
0
                groupData.aiBones[iOutIndex] = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts,
1073
0
                        iIndex, pcHeader->mainvertex_stc_size)
1074
0
                                                       .vertindex;
1075
0
            }
1076
1077
            // now read the normal vector
1078
7.20k
            if (AI_MDL7_FRAMEVERTEX030305_STCSIZE <= pcHeader->mainvertex_stc_size) {
1079
                // read the full normal vector
1080
7.20k
                aiVector3D &vNormal = groupData.vNormals[iOutIndex];
1081
7.20k
                vNormal.x = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts, iIndex, pcHeader->mainvertex_stc_size).norm[0];
1082
7.20k
                AI_SWAP4(vNormal.x);
1083
7.20k
                vNormal.y = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts, iIndex, pcHeader->mainvertex_stc_size).norm[1];
1084
7.20k
                AI_SWAP4(vNormal.y);
1085
7.20k
                vNormal.z = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts, iIndex, pcHeader->mainvertex_stc_size).norm[2];
1086
7.20k
                AI_SWAP4(vNormal.z);
1087
7.20k
            } else if (AI_MDL7_FRAMEVERTEX120503_STCSIZE <= pcHeader->mainvertex_stc_size) {
1088
                // read the normal vector from Quake2's smart table
1089
0
                aiVector3D &vNormal = groupData.vNormals[iOutIndex];
1090
0
                MD2::LookupNormalIndex(_AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts, iIndex,
1091
0
                                               pcHeader->mainvertex_stc_size)
1092
0
                                               .norm162index,
1093
0
                        vNormal);
1094
0
            }
1095
            // validate and process the first uv coordinate set
1096
7.20k
            if (pcHeader->triangle_stc_size >= AI_MDL7_TRIANGLE_STD_SIZE_ONE_UV) {
1097
1098
7.20k
                if (groupInfo.pcGroup->num_stpts) {
1099
0
                    AI_SWAP2(pcGroupTris->skinsets[0].st_index[0]);
1100
0
                    AI_SWAP2(pcGroupTris->skinsets[0].st_index[1]);
1101
0
                    AI_SWAP2(pcGroupTris->skinsets[0].st_index[2]);
1102
1103
0
                    iIndex = pcGroupTris->skinsets[0].st_index[c];
1104
0
                    if (iIndex > (unsigned int)groupInfo.pcGroup->num_stpts) {
1105
0
                        iIndex = groupInfo.pcGroup->num_stpts - 1;
1106
0
                        ASSIMP_LOG_WARN("Index overflow in MDL7 UV coordinate list (#1)");
1107
0
                    }
1108
1109
0
                    float u = groupInfo.pcGroupUVs[iIndex].u;
1110
0
                    float v = 1.0f - groupInfo.pcGroupUVs[iIndex].v; // DX to OGL
1111
1112
0
                    groupData.vTextureCoords1[iOutIndex].x = u;
1113
0
                    groupData.vTextureCoords1[iOutIndex].y = v;
1114
0
                }
1115
                // assign the material index, but only if it is existing
1116
7.20k
                if (pcHeader->triangle_stc_size >= AI_MDL7_TRIANGLE_STD_SIZE_ONE_UV_WITH_MATINDEX) {
1117
7.20k
                    AI_SWAP4(pcGroupTris->skinsets[0].material);
1118
7.20k
                    groupData.pcFaces[iTriangle].iMatIndex[0] = pcGroupTris->skinsets[0].material;
1119
7.20k
                }
1120
7.20k
            }
1121
            // validate and process the second uv coordinate set
1122
7.20k
            if (pcHeader->triangle_stc_size >= AI_MDL7_TRIANGLE_STD_SIZE_TWO_UV) {
1123
1124
0
                if (groupInfo.pcGroup->num_stpts) {
1125
0
                    AI_SWAP2(pcGroupTris->skinsets[1].st_index[0]);
1126
0
                    AI_SWAP2(pcGroupTris->skinsets[1].st_index[1]);
1127
0
                    AI_SWAP2(pcGroupTris->skinsets[1].st_index[2]);
1128
0
                    AI_SWAP4(pcGroupTris->skinsets[1].material);
1129
1130
0
                    iIndex = pcGroupTris->skinsets[1].st_index[c];
1131
0
                    if (iIndex > (unsigned int)groupInfo.pcGroup->num_stpts) {
1132
0
                        iIndex = groupInfo.pcGroup->num_stpts - 1;
1133
0
                        ASSIMP_LOG_WARN("Index overflow in MDL7 UV coordinate list (#2)");
1134
0
                    }
1135
1136
0
                    float u = groupInfo.pcGroupUVs[iIndex].u;
1137
0
                    float v = 1.0f - groupInfo.pcGroupUVs[iIndex].v;
1138
1139
0
                    groupData.vTextureCoords2[iOutIndex].x = u;
1140
0
                    groupData.vTextureCoords2[iOutIndex].y = v; // DX to OGL
1141
1142
                    // check whether we do really need the second texture
1143
                    // coordinate set ... wastes memory and loading time
1144
0
                    if (0 != iIndex && (u != groupData.vTextureCoords1[iOutIndex].x ||
1145
0
                                               v != groupData.vTextureCoords1[iOutIndex].y))
1146
0
                        groupData.bNeed2UV = true;
1147
1148
                    // if the material differs, we need a second skin, too
1149
0
                    if (pcGroupTris->skinsets[1].material != pcGroupTris->skinsets[0].material)
1150
0
                        groupData.bNeed2UV = true;
1151
0
                }
1152
                // assign the material index
1153
0
                groupData.pcFaces[iTriangle].iMatIndex[1] = pcGroupTris->skinsets[1].material;
1154
0
            }
1155
7.20k
        }
1156
        // get the next triangle in the list
1157
2.40k
        pcGroupTris = (MDL::Triangle_MDL7 *)((const char *)pcGroupTris + pcHeader->triangle_stc_size);
1158
2.40k
    }
1159
20
}
1160
1161
// ------------------------------------------------------------------------------------------------
1162
// handle frames in a MDL7 file
1163
bool MDLImporter::ProcessFrames_3DGS_MDL7(const MDL::IntGroupInfo_MDL7 &groupInfo,
1164
        MDL::IntGroupData_MDL7 &groupData,
1165
        MDL::IntSharedData_MDL7 &shared,
1166
        const unsigned char *szCurrent,
1167
20
        const unsigned char **szCurrentOut) {
1168
20
    ai_assert(nullptr != szCurrent);
1169
20
    ai_assert(nullptr != szCurrentOut);
1170
1171
20
    const MDL::Header_MDL7 *pcHeader = (const MDL::Header_MDL7 *)mBuffer;
1172
1173
    // if we have no bones we can simply skip all frames,
1174
    // otherwise we'll need to process them.
1175
    // FIX: If we need another frame than the first we must apply frame vertex replacements ...
1176
20
    for (unsigned int iFrame = 0; iFrame < (unsigned int)groupInfo.pcGroup->numframes; ++iFrame) {
1177
0
        MDL::IntFrameInfo_MDL7 frame((BE_NCONST MDL::Frame_MDL7 *)szCurrent, iFrame);
1178
1179
0
        VALIDATE_FILE_SIZE((const unsigned char *)(frame.pcFrame + 1));
1180
0
        AI_SWAP4(frame.pcFrame->vertices_count);
1181
0
        AI_SWAP4(frame.pcFrame->transmatrix_count);
1182
1183
0
        const unsigned int iAdd = pcHeader->frame_stc_size +
1184
0
                                  frame.pcFrame->vertices_count * pcHeader->framevertex_stc_size +
1185
0
                                  frame.pcFrame->transmatrix_count * pcHeader->bonetrans_stc_size;
1186
1187
0
        if (((const char *)szCurrent - (const char *)pcHeader) + iAdd > (unsigned int)pcHeader->data_size) {
1188
0
            ASSIMP_LOG_WARN("Index overflow in frame area. "
1189
0
                            "Ignoring all frames and all further mesh groups, too.");
1190
1191
            // don't parse more groups if we can't even read one
1192
            // FIXME: sometimes this seems to occur even for valid files ...
1193
0
            *szCurrentOut = szCurrent;
1194
0
            return false;
1195
0
        }
1196
        // our output frame?
1197
0
        if (configFrameID == iFrame) {
1198
0
            BE_NCONST MDL::Vertex_MDL7 *pcFrameVertices = (BE_NCONST MDL::Vertex_MDL7 *)(szCurrent + pcHeader->frame_stc_size);
1199
1200
0
            for (unsigned int qq = 0; qq < frame.pcFrame->vertices_count; ++qq) {
1201
                // I assume this are simple replacements for normal vertices, the bone index serving
1202
                // as the index of the vertex to be replaced.
1203
0
                uint16_t iIndex = _AI_MDL7_ACCESS(pcFrameVertices, qq, pcHeader->framevertex_stc_size, MDL::Vertex_MDL7).vertindex;
1204
0
                AI_SWAP2(iIndex);
1205
0
                if (iIndex >= groupInfo.pcGroup->numverts) {
1206
0
                    ASSIMP_LOG_WARN("Invalid vertex index in frame vertex section");
1207
0
                    continue;
1208
0
                }
1209
1210
0
                aiVector3D vPosition, vNormal;
1211
1212
0
                vPosition.x = _AI_MDL7_ACCESS_VERT(pcFrameVertices, qq, pcHeader->framevertex_stc_size).x;
1213
0
                AI_SWAP4(vPosition.x);
1214
0
                vPosition.y = _AI_MDL7_ACCESS_VERT(pcFrameVertices, qq, pcHeader->framevertex_stc_size).y;
1215
0
                AI_SWAP4(vPosition.y);
1216
0
                vPosition.z = _AI_MDL7_ACCESS_VERT(pcFrameVertices, qq, pcHeader->framevertex_stc_size).z;
1217
0
                AI_SWAP4(vPosition.z);
1218
1219
                // now read the normal vector
1220
0
                if (AI_MDL7_FRAMEVERTEX030305_STCSIZE <= pcHeader->mainvertex_stc_size) {
1221
                    // read the full normal vector
1222
0
                    vNormal.x = _AI_MDL7_ACCESS_VERT(pcFrameVertices, qq, pcHeader->framevertex_stc_size).norm[0];
1223
0
                    AI_SWAP4(vNormal.x);
1224
0
                    vNormal.y = _AI_MDL7_ACCESS_VERT(pcFrameVertices, qq, pcHeader->framevertex_stc_size).norm[1];
1225
0
                    AI_SWAP4(vNormal.y);
1226
0
                    vNormal.z = _AI_MDL7_ACCESS_VERT(pcFrameVertices, qq, pcHeader->framevertex_stc_size).norm[2];
1227
0
                    AI_SWAP4(vNormal.z);
1228
0
                } else if (AI_MDL7_FRAMEVERTEX120503_STCSIZE <= pcHeader->mainvertex_stc_size) {
1229
                    // read the normal vector from Quake2's smart table
1230
0
                    MD2::LookupNormalIndex(_AI_MDL7_ACCESS_VERT(pcFrameVertices, qq,
1231
0
                                                   pcHeader->framevertex_stc_size)
1232
0
                                                   .norm162index,
1233
0
                            vNormal);
1234
0
                }
1235
1236
                // FIXME: O(n^2) at the moment ...
1237
0
                BE_NCONST MDL::Triangle_MDL7 *pcGroupTris = groupInfo.pcGroupTris;
1238
0
                unsigned int iOutIndex = 0;
1239
0
                for (unsigned int iTriangle = 0; iTriangle < (unsigned int)groupInfo.pcGroup->numtris; ++iTriangle) {
1240
                    // iterate through all indices of the current triangle
1241
0
                    for (unsigned int c = 0; c < 3; ++c, ++iOutIndex) {
1242
                        // replace the vertex with the new data
1243
0
                        const unsigned int iCurIndex = pcGroupTris->v_index[c];
1244
0
                        if (iCurIndex == iIndex) {
1245
0
                            groupData.vPositions[iOutIndex] = vPosition;
1246
0
                            groupData.vNormals[iOutIndex] = vNormal;
1247
0
                        }
1248
0
                    }
1249
                    // get the next triangle in the list
1250
0
                    pcGroupTris = (BE_NCONST MDL::Triangle_MDL7 *)((const char *)
1251
0
                                                                           pcGroupTris +
1252
0
                                                                   pcHeader->triangle_stc_size);
1253
0
                }
1254
0
            }
1255
0
        }
1256
        // parse bone trafo matrix keys (only if there are bones ...)
1257
0
        if (shared.apcOutBones) {
1258
0
            ParseBoneTrafoKeys_3DGS_MDL7(groupInfo, frame, shared);
1259
0
        }
1260
0
        szCurrent += iAdd;
1261
0
    }
1262
20
    *szCurrentOut = szCurrent;
1263
20
    return true;
1264
20
}
1265
1266
// ------------------------------------------------------------------------------------------------
1267
// Sort faces by material, handle multiple UVs correctly
1268
void MDLImporter::SortByMaterials_3DGS_MDL7(
1269
        const MDL::IntGroupInfo_MDL7 &groupInfo,
1270
        MDL::IntGroupData_MDL7 &groupData,
1271
20
        MDL::IntSplitGroupData_MDL7 &splitGroupData) {
1272
20
    const unsigned int iNumMaterials = (unsigned int)splitGroupData.shared.pcMats.size();
1273
20
    if (!groupData.bNeed2UV) {
1274
        // if we don't need a second set of texture coordinates there is no reason to keep it in memory ...
1275
20
        groupData.vTextureCoords2.clear();
1276
1277
        // allocate the array
1278
20
        splitGroupData.aiSplit = new std::vector<unsigned int> *[iNumMaterials];
1279
1280
80
        for (unsigned int m = 0; m < iNumMaterials; ++m)
1281
60
            splitGroupData.aiSplit[m] = new std::vector<unsigned int>();
1282
1283
        // iterate through all faces and sort by material
1284
2.42k
        for (unsigned int iFace = 0; iFace < (unsigned int)groupInfo.pcGroup->numtris; ++iFace) {
1285
            // check range
1286
2.40k
            if (groupData.pcFaces[iFace].iMatIndex[0] >= iNumMaterials) {
1287
                // use the last material instead
1288
3
                splitGroupData.aiSplit[iNumMaterials - 1]->push_back(iFace);
1289
1290
                // sometimes MED writes -1, but normally only if there is only
1291
                // one skin assigned. No warning in this case
1292
3
                if (0xFFFFFFFF != groupData.pcFaces[iFace].iMatIndex[0])
1293
3
                    ASSIMP_LOG_WARN("Index overflow in MDL7 material list [#0]");
1294
3
            } else
1295
2.39k
                splitGroupData.aiSplit[groupData.pcFaces[iFace].iMatIndex[0]]->push_back(iFace);
1296
2.40k
        }
1297
20
    } else {
1298
        // we need to build combined materials for each combination of
1299
0
        std::vector<MDL::IntMaterial_MDL7> avMats;
1300
0
        avMats.reserve(iNumMaterials * 2);
1301
1302
        // fixme: why on the heap?
1303
0
        std::vector<std::vector<unsigned int> *> aiTempSplit(iNumMaterials * 2);
1304
0
        for (unsigned int m = 0; m < iNumMaterials; ++m)
1305
0
            aiTempSplit[m] = new std::vector<unsigned int>();
1306
1307
        // iterate through all faces and sort by material
1308
0
        for (unsigned int iFace = 0; iFace < (unsigned int)groupInfo.pcGroup->numtris; ++iFace) {
1309
            // check range
1310
0
            unsigned int iMatIndex = groupData.pcFaces[iFace].iMatIndex[0];
1311
0
            if (iMatIndex >= iNumMaterials) {
1312
                // sometimes MED writes -1, but normally only if there is only
1313
                // one skin assigned. No warning in this case
1314
0
                if (UINT_MAX != iMatIndex)
1315
0
                    ASSIMP_LOG_WARN("Index overflow in MDL7 material list [#1]");
1316
0
                iMatIndex = iNumMaterials - 1;
1317
0
            }
1318
0
            unsigned int iMatIndex2 = groupData.pcFaces[iFace].iMatIndex[1];
1319
1320
0
            unsigned int iNum = iMatIndex;
1321
0
            if (UINT_MAX != iMatIndex2 && iMatIndex != iMatIndex2) {
1322
0
                if (iMatIndex2 >= iNumMaterials) {
1323
                    // sometimes MED writes -1, but normally only if there is only
1324
                    // one skin assigned. No warning in this case
1325
0
                    ASSIMP_LOG_WARN("Index overflow in MDL7 material list [#2]");
1326
0
                    iMatIndex2 = iNumMaterials - 1;
1327
0
                }
1328
1329
                // do a slow search in the list ...
1330
0
                iNum = 0;
1331
0
                bool bFound = false;
1332
0
                for (std::vector<MDL::IntMaterial_MDL7>::iterator i = avMats.begin(); i != avMats.end(); ++i, ++iNum) {
1333
0
                    if ((*i).iOldMatIndices[0] == iMatIndex && (*i).iOldMatIndices[1] == iMatIndex2) {
1334
                        // reuse this material
1335
0
                        bFound = true;
1336
0
                        break;
1337
0
                    }
1338
0
                }
1339
0
                if (!bFound) {
1340
                    //  build a new material ...
1341
0
                    MDL::IntMaterial_MDL7 sHelper;
1342
0
                    sHelper.pcMat = new aiMaterial();
1343
0
                    sHelper.iOldMatIndices[0] = iMatIndex;
1344
0
                    sHelper.iOldMatIndices[1] = iMatIndex2;
1345
0
                    JoinSkins_3DGS_MDL7(splitGroupData.shared.pcMats[iMatIndex],
1346
0
                            splitGroupData.shared.pcMats[iMatIndex2], sHelper.pcMat);
1347
1348
                    // and add it to the list
1349
0
                    avMats.push_back(sHelper);
1350
0
                    iNum = (unsigned int)avMats.size() - 1;
1351
0
                }
1352
                // adjust the size of the file array
1353
0
                if (iNum == aiTempSplit.size()) {
1354
0
                    aiTempSplit.push_back(new std::vector<unsigned int>());
1355
0
                }
1356
0
            }
1357
0
            aiTempSplit[iNum]->push_back(iFace);
1358
0
        }
1359
1360
        // now add the newly created materials to the old list
1361
0
        if (0 == groupInfo.iIndex) {
1362
0
            splitGroupData.shared.pcMats.resize(avMats.size());
1363
0
            for (unsigned int o = 0; o < avMats.size(); ++o)
1364
0
                splitGroupData.shared.pcMats[o] = avMats[o].pcMat;
1365
0
        } else {
1366
            // This might result in redundant materials ...
1367
0
            splitGroupData.shared.pcMats.resize(iNumMaterials + avMats.size());
1368
0
            for (unsigned int o = iNumMaterials; o < avMats.size(); ++o)
1369
0
                splitGroupData.shared.pcMats[o] = avMats[o].pcMat;
1370
0
        }
1371
1372
        // and build the final face-to-material array
1373
0
        splitGroupData.aiSplit = new std::vector<unsigned int> *[aiTempSplit.size()];
1374
0
        for (unsigned int m = 0; m < iNumMaterials; ++m)
1375
0
            splitGroupData.aiSplit[m] = aiTempSplit[m];
1376
0
    }
1377
20
}
1378
1379
// ------------------------------------------------------------------------------------------------
1380
// Read a MDL7 file
1381
4
void MDLImporter::InternReadFile_3DGS_MDL7() {
1382
4
    ai_assert(nullptr != pScene);
1383
1384
4
    MDL::IntSharedData_MDL7 sharedData;
1385
1386
    // current cursor position in the file
1387
4
    BE_NCONST MDL::Header_MDL7 *pcHeader = (BE_NCONST MDL::Header_MDL7 *)this->mBuffer;
1388
4
    const unsigned char *szCurrent = (const unsigned char *)(pcHeader + 1);
1389
1390
4
    AI_SWAP4(pcHeader->version);
1391
4
    AI_SWAP4(pcHeader->bones_num);
1392
4
    AI_SWAP4(pcHeader->groups_num);
1393
4
    AI_SWAP4(pcHeader->data_size);
1394
4
    AI_SWAP4(pcHeader->entlump_size);
1395
4
    AI_SWAP4(pcHeader->medlump_size);
1396
4
    AI_SWAP2(pcHeader->bone_stc_size);
1397
4
    AI_SWAP2(pcHeader->skin_stc_size);
1398
4
    AI_SWAP2(pcHeader->colorvalue_stc_size);
1399
4
    AI_SWAP2(pcHeader->material_stc_size);
1400
4
    AI_SWAP2(pcHeader->skinpoint_stc_size);
1401
4
    AI_SWAP2(pcHeader->triangle_stc_size);
1402
4
    AI_SWAP2(pcHeader->mainvertex_stc_size);
1403
4
    AI_SWAP2(pcHeader->framevertex_stc_size);
1404
4
    AI_SWAP2(pcHeader->bonetrans_stc_size);
1405
4
    AI_SWAP2(pcHeader->frame_stc_size);
1406
1407
    // validate the header of the file. There are some structure
1408
    // sizes that are expected by the loader to be constant
1409
4
    this->ValidateHeader_3DGS_MDL7(pcHeader);
1410
1411
    // load all bones (they are shared by all groups, so
1412
    // we'll need to add them to all groups/meshes later)
1413
    // apcBonesOut is a list of all bones or nullptr if they could not been loaded
1414
4
    szCurrent += pcHeader->bones_num * pcHeader->bone_stc_size;
1415
4
    sharedData.apcOutBones = this->LoadBones_3DGS_MDL7();
1416
1417
    // vector to held all created meshes
1418
4
    std::vector<aiMesh *> *avOutList;
1419
1420
    // 3 meshes per group - that should be OK for most models
1421
4
    avOutList = new std::vector<aiMesh *>[pcHeader->groups_num];
1422
36
    for (uint32_t i = 0; i < pcHeader->groups_num; ++i)
1423
32
        avOutList[i].reserve(3);
1424
1425
    // buffer to held the names of all groups in the file
1426
4
    const size_t buffersize(AI_MDL7_MAX_GROUPNAMESIZE * pcHeader->groups_num);
1427
4
    char *aszGroupNameBuffer = new char[buffersize];
1428
1429
    // read all groups
1430
27
    for (unsigned int iGroup = 0; iGroup < (unsigned int)pcHeader->groups_num; ++iGroup) {
1431
23
        MDL::IntGroupInfo_MDL7 groupInfo((BE_NCONST MDL::Group_MDL7 *)szCurrent, iGroup);
1432
23
        szCurrent = (const unsigned char *)(groupInfo.pcGroup + 1);
1433
1434
23
        VALIDATE_FILE_SIZE(szCurrent);
1435
1436
23
        AI_SWAP4(groupInfo.pcGroup->groupdata_size);
1437
23
        AI_SWAP4(groupInfo.pcGroup->numskins);
1438
23
        AI_SWAP4(groupInfo.pcGroup->num_stpts);
1439
23
        AI_SWAP4(groupInfo.pcGroup->numtris);
1440
23
        AI_SWAP4(groupInfo.pcGroup->numverts);
1441
23
        AI_SWAP4(groupInfo.pcGroup->numframes);
1442
1443
23
        if (1 != groupInfo.pcGroup->typ) {
1444
            // Not a triangle-based mesh
1445
4
            ASSIMP_LOG_WARN("[3DGS MDL7] Not a triangle mesh group. Continuing happily");
1446
4
        }
1447
1448
        // store the name of the group
1449
23
        const unsigned int ofs = iGroup * AI_MDL7_MAX_GROUPNAMESIZE;
1450
23
        ::memcpy(&aszGroupNameBuffer[ofs],
1451
23
                groupInfo.pcGroup->name, AI_MDL7_MAX_GROUPNAMESIZE);
1452
1453
        // make sure '\0' is at the end
1454
23
        aszGroupNameBuffer[ofs + AI_MDL7_MAX_GROUPNAMESIZE - 1] = '\0';
1455
1456
        // read all skins
1457
23
        sharedData.pcMats.reserve(sharedData.pcMats.size() + groupInfo.pcGroup->numskins);
1458
23
        sharedData.abNeedMaterials.resize(sharedData.abNeedMaterials.size() +
1459
23
                                                  groupInfo.pcGroup->numskins,
1460
23
                false);
1461
1462
37
        for (unsigned int iSkin = 0; iSkin < (unsigned int)groupInfo.pcGroup->numskins; ++iSkin) {
1463
14
            ParseSkinLump_3DGS_MDL7(szCurrent, &szCurrent, sharedData.pcMats);
1464
14
        }
1465
        // if we have absolutely no skin loaded we need to generate a default material
1466
23
        if (sharedData.pcMats.empty()) {
1467
0
            const int iMode = (int)aiShadingMode_Gouraud;
1468
0
            sharedData.pcMats.push_back(new aiMaterial());
1469
0
            aiMaterial *pcHelper = (aiMaterial *)sharedData.pcMats[0];
1470
0
            pcHelper->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL);
1471
1472
0
            aiColor3D clr;
1473
0
            clr.b = clr.g = clr.r = 0.6f;
1474
0
            pcHelper->AddProperty<aiColor3D>(&clr, 1, AI_MATKEY_COLOR_DIFFUSE);
1475
0
            pcHelper->AddProperty<aiColor3D>(&clr, 1, AI_MATKEY_COLOR_SPECULAR);
1476
1477
0
            clr.b = clr.g = clr.r = 0.05f;
1478
0
            pcHelper->AddProperty<aiColor3D>(&clr, 1, AI_MATKEY_COLOR_AMBIENT);
1479
1480
0
            aiString szName;
1481
0
            szName.Set(AI_DEFAULT_MATERIAL_NAME);
1482
0
            pcHelper->AddProperty(&szName, AI_MATKEY_NAME);
1483
1484
0
            sharedData.abNeedMaterials.resize(1, false);
1485
0
        }
1486
1487
        // now get a pointer to all texture coords in the group
1488
23
        groupInfo.pcGroupUVs = (BE_NCONST MDL::TexCoord_MDL7 *)szCurrent;
1489
23
        for (int i = 0; i < groupInfo.pcGroup->num_stpts; ++i) {
1490
0
            AI_SWAP4(groupInfo.pcGroupUVs[i].u);
1491
0
            AI_SWAP4(groupInfo.pcGroupUVs[i].v);
1492
0
        }
1493
23
        szCurrent += pcHeader->skinpoint_stc_size * groupInfo.pcGroup->num_stpts;
1494
1495
        // now get a pointer to all triangle in the group
1496
23
        groupInfo.pcGroupTris = (Triangle_MDL7 *)szCurrent;
1497
23
        szCurrent += pcHeader->triangle_stc_size * groupInfo.pcGroup->numtris;
1498
1499
        // now get a pointer to all vertices in the group
1500
23
        groupInfo.pcGroupVerts = (BE_NCONST MDL::Vertex_MDL7 *)szCurrent;
1501
1.26k
        for (int i = 0; i < groupInfo.pcGroup->numverts; ++i) {
1502
1.24k
            AI_SWAP4(groupInfo.pcGroupVerts[i].x);
1503
1.24k
            AI_SWAP4(groupInfo.pcGroupVerts[i].y);
1504
1.24k
            AI_SWAP4(groupInfo.pcGroupVerts[i].z);
1505
1506
1.24k
            AI_SWAP2(groupInfo.pcGroupVerts[i].vertindex);
1507
            //We can not swap the normal information now as we don't know which of the two kinds it is
1508
1.24k
        }
1509
23
        szCurrent += pcHeader->mainvertex_stc_size * groupInfo.pcGroup->numverts;
1510
23
        VALIDATE_FILE_SIZE(szCurrent);
1511
1512
23
        MDL::IntSplitGroupData_MDL7 splitGroupData(sharedData, avOutList[iGroup]);
1513
23
        MDL::IntGroupData_MDL7 groupData;
1514
23
        if (groupInfo.pcGroup->numtris && groupInfo.pcGroup->numverts) {
1515
            // build output vectors
1516
20
            const unsigned int iNumVertices = groupInfo.pcGroup->numtris * 3;
1517
20
            groupData.vPositions.resize(iNumVertices);
1518
20
            groupData.vNormals.resize(iNumVertices);
1519
1520
20
            if (sharedData.apcOutBones) groupData.aiBones.resize(iNumVertices, UINT_MAX);
1521
1522
            // it is also possible that there are 0 UV coordinate sets
1523
20
            if (groupInfo.pcGroup->num_stpts) {
1524
0
                groupData.vTextureCoords1.resize(iNumVertices, aiVector3D());
1525
1526
                // check whether the triangle data structure is large enough
1527
                // to contain a second UV coordinate set
1528
0
                if (pcHeader->triangle_stc_size >= AI_MDL7_TRIANGLE_STD_SIZE_TWO_UV) {
1529
0
                    groupData.vTextureCoords2.resize(iNumVertices, aiVector3D());
1530
0
                    groupData.bNeed2UV = true;
1531
0
                }
1532
0
            }
1533
20
            groupData.pcFaces.resize(groupInfo.pcGroup->numtris);
1534
1535
            // read all faces into the preallocated arrays
1536
20
            ReadFaces_3DGS_MDL7(groupInfo, groupData);
1537
1538
            // sort by materials
1539
20
            SortByMaterials_3DGS_MDL7(groupInfo, groupData,
1540
20
                    splitGroupData);
1541
1542
80
            for (unsigned int qq = 0; qq < sharedData.pcMats.size(); ++qq) {
1543
60
                if (!splitGroupData.aiSplit[qq]->empty())
1544
20
                    sharedData.abNeedMaterials[qq] = true;
1545
60
            }
1546
20
        } else
1547
23
            ASSIMP_LOG_WARN("[3DGS MDL7] Mesh group consists of 0 "
1548
23
                            "vertices or faces. It will be skipped.");
1549
1550
        // process all frames and generate output meshes
1551
23
        ProcessFrames_3DGS_MDL7(groupInfo, groupData, sharedData, szCurrent, &szCurrent);
1552
23
        GenerateOutputMeshes_3DGS_MDL7(groupData, splitGroupData);
1553
23
    }
1554
1555
    // generate a nodegraph and subnodes for each group
1556
4
    pScene->mRootNode = new aiNode();
1557
1558
    // now we need to build a final mesh list
1559
12
    for (uint32_t i = 0; i < pcHeader->groups_num; ++i)
1560
8
        pScene->mNumMeshes += (unsigned int)avOutList[i].size();
1561
1562
4
    pScene->mMeshes = new aiMesh *[pScene->mNumMeshes];
1563
4
    {
1564
4
        unsigned int p = 0, q = 0;
1565
12
        for (uint32_t i = 0; i < pcHeader->groups_num; ++i) {
1566
16
            for (unsigned int a = 0; a < avOutList[i].size(); ++a) {
1567
8
                pScene->mMeshes[p++] = avOutList[i][a];
1568
8
            }
1569
8
            if (!avOutList[i].empty()) ++pScene->mRootNode->mNumChildren;
1570
8
        }
1571
        // we will later need an extra node to serve as parent for all bones
1572
4
        if (sharedData.apcOutBones) ++pScene->mRootNode->mNumChildren;
1573
4
        this->pScene->mRootNode->mChildren = new aiNode *[pScene->mRootNode->mNumChildren];
1574
4
        p = 0;
1575
12
        for (uint32_t i = 0; i < pcHeader->groups_num; ++i) {
1576
8
            if (avOutList[i].empty()) continue;
1577
1578
8
            aiNode *const pcNode = pScene->mRootNode->mChildren[p] = new aiNode();
1579
8
            pcNode->mNumMeshes = (unsigned int)avOutList[i].size();
1580
8
            pcNode->mMeshes = new unsigned int[pcNode->mNumMeshes];
1581
8
            pcNode->mParent = this->pScene->mRootNode;
1582
16
            for (unsigned int a = 0; a < pcNode->mNumMeshes; ++a)
1583
8
                pcNode->mMeshes[a] = q + a;
1584
8
            q += (unsigned int)avOutList[i].size();
1585
1586
            // setup the name of the node
1587
8
            char *const szBuffer = &aszGroupNameBuffer[i * AI_MDL7_MAX_GROUPNAMESIZE];
1588
8
            if ('\0' == *szBuffer) {
1589
8
                const size_t maxSize(buffersize - (i * AI_MDL7_MAX_GROUPNAMESIZE));
1590
8
                pcNode->mName.length = ai_snprintf(szBuffer, maxSize, "Group_%u", p);
1591
8
            } else {
1592
0
                pcNode->mName.length = (ai_uint32)::strlen(szBuffer);
1593
0
            }
1594
8
            ::strncpy(pcNode->mName.data, szBuffer, AI_MAXLEN - 1);
1595
8
            ++p;
1596
8
        }
1597
4
    }
1598
1599
    // if there is only one root node with a single child we can optimize it a bit ...
1600
4
    if (1 == pScene->mRootNode->mNumChildren && !sharedData.apcOutBones) {
1601
0
        aiNode *pcOldRoot = this->pScene->mRootNode;
1602
0
        pScene->mRootNode = pcOldRoot->mChildren[0];
1603
0
        pcOldRoot->mChildren[0] = nullptr;
1604
0
        delete pcOldRoot;
1605
0
        pScene->mRootNode->mParent = nullptr;
1606
0
    } else
1607
4
        pScene->mRootNode->mName.Set("<mesh_root>");
1608
1609
4
    delete[] avOutList;
1610
4
    delete[] aszGroupNameBuffer;
1611
4
    AI_DEBUG_INVALIDATE_PTR(avOutList);
1612
4
    AI_DEBUG_INVALIDATE_PTR(aszGroupNameBuffer);
1613
1614
    // build a final material list.
1615
4
    CopyMaterials_3DGS_MDL7(sharedData);
1616
4
    HandleMaterialReferences_3DGS_MDL7();
1617
1618
    // generate output bone animations and add all bones to the scenegraph
1619
4
    if (sharedData.apcOutBones) {
1620
        // this step adds empty dummy bones to the nodegraph
1621
        // insert another dummy node to avoid name conflicts
1622
0
        aiNode *const pc = pScene->mRootNode->mChildren[pScene->mRootNode->mNumChildren - 1] = new aiNode();
1623
1624
0
        pc->mName.Set("<skeleton_root>");
1625
1626
        // add bones to the nodegraph
1627
0
        AddBonesToNodeGraph_3DGS_MDL7((const Assimp::MDL::IntBone_MDL7 **)
1628
0
                                              sharedData.apcOutBones,
1629
0
                pc, 0xffff);
1630
1631
        // this steps build a valid output animation
1632
0
        BuildOutputAnims_3DGS_MDL7((const Assimp::MDL::IntBone_MDL7 **)
1633
0
                                           sharedData.apcOutBones);
1634
0
    }
1635
4
}
1636
1637
// ------------------------------------------------------------------------------------------------
1638
// Copy materials
1639
1
void MDLImporter::CopyMaterials_3DGS_MDL7(MDL::IntSharedData_MDL7 &shared) {
1640
1
    pScene->mNumMaterials = (unsigned int)shared.pcMats.size();
1641
1
    pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials];
1642
4
    for (unsigned int i = 0; i < pScene->mNumMaterials; ++i)
1643
3
        pScene->mMaterials[i] = shared.pcMats[i];
1644
1
}
1645
1646
// ------------------------------------------------------------------------------------------------
1647
// Process material references
1648
1
void MDLImporter::HandleMaterialReferences_3DGS_MDL7() {
1649
    // search for referrer materials
1650
4
    for (unsigned int i = 0; i < pScene->mNumMaterials; ++i) {
1651
3
        int iIndex = 0;
1652
3
        if (AI_SUCCESS == aiGetMaterialInteger(pScene->mMaterials[i], AI_MDL7_REFERRER_MATERIAL, &iIndex)) {
1653
0
            for (unsigned int a = 0; a < pScene->mNumMeshes; ++a) {
1654
0
                aiMesh *const pcMesh = pScene->mMeshes[a];
1655
0
                if (i == pcMesh->mMaterialIndex) {
1656
0
                    pcMesh->mMaterialIndex = iIndex;
1657
0
                }
1658
0
            }
1659
            // collapse the rest of the array
1660
0
            delete pScene->mMaterials[i];
1661
0
            for (unsigned int pp = i; pp < pScene->mNumMaterials - 1; ++pp) {
1662
1663
0
                pScene->mMaterials[pp] = pScene->mMaterials[pp + 1];
1664
0
                for (unsigned int a = 0; a < pScene->mNumMeshes; ++a) {
1665
0
                    aiMesh *const pcMesh = pScene->mMeshes[a];
1666
0
                    if (pcMesh->mMaterialIndex > i) --pcMesh->mMaterialIndex;
1667
0
                }
1668
0
            }
1669
0
            --pScene->mNumMaterials;
1670
0
        }
1671
3
    }
1672
1
}
1673
1674
// ------------------------------------------------------------------------------------------------
1675
// Read bone transformation keys
1676
void MDLImporter::ParseBoneTrafoKeys_3DGS_MDL7(
1677
        const MDL::IntGroupInfo_MDL7 &groupInfo,
1678
        IntFrameInfo_MDL7 &frame,
1679
0
        MDL::IntSharedData_MDL7 &shared) {
1680
0
    const MDL::Header_MDL7 *const pcHeader = (const MDL::Header_MDL7 *)this->mBuffer;
1681
1682
    // only the first group contains bone animation keys
1683
0
    if (frame.pcFrame->transmatrix_count) {
1684
0
        if (!groupInfo.iIndex) {
1685
            // skip all frames vertices. We can't support them
1686
0
            const MDL::BoneTransform_MDL7 *pcBoneTransforms = (const MDL::BoneTransform_MDL7 *)(((const char *)frame.pcFrame) + pcHeader->frame_stc_size +
1687
0
                                                                                                frame.pcFrame->vertices_count * pcHeader->framevertex_stc_size);
1688
1689
            // read all transformation matrices
1690
0
            for (unsigned int iTrafo = 0; iTrafo < frame.pcFrame->transmatrix_count; ++iTrafo) {
1691
0
                if (pcBoneTransforms->bone_index >= pcHeader->bones_num) {
1692
0
                    ASSIMP_LOG_WARN("Index overflow in frame area. "
1693
0
                                    "Unable to parse this bone transformation");
1694
0
                } else {
1695
0
                    AddAnimationBoneTrafoKey_3DGS_MDL7(frame.iIndex,
1696
0
                            pcBoneTransforms, shared.apcOutBones);
1697
0
                }
1698
0
                pcBoneTransforms = (const MDL::BoneTransform_MDL7 *)((const char *)pcBoneTransforms + pcHeader->bonetrans_stc_size);
1699
0
            }
1700
0
        } else {
1701
0
            ASSIMP_LOG_WARN("Ignoring animation keyframes in groups != 0");
1702
0
        }
1703
0
    }
1704
0
}
1705
1706
// ------------------------------------------------------------------------------------------------
1707
// Attach bones to the output nodegraph
1708
void MDLImporter::AddBonesToNodeGraph_3DGS_MDL7(const MDL::IntBone_MDL7 **apcBones,
1709
0
        aiNode *pcParent, uint16_t iParentIndex) {
1710
0
    ai_assert(nullptr != apcBones);
1711
0
    ai_assert(nullptr != pcParent);
1712
1713
    // get a pointer to the header ...
1714
0
    const MDL::Header_MDL7 *const pcHeader = (const MDL::Header_MDL7 *)this->mBuffer;
1715
1716
0
    const MDL::IntBone_MDL7 **apcBones2 = apcBones;
1717
0
    for (uint32_t i = 0; i < pcHeader->bones_num; ++i) {
1718
1719
0
        const MDL::IntBone_MDL7 *const pcBone = *apcBones2++;
1720
0
        if (pcBone->iParent == iParentIndex) {
1721
0
            ++pcParent->mNumChildren;
1722
0
        }
1723
0
    }
1724
0
    pcParent->mChildren = new aiNode *[pcParent->mNumChildren];
1725
0
    unsigned int qq = 0;
1726
0
    for (uint32_t i = 0; i < pcHeader->bones_num; ++i) {
1727
1728
0
        const MDL::IntBone_MDL7 *const pcBone = *apcBones++;
1729
0
        if (pcBone->iParent != iParentIndex) continue;
1730
1731
0
        aiNode *pcNode = pcParent->mChildren[qq++] = new aiNode();
1732
0
        pcNode->mName = aiString(pcBone->mName);
1733
1734
0
        AddBonesToNodeGraph_3DGS_MDL7(apcBones, pcNode, (uint16_t)i);
1735
0
    }
1736
0
}
1737
1738
// ------------------------------------------------------------------------------------------------
1739
// Build output animations
1740
void MDLImporter::BuildOutputAnims_3DGS_MDL7(
1741
0
        const MDL::IntBone_MDL7 **apcBonesOut) {
1742
0
    ai_assert(nullptr != apcBonesOut);
1743
0
    const MDL::Header_MDL7 *const pcHeader = (const MDL::Header_MDL7 *)mBuffer;
1744
1745
    // one animation ...
1746
0
    aiAnimation *pcAnim = new aiAnimation();
1747
0
    for (uint32_t i = 0; i < pcHeader->bones_num; ++i) {
1748
0
        if (!apcBonesOut[i]->pkeyPositions.empty()) {
1749
1750
            // get the last frame ... (needn't be equal to pcHeader->frames_num)
1751
0
            for (size_t qq = 0; qq < apcBonesOut[i]->pkeyPositions.size(); ++qq) {
1752
0
                pcAnim->mDuration = std::max(pcAnim->mDuration, (double)
1753
0
                                                                        apcBonesOut[i]
1754
0
                                                                                ->pkeyPositions[qq]
1755
0
                                                                                .mTime);
1756
0
            }
1757
0
            ++pcAnim->mNumChannels;
1758
0
        }
1759
0
    }
1760
0
    if (pcAnim->mDuration) {
1761
0
        pcAnim->mChannels = new aiNodeAnim *[pcAnim->mNumChannels];
1762
1763
0
        unsigned int iCnt = 0;
1764
0
        for (uint32_t i = 0; i < pcHeader->bones_num; ++i) {
1765
0
            if (!apcBonesOut[i]->pkeyPositions.empty()) {
1766
0
                const MDL::IntBone_MDL7 *const intBone = apcBonesOut[i];
1767
1768
0
                aiNodeAnim *const pcNodeAnim = pcAnim->mChannels[iCnt++] = new aiNodeAnim();
1769
0
                pcNodeAnim->mNodeName = aiString(intBone->mName);
1770
1771
                // allocate enough storage for all keys
1772
0
                pcNodeAnim->mNumPositionKeys = (unsigned int)intBone->pkeyPositions.size();
1773
0
                pcNodeAnim->mNumScalingKeys = (unsigned int)intBone->pkeyPositions.size();
1774
0
                pcNodeAnim->mNumRotationKeys = (unsigned int)intBone->pkeyPositions.size();
1775
1776
0
                pcNodeAnim->mPositionKeys = new aiVectorKey[pcNodeAnim->mNumPositionKeys];
1777
0
                pcNodeAnim->mScalingKeys = new aiVectorKey[pcNodeAnim->mNumPositionKeys];
1778
0
                pcNodeAnim->mRotationKeys = new aiQuatKey[pcNodeAnim->mNumPositionKeys];
1779
1780
                // copy all keys
1781
0
                for (unsigned int qq = 0; qq < pcNodeAnim->mNumPositionKeys; ++qq) {
1782
0
                    pcNodeAnim->mPositionKeys[qq] = intBone->pkeyPositions[qq];
1783
0
                    pcNodeAnim->mScalingKeys[qq] = intBone->pkeyScalings[qq];
1784
0
                    pcNodeAnim->mRotationKeys[qq] = intBone->pkeyRotations[qq];
1785
0
                }
1786
0
            }
1787
0
        }
1788
1789
        // store the output animation
1790
0
        pScene->mNumAnimations = 1;
1791
0
        pScene->mAnimations = new aiAnimation *[1];
1792
0
        pScene->mAnimations[0] = pcAnim;
1793
0
    } else
1794
0
        delete pcAnim;
1795
0
}
1796
1797
// ------------------------------------------------------------------------------------------------
1798
void MDLImporter::AddAnimationBoneTrafoKey_3DGS_MDL7(unsigned int iTrafo,
1799
        const MDL::BoneTransform_MDL7 *pcBoneTransforms,
1800
0
        MDL::IntBone_MDL7 **apcBonesOut) {
1801
0
    ai_assert(nullptr != pcBoneTransforms);
1802
0
    ai_assert(nullptr != apcBonesOut);
1803
1804
    // first .. get the transformation matrix
1805
0
    aiMatrix4x4 mTransform;
1806
0
    mTransform.a1 = pcBoneTransforms->m[0];
1807
0
    mTransform.b1 = pcBoneTransforms->m[1];
1808
0
    mTransform.c1 = pcBoneTransforms->m[2];
1809
0
    mTransform.d1 = pcBoneTransforms->m[3];
1810
1811
0
    mTransform.a2 = pcBoneTransforms->m[4];
1812
0
    mTransform.b2 = pcBoneTransforms->m[5];
1813
0
    mTransform.c2 = pcBoneTransforms->m[6];
1814
0
    mTransform.d2 = pcBoneTransforms->m[7];
1815
1816
0
    mTransform.a3 = pcBoneTransforms->m[8];
1817
0
    mTransform.b3 = pcBoneTransforms->m[9];
1818
0
    mTransform.c3 = pcBoneTransforms->m[10];
1819
0
    mTransform.d3 = pcBoneTransforms->m[11];
1820
1821
    // now decompose the transformation matrix into separate
1822
    // scaling, rotation and translation
1823
0
    aiVectorKey vScaling, vPosition;
1824
0
    aiQuatKey qRotation;
1825
1826
    // FIXME: Decompose will assert in debug builds if the matrix is invalid ...
1827
0
    mTransform.Decompose(vScaling.mValue, qRotation.mValue, vPosition.mValue);
1828
1829
    // now generate keys
1830
0
    vScaling.mTime = qRotation.mTime = vPosition.mTime = (double)iTrafo;
1831
1832
    // add the keys to the bone
1833
0
    MDL::IntBone_MDL7 *const pcBoneOut = apcBonesOut[pcBoneTransforms->bone_index];
1834
0
    pcBoneOut->pkeyPositions.push_back(vPosition);
1835
0
    pcBoneOut->pkeyScalings.push_back(vScaling);
1836
0
    pcBoneOut->pkeyRotations.push_back(qRotation);
1837
0
}
1838
1839
// ------------------------------------------------------------------------------------------------
1840
// Construct output meshes
1841
void MDLImporter::GenerateOutputMeshes_3DGS_MDL7(
1842
        MDL::IntGroupData_MDL7 &groupData,
1843
20
        MDL::IntSplitGroupData_MDL7 &splitGroupData) {
1844
20
    const MDL::IntSharedData_MDL7 &shared = splitGroupData.shared;
1845
1846
    // get a pointer to the header ...
1847
20
    const MDL::Header_MDL7 *const pcHeader = (const MDL::Header_MDL7 *)this->mBuffer;
1848
20
    const unsigned int iNumOutBones = pcHeader->bones_num;
1849
1850
80
    for (std::vector<aiMaterial *>::size_type i = 0; i < shared.pcMats.size(); ++i) {
1851
60
        if (splitGroupData.aiSplit == nullptr) {
1852
0
            continue;
1853
0
        }
1854
1855
60
        if (!splitGroupData.aiSplit[i]->empty()) {
1856
1857
            // allocate the output mesh
1858
20
            aiMesh *pcMesh = new aiMesh();
1859
1860
20
            pcMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
1861
20
            pcMesh->mMaterialIndex = (unsigned int)i;
1862
1863
            // allocate output storage
1864
20
            pcMesh->mNumFaces = (unsigned int)splitGroupData.aiSplit[i]->size();
1865
20
            pcMesh->mFaces = new aiFace[pcMesh->mNumFaces];
1866
1867
20
            pcMesh->mNumVertices = pcMesh->mNumFaces * 3;
1868
20
            pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices];
1869
20
            pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices];
1870
1871
20
            if (!groupData.vTextureCoords1.empty()) {
1872
0
                pcMesh->mNumUVComponents[0] = 2;
1873
0
                pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices];
1874
0
                if (!groupData.vTextureCoords2.empty()) {
1875
0
                    pcMesh->mNumUVComponents[1] = 2;
1876
0
                    pcMesh->mTextureCoords[1] = new aiVector3D[pcMesh->mNumVertices];
1877
0
                }
1878
0
            }
1879
1880
            // iterate through all faces and build an unique set of vertices
1881
20
            unsigned int iCurrent = 0;
1882
2.42k
            for (unsigned int iFace = 0; iFace < pcMesh->mNumFaces; ++iFace) {
1883
2.40k
                pcMesh->mFaces[iFace].mNumIndices = 3;
1884
2.40k
                pcMesh->mFaces[iFace].mIndices = new unsigned int[3];
1885
1886
2.40k
                unsigned int iSrcFace = splitGroupData.aiSplit[i]->operator[](iFace);
1887
2.40k
                const MDL::IntFace_MDL7 &oldFace = groupData.pcFaces[iSrcFace];
1888
1889
                // iterate through all face indices
1890
9.60k
                for (unsigned int c = 0; c < 3; ++c) {
1891
7.20k
                    const uint32_t iIndex = oldFace.mIndices[c];
1892
7.20k
                    pcMesh->mVertices[iCurrent] = groupData.vPositions[iIndex];
1893
7.20k
                    pcMesh->mNormals[iCurrent] = groupData.vNormals[iIndex];
1894
1895
7.20k
                    if (!groupData.vTextureCoords1.empty()) {
1896
1897
0
                        pcMesh->mTextureCoords[0][iCurrent] = groupData.vTextureCoords1[iIndex];
1898
0
                        if (!groupData.vTextureCoords2.empty()) {
1899
0
                            pcMesh->mTextureCoords[1][iCurrent] = groupData.vTextureCoords2[iIndex];
1900
0
                        }
1901
0
                    }
1902
7.20k
                    pcMesh->mFaces[iFace].mIndices[c] = iCurrent++;
1903
7.20k
                }
1904
2.40k
            }
1905
1906
            // if we have bones in the mesh we'll need to generate
1907
            // proper vertex weights for them
1908
20
            if (!groupData.aiBones.empty()) {
1909
0
                std::vector<std::vector<unsigned int>> aaiVWeightList;
1910
0
                aaiVWeightList.resize(iNumOutBones);
1911
1912
0
                int iCurrentWeight = 0;
1913
0
                for (unsigned int iFace = 0; iFace < pcMesh->mNumFaces; ++iFace) {
1914
0
                    unsigned int iSrcFace = splitGroupData.aiSplit[i]->operator[](iFace);
1915
0
                    const MDL::IntFace_MDL7 &oldFace = groupData.pcFaces[iSrcFace];
1916
1917
                    // iterate through all face indices
1918
0
                    for (unsigned int c = 0; c < 3; ++c) {
1919
0
                        unsigned int iBone = groupData.aiBones[oldFace.mIndices[c]];
1920
0
                        if (UINT_MAX != iBone) {
1921
0
                            if (iBone >= iNumOutBones) {
1922
0
                                ASSIMP_LOG_ERROR("Bone index overflow. "
1923
0
                                                 "The bone index of a vertex exceeds the allowed range. ");
1924
0
                                iBone = iNumOutBones - 1;
1925
0
                            }
1926
0
                            aaiVWeightList[iBone].push_back(iCurrentWeight);
1927
0
                        }
1928
0
                        ++iCurrentWeight;
1929
0
                    }
1930
0
                }
1931
                // now check which bones are required ...
1932
0
                for (std::vector<std::vector<unsigned int>>::const_iterator k = aaiVWeightList.begin(); k != aaiVWeightList.end(); ++k) {
1933
0
                    if (!(*k).empty()) {
1934
0
                        ++pcMesh->mNumBones;
1935
0
                    }
1936
0
                }
1937
0
                pcMesh->mBones = new aiBone *[pcMesh->mNumBones];
1938
0
                iCurrent = 0;
1939
0
                for (std::vector<std::vector<unsigned int>>::const_iterator k = aaiVWeightList.begin(); k != aaiVWeightList.end(); ++k, ++iCurrent) {
1940
0
                    if ((*k).empty())
1941
0
                        continue;
1942
1943
                    // seems we'll need this node
1944
0
                    aiBone *pcBone = pcMesh->mBones[iCurrent] = new aiBone();
1945
0
                    pcBone->mName = aiString(shared.apcOutBones[iCurrent]->mName);
1946
0
                    pcBone->mOffsetMatrix = shared.apcOutBones[iCurrent]->mOffsetMatrix;
1947
1948
                    // setup vertex weights
1949
0
                    pcBone->mNumWeights = (unsigned int)(*k).size();
1950
0
                    pcBone->mWeights = new aiVertexWeight[pcBone->mNumWeights];
1951
1952
0
                    for (unsigned int weight = 0; weight < pcBone->mNumWeights; ++weight) {
1953
0
                        pcBone->mWeights[weight].mVertexId = (*k)[weight];
1954
0
                        pcBone->mWeights[weight].mWeight = 1.0f;
1955
0
                    }
1956
0
                }
1957
0
            }
1958
            // add the mesh to the list of output meshes
1959
20
            splitGroupData.avOutList.push_back(pcMesh);
1960
20
        }
1961
60
    }
1962
20
}
1963
1964
// ------------------------------------------------------------------------------------------------
1965
// Join to materials
1966
void MDLImporter::JoinSkins_3DGS_MDL7(
1967
        aiMaterial *pcMat1,
1968
        aiMaterial *pcMat2,
1969
0
        aiMaterial *pcMatOut) {
1970
0
    ai_assert(nullptr != pcMat1);
1971
0
    ai_assert(nullptr != pcMat2);
1972
0
    ai_assert(nullptr != pcMatOut);
1973
1974
    // first create a full copy of the first skin property set
1975
    // and assign it to the output material
1976
0
    aiMaterial::CopyPropertyList(pcMatOut, pcMat1);
1977
1978
0
    int iVal = 0;
1979
0
    pcMatOut->AddProperty<int>(&iVal, 1, AI_MATKEY_UVWSRC_DIFFUSE(0));
1980
1981
    // then extract the diffuse texture from the second skin,
1982
    // setup 1 as UV source and we have it
1983
0
    aiString sString;
1984
0
    if (AI_SUCCESS == aiGetMaterialString(pcMat2, AI_MATKEY_TEXTURE_DIFFUSE(0), &sString)) {
1985
0
        iVal = 1;
1986
0
        pcMatOut->AddProperty<int>(&iVal, 1, AI_MATKEY_UVWSRC_DIFFUSE(1));
1987
0
        pcMatOut->AddProperty(&sString, AI_MATKEY_TEXTURE_DIFFUSE(1));
1988
0
    }
1989
0
}
1990
1991
// ------------------------------------------------------------------------------------------------
1992
// Read a Half-life 1 MDL
1993
4
void MDLImporter::InternReadFile_HL1(const std::string &pFile, const uint32_t iMagicWord) {
1994
    // We can't correctly load an MDL from a MDL "sequence" file.
1995
4
    if (iMagicWord == AI_MDL_MAGIC_NUMBER_BE_HL2b || iMagicWord == AI_MDL_MAGIC_NUMBER_LE_HL2b)
1996
0
        throw DeadlyImportError("Impossible to properly load a model from an MDL sequence file.");
1997
1998
    // Check if the buffer is large enough to hold the header
1999
4
    if (iFileSize < sizeof(HalfLife::Header_HL1)) {
2000
0
        throw DeadlyImportError("HL1 MDL file is too small to contain header.");
2001
0
    }
2002
2003
    // Read the MDL file.
2004
4
    HalfLife::HL1MDLLoader loader(
2005
4
            pScene,
2006
4
            mIOHandler,
2007
4
            mBuffer,
2008
4
            iFileSize,
2009
4
            pFile,
2010
4
            mHL1ImportSettings);
2011
4
}
2012
2013
// ------------------------------------------------------------------------------------------------
2014
// Read a half-life 2 MDL
2015
0
void MDLImporter::InternReadFile_HL2() {
2016
    //const MDL::Header_HL2* pcHeader = (const MDL::Header_HL2*)this->mBuffer;
2017
0
    throw DeadlyImportError("HL2 MDLs are not implemented");
2018
0
}
2019
2020
#endif // !! ASSIMP_BUILD_NO_MDL_IMPORTER