Coverage Report

Created: 2026-04-29 07:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/assimp/code/AssetLib/MDL/HalfLife/HL1MDLLoader.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 HL1MDLLoader.cpp
43
 *  @brief Implementation for the Half-Life 1 MDL loader.
44
 */
45
46
#include "HL1MDLLoader.h"
47
#include "HL1ImportDefinitions.h"
48
#include "HL1MeshTrivert.h"
49
#include "UniqueNameGenerator.h"
50
51
#include <assimp/BaseImporter.h>
52
#include <assimp/StringUtils.h>
53
#include <assimp/ai_assert.h>
54
#include <assimp/qnan.h>
55
#include <assimp/DefaultLogger.hpp>
56
#include <assimp/Importer.hpp>
57
58
#include <iomanip>
59
#include <sstream>
60
#include <map>
61
62
#ifdef MDL_HALFLIFE_LOG_WARN_HEADER
63
#undef MDL_HALFLIFE_LOG_WARN_HEADER
64
#endif
65
0
#define MDL_HALFLIFE_LOG_HEADER "[Half-Life 1 MDL] "
66
#include "LogFunctions.h"
67
68
namespace Assimp {
69
namespace MDL {
70
namespace HalfLife {
71
72
#ifdef _MSC_VER
73
#    pragma warning(disable : 4706)
74
#endif // _MSC_VER
75
76
// ------------------------------------------------------------------------------------------------
77
HL1MDLLoader::HL1MDLLoader(
78
    aiScene *scene,
79
    IOSystem *io,
80
    const unsigned char *buffer,
81
    size_t buffer_length,
82
    const std::string &file_path,
83
    const HL1ImportSettings &import_settings) :
84
0
    scene_(scene),
85
0
    io_(io),
86
0
    buffer_(HL1DataBuffer::view(buffer, buffer_length)),
87
0
    file_path_(file_path),
88
0
    import_settings_(import_settings),
89
0
    header_(nullptr),
90
0
    texture_header_(nullptr),
91
0
    anim_headers_(),
92
0
    texture_buffer_(),
93
0
    anim_buffers_(),
94
0
    num_sequence_groups_(0),
95
0
    rootnode_children_(),
96
0
    unique_name_generator_(),
97
0
    unique_sequence_names_(),
98
0
    unique_sequence_groups_names_(),
99
0
    temp_bones_(),
100
0
    num_blend_controllers_(0),
101
0
    total_models_(0) {
102
0
    load_file();
103
0
}
104
105
// ------------------------------------------------------------------------------------------------
106
0
HL1MDLLoader::~HL1MDLLoader() {
107
0
    release_resources();
108
0
}
109
110
// ------------------------------------------------------------------------------------------------
111
0
void HL1MDLLoader::release_resources() {
112
    // Root has some children nodes. so let's proceed them
113
0
    if (!rootnode_children_.empty()) {
114
        // Here, it means that the nodes were not added to the
115
        // scene root node. We still have to delete them.
116
0
        for (auto it = rootnode_children_.begin(); it != rootnode_children_.end(); ++it) {
117
0
            if (*it) {
118
0
                delete *it;
119
0
            }
120
0
        }
121
        // Ensure this happens only once.
122
0
        rootnode_children_.clear();
123
0
    }
124
0
}
125
126
// ------------------------------------------------------------------------------------------------
127
0
void HL1MDLLoader::load_file() {
128
0
    try {
129
0
        header_ = get_buffer_data<Header_HL1>(0, 1);
130
0
        validate_header(header_, false);
131
132
        // Create the root scene node.
133
0
        scene_->mRootNode = new aiNode(AI_MDL_HL1_NODE_ROOT);
134
135
0
        load_texture_file();
136
137
0
        if (import_settings_.read_animations) {
138
0
            load_sequence_groups_files();
139
0
        }
140
141
0
        read_textures();
142
0
        read_skins();
143
144
0
        read_bones();
145
0
        read_meshes();
146
147
0
        if (import_settings_.read_animations) {
148
0
            read_sequence_groups_info();
149
0
            read_animations();
150
0
            read_sequence_infos();
151
0
            if (import_settings_.read_sequence_transitions)
152
0
                read_sequence_transitions();
153
0
        }
154
155
0
        if (import_settings_.read_attachments) {
156
0
            read_attachments();
157
0
        }
158
159
0
        if (import_settings_.read_hitboxes) {
160
0
            read_hitboxes();
161
0
        }
162
163
0
        if (import_settings_.read_bone_controllers) {
164
0
            read_bone_controllers();
165
0
        }
166
167
0
        read_global_info();
168
169
0
        if (!header_->numbodyparts) {
170
            // This could be an MDL external texture file. In this case,
171
            // add this flag to allow the scene to be loaded even if it
172
            // has no meshes.
173
0
            scene_->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
174
0
        }
175
176
        // Append children to root node.
177
0
        if (rootnode_children_.size()) {
178
0
            scene_->mRootNode->addChildren(
179
0
                    static_cast<unsigned int>(rootnode_children_.size()),
180
0
                    rootnode_children_.data());
181
182
            // Clear the list of nodes so they will not be destroyed
183
            // when resources are released.
184
0
            rootnode_children_.clear();
185
0
        }
186
187
0
        release_resources();
188
189
0
    } catch (...) {
190
0
        release_resources();
191
0
        throw;
192
0
    }
193
0
}
194
195
// ------------------------------------------------------------------------------------------------
196
0
void HL1MDLLoader::validate_header(const Header_HL1 *header, bool is_texture_header) {
197
0
    if (is_texture_header) {
198
        // Every single Half-Life model is assumed to have at least one texture.
199
0
        if (!header->numtextures) {
200
0
            throw DeadlyImportError(MDL_HALFLIFE_LOG_HEADER "There are no textures in the file");
201
0
        }
202
203
0
        if (header->numtextures > AI_MDL_HL1_MAX_TEXTURES) {
204
0
            log_warning_limit_exceeded<AI_MDL_HL1_MAX_TEXTURES>(header->numtextures, "textures");
205
0
        }
206
207
0
        if (header->numskinfamilies > AI_MDL_HL1_MAX_SKIN_FAMILIES) {
208
0
            log_warning_limit_exceeded<AI_MDL_HL1_MAX_SKIN_FAMILIES>(header->numskinfamilies, "skin families");
209
0
        }
210
211
0
    } else {
212
213
0
        if (header->numbodyparts > AI_MDL_HL1_MAX_BODYPARTS) {
214
0
            log_warning_limit_exceeded<AI_MDL_HL1_MAX_BODYPARTS>(header->numbodyparts, "bodyparts");
215
0
        }
216
217
0
        if (header->numbones > AI_MDL_HL1_MAX_BONES) {
218
0
            log_warning_limit_exceeded<AI_MDL_HL1_MAX_BONES>(header->numbones, "bones");
219
0
        }
220
221
0
        if (header->numbonecontrollers > AI_MDL_HL1_MAX_BONE_CONTROLLERS) {
222
0
            log_warning_limit_exceeded<AI_MDL_HL1_MAX_BONE_CONTROLLERS>(header->numbonecontrollers, "bone controllers");
223
0
        }
224
225
0
        if (header->numseq > AI_MDL_HL1_MAX_SEQUENCES) {
226
0
            log_warning_limit_exceeded<AI_MDL_HL1_MAX_SEQUENCES>(header->numseq, "sequences");
227
0
        }
228
229
0
        if (header->numseqgroups > AI_MDL_HL1_MAX_SEQUENCE_GROUPS) {
230
0
            log_warning_limit_exceeded<AI_MDL_HL1_MAX_SEQUENCE_GROUPS>(header->numseqgroups, "sequence groups");
231
0
        }
232
233
0
        if (header->numattachments > AI_MDL_HL1_MAX_ATTACHMENTS) {
234
0
            log_warning_limit_exceeded<AI_MDL_HL1_MAX_ATTACHMENTS>(header->numattachments, "attachments");
235
0
        }
236
0
    }
237
0
}
238
239
// ------------------------------------------------------------------------------------------------
240
/*
241
    Load textures.
242
243
    There are two ways for textures to be stored in a Half-Life model:
244
245
    1. Directly in the MDL file (filePath) or
246
    2. In an external MDL file.
247
248
    Due to the way StudioMDL works (tool used to compile SMDs into MDLs),
249
    it is assumed that an external texture file follows the naming
250
    convention: <YourModelName>T.mdl. Note the extra (T) at the end of the
251
    model name.
252
253
    .e.g For a given model named MyModel.mdl
254
255
    The external texture file name would be MyModelT.mdl
256
*/
257
0
void HL1MDLLoader::load_texture_file() {
258
0
    if (header_->numtextures == 0) {
259
        // Load an external MDL texture file.
260
0
        std::string texture_file_path =
261
0
                DefaultIOSystem::absolutePath(file_path_) + io_->getOsSeparator() +
262
0
                DefaultIOSystem::completeBaseName(file_path_) + "T." +
263
0
                BaseImporter::GetExtension(file_path_);
264
265
0
        load_file_into_buffer<Header_HL1>(texture_file_path, texture_buffer_);
266
0
    } else {
267
        // Model has no external texture file. This means the texture is stored inside the main MDL file.
268
0
        texture_buffer_ = HL1DataBuffer::view(buffer_);
269
0
    }
270
271
0
    texture_header_ = get_texture_buffer_data<Header_HL1>(0, 1);
272
273
    // Validate texture header.
274
0
    validate_header(texture_header_, true);
275
0
}
276
277
// ------------------------------------------------------------------------------------------------
278
/*
279
    Load sequence group files if any.
280
281
    Due to the way StudioMDL works (tool used to compile SMDs into MDLs),
282
    it is assumed that a sequence group file follows the naming
283
    convention: <YourModelName>0X.mdl. Note the extra (0X) at the end of
284
    the model name, where (X) is the sequence group.
285
286
    .e.g For a given model named MyModel.mdl
287
288
    Sequence group 1 => MyModel01.mdl
289
    Sequence group 2 => MyModel02.mdl
290
    Sequence group X => MyModel0X.mdl
291
292
*/
293
0
void HL1MDLLoader::load_sequence_groups_files() {
294
0
    if (header_->numseqgroups <= 1) {
295
0
        return;
296
0
    }
297
298
0
    num_sequence_groups_ = header_->numseqgroups;
299
300
0
    anim_buffers_.resize(num_sequence_groups_);
301
0
    anim_headers_.resize(num_sequence_groups_, nullptr);
302
303
0
    std::string file_path_without_extension =
304
0
            DefaultIOSystem::absolutePath(file_path_) +
305
0
            io_->getOsSeparator() +
306
0
            DefaultIOSystem::completeBaseName(file_path_);
307
308
0
    for (int i = 1; i < num_sequence_groups_; ++i) {
309
0
        std::stringstream ss;
310
0
        ss << file_path_without_extension;
311
0
        ss << std::setw(2) << std::setfill('0') << i;
312
0
        ss << '.' << BaseImporter::GetExtension(file_path_);
313
314
0
        std::string sequence_file_path = ss.str();
315
316
0
        load_file_into_buffer<SequenceHeader_HL1>(sequence_file_path, anim_buffers_[i]);
317
318
0
        anim_headers_[i] = get_anim_buffer_data<SequenceHeader_HL1>(i, 0, 1);
319
0
    }
320
0
}
321
322
// ------------------------------------------------------------------------------------------------
323
// Read an MDL texture.
324
void HL1MDLLoader::read_texture(const Texture_HL1 *ptexture,
325
        const uint8_t *data, const uint8_t *pal, aiTexture *pResult,
326
0
        aiColor3D &last_palette_color) {
327
0
    pResult->mFilename = ptexture->name;
328
0
    pResult->mWidth = static_cast<unsigned int>(ptexture->width);
329
0
    pResult->mHeight = static_cast<unsigned int>(ptexture->height);
330
0
    pResult->achFormatHint[0] = 'r';
331
0
    pResult->achFormatHint[1] = 'g';
332
0
    pResult->achFormatHint[2] = 'b';
333
0
    pResult->achFormatHint[3] = 'a';
334
0
    pResult->achFormatHint[4] = '8';
335
0
    pResult->achFormatHint[5] = '8';
336
0
    pResult->achFormatHint[6] = '8';
337
0
    pResult->achFormatHint[7] = '8';
338
0
    pResult->achFormatHint[8] = '\0';
339
340
0
    const size_t num_pixels = pResult->mWidth * pResult->mHeight;
341
0
    aiTexel *out = pResult->pcData = new aiTexel[num_pixels];
342
343
    // Convert indexed 8 bit to 32 bit RGBA.
344
0
    for (size_t i = 0; i < num_pixels; ++i, ++out) {
345
0
        out->r = pal[data[i] * 3];
346
0
        out->g = pal[data[i] * 3 + 1];
347
0
        out->b = pal[data[i] * 3 + 2];
348
0
        out->a = 255;
349
0
    }
350
351
    // Get the last palette color.
352
0
    last_palette_color.r = pal[255 * 3];
353
0
    last_palette_color.g = pal[255 * 3 + 1];
354
0
    last_palette_color.b = pal[255 * 3 + 2];
355
0
}
356
357
// ------------------------------------------------------------------------------------------------
358
0
void HL1MDLLoader::read_textures() {
359
0
    scene_->mTextures = new aiTexture *[texture_header_->numtextures];
360
0
    scene_->mMaterials = new aiMaterial *[texture_header_->numtextures];
361
362
0
    const Texture_HL1 *ptexture = get_texture_buffer_data<Texture_HL1>(texture_header_->textureindex, texture_header_->numtextures);
363
364
0
    for (int i = 0; i < texture_header_->numtextures; ++i) {
365
0
        scene_->mTextures[i] = new aiTexture();
366
0
        ++scene_->mNumTextures;
367
368
0
        const uint8_t *data = get_texture_buffer_data<uint8_t>(ptexture[i].index, ptexture[i].width * ptexture[i].height);
369
0
        const uint8_t *pal = get_texture_buffer_data<uint8_t>(ptexture[i].index + ptexture[i].width * ptexture[i].height, 256 * 3);
370
371
0
        aiColor3D last_palette_color;
372
0
        read_texture(&ptexture[i], data, pal, scene_->mTextures[i], last_palette_color);
373
374
0
        aiMaterial *scene_material = new aiMaterial();
375
0
        scene_->mMaterials[i] = scene_material;
376
0
        ++scene_->mNumMaterials;
377
378
0
        const aiTextureType texture_type = aiTextureType_DIFFUSE;
379
0
        aiString texture_name(ptexture[i].name);
380
0
        scene_material->AddProperty(&texture_name, AI_MATKEY_TEXTURE(texture_type, 0));
381
382
        // Is this a chrome texture?
383
0
        int chrome = ptexture[i].flags & AI_MDL_HL1_STUDIO_NF_CHROME ? 1 : 0;
384
0
        scene_material->AddProperty(&chrome, 1, AI_MDL_HL1_MATKEY_CHROME(texture_type, 0));
385
386
0
        if (ptexture[i].flags & AI_MDL_HL1_STUDIO_NF_FLATSHADE) {
387
            // Flat shading.
388
0
            const aiShadingMode shading_mode = aiShadingMode_Flat;
389
0
            scene_material->AddProperty(&shading_mode, 1, AI_MATKEY_SHADING_MODEL);
390
0
        }
391
392
0
        if (ptexture[i].flags & AI_MDL_HL1_STUDIO_NF_ADDITIVE) {
393
            // Additive texture.
394
0
            const aiBlendMode blend_mode = aiBlendMode_Additive;
395
0
            scene_material->AddProperty(&blend_mode, 1, AI_MATKEY_BLEND_FUNC);
396
0
        } else if (ptexture[i].flags & AI_MDL_HL1_STUDIO_NF_MASKED) {
397
            // Texture with 1 bit alpha test.
398
0
            const aiTextureFlags use_alpha = aiTextureFlags_UseAlpha;
399
0
            scene_material->AddProperty(&use_alpha, 1, AI_MATKEY_TEXFLAGS(texture_type, 0));
400
0
            scene_material->AddProperty(&last_palette_color, 1, AI_MATKEY_COLOR_TRANSPARENT);
401
0
        }
402
0
    }
403
0
}
404
405
// ------------------------------------------------------------------------------------------------
406
0
void HL1MDLLoader::read_skins() {
407
    // Read skins, if any.
408
0
    if (texture_header_->numskinfamilies <= 1) {
409
0
        return;
410
0
    }
411
412
    // Pointer to base texture index.
413
0
    const short *default_skin_ptr = get_texture_buffer_data<short>(
414
0
            texture_header_->skinindex,
415
0
            texture_header_->numskinref);
416
417
    // Start at first replacement skin.
418
0
    const short *replacement_skin_ptr = get_texture_buffer_data<short>(
419
0
            texture_header_->skinindex + texture_header_->numskinref * sizeof(short),
420
0
            (texture_header_->numskinfamilies - 1) * texture_header_->numskinref);
421
422
0
    for (int i = 1; i < texture_header_->numskinfamilies; ++i, replacement_skin_ptr += texture_header_->numskinref) {
423
0
        for (int j = 0; j < texture_header_->numskinref; ++j) {
424
0
            if (default_skin_ptr[j] != replacement_skin_ptr[j]) {
425
                // Save replacement textures.
426
0
                aiString skinMaterialId(scene_->mTextures[replacement_skin_ptr[j]]->mFilename);
427
0
                scene_->mMaterials[default_skin_ptr[j]]->AddProperty(&skinMaterialId, AI_MATKEY_TEXTURE_DIFFUSE(i));
428
0
            }
429
0
        }
430
0
    }
431
0
}
432
433
// ------------------------------------------------------------------------------------------------
434
0
void HL1MDLLoader::read_bones() {
435
0
    if (!header_->numbones) {
436
0
        return;
437
0
    }
438
439
0
    const Bone_HL1 *pbone = get_buffer_data<Bone_HL1>(header_->boneindex, header_->numbones);
440
441
0
    std::vector<std::string> unique_bones_names(header_->numbones);
442
0
    for (int i = 0; i < header_->numbones; ++i) {
443
0
        unique_bones_names[i] = pbone[i].name;
444
0
    }
445
446
    // Ensure bones have unique names.
447
0
    unique_name_generator_.set_template_name("Bone");
448
0
    unique_name_generator_.make_unique(unique_bones_names);
449
450
0
    temp_bones_.resize(header_->numbones);
451
452
    // Create the main 'bones' node that will contain all MDL root bones.
453
0
    aiNode *bones_node = new aiNode(AI_MDL_HL1_NODE_BONES);
454
0
    rootnode_children_.push_back(bones_node);
455
456
    // Store roots bones IDs temporarily.
457
0
    std::vector<int> roots;
458
459
    // Create bone matrices in local space.
460
0
    for (int i = 0; i < header_->numbones; ++i) {
461
0
        aiNode *bone_node = temp_bones_[i].node = new aiNode(unique_bones_names[i]);
462
463
0
        aiVector3D angles(pbone[i].value[3], pbone[i].value[4], pbone[i].value[5]);
464
0
        temp_bones_[i].absolute_transform = bone_node->mTransformation =
465
0
                aiMatrix4x4(aiVector3D(1), aiQuaternion(angles.y, angles.z, angles.x),
466
0
                        aiVector3D(pbone[i].value[0], pbone[i].value[1], pbone[i].value[2]));
467
468
0
        if (pbone[i].parent == -1) {
469
0
            bone_node->mParent = bones_node;
470
0
            roots.push_back(i); // This bone has no parent. Add it to the roots list.
471
0
        } else {
472
0
            bone_node->mParent = temp_bones_[pbone[i].parent].node;
473
0
            temp_bones_[pbone[i].parent].children.push_back(i); // Add this bone to the parent bone's children list.
474
475
0
            temp_bones_[i].absolute_transform =
476
0
                    temp_bones_[pbone[i].parent].absolute_transform * bone_node->mTransformation;
477
0
        }
478
479
0
        temp_bones_[i].offset_matrix = temp_bones_[i].absolute_transform;
480
0
        temp_bones_[i].offset_matrix.Inverse();
481
0
    }
482
483
    // Allocate memory for each MDL root bone.
484
0
    bones_node->mNumChildren = static_cast<unsigned int>(roots.size());
485
0
    bones_node->mChildren = new aiNode *[bones_node->mNumChildren];
486
487
    // Build all bones children hierarchy starting from each MDL root bone.
488
0
    for (size_t i = 0; i < roots.size(); ++i)
489
0
    {
490
0
        const TempBone &root_bone = temp_bones_[roots[i]];
491
0
        bones_node->mChildren[i] = root_bone.node;
492
0
        build_bone_children_hierarchy(root_bone);
493
0
    }
494
0
}
495
496
void HL1MDLLoader::build_bone_children_hierarchy(const TempBone &bone)
497
0
{
498
0
    if (bone.children.empty())
499
0
        return;
500
501
0
    aiNode* bone_node = bone.node;
502
0
    bone_node->mNumChildren = static_cast<unsigned int>(bone.children.size());
503
0
    bone_node->mChildren = new aiNode *[bone_node->mNumChildren];
504
505
    // Build each child bone's hierarchy recursively.
506
0
    for (size_t i = 0; i < bone.children.size(); ++i)
507
0
    {
508
0
        const TempBone &child_bone = temp_bones_[bone.children[i]];
509
0
        bone_node->mChildren[i] = child_bone.node;
510
0
        build_bone_children_hierarchy(child_bone);
511
0
    }
512
0
}
513
514
// ------------------------------------------------------------------------------------------------
515
/*
516
    Read meshes.
517
518
    Half-Life MDLs are structured such that each MDL
519
    contains one or more 'bodypart(s)', which contain one
520
    or more 'model(s)', which contains one or more mesh(es).
521
522
    * Bodyparts are used to group models that may be replaced
523
    in the game .e.g a character could have a 'heads' group,
524
    'torso' group, 'shoes' group, with each group containing
525
    different 'model(s)'.
526
527
    * Models, also called 'sub models', contain vertices as
528
    well as a reference to each mesh used by the sub model.
529
530
    * Meshes contain a list of tris, also known as 'triverts'.
531
    Each tris contains the following information:
532
533
        1. The index of the position to use for the vertex.
534
        2. The index of the normal to use for the vertex.
535
        3. The S coordinate to use for the vertex UV.
536
        4. The T coordinate ^
537
538
    These tris represent the way to represent the triangles
539
    for each mesh. Depending on how the tool compiled the MDL,
540
    those triangles were saved as strips and or fans.
541
542
    NOTE: Each tris is NOT unique. This means that you
543
    might encounter the same vertex index but with a different
544
    normal index, S coordinate, T coordinate.
545
546
    In addition, each mesh contains the texture's index.
547
548
    ------------------------------------------------------
549
    With the details above, there are several things to
550
    take into consideration.
551
552
    * The Half-Life models store the vertices by sub model
553
    rather than by mesh. Due to Assimp's structure, it
554
    is necessary to remap each model vertex to be used
555
    per mesh. Unfortunately, this has the consequence
556
    to duplicate vertices.
557
558
    * Because the mesh triangles are comprised of strips and
559
    fans, it is necessary to convert each primitive to
560
    triangles, respectively (3 indices per face).
561
*/
562
0
void HL1MDLLoader::read_meshes() {
563
0
    if (!header_->numbodyparts) {
564
0
        return;
565
0
    }
566
567
0
    int total_verts = 0;
568
0
    int total_triangles = 0;
569
0
    total_models_ = 0;
570
571
0
    const Bodypart_HL1 *pbodypart = get_buffer_data<Bodypart_HL1>(header_->bodypartindex, header_->numbodyparts);
572
0
    const Model_HL1 *pmodel = nullptr;
573
0
    const Mesh_HL1 *pmesh = nullptr;
574
575
0
    const Texture_HL1 *ptexture = get_texture_buffer_data<Texture_HL1>(texture_header_->textureindex, texture_header_->numtextures);
576
0
    const short *pskinref = get_texture_buffer_data<short>(texture_header_->skinindex, texture_header_->numskinref);
577
578
0
    scene_->mNumMeshes = 0;
579
580
0
    std::vector<std::string> unique_bodyparts_names;
581
0
    unique_bodyparts_names.resize(header_->numbodyparts);
582
583
    // Count the number of meshes.
584
585
0
    for (int i = 0; i < header_->numbodyparts; ++i, ++pbodypart) {
586
0
        unique_bodyparts_names[i] = pbodypart->name;
587
588
0
        pmodel = get_buffer_data<Model_HL1>(pbodypart->modelindex, pbodypart->nummodels);
589
0
        for (int j = 0; j < pbodypart->nummodels; ++j, ++pmodel) {
590
0
            scene_->mNumMeshes += pmodel->nummesh;
591
0
            total_verts += pmodel->numverts;
592
0
        }
593
594
0
        total_models_ += pbodypart->nummodels;
595
0
    }
596
597
    // Display limit infos.
598
0
    if (total_verts > AI_MDL_HL1_MAX_VERTICES) {
599
0
        log_warning_limit_exceeded<AI_MDL_HL1_MAX_VERTICES>(total_verts, "vertices");
600
0
    }
601
602
0
    if (scene_->mNumMeshes > AI_MDL_HL1_MAX_MESHES) {
603
0
        log_warning_limit_exceeded<AI_MDL_HL1_MAX_MESHES>(scene_->mNumMeshes, "meshes");
604
0
    }
605
606
0
    if (total_models_ > AI_MDL_HL1_MAX_MODELS) {
607
0
        log_warning_limit_exceeded<AI_MDL_HL1_MAX_MODELS>(total_models_, "models");
608
0
    }
609
610
    // Ensure bodyparts have unique names.
611
0
    unique_name_generator_.set_template_name("Bodypart");
612
0
    unique_name_generator_.make_unique(unique_bodyparts_names);
613
614
    // Now do the same for each model.
615
0
    pbodypart = get_buffer_data<Bodypart_HL1>(header_->bodypartindex, header_->numbodyparts);
616
617
    // Prepare template name for bodypart models.
618
0
    std::vector<std::string> unique_models_names;
619
0
    unique_models_names.resize(total_models_);
620
621
0
    unsigned int model_index = 0;
622
623
0
    for (int i = 0; i < header_->numbodyparts; ++i, ++pbodypart) {
624
0
        pmodel = get_buffer_data<Model_HL1>(pbodypart->modelindex, pbodypart->nummodels);
625
0
        for (int j = 0; j < pbodypart->nummodels; ++j, ++pmodel, ++model_index)
626
0
            unique_models_names[model_index] = pmodel->name;
627
0
    }
628
629
0
    unique_name_generator_.set_template_name("Model");
630
0
    unique_name_generator_.make_unique(unique_models_names);
631
632
0
    unsigned int mesh_index = 0;
633
634
0
    scene_->mMeshes = new aiMesh *[scene_->mNumMeshes];
635
636
0
    pbodypart = get_buffer_data<Bodypart_HL1>(header_->bodypartindex, header_->numbodyparts);
637
638
    /* Create a node that will represent the mesh hierarchy.
639
640
        <MDL_bodyparts>
641
            |
642
            +-- bodypart --+-- model -- [mesh index, mesh index, ...]
643
            |              |
644
            |              +-- model -- [mesh index, mesh index, ...]
645
            |              |
646
            |              ...
647
            |
648
            |-- bodypart -- ...
649
            |
650
            ...
651
     */
652
0
    aiNode *bodyparts_node = new aiNode(AI_MDL_HL1_NODE_BODYPARTS);
653
0
    rootnode_children_.push_back(bodyparts_node);
654
0
    bodyparts_node->mNumChildren = static_cast<unsigned int>(header_->numbodyparts);
655
0
    bodyparts_node->mChildren = new aiNode *[bodyparts_node->mNumChildren];
656
0
    aiNode **bodyparts_node_ptr = bodyparts_node->mChildren;
657
658
    // The following variables are defined here so they don't have
659
    // to be recreated every iteration.
660
661
    // Model_HL1 vertices, in bind pose space.
662
0
    std::vector<aiVector3D> bind_pose_vertices;
663
664
    // Model_HL1 normals, in bind pose space.
665
0
    std::vector<aiVector3D> bind_pose_normals;
666
667
    // Used to contain temporary information for building a mesh.
668
0
    std::vector<HL1MeshTrivert> triverts;
669
670
0
    std::vector<short> tricmds;
671
672
    // Which triverts to use for the mesh.
673
0
    std::vector<short> mesh_triverts_indices;
674
675
0
    std::vector<HL1MeshFace> mesh_faces;
676
677
    /* triverts that have the same vertindex, but have different normindex,s,t values.
678
       Similar triverts are mapped from vertindex to a list of similar triverts. */
679
0
    std::map<short, std::set<short>> triverts_similars;
680
681
    // triverts per bone.
682
0
    std::map<int, std::set<short>> bone_triverts;
683
684
    /** This function adds a trivert index to the list of triverts per bone.
685
     * \param[in] bone The bone that affects the trivert at index \p trivert_index.
686
     * \param[in] trivert_index The trivert index.
687
     */
688
0
    auto AddTrivertToBone = [&](int bone, short trivert_index) {
689
0
        if (bone_triverts.count(bone) == 0)
690
0
            bone_triverts.insert({ bone, std::set<short>{ trivert_index }});
691
0
        else
692
0
            bone_triverts[bone].insert(trivert_index);
693
0
    };
694
695
    /** This function creates and appends a new trivert to the list of triverts.
696
     * \param[in] trivert The trivert to use as a prototype.
697
     * \param[in] bone The bone that affects \p trivert.
698
     */
699
0
    auto AddSimilarTrivert = [&](const Trivert &trivert, const int bone) {
700
0
        HL1MeshTrivert new_trivert(trivert);
701
0
        new_trivert.localindex = static_cast<short>(mesh_triverts_indices.size());
702
703
0
        short new_trivert_index = static_cast<short>(triverts.size());
704
705
0
        if (triverts_similars.count(trivert.vertindex) == 0)
706
0
            triverts_similars.insert({ trivert.vertindex, std::set<short>{ new_trivert_index }});
707
0
        else
708
0
            triverts_similars[trivert.vertindex].insert(new_trivert_index);
709
710
0
        triverts.push_back(new_trivert);
711
712
0
        mesh_triverts_indices.push_back(new_trivert_index);
713
0
        tricmds.push_back(new_trivert.localindex);
714
0
        AddTrivertToBone(bone, new_trivert.localindex);
715
0
    };
716
717
0
    model_index = 0;
718
719
0
    for (int i = 0; i < header_->numbodyparts; ++i, ++pbodypart, ++bodyparts_node_ptr) {
720
0
        pmodel = get_buffer_data<Model_HL1>(pbodypart->modelindex, pbodypart->nummodels);
721
722
        // Create bodypart node for the mesh tree hierarchy.
723
0
        aiNode *bodypart_node = (*bodyparts_node_ptr) = new aiNode(unique_bodyparts_names[i]);
724
0
        bodypart_node->mParent = bodyparts_node;
725
0
        bodypart_node->mMetaData = aiMetadata::Alloc(1);
726
0
        bodypart_node->mMetaData->Set(0, "Base", pbodypart->base);
727
728
0
        bodypart_node->mNumChildren = static_cast<unsigned int>(pbodypart->nummodels);
729
0
        bodypart_node->mChildren = new aiNode *[bodypart_node->mNumChildren];
730
0
        aiNode **bodypart_models_ptr = bodypart_node->mChildren;
731
732
0
        for (int j = 0; j < pbodypart->nummodels;
733
0
                ++j, ++pmodel, ++bodypart_models_ptr, ++model_index) {
734
735
0
            pmesh = get_buffer_data<Mesh_HL1>(pmodel->meshindex, pmodel->nummesh);
736
737
0
            const uint8_t *pvertbone = get_buffer_data<uint8_t>(pmodel->vertinfoindex, pmodel->numverts);
738
0
            const uint8_t *pnormbone = get_buffer_data<uint8_t>(pmodel->norminfoindex, pmodel->numnorms);
739
0
            const vec3_t *pstudioverts = get_buffer_data<vec3_t>(pmodel->vertindex, pmodel->numverts);
740
0
            const vec3_t *pstudionorms = get_buffer_data<vec3_t>(pmodel->normindex, pmodel->numnorms);
741
742
            // Each vertex and normal is in local space, so transform
743
            // each of them to bring them in bind pose.
744
0
            bind_pose_vertices.resize(pmodel->numverts);
745
0
            bind_pose_normals.resize(pmodel->numnorms);
746
0
            for (size_t k = 0; k < bind_pose_vertices.size(); ++k) {
747
0
                const vec3_t &vert = pstudioverts[k];
748
0
                bind_pose_vertices[k] = temp_bones_[pvertbone[k]].absolute_transform * aiVector3D(vert[0], vert[1], vert[2]);
749
0
            }
750
0
            for (size_t k = 0; k < bind_pose_normals.size(); ++k) {
751
0
                const vec3_t &norm = pstudionorms[k];
752
                // Compute the normal matrix to transform the normal into bind pose,
753
                // without affecting its length.
754
0
                const aiMatrix4x4 normal_matrix = aiMatrix4x4(temp_bones_[pnormbone[k]].absolute_transform).Inverse().Transpose();
755
0
                bind_pose_normals[k] = normal_matrix * aiVector3D(norm[0], norm[1], norm[2]);
756
0
            }
757
758
            // Create model node for the mesh tree hierarchy.
759
0
            aiNode *model_node = (*bodypart_models_ptr) = new aiNode(unique_models_names[model_index]);
760
0
            model_node->mParent = bodypart_node;
761
0
            model_node->mNumMeshes = static_cast<unsigned int>(pmodel->nummesh);
762
0
            model_node->mMeshes = new unsigned int[model_node->mNumMeshes];
763
0
            unsigned int *model_meshes_ptr = model_node->mMeshes;
764
765
0
            for (int k = 0; k < pmodel->nummesh; ++k, ++pmesh, ++mesh_index, ++model_meshes_ptr) {
766
0
                *model_meshes_ptr = mesh_index;
767
768
                // Read triverts.
769
0
                short *ptricmds = (short *)((uint8_t *)header_ + pmesh->triindex);
770
0
                float texcoords_s_scale = 1.0f / (float)ptexture[pskinref[pmesh->skinref]].width;
771
0
                float texcoords_t_scale = 1.0f / (float)ptexture[pskinref[pmesh->skinref]].height;
772
773
                // Reset the data for the upcoming mesh.
774
0
                triverts.clear();
775
0
                triverts.resize(pmodel->numverts);
776
0
                mesh_triverts_indices.clear();
777
0
                mesh_faces.clear();
778
0
                triverts_similars.clear();
779
0
                bone_triverts.clear();
780
781
0
                int l;
782
0
                while ((l = *(ptricmds++))) {
783
0
                    bool is_triangle_fan = false;
784
785
0
                    if (l < 0) {
786
0
                        l = -l;
787
0
                        is_triangle_fan = true;
788
0
                    }
789
790
                    // Clear the list of tris for the upcoming tris.
791
0
                    tricmds.clear();
792
793
0
                    for (; l > 0; l--, ptricmds += 4) {
794
0
                        const Trivert *input_trivert = reinterpret_cast<const Trivert *>(ptricmds);
795
0
                        const int bone = pvertbone[input_trivert->vertindex];
796
797
0
                        HL1MeshTrivert *private_trivert = &triverts[input_trivert->vertindex];
798
0
                        if (private_trivert->localindex == -1) {
799
                            // First time referenced.
800
0
                            *private_trivert = *input_trivert;
801
0
                            private_trivert->localindex = static_cast<short>(mesh_triverts_indices.size());
802
0
                            mesh_triverts_indices.push_back(input_trivert->vertindex);
803
0
                            tricmds.push_back(private_trivert->localindex);
804
0
                            AddTrivertToBone(bone, private_trivert->localindex);
805
0
                        } else if (*private_trivert == *input_trivert) {
806
                            // Exists and is the same.
807
0
                            tricmds.push_back(private_trivert->localindex);
808
0
                        } else {
809
                            // No similar trivert associated to the trivert currently processed.
810
0
                            if (triverts_similars.count(input_trivert->vertindex) == 0)
811
0
                                AddSimilarTrivert(*input_trivert, bone);
812
0
                            else {
813
                                // Search in the list of similar triverts to see if the
814
                                // trivert in process is already registered.
815
0
                                short similar_index = -1;
816
0
                                for (auto it = triverts_similars[input_trivert->vertindex].cbegin();
817
0
                                        similar_index == -1 && it != triverts_similars[input_trivert->vertindex].cend();
818
0
                                        ++it) {
819
0
                                    if (triverts[*it] == *input_trivert)
820
0
                                        similar_index = *it;
821
0
                                }
822
823
                                // If a similar trivert has been found, reuse it.
824
                                // Otherwise, add it.
825
0
                                if (similar_index == -1)
826
0
                                    AddSimilarTrivert(*input_trivert, bone);
827
0
                                else
828
0
                                    tricmds.push_back(triverts[similar_index].localindex);
829
0
                            }
830
0
                        }
831
0
                    }
832
833
                    // Build mesh faces.
834
0
                    const int num_faces = static_cast<int>(tricmds.size() - 2);
835
0
                    mesh_faces.reserve(num_faces);
836
837
0
                    if (is_triangle_fan) {
838
0
                        for (int faceIdx = 0; faceIdx < num_faces; ++faceIdx) {
839
0
                            mesh_faces.push_back(HL1MeshFace{
840
0
                                    tricmds[0],
841
0
                                    tricmds[faceIdx + 1],
842
0
                                    tricmds[faceIdx + 2] });
843
0
                        }
844
0
                    } else {
845
0
                        for (int faceIdx = 0; faceIdx < num_faces; ++faceIdx) {
846
0
                            if (faceIdx & 1) {
847
                                // Preserve winding order.
848
0
                                mesh_faces.push_back(HL1MeshFace{
849
0
                                        tricmds[faceIdx + 1],
850
0
                                        tricmds[faceIdx],
851
0
                                        tricmds[faceIdx + 2] });
852
0
                            } else {
853
0
                                mesh_faces.push_back(HL1MeshFace{
854
0
                                        tricmds[faceIdx],
855
0
                                        tricmds[faceIdx + 1],
856
0
                                        tricmds[faceIdx + 2] });
857
0
                            }
858
0
                        }
859
0
                    }
860
861
0
                    total_triangles += num_faces;
862
0
                }
863
864
                // Create the scene mesh.
865
0
                aiMesh *scene_mesh = scene_->mMeshes[mesh_index] = new aiMesh();
866
0
                scene_mesh->mPrimitiveTypes = aiPrimitiveType::aiPrimitiveType_TRIANGLE;
867
0
                scene_mesh->mMaterialIndex = pskinref[pmesh->skinref];
868
869
0
                scene_mesh->mNumVertices = static_cast<unsigned int>(mesh_triverts_indices.size());
870
871
0
                if (scene_mesh->mNumVertices) {
872
0
                    scene_mesh->mVertices = new aiVector3D[scene_mesh->mNumVertices];
873
0
                    scene_mesh->mNormals = new aiVector3D[scene_mesh->mNumVertices];
874
875
0
                    scene_mesh->mNumUVComponents[0] = 2;
876
0
                    scene_mesh->mTextureCoords[0] = new aiVector3D[scene_mesh->mNumVertices];
877
878
                    // Add vertices.
879
0
                    for (unsigned int v = 0; v < scene_mesh->mNumVertices; ++v) {
880
0
                        const HL1MeshTrivert *pTrivert = &triverts[mesh_triverts_indices[v]];
881
0
                        scene_mesh->mVertices[v] = bind_pose_vertices[pTrivert->vertindex];
882
0
                        scene_mesh->mNormals[v] = bind_pose_normals[pTrivert->normindex];
883
0
                        scene_mesh->mTextureCoords[0][v] = aiVector3D(
884
0
                                pTrivert->s * texcoords_s_scale,
885
0
                                pTrivert->t * -texcoords_t_scale, 0);
886
0
                    }
887
888
                    // Add face and indices.
889
0
                    scene_mesh->mNumFaces = static_cast<unsigned int>(mesh_faces.size());
890
0
                    scene_mesh->mFaces = new aiFace[scene_mesh->mNumFaces];
891
892
0
                    for (unsigned int f = 0; f < scene_mesh->mNumFaces; ++f) {
893
0
                        aiFace *face = &scene_mesh->mFaces[f];
894
0
                        face->mNumIndices = 3;
895
0
                        face->mIndices = new unsigned int[3];
896
0
                        face->mIndices[0] = mesh_faces[f].v2;
897
0
                        face->mIndices[1] = mesh_faces[f].v1;
898
0
                        face->mIndices[2] = mesh_faces[f].v0;
899
0
                    }
900
901
                    // Add mesh bones.
902
0
                    scene_mesh->mNumBones = static_cast<unsigned int>(bone_triverts.size());
903
0
                    scene_mesh->mBones = new aiBone *[scene_mesh->mNumBones];
904
905
0
                    aiBone **scene_bone_ptr = scene_mesh->mBones;
906
907
0
                    for (auto bone_it = bone_triverts.cbegin();
908
0
                            bone_it != bone_triverts.cend();
909
0
                            ++bone_it, ++scene_bone_ptr) {
910
0
                        const int bone_index = bone_it->first;
911
912
0
                        aiBone *scene_bone = (*scene_bone_ptr) = new aiBone();
913
0
                        scene_bone->mName = temp_bones_[bone_index].node->mName;
914
915
0
                        scene_bone->mOffsetMatrix = temp_bones_[bone_index].offset_matrix;
916
917
0
                        auto vertex_ids = bone_triverts.at(bone_index);
918
919
                        // Add vertex weight per bone.
920
0
                        scene_bone->mNumWeights = static_cast<unsigned int>(vertex_ids.size());
921
0
                        aiVertexWeight *vertex_weight_ptr = scene_bone->mWeights = new aiVertexWeight[scene_bone->mNumWeights];
922
923
0
                        for (auto vertex_it = vertex_ids.begin();
924
0
                                vertex_it != vertex_ids.end();
925
0
                                ++vertex_it, ++vertex_weight_ptr) {
926
0
                            vertex_weight_ptr->mVertexId = *vertex_it;
927
0
                            vertex_weight_ptr->mWeight = 1.0f;
928
0
                        }
929
0
                    }
930
0
                }
931
0
            }
932
0
        }
933
0
    }
934
935
0
    if (total_triangles > AI_MDL_HL1_MAX_TRIANGLES) {
936
0
        log_warning_limit_exceeded<AI_MDL_HL1_MAX_TRIANGLES>(total_triangles, "triangles");
937
0
    }
938
0
}
939
940
// ------------------------------------------------------------------------------------------------
941
0
void HL1MDLLoader::read_animations() {
942
0
    if (!header_->numseq) {
943
0
        return;
944
0
    }
945
946
0
    const SequenceDesc_HL1 *pseqdesc = get_buffer_data<SequenceDesc_HL1>(header_->seqindex, header_->numseq);
947
0
    const SequenceGroup_HL1 *pseqgroup = nullptr;
948
0
    const AnimValueOffset_HL1 *panim = nullptr;
949
0
    const AnimValue_HL1 *panimvalue = nullptr;
950
951
0
    unique_sequence_names_.resize(header_->numseq);
952
0
    for (int i = 0; i < header_->numseq; ++i)
953
0
        unique_sequence_names_[i] = pseqdesc[i].label;
954
955
    // Ensure sequences have unique names.
956
0
    unique_name_generator_.set_template_name("Sequence");
957
0
    unique_name_generator_.make_unique(unique_sequence_names_);
958
959
0
    scene_->mNumAnimations = 0;
960
961
0
    int highest_num_blend_animations = SequenceBlendMode_HL1::NoBlend;
962
963
    // Count the total number of animations.
964
0
    for (int i = 0; i < header_->numseq; ++i, ++pseqdesc) {
965
0
        scene_->mNumAnimations += pseqdesc->numblends;
966
0
        highest_num_blend_animations = std::max(pseqdesc->numblends, highest_num_blend_animations);
967
0
    }
968
969
    // Get the number of available blend controllers for global info.
970
0
    get_num_blend_controllers(highest_num_blend_animations, num_blend_controllers_);
971
972
0
    pseqdesc = get_buffer_data<SequenceDesc_HL1>(header_->seqindex, header_->numseq);
973
974
0
    aiAnimation **scene_animations_ptr = scene_->mAnimations = new aiAnimation *[scene_->mNumAnimations]();
975
976
0
    for (int sequence = 0; sequence < header_->numseq; ++sequence, ++pseqdesc) {
977
0
        pseqgroup = get_buffer_data<SequenceGroup_HL1>(header_->seqgroupindex + pseqdesc->seqgroup * sizeof(SequenceGroup_HL1), 1);
978
979
0
        if (pseqdesc->seqgroup == 0) {
980
0
            panim = get_buffer_data<AnimValueOffset_HL1>(pseqgroup->unused2 + pseqdesc->animindex, pseqdesc->numblends * header_->numbones);
981
0
        } else {
982
0
            panim = get_anim_buffer_data<AnimValueOffset_HL1>(pseqdesc->seqgroup, pseqdesc->animindex, pseqdesc->numblends * header_->numbones);
983
0
        }
984
985
0
        for (int blend = 0; blend < pseqdesc->numblends; ++blend, ++scene_animations_ptr) {
986
987
0
            const Bone_HL1 *pbone = get_buffer_data<Bone_HL1>(header_->boneindex, header_->numbones);
988
989
0
            aiAnimation *scene_animation = (*scene_animations_ptr) = new aiAnimation();
990
991
0
            scene_animation->mName = unique_sequence_names_[sequence];
992
0
            scene_animation->mTicksPerSecond = pseqdesc->fps;
993
0
            scene_animation->mDuration = static_cast<double>(pseqdesc->fps) * pseqdesc->numframes;
994
0
            scene_animation->mNumChannels = static_cast<unsigned int>(header_->numbones);
995
0
            scene_animation->mChannels = new aiNodeAnim *[scene_animation->mNumChannels]();
996
997
0
            for (int bone = 0; bone < header_->numbones; bone++, ++pbone, ++panim) {
998
0
                aiNodeAnim *node_anim = scene_animation->mChannels[bone] = new aiNodeAnim();
999
0
                node_anim->mNodeName = temp_bones_[bone].node->mName;
1000
1001
0
                node_anim->mNumPositionKeys = pseqdesc->numframes;
1002
0
                node_anim->mNumRotationKeys = node_anim->mNumPositionKeys;
1003
0
                node_anim->mNumScalingKeys = 0;
1004
1005
0
                node_anim->mPositionKeys = new aiVectorKey[node_anim->mNumPositionKeys];
1006
0
                node_anim->mRotationKeys = new aiQuatKey[node_anim->mNumRotationKeys];
1007
1008
0
                for (int frame = 0; frame < pseqdesc->numframes; ++frame) {
1009
0
                    aiVectorKey *position_key = &node_anim->mPositionKeys[frame];
1010
0
                    aiQuatKey *rotation_key = &node_anim->mRotationKeys[frame];
1011
1012
0
                    aiVector3D angle1;
1013
0
                    for (int j = 0; j < 3; ++j) {
1014
0
                        if (panim->offset[j + 3] != 0) {
1015
                            // Read compressed rotation delta.
1016
0
                            panimvalue = (const AnimValue_HL1 *)((uint8_t *)panim + panim->offset[j + 3]);
1017
0
                            extract_anim_value(panimvalue, frame, pbone->scale[j + 3], angle1[j]);
1018
0
                        }
1019
1020
                        // Add the default rotation value.
1021
0
                        angle1[j] += pbone->value[j + 3];
1022
1023
0
                        if (panim->offset[j] != 0) {
1024
                            // Read compressed position delta.
1025
0
                            panimvalue = (const AnimValue_HL1 *)((uint8_t *)panim + panim->offset[j]);
1026
0
                            extract_anim_value(panimvalue, frame, pbone->scale[j], position_key->mValue[j]);
1027
0
                        }
1028
1029
                        // Add the default position value.
1030
0
                        position_key->mValue[j] += pbone->value[j];
1031
0
                    }
1032
1033
0
                    position_key->mTime = rotation_key->mTime = static_cast<double>(frame);
1034
                    /* The Half-Life engine uses X as forward, Y as left, Z as up. Therefore,
1035
                       pitch,yaw,roll is represented as (YZX). */
1036
0
                    rotation_key->mValue = aiQuaternion(angle1.y, angle1.z, angle1.x);
1037
0
                    rotation_key->mValue.Normalize();
1038
0
                }
1039
0
            }
1040
0
        }
1041
0
    }
1042
0
}
1043
1044
// ------------------------------------------------------------------------------------------------
1045
0
void HL1MDLLoader::read_sequence_groups_info() {
1046
0
    if (!header_->numseqgroups) {
1047
0
        return;
1048
0
    }
1049
1050
0
    aiNode *sequence_groups_node = new aiNode(AI_MDL_HL1_NODE_SEQUENCE_GROUPS);
1051
0
    rootnode_children_.push_back(sequence_groups_node);
1052
1053
0
    sequence_groups_node->mNumChildren = static_cast<unsigned int>(header_->numseqgroups);
1054
0
    sequence_groups_node->mChildren = new aiNode *[sequence_groups_node->mNumChildren];
1055
1056
0
    const SequenceGroup_HL1 *pseqgroup = get_buffer_data<SequenceGroup_HL1>(header_->seqgroupindex, header_->numseqgroups);
1057
1058
0
    unique_sequence_groups_names_.resize(header_->numseqgroups);
1059
0
    for (int i = 0; i < header_->numseqgroups; ++i) {
1060
0
        unique_sequence_groups_names_[i] = pseqgroup[i].label;
1061
0
    }
1062
1063
    // Ensure sequence groups have unique names.
1064
0
    unique_name_generator_.set_template_name("SequenceGroup");
1065
0
    unique_name_generator_.make_unique(unique_sequence_groups_names_);
1066
1067
0
    for (int i = 0; i < header_->numseqgroups; ++i, ++pseqgroup) {
1068
0
        aiNode *sequence_group_node = sequence_groups_node->mChildren[i] = new aiNode(unique_sequence_groups_names_[i]);
1069
0
        sequence_group_node->mParent = sequence_groups_node;
1070
1071
0
        aiMetadata *md = sequence_group_node->mMetaData = aiMetadata::Alloc(1);
1072
0
        if (i == 0) {
1073
            /* StudioMDL does not write the file name for the default sequence group,
1074
               so we will write it. */
1075
0
            md->Set(0, "File", aiString(file_path_));
1076
0
        } else {
1077
0
            md->Set(0, "File", aiString(pseqgroup->name));
1078
0
        }
1079
0
    }
1080
0
}
1081
1082
// ------------------------------------------------------------------------------------------------
1083
0
void HL1MDLLoader::read_sequence_infos() {
1084
0
    if (!header_->numseq) {
1085
0
        return;
1086
0
    }
1087
1088
0
    const SequenceDesc_HL1 *pseqdesc = get_buffer_data<SequenceDesc_HL1>(header_->seqindex, header_->numseq);
1089
1090
0
    aiNode *sequence_infos_node = new aiNode(AI_MDL_HL1_NODE_SEQUENCE_INFOS);
1091
0
    rootnode_children_.push_back(sequence_infos_node);
1092
1093
0
    sequence_infos_node->mNumChildren = static_cast<unsigned int>(header_->numseq);
1094
0
    sequence_infos_node->mChildren = new aiNode *[sequence_infos_node->mNumChildren];
1095
1096
0
    std::vector<aiNode *> sequence_info_node_children;
1097
1098
0
    int animation_index = 0;
1099
0
    for (int i = 0; i < header_->numseq; ++i, ++pseqdesc) {
1100
        // Clear the list of children for the upcoming sequence info node.
1101
0
        sequence_info_node_children.clear();
1102
1103
0
        aiNode *sequence_info_node = sequence_infos_node->mChildren[i] = new aiNode(unique_sequence_names_[i]);
1104
0
        sequence_info_node->mParent = sequence_infos_node;
1105
1106
        // Setup sequence info node Metadata.
1107
0
        aiMetadata *md = sequence_info_node->mMetaData = aiMetadata::Alloc(16);
1108
0
        md->Set(0, "AnimationIndex", animation_index);
1109
0
        animation_index += pseqdesc->numblends;
1110
1111
        // Reference the sequence group by name. This allows us to search a particular
1112
        // sequence group by name using aiNode(s).
1113
0
        md->Set(1, "SequenceGroup", aiString(unique_sequence_groups_names_[pseqdesc->seqgroup]));
1114
0
        md->Set(2, "FramesPerSecond", pseqdesc->fps);
1115
0
        md->Set(3, "NumFrames", pseqdesc->numframes);
1116
0
        md->Set(4, "NumBlends", pseqdesc->numblends);
1117
0
        md->Set(5, "Activity", pseqdesc->activity);
1118
0
        md->Set(6, "ActivityWeight", pseqdesc->actweight);
1119
0
        md->Set(7, "MotionFlags", pseqdesc->motiontype);
1120
0
        md->Set(8, "MotionBone", temp_bones_[pseqdesc->motionbone].node->mName);
1121
0
        md->Set(9, "LinearMovement", aiVector3D(pseqdesc->linearmovement[0], pseqdesc->linearmovement[1], pseqdesc->linearmovement[2]));
1122
0
        md->Set(10, "BBMin", aiVector3D(pseqdesc->bbmin[0], pseqdesc->bbmin[1], pseqdesc->bbmin[2]));
1123
0
        md->Set(11, "BBMax", aiVector3D(pseqdesc->bbmax[0], pseqdesc->bbmax[1], pseqdesc->bbmax[2]));
1124
0
        md->Set(12, "EntryNode", pseqdesc->entrynode);
1125
0
        md->Set(13, "ExitNode", pseqdesc->exitnode);
1126
0
        md->Set(14, "NodeFlags", pseqdesc->nodeflags);
1127
0
        md->Set(15, "Flags", pseqdesc->flags);
1128
1129
0
        if (import_settings_.read_blend_controllers) {
1130
0
            int num_blend_controllers;
1131
0
            if (get_num_blend_controllers(pseqdesc->numblends, num_blend_controllers) && num_blend_controllers) {
1132
                // Read blend controllers info.
1133
0
                aiNode *blend_controllers_node = new aiNode(AI_MDL_HL1_NODE_BLEND_CONTROLLERS);
1134
0
                sequence_info_node_children.push_back(blend_controllers_node);
1135
0
                blend_controllers_node->mParent = sequence_info_node;
1136
0
                blend_controllers_node->mNumChildren = static_cast<unsigned int>(num_blend_controllers);
1137
0
                blend_controllers_node->mChildren = new aiNode *[blend_controllers_node->mNumChildren];
1138
1139
0
                for (unsigned int j = 0; j < blend_controllers_node->mNumChildren; ++j) {
1140
0
                    aiNode *blend_controller_node = blend_controllers_node->mChildren[j] = new aiNode();
1141
0
                    blend_controller_node->mParent = blend_controllers_node;
1142
1143
0
                    aiMetadata *metaData = blend_controller_node->mMetaData = aiMetadata::Alloc(3);
1144
0
                    metaData->Set(0, "Start", pseqdesc->blendstart[j]);
1145
0
                    metaData->Set(1, "End", pseqdesc->blendend[j]);
1146
0
                    metaData->Set(2, "MotionFlags", pseqdesc->blendtype[j]);
1147
0
                }
1148
0
            }
1149
0
        }
1150
1151
0
        if (import_settings_.read_animation_events && pseqdesc->numevents) {
1152
            // Read animation events.
1153
1154
0
            if (pseqdesc->numevents > AI_MDL_HL1_MAX_EVENTS) {
1155
0
                log_warning_limit_exceeded<AI_MDL_HL1_MAX_EVENTS>(
1156
0
                        "Sequence " + std::string(pseqdesc->label),
1157
0
                        pseqdesc->numevents, "animation events");
1158
0
            }
1159
1160
0
            const AnimEvent_HL1 *pevent = get_buffer_data<AnimEvent_HL1>(pseqdesc->eventindex, pseqdesc->numevents);
1161
1162
0
            aiNode *pEventsNode = new aiNode(AI_MDL_HL1_NODE_ANIMATION_EVENTS);
1163
0
            sequence_info_node_children.push_back(pEventsNode);
1164
0
            pEventsNode->mParent = sequence_info_node;
1165
0
            pEventsNode->mNumChildren = static_cast<unsigned int>(pseqdesc->numevents);
1166
0
            pEventsNode->mChildren = new aiNode *[pEventsNode->mNumChildren];
1167
1168
0
            for (unsigned int j = 0; j < pEventsNode->mNumChildren; ++j, ++pevent) {
1169
0
                aiNode *pEvent = pEventsNode->mChildren[j] = new aiNode();
1170
0
                pEvent->mParent = pEventsNode;
1171
1172
0
                aiMetadata *metaData = pEvent->mMetaData = aiMetadata::Alloc(3);
1173
0
                metaData->Set(0, "Frame", pevent->frame);
1174
0
                metaData->Set(1, "ScriptEvent", pevent->event);
1175
0
                metaData->Set(2, "Options", aiString(pevent->options));
1176
0
            }
1177
0
        }
1178
1179
0
        if (sequence_info_node_children.size()) {
1180
0
            sequence_info_node->addChildren(
1181
0
                    static_cast<unsigned int>(sequence_info_node_children.size()),
1182
0
                    sequence_info_node_children.data());
1183
0
        }
1184
0
    }
1185
0
}
1186
1187
// ------------------------------------------------------------------------------------------------
1188
0
void HL1MDLLoader::read_sequence_transitions() {
1189
0
    if (!header_->numtransitions) {
1190
0
        return;
1191
0
    }
1192
1193
    // Read sequence transition graph.
1194
0
    aiNode *transition_graph_node = new aiNode(AI_MDL_HL1_NODE_SEQUENCE_TRANSITION_GRAPH);
1195
0
    rootnode_children_.push_back(transition_graph_node);
1196
1197
0
    const uint8_t *ptransitions = get_buffer_data<uint8_t>(header_->transitionindex, header_->numtransitions * header_->numtransitions);
1198
0
    aiMetadata *md = transition_graph_node->mMetaData = aiMetadata::Alloc(header_->numtransitions * header_->numtransitions);
1199
0
    for (unsigned int i = 0; i < md->mNumProperties; ++i)
1200
0
        md->Set(i, std::to_string(i), static_cast<int>(ptransitions[i]));
1201
0
}
1202
1203
0
void HL1MDLLoader::read_attachments() {
1204
0
    if (!header_->numattachments) {
1205
0
        return;
1206
0
    }
1207
1208
0
    const Attachment_HL1 *pattach = get_buffer_data<Attachment_HL1>(header_->attachmentindex, header_->numattachments);
1209
1210
0
    aiNode *attachments_node = new aiNode(AI_MDL_HL1_NODE_ATTACHMENTS);
1211
0
    rootnode_children_.push_back(attachments_node);
1212
0
    attachments_node->mNumChildren = static_cast<unsigned int>(header_->numattachments);
1213
0
    attachments_node->mChildren = new aiNode *[attachments_node->mNumChildren];
1214
1215
0
    for (int i = 0; i < header_->numattachments; ++i, ++pattach) {
1216
0
        aiNode *attachment_node = attachments_node->mChildren[i] = new aiNode();
1217
0
        attachment_node->mParent = attachments_node;
1218
0
        attachment_node->mMetaData = aiMetadata::Alloc(2);
1219
0
        attachment_node->mMetaData->Set(0, "Position", aiVector3D(pattach->org[0], pattach->org[1], pattach->org[2]));
1220
        // Reference the bone by name. This allows us to search a particular
1221
        // bone by name using aiNode(s).
1222
0
        attachment_node->mMetaData->Set(1, "Bone", temp_bones_[pattach->bone].node->mName);
1223
0
    }
1224
0
}
1225
1226
// ------------------------------------------------------------------------------------------------
1227
0
void HL1MDLLoader::read_hitboxes() {
1228
0
    if (!header_->numhitboxes) {
1229
0
        return;
1230
0
    }
1231
1232
0
    const Hitbox_HL1 *phitbox = get_buffer_data<Hitbox_HL1>(header_->hitboxindex, header_->numhitboxes);
1233
1234
0
    aiNode *hitboxes_node = new aiNode(AI_MDL_HL1_NODE_HITBOXES);
1235
0
    rootnode_children_.push_back(hitboxes_node);
1236
0
    hitboxes_node->mNumChildren = static_cast<unsigned int>(header_->numhitboxes);
1237
0
    hitboxes_node->mChildren = new aiNode *[hitboxes_node->mNumChildren];
1238
1239
0
    for (int i = 0; i < header_->numhitboxes; ++i, ++phitbox) {
1240
0
        aiNode *hitbox_node = hitboxes_node->mChildren[i] = new aiNode();
1241
0
        hitbox_node->mParent = hitboxes_node;
1242
1243
0
        aiMetadata *md = hitbox_node->mMetaData = aiMetadata::Alloc(4);
1244
        // Reference the bone by name. This allows us to search a particular
1245
        // bone by name using aiNode(s).
1246
0
        md->Set(0, "Bone", temp_bones_[phitbox->bone].node->mName);
1247
0
        md->Set(1, "HitGroup", phitbox->group);
1248
0
        md->Set(2, "BBMin", aiVector3D(phitbox->bbmin[0], phitbox->bbmin[1], phitbox->bbmin[2]));
1249
0
        md->Set(3, "BBMax", aiVector3D(phitbox->bbmax[0], phitbox->bbmax[1], phitbox->bbmax[2]));
1250
0
    }
1251
0
}
1252
1253
// ------------------------------------------------------------------------------------------------
1254
0
void HL1MDLLoader::read_bone_controllers() {
1255
0
    if (!header_->numbonecontrollers) {
1256
0
        return;
1257
0
    }
1258
1259
0
    const BoneController_HL1 *pbonecontroller = get_buffer_data<BoneController_HL1>(
1260
0
            header_->bonecontrollerindex,
1261
0
            header_->numbonecontrollers);
1262
1263
0
    aiNode *bones_controller_node = new aiNode(AI_MDL_HL1_NODE_BONE_CONTROLLERS);
1264
0
    rootnode_children_.push_back(bones_controller_node);
1265
0
    bones_controller_node->mNumChildren = static_cast<unsigned int>(header_->numbonecontrollers);
1266
0
    bones_controller_node->mChildren = new aiNode *[bones_controller_node->mNumChildren];
1267
1268
0
    for (int i = 0; i < header_->numbonecontrollers; ++i, ++pbonecontroller) {
1269
0
        aiNode *bone_controller_node = bones_controller_node->mChildren[i] = new aiNode();
1270
0
        bone_controller_node->mParent = bones_controller_node;
1271
1272
0
        aiMetadata *md = bone_controller_node->mMetaData = aiMetadata::Alloc(5);
1273
        // Reference the bone by name. This allows us to search a particular
1274
        // bone by name using aiNode(s).
1275
0
        md->Set(0, "Bone", temp_bones_[pbonecontroller->bone].node->mName);
1276
0
        md->Set(1, "MotionFlags", pbonecontroller->type);
1277
0
        md->Set(2, "Start", pbonecontroller->start);
1278
0
        md->Set(3, "End", pbonecontroller->end);
1279
0
        md->Set(4, "Channel", pbonecontroller->index);
1280
0
    }
1281
0
}
1282
1283
// ------------------------------------------------------------------------------------------------
1284
0
void HL1MDLLoader::read_global_info() {
1285
0
    aiNode *global_info_node = new aiNode(AI_MDL_HL1_NODE_GLOBAL_INFO);
1286
0
    rootnode_children_.push_back(global_info_node);
1287
1288
0
    aiMetadata *md = global_info_node->mMetaData = aiMetadata::Alloc(import_settings_.read_misc_global_info ? 16 : 11);
1289
0
    md->Set(0, "Version", AI_MDL_HL1_VERSION);
1290
0
    md->Set(1, "NumBodyparts", header_->numbodyparts);
1291
0
    md->Set(2, "NumModels", total_models_);
1292
0
    md->Set(3, "NumBones", header_->numbones);
1293
0
    md->Set(4, "NumAttachments", import_settings_.read_attachments ? header_->numattachments : 0);
1294
0
    md->Set(5, "NumSkinFamilies", texture_header_->numskinfamilies);
1295
0
    md->Set(6, "NumHitboxes", import_settings_.read_hitboxes ? header_->numhitboxes : 0);
1296
0
    md->Set(7, "NumBoneControllers", import_settings_.read_bone_controllers ? header_->numbonecontrollers : 0);
1297
0
    md->Set(8, "NumSequences", import_settings_.read_animations ? header_->numseq : 0);
1298
0
    md->Set(9, "NumBlendControllers", import_settings_.read_blend_controllers ? num_blend_controllers_ : 0);
1299
0
    md->Set(10, "NumTransitionNodes", import_settings_.read_sequence_transitions ? header_->numtransitions : 0);
1300
1301
0
    if (import_settings_.read_misc_global_info) {
1302
0
        md->Set(11, "EyePosition", aiVector3D(header_->eyeposition[0], header_->eyeposition[1], header_->eyeposition[2]));
1303
0
        md->Set(12, "HullMin", aiVector3D(header_->min[0], header_->min[1], header_->min[2]));
1304
0
        md->Set(13, "HullMax", aiVector3D(header_->max[0], header_->max[1], header_->max[2]));
1305
0
        md->Set(14, "CollisionMin", aiVector3D(header_->bbmin[0], header_->bbmin[1], header_->bbmin[2]));
1306
0
        md->Set(15, "CollisionMax", aiVector3D(header_->bbmax[0], header_->bbmax[1], header_->bbmax[2]));
1307
0
    }
1308
0
}
1309
1310
// ------------------------------------------------------------------------------------------------
1311
/** @brief This method reads a compressed anim value.
1312
*
1313
*   @note The structure of this method is taken from HL2 source code.
1314
*   Although this is from HL2, it's implementation is almost identical
1315
*   to code found in HL1 SDK. See HL1 and HL2 SDKs for more info.
1316
*
1317
*   source:
1318
*       HL1 source code.
1319
*           file: studio_render.cpp
1320
*           function(s): CalcBoneQuaternion and CalcBonePosition
1321
*
1322
*       HL2 source code.
1323
*           file: bone_setup.cpp
1324
*           function(s): ExtractAnimValue
1325
*/
1326
void HL1MDLLoader::extract_anim_value(
1327
        const AnimValue_HL1 *panimvalue,
1328
0
        int frame, float bone_scale, ai_real &value) {
1329
0
    int k = frame;
1330
1331
    // find span of values that includes the frame we want
1332
0
    while (panimvalue->num.total <= k) {
1333
0
        k -= panimvalue->num.total;
1334
0
        panimvalue += panimvalue->num.valid + 1;
1335
0
    }
1336
1337
    // Bah, missing blend!
1338
0
    if (panimvalue->num.valid > k) {
1339
0
        value = panimvalue[k + 1].value * bone_scale;
1340
0
    } else {
1341
0
        value = panimvalue[panimvalue->num.valid].value * bone_scale;
1342
0
    }
1343
0
}
1344
1345
// ------------------------------------------------------------------------------------------------
1346
// Get the number of blend controllers.
1347
0
bool HL1MDLLoader::get_num_blend_controllers(const int num_blend_animations, int &num_blend_controllers) {
1348
1349
0
    switch (num_blend_animations) {
1350
0
        case SequenceBlendMode_HL1::NoBlend:
1351
0
            num_blend_controllers = 0;
1352
0
            return true;
1353
0
        case SequenceBlendMode_HL1::TwoWayBlending:
1354
0
            num_blend_controllers = 1;
1355
0
            return true;
1356
0
        case SequenceBlendMode_HL1::FourWayBlending:
1357
0
            num_blend_controllers = 2;
1358
0
            return true;
1359
0
        default:
1360
0
            num_blend_controllers = 0;
1361
            ASSIMP_LOG_WARN(MDL_HALFLIFE_LOG_HEADER "Unsupported number of blend animations (", num_blend_animations, ")");
1362
0
            return false;
1363
0
    }
1364
0
}
1365
1366
} // namespace HalfLife
1367
} // namespace MDL
1368
} // namespace Assimp