Coverage Report

Created: 2025-06-22 07:30

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