Coverage Report

Created: 2026-10-28 07:46

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