瀏覽代碼

Added support to load Half-Life 1 MDL files.

Added code to use Half-Life 1 MDL loader in MDLLoader.cpp.
Added Half-Life 1 MDL loader files to CMakeLists.
Added new options in config.h to use with Half-Life 1 MDL loader.
Marc-Antoine Lortie 5 年之前
父節點
當前提交
eed0bd3ef6

+ 10 - 0
code/CMakeLists.txt

@@ -454,6 +454,16 @@ ADD_ASSIMP_IMPORTER( MDL
   MDL/MDLLoader.cpp
   MDL/MDLLoader.cpp
   MDL/MDLLoader.h
   MDL/MDLLoader.h
   MDL/MDLMaterialLoader.cpp
   MDL/MDLMaterialLoader.cpp
+  MDL/HalfLife/HalfLifeMDLBaseHeader.h
+  MDL/HalfLife/HL1FileData.h
+  MDL/HalfLife/HL1MDLLoader.cpp
+  MDL/HalfLife/HL1MDLLoader.h
+  MDL/HalfLife/HL1ImportDefinitions.h
+  MDL/HalfLife/HL1ImportSettings.h
+  MDL/HalfLife/HL1MeshTrivert.h
+  MDL/HalfLife/LogFunctions.h
+  MDL/HalfLife/UniqueNameGenerator.cpp
+  MDL/HalfLife/UniqueNameGenerator.h
 )
 )
 
 
 SET( MaterialSystem_SRCS
 SET( MaterialSystem_SRCS

+ 355 - 0
code/MDL/HalfLife/HL1FileData.h

@@ -0,0 +1,355 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+
+/** @file  HL1FileData.h
+ *  @brief Definition of in-memory structures for the
+ *        Half-Life 1 MDL file format.
+ */
+
+#ifndef AI_HL1FILEDATA_INCLUDED
+#define AI_HL1FILEDATA_INCLUDED
+
+#include "HalfLifeMDLBaseHeader.h"
+
+#include <assimp/Compiler/pushpack1.h>
+#include <assimp/types.h>
+
+namespace Assimp {
+namespace MDL {
+namespace HalfLife {
+
+using vec3_t = float[3];
+
+struct Header_HL1 : HalfLifeMDLBaseHeader {
+    char name[64];
+    int32_t length;
+
+    vec3_t eyeposition; // ideal eye position
+    vec3_t min; // ideal movement hull size
+    vec3_t max;
+
+    vec3_t bbmin; // clipping bounding box
+    vec3_t bbmax;
+
+    int32_t unused; // was flags
+
+    int32_t numbones; // bones
+    int32_t boneindex;
+
+    int32_t numbonecontrollers; // bone controllers
+    int32_t bonecontrollerindex;
+
+    int32_t numhitboxes; // complex bounding boxes
+    int32_t hitboxindex;
+
+    int32_t numseq; // animation sequences
+    int32_t seqindex;
+
+    int32_t numseqgroups; // demand loaded sequences
+    int32_t seqgroupindex;
+
+    int32_t numtextures; // raw textures
+    int32_t textureindex;
+    int32_t texturedataindex;
+
+    int32_t numskinref; // replaceable textures
+    int32_t numskinfamilies;
+    int32_t skinindex;
+
+    int32_t numbodyparts;
+    int32_t bodypartindex;
+
+    int32_t numattachments; // queryable attachable points
+    int32_t attachmentindex;
+
+    int32_t unused2; // was "soundtable"
+    int32_t unused3; // was "soundindex"
+    int32_t unused4; // was "soundgroups"
+    int32_t unused5; // was "soundgroupindex"
+
+    int32_t numtransitions; // animation node to animation node transition graph
+    int32_t transitionindex;
+} PACK_STRUCT;
+
+// header for demand loaded sequence group data
+struct SequenceHeader_HL1 : HalfLifeMDLBaseHeader {
+    char name[64];
+    int32_t length;
+} PACK_STRUCT;
+
+// bones
+struct Bone_HL1 {
+    char name[32]; // bone name for symbolic links
+    int32_t parent; // parent bone
+    int32_t unused; // was "flags" -- ??
+    int32_t bonecontroller[6]; // bone controller index, -1 == none
+    float value[6]; // default DoF values
+    float scale[6]; // scale for delta DoF values
+} PACK_STRUCT;
+
+// bone controllers
+struct BoneController_HL1 {
+    int32_t bone; // -1 == 0
+    int32_t type; // X, Y, Z, XR, YR, ZR, M
+    float start;
+    float end;
+    int32_t unused; // was "rest" - byte index value at rest
+    int32_t index; // 0-3 user set controller, 4 mouth
+} PACK_STRUCT;
+
+// intersection boxes
+struct Hitbox_HL1 {
+    int32_t bone;
+    int32_t group; // intersection group
+    vec3_t bbmin; // bounding box
+    vec3_t bbmax;
+} PACK_STRUCT;
+
+//
+// demand loaded sequence groups
+//
+struct SequenceGroup_HL1 {
+    char label[32]; // textual name
+    char name[64]; // file name
+    int32_t unused; // was "cache"  - index pointer
+    int32_t unused2; // was "data" -  hack for group 0
+} PACK_STRUCT;
+
+// The type of blending for a sequence.
+enum SequenceBlendMode_HL1 {
+    NoBlend = 1,
+    TwoWayBlending = 2,
+    FourWayBlending = 4,
+};
+
+// sequence descriptions
+struct SequenceDesc_HL1 {
+    char label[32]; // sequence label
+
+    float fps; // frames per second
+    int32_t flags; // looping/non-looping flags
+
+    int32_t activity;
+    int32_t actweight;
+
+    int32_t numevents;
+    int32_t eventindex;
+
+    int32_t numframes; // number of frames per sequence
+
+    int32_t unused; // was "numpivots" - number of foot pivots
+    int32_t unused2; // was "pivotindex"
+
+    int32_t motiontype;
+    int32_t motionbone;
+    vec3_t linearmovement;
+    int32_t unused3; // was "automoveposindex"
+    int32_t unused4; // was "automoveangleindex"
+
+    vec3_t bbmin; // per sequence bounding box
+    vec3_t bbmax;
+
+    int32_t numblends;
+    int32_t animindex; // mstudioanim_t pointer relative to start of sequence group data
+                   // [blend][bone][X, Y, Z, XR, YR, ZR]
+
+    int32_t blendtype[2]; // X, Y, Z, XR, YR, ZR
+    float blendstart[2]; // starting value
+    float blendend[2]; // ending value
+    int32_t unused5; // was "blendparent"
+
+    int32_t seqgroup; // sequence group for demand loading
+
+    int32_t entrynode; // transition node at entry
+    int32_t exitnode; // transition node at exit
+    int32_t nodeflags; // transition rules
+
+    int32_t unused6; // was "nextseq" - auto advancing sequences
+} PACK_STRUCT;
+
+// events
+struct AnimEvent_HL1 {
+    int32_t frame;
+    int32_t event;
+    int32_t unused; // was "type"
+    char options[64];
+} PACK_STRUCT;
+
+// attachment
+struct Attachment_HL1 {
+    char unused[32]; // was "name"
+    int32_t unused2; // was "type"
+    int32_t bone;
+    vec3_t org; // attachment point
+    vec3_t unused3[3]; // was "vectors"
+} PACK_STRUCT;
+
+struct AnimValueOffset_HL1 {
+    unsigned short offset[6];
+} PACK_STRUCT;
+
+// animation frames
+union AnimValue_HL1 {
+    struct {
+        uint8_t valid;
+        uint8_t total;
+    } num PACK_STRUCT;
+    short value;
+} PACK_STRUCT;
+
+// body part index
+struct Bodypart_HL1 {
+    char name[64];
+    int32_t nummodels;
+    int32_t base;
+    int32_t modelindex; // index into models array
+} PACK_STRUCT;
+
+// skin info
+struct Texture_HL1 {
+    char name[64];
+    int32_t flags;
+    int32_t width;
+    int32_t height;
+    int32_t index;
+} PACK_STRUCT;
+
+// studio models
+struct Model_HL1 {
+    char name[64];
+
+    int32_t unused; // was "type"
+
+    float unused2; // was "boundingradius"
+
+    int32_t nummesh;
+    int32_t meshindex;
+
+    int32_t numverts; // number of unique vertices
+    int32_t vertinfoindex; // vertex bone info
+    int32_t vertindex; // vertex vec3_t
+    int32_t numnorms; // number of unique surface normals
+    int32_t norminfoindex; // normal bone info
+    int32_t normindex; // normal vec3_t
+
+    int32_t unused3; // was "numgroups" - deformation groups
+    int32_t unused4; // was "groupindex"
+} PACK_STRUCT;
+
+// meshes
+struct Mesh_HL1 {
+    int32_t numtris;
+    int32_t triindex;
+    int32_t skinref;
+    int32_t numnorms; // per mesh normals
+    int32_t unused; // was "normindex" - normal vec3_t
+} PACK_STRUCT;
+
+struct Trivert {
+    short vertindex; // index into vertex array
+    short normindex; // index into normal array
+    short s, t; // s,t position on skin
+} PACK_STRUCT;
+
+#include <assimp/Compiler/poppack1.h>
+
+#if (!defined AI_MDL_HL1_VERSION)
+#define AI_MDL_HL1_VERSION 10
+#endif
+#if (!defined AI_MDL_HL1_MAX_TRIANGLES)
+#define AI_MDL_HL1_MAX_TRIANGLES 20000
+#endif
+#if (!defined AI_MDL_HL1_MAX_VERTICES)
+#define AI_MDL_HL1_MAX_VERTICES 2048
+#endif
+#if (!defined AI_MDL_HL1_MAX_SEQUENCES)
+#define AI_MDL_HL1_MAX_SEQUENCES 2048
+#endif
+#if (!defined AI_MDL_HL1_MAX_SEQUENCE_GROUPS)
+#define AI_MDL_HL1_MAX_SEQUENCE_GROUPS 32
+#endif
+#if (!defined AI_MDL_HL1_MAX_TEXTURES)
+#define AI_MDL_HL1_MAX_TEXTURES 100
+#endif
+#if (!defined AI_MDL_HL1_MAX_SKIN_FAMILIES)
+#define AI_MDL_HL1_MAX_SKIN_FAMILIES 100
+#endif
+#if (!defined AI_MDL_HL1_MAX_BONES)
+#define AI_MDL_HL1_MAX_BONES 128
+#endif
+#if (!defined AI_MDL_HL1_MAX_BODYPARTS)
+#define AI_MDL_HL1_MAX_BODYPARTS 32
+#endif
+#if (!defined AI_MDL_HL1_MAX_MODELS)
+#define AI_MDL_HL1_MAX_MODELS 32
+#endif
+#if (!defined AI_MDL_HL1_MAX_MESHES)
+#define AI_MDL_HL1_MAX_MESHES 256
+#endif
+#if (!defined AI_MDL_HL1_MAX_EVENTS)
+#define AI_MDL_HL1_MAX_EVENTS 1024
+#endif
+#if (!defined AI_MDL_HL1_MAX_BONE_CONTROLLERS)
+#define AI_MDL_HL1_MAX_BONE_CONTROLLERS 8
+#endif
+#if (!defined AI_MDL_HL1_MAX_ATTACHMENTS)
+#define AI_MDL_HL1_MAX_ATTACHMENTS 512
+#endif
+
+// lighting options
+#if (!defined AI_MDL_HL1_STUDIO_NF_FLATSHADE)
+#define AI_MDL_HL1_STUDIO_NF_FLATSHADE 0x0001
+#endif
+#if (!defined AI_MDL_HL1_STUDIO_NF_CHROME)
+#define AI_MDL_HL1_STUDIO_NF_CHROME 0x0002
+#endif
+#if (!defined AI_MDL_HL1_STUDIO_NF_ADDITIVE)
+#define AI_MDL_HL1_STUDIO_NF_ADDITIVE 0x0020
+#endif
+#if (!defined AI_MDL_HL1_STUDIO_NF_MASKED)
+#define AI_MDL_HL1_STUDIO_NF_MASKED 0x0040
+#endif
+
+} // namespace HalfLife
+} // namespace MDL
+} // namespace Assimp
+
+#endif // AI_HL1FILEDATA_INCLUDED

+ 64 - 0
code/MDL/HalfLife/HL1ImportDefinitions.h

@@ -0,0 +1,64 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+
+/** @file HL1ImportDefinitions.h
+ *  @brief HL1 MDL loader specific definitions.
+ */
+
+#ifndef AI_MDL_HL1_IMPORT_DEFINITIONS_INCLUDED
+#define AI_MDL_HL1_IMPORT_DEFINITIONS_INCLUDED
+
+#define AI_MDL_HL1_NODE_ROOT "<MDL_root>"
+#define AI_MDL_HL1_NODE_BODYPARTS "<MDL_bodyparts>"
+#define AI_MDL_HL1_NODE_BONES "<MDL_bones>"
+#define AI_MDL_HL1_NODE_BONE_CONTROLLERS "<MDL_bone_controllers>"
+#define AI_MDL_HL1_NODE_SEQUENCE_INFOS "<MDL_sequence_infos>"
+#define AI_MDL_HL1_NODE_SEQUENCE_GROUPS "<MDL_sequence_groups>"
+#define AI_MDL_HL1_NODE_SEQUENCE_TRANSITION_GRAPH "<MDL_sequence_transition_graph>"
+#define AI_MDL_HL1_NODE_ATTACHMENTS "<MDL_attachments>"
+#define AI_MDL_HL1_NODE_HITBOXES "<MDL_hitboxes>"
+#define AI_MDL_HL1_NODE_GLOBAL_INFO "<MDL_global_info>"
+#define AI_MDL_HL1_NODE_ANIMATION_EVENTS "AnimationEvents"
+#define AI_MDL_HL1_NODE_BLEND_CONTROLLERS "BlendControllers"
+
+#define AI_MDL_HL1_MATKEY_CHROME(type, N) "$mat.HL1.chrome", type, N
+
+#endif // AI_MDL_HL1_IMPORT_DEFINITIONS_INCLUDED

+ 85 - 0
code/MDL/HalfLife/HL1ImportSettings.h

@@ -0,0 +1,85 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+
+/** @file HL1ImportSettings.h
+ *  @brief Half-Life 1 MDL loader configuration settings.
+ */
+
+#ifndef AI_HL1IMPORTSETTINGS_INCLUDED
+#define AI_HL1IMPORTSETTINGS_INCLUDED
+
+#include <string>
+
+namespace Assimp {
+namespace MDL {
+namespace HalfLife {
+
+struct HL1ImportSettings {
+    HL1ImportSettings() :
+        read_animations(false),
+        read_animation_events(false),
+        read_blend_controllers(false),
+        read_sequence_groups_info(false),
+        read_sequence_transitions(false),
+        read_attachments(false),
+        read_bone_controllers(false),
+        read_hitboxes(false),
+        read_textures(false),
+        read_misc_global_info(false) {
+    }
+
+    bool read_animations;
+    bool read_animation_events;
+    bool read_blend_controllers;
+    bool read_sequence_groups_info;
+    bool read_sequence_transitions;
+    bool read_attachments;
+    bool read_bone_controllers;
+    bool read_hitboxes;
+    bool read_textures;
+    bool read_misc_global_info;
+};
+
+} // namespace HalfLife
+} // namespace MDL
+} // namespace Assimp
+
+#endif // AI_HL1IMPORTSETTINGS_INCLUDED

+ 1331 - 0
code/MDL/HalfLife/HL1MDLLoader.cpp

@@ -0,0 +1,1331 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+
+/** @file HL1MDLLoader.cpp
+ *  @brief Implementation for the Half-Life 1 MDL loader.
+ */
+
+#include "HL1MDLLoader.h"
+#include "HL1ImportDefinitions.h"
+#include "HL1MeshTrivert.h"
+#include "UniqueNameGenerator.h"
+
+#include <assimp/BaseImporter.h>
+#include <assimp/DefaultIOSystem.h>
+#include <assimp/Exceptional.h>
+#include <assimp/StringUtils.h>
+#include <assimp/ai_assert.h>
+#include <assimp/qnan.h>
+#include <assimp/scene.h>
+#include <assimp/types.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/Importer.hpp>
+
+#include <iomanip>
+#include <memory>
+#include <sstream>
+
+#ifdef MDL_HALFLIFE_LOG_WARN_HEADER
+#undef MDL_HALFLIFE_LOG_WARN_HEADER
+#endif
+#define MDL_HALFLIFE_LOG_HEADER "[Half-Life 1 MDL] "
+#include "LogFunctions.h"
+
+namespace Assimp {
+namespace MDL {
+namespace HalfLife {
+
+// ------------------------------------------------------------------------------------------------
+HL1MDLLoader::HL1MDLLoader(
+    aiScene *scene,
+    IOSystem *io,
+    const unsigned char *buffer,
+    const std::string &file_path,
+    const HL1ImportSettings &import_settings) :
+    scene_(scene),
+    io_(io),
+    buffer_(buffer),
+    file_path_(file_path),
+    import_settings_(import_settings),
+    texture_buffer_(nullptr),
+    anim_buffers_(nullptr),
+    anim_headers_(nullptr),
+    num_sequence_groups_(0),
+    rootnode_children_(),
+    unique_name_generator_(),
+    temp_bones_(),
+    num_blend_controllers_(0),
+    total_models_(0) {
+    load_file();
+}
+
+// ------------------------------------------------------------------------------------------------
+HL1MDLLoader::~HL1MDLLoader() {
+    release_resources();
+}
+
+// ------------------------------------------------------------------------------------------------
+void HL1MDLLoader::release_resources() {
+    if (buffer_ != texture_buffer_) {
+        delete[] texture_buffer_;
+        texture_buffer_ = nullptr;
+    }
+
+    if (num_sequence_groups_ && anim_buffers_) {
+        for (int i = 1; i < num_sequence_groups_; ++i) {
+            if (anim_buffers_[i]) {
+                delete[] anim_buffers_[i];
+                anim_buffers_[i] = nullptr;
+            }
+        }
+
+        delete[] anim_buffers_;
+        anim_buffers_ = nullptr;
+    }
+
+    if (anim_headers_) {
+        delete[] anim_headers_;
+        anim_headers_ = nullptr;
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void HL1MDLLoader::load_file() {
+
+    try {
+        header_ = (const Header_HL1 *)buffer_;
+        validate_header(header_, false);
+
+        // Create the root scene node.
+        scene_->mRootNode = new aiNode(AI_MDL_HL1_NODE_ROOT);
+
+        load_texture_file();
+
+        if (import_settings_.read_animations)
+            load_sequence_groups_files();
+
+        read_textures();
+        read_skins();
+
+        read_bones();
+        read_meshes();
+
+        if (import_settings_.read_animations) {
+            read_sequence_groups_info();
+            read_animations();
+            read_sequence_infos();
+            if (import_settings_.read_sequence_transitions)
+                read_sequence_transitions();
+        }
+
+        if (import_settings_.read_attachments)
+            read_attachments();
+
+        if (import_settings_.read_hitboxes)
+            read_hitboxes();
+
+        if (import_settings_.read_bone_controllers)
+            read_bone_controllers();
+
+        read_global_info();
+
+        // Append children to root node.
+        if (rootnode_children_.size()) {
+            scene_->mRootNode->addChildren(
+                    static_cast<unsigned int>(rootnode_children_.size()),
+                    rootnode_children_.data());
+        }
+
+        release_resources();
+
+    } catch (const std::exception &e) {
+        release_resources();
+        throw e;
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void HL1MDLLoader::validate_header(const Header_HL1 *header, bool is_texture_header) {
+    if (is_texture_header) {
+        // Every single Half-Life model is assumed to have at least one texture.
+        if (!header->numtextures)
+            throw DeadlyImportError(MDL_HALFLIFE_LOG_HEADER "There are no textures in the file");
+
+        if (header->numtextures > AI_MDL_HL1_MAX_TEXTURES)
+            log_warning_limit_exceeded<AI_MDL_HL1_MAX_TEXTURES>(header->numtextures, "textures");
+
+        if (header->numskinfamilies > AI_MDL_HL1_MAX_SKIN_FAMILIES)
+            log_warning_limit_exceeded<AI_MDL_HL1_MAX_SKIN_FAMILIES>(header->numskinfamilies, "skin families");
+
+    } else {
+        // Every single Half-Life model is assumed to have at least one bodypart.
+        if (!header->numbodyparts)
+            throw DeadlyImportError(MDL_HALFLIFE_LOG_HEADER "Model has no bodyparts");
+
+        // Every single Half-Life model is assumed to have at least one bone.
+        if (!header->numbones)
+            throw DeadlyImportError(MDL_HALFLIFE_LOG_HEADER "Model has no bones");
+
+        // Every single Half-Life model is assumed to have at least one sequence group,
+        // which is the "default" sequence group.
+        if (!header->numseqgroups)
+            throw DeadlyImportError(MDL_HALFLIFE_LOG_HEADER "Model has no sequence groups");
+
+        if (header->numbodyparts > AI_MDL_HL1_MAX_BODYPARTS)
+            log_warning_limit_exceeded<AI_MDL_HL1_MAX_BODYPARTS>(header->numbodyparts, "bodyparts");
+
+        if (header->numbones > AI_MDL_HL1_MAX_BONES)
+            log_warning_limit_exceeded<AI_MDL_HL1_MAX_BONES>(header->numbones, "bones");
+
+        if (header->numbonecontrollers > AI_MDL_HL1_MAX_BONE_CONTROLLERS)
+            log_warning_limit_exceeded<AI_MDL_HL1_MAX_BONE_CONTROLLERS>(header->numbonecontrollers, "bone controllers");
+
+        if (header->numseq > AI_MDL_HL1_MAX_SEQUENCES)
+            log_warning_limit_exceeded<AI_MDL_HL1_MAX_SEQUENCES>(header->numseq, "sequences");
+
+        if (header->numseqgroups > AI_MDL_HL1_MAX_SEQUENCE_GROUPS)
+            log_warning_limit_exceeded<AI_MDL_HL1_MAX_SEQUENCE_GROUPS>(header->numseqgroups, "sequence groups");
+
+        if (header->numattachments > AI_MDL_HL1_MAX_ATTACHMENTS)
+            log_warning_limit_exceeded<AI_MDL_HL1_MAX_ATTACHMENTS>(header->numattachments, "attachments");
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+/*
+    Load textures.
+
+    There are two ways for textures to be stored in a Half-Life model:
+
+    1. Directly in the MDL file (filePath) or
+    2. In an external MDL file.
+
+    Due to the way StudioMDL works (tool used to compile SMDs into MDLs),
+    it is assumed that an external texture file follows the naming
+    convention: <YourModelName>T.mdl. Note the extra (T) at the end of the
+    model name.
+
+    .e.g For a given model named MyModel.mdl
+
+    The external texture file name would be MyModelT.mdl
+*/
+void HL1MDLLoader::load_texture_file() {
+    if (header_->numtextures == 0) {
+        // Load an external MDL texture file.
+        std::string texture_file_path =
+                DefaultIOSystem::absolutePath(file_path_) + io_->getOsSeparator() +
+                DefaultIOSystem::completeBaseName(file_path_) + "T." +
+                BaseImporter::GetExtension(file_path_);
+
+        load_file_into_buffer<Header_HL1>(texture_file_path, texture_buffer_);
+    } else {
+        /* Model has no external texture file. This means the texture
+        is stored inside the main MDL file. */
+        texture_buffer_ = const_cast<unsigned char *>(buffer_);
+    }
+
+    texture_header_ = (const Header_HL1 *)texture_buffer_;
+
+    // Validate texture header.
+    validate_header(texture_header_, true);
+}
+
+// ------------------------------------------------------------------------------------------------
+/*
+    Load sequence group files if any.
+
+    Due to the way StudioMDL works (tool used to compile SMDs into MDLs),
+    it is assumed that a sequence group file follows the naming
+    convention: <YourModelName>0X.mdl. Note the extra (0X) at the end of
+    the model name, where (X) is the sequence group.
+
+    .e.g For a given model named MyModel.mdl
+
+    Sequence group 1 => MyModel01.mdl
+    Sequence group 2 => MyModel02.mdl
+    Sequence group X => MyModel0X.mdl
+
+*/
+void HL1MDLLoader::load_sequence_groups_files() {
+    if (header_->numseqgroups <= 1)
+        return;
+
+    num_sequence_groups_ = header_->numseqgroups;
+
+    anim_buffers_ = new unsigned char *[num_sequence_groups_];
+    anim_headers_ = new SequenceHeader_HL1 *[num_sequence_groups_];
+    for (int i = 0; i < num_sequence_groups_; ++i) {
+        anim_buffers_[i] = NULL;
+        anim_headers_[i] = NULL;
+    }
+
+    std::string file_path_without_extension =
+            DefaultIOSystem::absolutePath(file_path_) +
+            io_->getOsSeparator() +
+            DefaultIOSystem::completeBaseName(file_path_);
+
+    for (int i = 1; i < num_sequence_groups_; ++i) {
+        std::stringstream ss;
+        ss << file_path_without_extension;
+        ss << std::setw(2) << std::setfill('0') << i;
+        ss << '.' << BaseImporter::GetExtension(file_path_);
+
+        std::string sequence_file_path = ss.str();
+
+        load_file_into_buffer<SequenceHeader_HL1>(sequence_file_path, anim_buffers_[i]);
+
+        anim_headers_[i] = (SequenceHeader_HL1 *)anim_buffers_[i];
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+/** @brief Read an MDL texture.
+*
+*   @note This method is taken from HL1 source code.
+*   source: file: studio_utils.c
+*           function(s): UploadTexture
+*/
+void HL1MDLLoader::read_texture(const Texture_HL1 *ptexture,
+        uint8_t *data, uint8_t *pal, aiTexture *pResult,
+        aiColor3D &last_palette_color) {
+    int outwidth, outheight;
+    int i, j;
+    int row1[256], row2[256], col1[256], col2[256];
+    unsigned char *pix1, *pix2, *pix3, *pix4;
+
+    // convert texture to power of 2
+    for (outwidth = 1; outwidth < ptexture->width; outwidth <<= 1)
+        ;
+
+    if (outwidth > 256)
+        outwidth = 256;
+
+    for (outheight = 1; outheight < ptexture->height; outheight <<= 1)
+        ;
+
+    if (outheight > 256)
+        outheight = 256;
+
+    pResult->mFilename = ptexture->name;
+    pResult->mWidth = outwidth;
+    pResult->mHeight = outheight;
+    strncpy(pResult->achFormatHint, "bgra8888", 8);
+
+    aiTexel *out = pResult->pcData = new aiTexel[outwidth * outheight];
+
+    for (i = 0; i < outwidth; i++) {
+        col1[i] = (int)((i + 0.25) * (ptexture->width / (float)outwidth));
+        col2[i] = (int)((i + 0.75) * (ptexture->width / (float)outwidth));
+    }
+
+    for (i = 0; i < outheight; i++) {
+        row1[i] = (int)((i + 0.25) * (ptexture->height / (float)outheight)) * ptexture->width;
+        row2[i] = (int)((i + 0.75) * (ptexture->height / (float)outheight)) * ptexture->width;
+    }
+
+    // scale down and convert to 32bit RGB
+    for (i = 0; i < outheight; i++) {
+        for (j = 0; j < outwidth; j++, out++) {
+            pix1 = &pal[data[row1[i] + col1[j]] * 3];
+            pix2 = &pal[data[row1[i] + col2[j]] * 3];
+            pix3 = &pal[data[row2[i] + col1[j]] * 3];
+            pix4 = &pal[data[row2[i] + col2[j]] * 3];
+
+            out->r = (pix1[0] + pix2[0] + pix3[0] + pix4[0]) >> 2;
+            out->g = (pix1[1] + pix2[1] + pix3[1] + pix4[1]) >> 2;
+            out->b = (pix1[2] + pix2[2] + pix3[2] + pix4[2]) >> 2;
+            out->a = 0xFF;
+        }
+    }
+
+    // Get the last palette color.
+    last_palette_color.r = pal[255 * 3];
+    last_palette_color.g = pal[255 * 3 + 1];
+    last_palette_color.b = pal[255 * 3 + 2];
+}
+
+// ------------------------------------------------------------------------------------------------
+void HL1MDLLoader::read_textures() {
+    const Texture_HL1 *ptexture = (const Texture_HL1 *)((uint8_t *)texture_header_ + texture_header_->textureindex);
+    unsigned char *pin = texture_buffer_;
+
+    scene_->mNumTextures = scene_->mNumMaterials = texture_header_->numtextures;
+    scene_->mTextures = new aiTexture *[scene_->mNumTextures];
+    scene_->mMaterials = new aiMaterial *[scene_->mNumMaterials];
+
+    for (int i = 0; i < texture_header_->numtextures; ++i) {
+        scene_->mTextures[i] = new aiTexture();
+
+        aiColor3D last_palette_color;
+        read_texture(&ptexture[i],
+                pin + ptexture[i].index,
+                pin + ptexture[i].width * ptexture[i].height + ptexture[i].index,
+                scene_->mTextures[i],
+                last_palette_color);
+
+        aiMaterial *scene_material = scene_->mMaterials[i] = new aiMaterial();
+
+        const aiTextureType texture_type = aiTextureType_DIFFUSE;
+        aiString texture_name(ptexture[i].name);
+        scene_material->AddProperty(&texture_name, AI_MATKEY_TEXTURE(texture_type, 0));
+
+        // Is this a chrome texture?
+        int chrome = ptexture[i].flags & AI_MDL_HL1_STUDIO_NF_CHROME ? 1 : 0;
+        scene_material->AddProperty(&chrome, 1, AI_MDL_HL1_MATKEY_CHROME(texture_type, 0));
+
+        if (ptexture[i].flags & AI_MDL_HL1_STUDIO_NF_FLATSHADE) {
+            // Flat shading.
+            const aiShadingMode shading_mode = aiShadingMode_Flat;
+            scene_material->AddProperty(&shading_mode, 1, AI_MATKEY_SHADING_MODEL);
+        }
+
+        if (ptexture[i].flags & AI_MDL_HL1_STUDIO_NF_ADDITIVE) {
+            // Additive texture.
+            const aiBlendMode blend_mode = aiBlendMode_Additive;
+            scene_material->AddProperty(&blend_mode, 1, AI_MATKEY_BLEND_FUNC);
+        } else if (ptexture[i].flags & AI_MDL_HL1_STUDIO_NF_MASKED) {
+            // Texture with 1 bit alpha test.
+            const aiTextureFlags use_alpha = aiTextureFlags_UseAlpha;
+            scene_material->AddProperty(&use_alpha, 1, AI_MATKEY_TEXFLAGS(texture_type, 0));
+            scene_material->AddProperty(&last_palette_color, 1, AI_MATKEY_COLOR_TRANSPARENT);
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void HL1MDLLoader::read_skins() {
+    // Read skins, if any.
+    if (texture_header_->numskinfamilies <= 1)
+        return;
+
+    // Pointer to base texture index.
+    short *default_skin_ptr = (short *)((uint8_t *)texture_header_ + texture_header_->skinindex);
+
+    // Start at first replacement skin.
+    short *replacement_skin_ptr = default_skin_ptr + texture_header_->numskinref;
+
+    for (int i = 1; i < texture_header_->numskinfamilies; ++i, replacement_skin_ptr += texture_header_->numskinref) {
+        for (int j = 0; j < texture_header_->numskinref; ++j) {
+            if (default_skin_ptr[j] != replacement_skin_ptr[j]) {
+                // Save replacement textures.
+                aiString skinMaterialId(scene_->mTextures[replacement_skin_ptr[j]]->mFilename);
+                scene_->mMaterials[default_skin_ptr[j]]->AddProperty(&skinMaterialId, AI_MATKEY_TEXTURE_DIFFUSE(i));
+            }
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void HL1MDLLoader::read_bones() {
+    const Bone_HL1 *pbone = (const Bone_HL1 *)((uint8_t *)header_ + header_->boneindex);
+
+    std::vector<std::string> unique_bones_names(header_->numbones);
+    for (int i = 0; i < header_->numbones; ++i)
+        unique_bones_names[i] = pbone[i].name;
+
+    // Ensure bones have unique names.
+    unique_name_generator_.set_template_name("Bone");
+    unique_name_generator_.make_unique(unique_bones_names);
+
+    temp_bones_.resize(header_->numbones);
+
+    aiNode *bones_node = new aiNode(AI_MDL_HL1_NODE_BONES);
+    rootnode_children_.push_back(bones_node);
+    bones_node->mNumChildren = static_cast<unsigned int>(header_->numbones);
+    bones_node->mChildren = new aiNode *[bones_node->mNumChildren];
+
+    // Create bone matrices in local space.
+    for (int i = 0; i < header_->numbones; ++i) {
+        aiNode *bone_node = temp_bones_[i].node = bones_node->mChildren[i] = new aiNode(unique_bones_names[i]);
+
+        aiVector3D angles(pbone[i].value[3], pbone[i].value[4], pbone[i].value[5]);
+        temp_bones_[i].absolute_transform = bone_node->mTransformation =
+                aiMatrix4x4(aiVector3D(1), aiQuaternion(angles.y, angles.z, angles.x),
+                        aiVector3D(pbone[i].value[0], pbone[i].value[1], pbone[i].value[2]));
+
+        if (pbone[i].parent == -1) {
+            bone_node->mParent = scene_->mRootNode;
+        } else {
+            bone_node->mParent = bones_node->mChildren[pbone[i].parent];
+
+            temp_bones_[i].absolute_transform =
+                    temp_bones_[pbone[i].parent].absolute_transform * bone_node->mTransformation;
+        }
+
+        temp_bones_[i].offset_matrix = temp_bones_[i].absolute_transform;
+        temp_bones_[i].offset_matrix.Inverse();
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+/*
+    Read meshes.
+
+    Half-Life MDLs are structured such that each MDL
+    contains one or more 'bodypart(s)', which contain one
+    or more 'model(s)', which contains one or more mesh(es).
+
+    * Bodyparts are used to group models that may be replaced
+    in the game .e.g a character could have a 'heads' group,
+    'torso' group, 'shoes' group, with each group containing
+    different 'model(s)'.
+
+    * Models, also called 'sub models', contain vertices as
+    well as a reference to each mesh used by the sub model.
+
+    * Meshes contain a list of tris, also known as 'triverts'.
+    Each tris contains the following information:
+
+        1. The index of the position to use for the vertex.
+        2. The index of the normal to use for the vertex.
+        3. The S coordinate to use for the vertex UV.
+        4. The T coordinate ^
+
+    These tris represent the way to represent the triangles
+    for each mesh. Depending on how the tool compiled the MDL,
+    those triangles were saved as strips and or fans.
+
+    NOTE: Each tris is NOT unique. This means that you
+    might encounter the same vertex index but with a different
+    normal index, S coordinate, T coordinate.
+
+    In addition, each mesh contains the texture's index.
+
+    ------------------------------------------------------
+    With the details above, there are several things to
+    take into consideration.
+
+    * The Half-Life models store the vertices by sub model
+    rather than by mesh. Due to Assimp's structure, it
+    is necessary to remap each model vertex to be used
+    per mesh. Unfortunately, this has the consequence
+    to duplicate vertices.
+
+    * Because the mesh triangles are comprised of strips and
+    fans, it is necessary to convert each primitive to
+    triangles, respectively (3 indices per face).
+*/
+void HL1MDLLoader::read_meshes() {
+
+    int total_verts = 0;
+    int total_triangles = 0;
+    total_models_ = 0;
+
+    const Bodypart_HL1 *pbodypart = (const Bodypart_HL1 *)((uint8_t *)header_ + header_->bodypartindex);
+    const Model_HL1 *pmodel = nullptr;
+    const Mesh_HL1 *pmesh = nullptr;
+
+    const Texture_HL1 *ptexture = (const Texture_HL1 *)((uint8_t *)texture_header_ + texture_header_->textureindex);
+    short *pskinref = (short *)((uint8_t *)texture_header_ + texture_header_->skinindex);
+
+    scene_->mNumMeshes = 0;
+
+    std::vector<std::string> unique_bodyparts_names;
+    unique_bodyparts_names.resize(header_->numbodyparts);
+
+    // Count the number of meshes.
+
+    for (int i = 0; i < header_->numbodyparts; ++i, ++pbodypart) {
+        unique_bodyparts_names[i] = pbodypart->name;
+
+        pmodel = (Model_HL1 *)((uint8_t *)header_ + pbodypart->modelindex);
+        for (int j = 0; j < pbodypart->nummodels; ++j, ++pmodel) {
+            scene_->mNumMeshes += pmodel->nummesh;
+            total_verts += pmodel->numverts;
+        }
+
+        total_models_ += pbodypart->nummodels;
+    }
+
+    // Display limit infos.
+    if (total_verts > AI_MDL_HL1_MAX_VERTICES)
+        log_warning_limit_exceeded<AI_MDL_HL1_MAX_VERTICES>(total_verts, "vertices");
+
+    if (scene_->mNumMeshes > AI_MDL_HL1_MAX_MESHES)
+        log_warning_limit_exceeded<AI_MDL_HL1_MAX_MESHES>(scene_->mNumMeshes, "meshes");
+
+    if (total_models_ > AI_MDL_HL1_MAX_MODELS)
+        log_warning_limit_exceeded<AI_MDL_HL1_MAX_MODELS>(total_models_, "models");
+
+    // Ensure bodyparts have unique names.
+    unique_name_generator_.set_template_name("Bodypart");
+    unique_name_generator_.make_unique(unique_bodyparts_names);
+
+    // Now do the same for each model.
+    pbodypart = (const Bodypart_HL1 *)((uint8_t *)header_ + header_->bodypartindex);
+
+    // Prepare template name for bodypart models.
+    std::vector<std::string> unique_models_names;
+    unique_models_names.resize(total_models_);
+
+    unsigned int model_index = 0;
+
+    for (int i = 0; i < header_->numbodyparts; ++i, ++pbodypart) {
+        pmodel = (Model_HL1 *)((uint8_t *)header_ + pbodypart->modelindex);
+        for (int j = 0; j < pbodypart->nummodels; ++j, ++pmodel, ++model_index)
+            unique_models_names[model_index] = pmodel->name;
+    }
+
+    unique_name_generator_.set_template_name("Model");
+    unique_name_generator_.make_unique(unique_models_names);
+
+    unsigned int mesh_index = 0;
+
+    scene_->mMeshes = new aiMesh *[scene_->mNumMeshes];
+
+    pbodypart = (const Bodypart_HL1 *)((uint8_t *)header_ + header_->bodypartindex);
+
+    /* Create a node that will represent the mesh hierarchy.
+
+        <MDL_bodyparts>
+            |
+            +-- bodypart --+-- model -- [mesh index, mesh index, ...]
+            |              |
+            |              +-- model -- [mesh index, mesh index, ...]
+            |              |           
+            |              ...
+            |
+            |-- bodypart -- ...
+            |
+            ...
+     */
+    aiNode *bodyparts_node = new aiNode(AI_MDL_HL1_NODE_BODYPARTS);
+    rootnode_children_.push_back(bodyparts_node);
+    bodyparts_node->mNumChildren = static_cast<unsigned int>(header_->numbodyparts);
+    bodyparts_node->mChildren = new aiNode *[bodyparts_node->mNumChildren];
+    aiNode **bodyparts_node_ptr = bodyparts_node->mChildren;
+
+    // The following variables are defined here so they don't have
+    // to be recreated every iteration.
+
+    // Model_HL1 vertices, in bind pose space.
+    std::vector<aiVector3D> bind_pose_vertices;
+
+    // Model_HL1 normals, in bind pose space.
+    std::vector<aiVector3D> bind_pose_normals;
+
+    // Used to contain temporary information for building a mesh.
+    std::vector<HL1MeshTrivert> triverts;
+
+    std::vector<short> tricmds;
+
+    // Which triverts to use for the mesh.
+    std::vector<short> mesh_triverts_indices;
+
+    std::vector<HL1MeshFace> mesh_faces;
+
+    /* triverts that have the same vertindex, but have different normindex,s,t values.
+       Similar triverts are mapped from vertindex to a list of similar triverts. */
+    std::map<short, std::set<short>> triverts_similars;
+
+    // triverts per bone.
+    std::map<int, std::set<short>> bone_triverts;
+
+    /** This function adds a trivert index to the list of triverts per bone.
+     * \param[in] bone The bone that affects the trivert at index \p trivert_index.
+     * \param[in] trivert_index The trivert index.
+     */
+    auto AddTrivertToBone = [&](int bone, short trivert_index) {
+        if (bone_triverts.count(bone) == 0)
+            bone_triverts.insert_or_assign(bone, std::set<short>{ trivert_index });
+        else
+            bone_triverts[bone].insert(trivert_index);
+    };
+
+    /** This function creates and appends a new trivert to the list of triverts.
+     * \param[in] trivert The trivert to use as a prototype.
+     * \param[in] bone The bone that affects \p trivert.
+     */
+    auto AddSimilarTrivert = [&](const Trivert &trivert, const int bone) {
+        HL1MeshTrivert new_trivert(trivert);
+        new_trivert.localindex = static_cast<short>(mesh_triverts_indices.size());
+
+        short new_trivert_index = static_cast<short>(triverts.size());
+
+        if (triverts_similars.count(trivert.vertindex) == 0)
+            triverts_similars.insert_or_assign(trivert.vertindex, std::set<short>{ new_trivert_index });
+        else
+            triverts_similars[trivert.vertindex].insert(new_trivert_index);
+
+        triverts.push_back(new_trivert);
+
+        mesh_triverts_indices.push_back(new_trivert_index);
+        tricmds.push_back(new_trivert.localindex);
+        AddTrivertToBone(bone, new_trivert.localindex);
+    };
+
+    model_index = 0;
+
+    for (int i = 0; i < header_->numbodyparts; ++i, ++pbodypart, ++bodyparts_node_ptr) {
+        pmodel = (const Model_HL1 *)((uint8_t *)header_ + pbodypart->modelindex);
+
+        // Create bodypart node for the mesh tree hierarchy.
+        aiNode *bodypart_node = (*bodyparts_node_ptr) = new aiNode(unique_bodyparts_names[i]);
+        bodypart_node->mParent = bodyparts_node;
+        bodypart_node->mMetaData = aiMetadata::Alloc(1);
+        bodypart_node->mMetaData->Set(0, "Base", pbodypart->base);
+
+        bodypart_node->mNumChildren = static_cast<unsigned int>(pbodypart->nummodels);
+        bodypart_node->mChildren = new aiNode *[bodypart_node->mNumChildren];
+        aiNode **bodypart_models_ptr = bodypart_node->mChildren;
+
+        for (int j = 0; j < pbodypart->nummodels;
+                ++j, ++pmodel, ++bodypart_models_ptr, ++model_index) {
+
+            pmesh = (const Mesh_HL1 *)((uint8_t *)header_ + pmodel->meshindex);
+
+            uint8_t *pvertbone = ((uint8_t *)header_ + pmodel->vertinfoindex);
+            uint8_t *pnormbone = ((uint8_t *)header_ + pmodel->norminfoindex);
+            vec3_t *pstudioverts = (vec3_t *)((uint8_t *)header_ + pmodel->vertindex);
+            vec3_t *pstudionorms = (vec3_t *)((uint8_t *)header_ + pmodel->normindex);
+
+            // Each vertex and normal is in local space, so transform
+            // each of them to bring them in bind pose.
+            bind_pose_vertices.resize(pmodel->numverts);
+            bind_pose_normals.resize(pmodel->numnorms);
+            for (size_t k = 0; k < bind_pose_vertices.size(); ++k) {
+                const vec3_t &vert = pstudioverts[k];
+                bind_pose_vertices[k] = temp_bones_[pvertbone[k]].absolute_transform * aiVector3D(vert[0], vert[1], vert[2]);
+            }
+            for (size_t k = 0; k < bind_pose_normals.size(); ++k) {
+                const vec3_t &norm = pstudionorms[k];
+                // Compute the normal matrix to transform the normal into bind pose,
+                // without affecting its length.
+                const aiMatrix4x4 normal_matrix = aiMatrix4x4(temp_bones_[pnormbone[k]].absolute_transform).Inverse().Transpose();
+                bind_pose_normals[k] = normal_matrix * aiVector3D(norm[0], norm[1], norm[2]);
+            }
+
+            // Create model node for the mesh tree hierarchy.
+            aiNode *model_node = (*bodypart_models_ptr) = new aiNode(unique_models_names[model_index]);
+            model_node->mParent = bodypart_node;
+            model_node->mNumMeshes = static_cast<unsigned int>(pmodel->nummesh);
+            model_node->mMeshes = new unsigned int[model_node->mNumMeshes];
+            unsigned int *model_meshes_ptr = model_node->mMeshes;
+
+            for (int k = 0; k < pmodel->nummesh; ++k, ++pmesh, ++mesh_index, ++model_meshes_ptr) {
+                *model_meshes_ptr = mesh_index;
+
+                // Read triverts.
+                short *ptricmds = (short *)((uint8_t *)header_ + pmesh->triindex);
+                float texcoords_s_scale = 1.0f / (float)ptexture[pskinref[pmesh->skinref]].width;
+                float texcoords_t_scale = 1.0f / (float)ptexture[pskinref[pmesh->skinref]].height;
+
+                // Reset the data for the upcoming mesh.
+                triverts.clear();
+                triverts.resize(pmodel->numverts);
+                mesh_triverts_indices.clear();
+                mesh_faces.clear();
+                triverts_similars.clear();
+                bone_triverts.clear();
+
+                int l;
+                while (l = *(ptricmds++)) {
+                    bool is_triangle_fan = false;
+
+                    if (l < 0) {
+                        l = -l;
+                        is_triangle_fan = true;
+                    }
+
+                    // Clear the list of tris for the upcoming tris.
+                    tricmds.clear();
+
+                    for (; l > 0; l--, ptricmds += 4) {
+                        const Trivert *input_trivert = reinterpret_cast<const Trivert *>(ptricmds);
+                        const int bone = pvertbone[input_trivert->vertindex];
+
+                        HL1MeshTrivert *private_trivert = &triverts[input_trivert->vertindex];
+                        if (private_trivert->localindex == -1) {
+                            // First time referenced.
+                            *private_trivert = *input_trivert;
+                            private_trivert->localindex = static_cast<short>(mesh_triverts_indices.size());
+                            mesh_triverts_indices.push_back(input_trivert->vertindex);
+                            tricmds.push_back(private_trivert->localindex);
+                            AddTrivertToBone(bone, private_trivert->localindex);
+                        } else if (*private_trivert == *input_trivert) {
+                            // Exists and is the same.
+                            tricmds.push_back(private_trivert->localindex);
+                        } else {
+                            // No similar trivert associated to the trivert currently processed.
+                            if (triverts_similars.count(input_trivert->vertindex) == 0)
+                                AddSimilarTrivert(*input_trivert, bone);
+                            else {
+                                // Search in the list of similar triverts to see if the
+                                // trivert in process is already registered.
+                                short similar_index = -1;
+                                for (auto it = triverts_similars[input_trivert->vertindex].cbegin();
+                                        similar_index == -1 && it != triverts_similars[input_trivert->vertindex].cend();
+                                        ++it) {
+                                    if (triverts[*it] == *input_trivert)
+                                        similar_index = *it;
+                                }
+
+                                // If a similar trivert has been found, reuse it.
+                                // Otherwise, add it.
+                                if (similar_index == -1)
+                                    AddSimilarTrivert(*input_trivert, bone);
+                                else
+                                    tricmds.push_back(triverts[similar_index].localindex);
+                            }
+                        }
+                    }
+
+                    // Build mesh faces.
+                    const int num_faces = static_cast<int>(tricmds.size() - 2);
+                    mesh_faces.reserve(num_faces);
+
+                    if (is_triangle_fan) {
+                        for (int i = 0; i < num_faces; ++i) {
+                            mesh_faces.push_back(HL1MeshFace{
+                                    tricmds[0],
+                                    tricmds[i + 1],
+                                    tricmds[i + 2] });
+                        }
+                    } else {
+                        for (int i = 0; i < num_faces; ++i) {
+                            if (i & 1) {
+                                // Preserve winding order.
+                                mesh_faces.push_back(HL1MeshFace{
+                                        tricmds[i + 1],
+                                        tricmds[i],
+                                        tricmds[i + 2] });
+                            } else {
+                                mesh_faces.push_back(HL1MeshFace{
+                                        tricmds[i],
+                                        tricmds[i + 1],
+                                        tricmds[i + 2] });
+                            }
+                        }
+                    }
+
+                    total_triangles += num_faces;
+                }
+
+                // Create the scene mesh.
+                aiMesh *scene_mesh = scene_->mMeshes[mesh_index] = new aiMesh();
+                scene_mesh->mPrimitiveTypes = aiPrimitiveType::aiPrimitiveType_TRIANGLE;
+                scene_mesh->mMaterialIndex = pskinref[pmesh->skinref];
+
+                scene_mesh->mNumVertices = static_cast<unsigned int>(mesh_triverts_indices.size());
+
+                if (scene_mesh->mNumVertices) {
+                    scene_mesh->mVertices = new aiVector3D[scene_mesh->mNumVertices];
+                    scene_mesh->mNormals = new aiVector3D[scene_mesh->mNumVertices];
+
+                    scene_mesh->mNumUVComponents[0] = 2;
+                    scene_mesh->mTextureCoords[0] = new aiVector3D[scene_mesh->mNumVertices];
+
+                    // Add vertices.
+                    for (unsigned int v = 0; v < scene_mesh->mNumVertices; ++v) {
+                        const HL1MeshTrivert *pTrivert = &triverts[mesh_triverts_indices[v]];
+                        scene_mesh->mVertices[v] = bind_pose_vertices[pTrivert->vertindex];
+                        scene_mesh->mNormals[v] = bind_pose_normals[pTrivert->normindex];
+                        scene_mesh->mTextureCoords[0][v] = aiVector3D(
+                                pTrivert->s * texcoords_s_scale,
+                                pTrivert->t * texcoords_t_scale, 0);
+                    }
+
+                    // Add face and indices.
+                    scene_mesh->mNumFaces = static_cast<unsigned int>(mesh_faces.size());
+                    scene_mesh->mFaces = new aiFace[scene_mesh->mNumFaces];
+
+                    for (unsigned int f = 0; f < scene_mesh->mNumFaces; ++f) {
+                        aiFace *face = &scene_mesh->mFaces[f];
+                        face->mNumIndices = 3;
+                        face->mIndices = new unsigned int[3];
+                        face->mIndices[0] = mesh_faces[f].v0;
+                        face->mIndices[1] = mesh_faces[f].v1;
+                        face->mIndices[2] = mesh_faces[f].v2;
+                    }
+
+                    // Add mesh bones.
+                    scene_mesh->mNumBones = static_cast<unsigned int>(bone_triverts.size());
+                    scene_mesh->mBones = new aiBone *[scene_mesh->mNumBones];
+
+                    aiBone **scene_bone_ptr = scene_mesh->mBones;
+
+                    for (auto bone_it = bone_triverts.cbegin();
+                            bone_it != bone_triverts.cend();
+                            ++bone_it, ++scene_bone_ptr) {
+                        const int bone_index = bone_it->first;
+
+                        aiBone *scene_bone = (*scene_bone_ptr) = new aiBone();
+                        scene_bone->mName = temp_bones_[bone_index].node->mName;
+
+                        scene_bone->mOffsetMatrix = temp_bones_[bone_index].offset_matrix;
+
+                        auto vertex_ids = bone_triverts.at(bone_index);
+
+                        // Add vertex weight per bone.
+                        scene_bone->mNumWeights = static_cast<unsigned int>(vertex_ids.size());
+                        aiVertexWeight *vertex_weight_ptr = scene_bone->mWeights = new aiVertexWeight[scene_bone->mNumWeights];
+
+                        for (auto vertex_it = vertex_ids.begin();
+                                vertex_it != vertex_ids.end();
+                                ++vertex_it, ++vertex_weight_ptr) {
+                            vertex_weight_ptr->mVertexId = *vertex_it;
+                            vertex_weight_ptr->mWeight = 1.0f;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    if (total_triangles > AI_MDL_HL1_MAX_TRIANGLES)
+        log_warning_limit_exceeded<AI_MDL_HL1_MAX_TRIANGLES>(total_triangles, "triangles");
+}
+
+// ------------------------------------------------------------------------------------------------
+void HL1MDLLoader::read_animations() {
+    if (!header_->numseq)
+        return;
+
+    const SequenceDesc_HL1 *pseqdesc = (const SequenceDesc_HL1 *)((uint8_t *)header_ + header_->seqindex);
+    const SequenceGroup_HL1 *pseqgroup = nullptr;
+    const AnimValueOffset_HL1 *panim = nullptr;
+    const AnimValue_HL1 *panimvalue = nullptr;
+
+    unique_sequence_names_.resize(header_->numseq);
+    for (int i = 0; i < header_->numseq; ++i)
+        unique_sequence_names_[i] = pseqdesc[i].label;
+
+    // Ensure sequences have unique names.
+    unique_name_generator_.set_template_name("Sequence");
+    unique_name_generator_.make_unique(unique_sequence_names_);
+
+    scene_->mNumAnimations = 0;
+
+    int highest_num_blend_animations = SequenceBlendMode_HL1::NoBlend;
+
+    // Count the total number of animations.
+    for (int i = 0; i < header_->numseq; ++i, ++pseqdesc) {
+        scene_->mNumAnimations += pseqdesc->numblends;
+        highest_num_blend_animations = std::max(pseqdesc->numblends, highest_num_blend_animations);
+    }
+
+    // Get the number of available blend controllers for global info.
+    get_num_blend_controllers(highest_num_blend_animations, num_blend_controllers_);
+
+    pseqdesc = (const SequenceDesc_HL1 *)((uint8_t *)header_ + header_->seqindex);
+
+    aiAnimation **scene_animations_ptr = scene_->mAnimations = new aiAnimation *[scene_->mNumAnimations];
+
+    for (int sequence = 0; sequence < header_->numseq; ++sequence, ++pseqdesc) {
+        pseqgroup = (const SequenceGroup_HL1 *)((uint8_t *)header_ + header_->seqgroupindex) + pseqdesc->seqgroup;
+
+        if (pseqdesc->seqgroup == 0)
+            panim = (const AnimValueOffset_HL1 *)((uint8_t *)header_ + pseqgroup->unused2 + pseqdesc->animindex);
+        else
+            panim = (const AnimValueOffset_HL1 *)((uint8_t *)anim_headers_[pseqdesc->seqgroup] + pseqdesc->animindex);
+
+        for (int blend = 0; blend < pseqdesc->numblends; ++blend, ++scene_animations_ptr) {
+
+            const Bone_HL1 *pbone = (const Bone_HL1 *)((uint8_t *)header_ + header_->boneindex);
+
+            aiAnimation *scene_animation = (*scene_animations_ptr) = new aiAnimation();
+
+            scene_animation->mName = unique_sequence_names_[sequence];
+            scene_animation->mTicksPerSecond = pseqdesc->fps;
+            scene_animation->mDuration = static_cast<double>(pseqdesc->fps) * pseqdesc->numframes;
+            scene_animation->mNumChannels = static_cast<unsigned int>(header_->numbones);
+            scene_animation->mChannels = new aiNodeAnim *[scene_animation->mNumChannels];
+
+            for (int bone = 0; bone < header_->numbones; bone++, ++pbone, ++panim) {
+                aiNodeAnim *node_anim = scene_animation->mChannels[bone] = new aiNodeAnim();
+                node_anim->mNodeName = temp_bones_[bone].node->mName;
+
+                node_anim->mNumPositionKeys = pseqdesc->numframes;
+                node_anim->mNumRotationKeys = node_anim->mNumPositionKeys;
+                node_anim->mNumScalingKeys = 0;
+
+                node_anim->mPositionKeys = new aiVectorKey[node_anim->mNumPositionKeys];
+                node_anim->mRotationKeys = new aiQuatKey[node_anim->mNumRotationKeys];
+
+                for (int frame = 0; frame < pseqdesc->numframes; ++frame) {
+                    aiVectorKey *position_key = &node_anim->mPositionKeys[frame];
+                    aiQuatKey *rotation_key = &node_anim->mRotationKeys[frame];
+
+                    aiVector3D angle1;
+                    for (int j = 0; j < 3; ++j) {
+                        if (panim->offset[j + 3] != 0) {
+                            // Read compressed rotation delta.
+                            panimvalue = (const AnimValue_HL1 *)((uint8_t *)panim + panim->offset[j + 3]);
+                            extract_anim_value(panimvalue, frame, pbone->scale[j + 3], angle1[j]);
+                        }
+
+                        // Add the default rotation value.
+                        angle1[j] += pbone->value[j + 3];
+
+                        if (panim->offset[j] != 0) {
+                            // Read compressed position delta.
+                            panimvalue = (const AnimValue_HL1 *)((uint8_t *)panim + panim->offset[j]);
+                            extract_anim_value(panimvalue, frame, pbone->scale[j], position_key->mValue[j]);
+                        }
+
+                        // Add the default position value.
+                        position_key->mValue[j] += pbone->value[j];
+                    }
+
+                    position_key->mTime = rotation_key->mTime = static_cast<double>(frame);
+                    /* The Half-Life engine uses X as forward, Y as left, Z as up. Therefore,
+                       pitch,yaw,roll is represented as (YZX). */
+                    rotation_key->mValue = aiQuaternion(angle1.y, angle1.z, angle1.x);
+                    rotation_key->mValue.Normalize();
+                }
+            }
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void HL1MDLLoader::read_sequence_groups_info() {
+
+    aiNode *sequence_groups_node = new aiNode(AI_MDL_HL1_NODE_SEQUENCE_GROUPS);
+    rootnode_children_.push_back(sequence_groups_node);
+
+    sequence_groups_node->mNumChildren = static_cast<unsigned int>(header_->numseqgroups);
+    sequence_groups_node->mChildren = new aiNode *[sequence_groups_node->mNumChildren];
+
+    const SequenceGroup_HL1 *pseqgroup = (const SequenceGroup_HL1 *)((uint8_t *)header_ + header_->seqgroupindex);
+
+    unique_sequence_groups_names_.resize(header_->numseqgroups);
+    for (int i = 0; i < header_->numseqgroups; ++i)
+        unique_sequence_groups_names_[i] = pseqgroup[i].label;
+
+    // Ensure sequence groups have unique names.
+    unique_name_generator_.set_template_name("SequenceGroup");
+    unique_name_generator_.make_unique(unique_sequence_groups_names_);
+
+    for (int i = 0; i < header_->numseqgroups; ++i, ++pseqgroup) {
+        aiNode *sequence_group_node = sequence_groups_node->mChildren[i] = new aiNode(unique_sequence_groups_names_[i]);
+        sequence_group_node->mParent = sequence_groups_node;
+
+        aiMetadata *md = sequence_group_node->mMetaData = aiMetadata::Alloc(1);
+        if (i == 0) {
+            /* StudioMDL does not write the file name for the default sequence group,
+               so we will write it. */
+            md->Set(0, "File", aiString(file_path_));
+        } else {
+            md->Set(0, "File", aiString(pseqgroup->name));
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void HL1MDLLoader::read_sequence_infos() {
+    if (!header_->numseq)
+        return;
+
+    const SequenceDesc_HL1 *pseqdesc = (const SequenceDesc_HL1 *)((uint8_t *)header_ + header_->seqindex);
+
+    aiNode *sequence_infos_node = new aiNode(AI_MDL_HL1_NODE_SEQUENCE_INFOS);
+    rootnode_children_.push_back(sequence_infos_node);
+
+    sequence_infos_node->mNumChildren = static_cast<unsigned int>(header_->numseq);
+    sequence_infos_node->mChildren = new aiNode *[sequence_infos_node->mNumChildren];
+
+    std::vector<aiNode *> sequence_info_node_children;
+
+    int animation_index = 0;
+    for (int i = 0; i < header_->numseq; ++i, ++pseqdesc) {
+        // Clear the list of children for the upcoming sequence info node.
+        sequence_info_node_children.clear();
+
+        aiNode *sequence_info_node = sequence_infos_node->mChildren[i] = new aiNode(unique_sequence_names_[i]);
+        sequence_info_node->mParent = sequence_infos_node;
+
+        // Setup sequence info node Metadata.
+        aiMetadata *md = sequence_info_node->mMetaData = aiMetadata::Alloc(16);
+        md->Set(0, "AnimationIndex", animation_index);
+        animation_index += pseqdesc->numblends;
+
+        // Reference the sequence group by name. This allows us to search a particular
+        // sequence group by name using aiNode(s).
+        md->Set(1, "SequenceGroup", aiString(unique_sequence_groups_names_[pseqdesc->seqgroup]));
+        md->Set(2, "FramesPerSecond", pseqdesc->fps);
+        md->Set(3, "NumFrames", pseqdesc->numframes);
+        md->Set(4, "NumBlends", pseqdesc->numblends);
+        md->Set(5, "Activity", pseqdesc->activity);
+        md->Set(6, "ActivityWeight", pseqdesc->actweight);
+        md->Set(7, "MotionFlags", pseqdesc->motiontype);
+        md->Set(8, "MotionBone", temp_bones_[pseqdesc->motionbone].node->mName);
+        md->Set(9, "LinearMovement", aiVector3D(pseqdesc->linearmovement[0], pseqdesc->linearmovement[1], pseqdesc->linearmovement[2]));
+        md->Set(10, "BBMin", aiVector3D(pseqdesc->bbmin[0], pseqdesc->bbmin[1], pseqdesc->bbmin[2]));
+        md->Set(11, "BBMax", aiVector3D(pseqdesc->bbmax[0], pseqdesc->bbmax[1], pseqdesc->bbmax[2]));
+        md->Set(12, "EntryNode", pseqdesc->entrynode);
+        md->Set(13, "ExitNode", pseqdesc->exitnode);
+        md->Set(14, "NodeFlags", pseqdesc->nodeflags);
+        md->Set(15, "Flags", pseqdesc->flags);
+
+        if (import_settings_.read_blend_controllers) {
+            int num_blend_controllers;
+            if (get_num_blend_controllers(pseqdesc->numblends, num_blend_controllers) && num_blend_controllers) {
+                // Read blend controllers info.
+                aiNode *blend_controllers_node = new aiNode(AI_MDL_HL1_NODE_BLEND_CONTROLLERS);
+                sequence_info_node_children.push_back(blend_controllers_node);
+                blend_controllers_node->mParent = sequence_info_node;
+                blend_controllers_node->mNumChildren = static_cast<unsigned int>(num_blend_controllers);
+                blend_controllers_node->mChildren = new aiNode *[blend_controllers_node->mNumChildren];
+
+                for (unsigned int j = 0; j < blend_controllers_node->mNumChildren; ++j) {
+                    aiNode *blend_controller_node = blend_controllers_node->mChildren[j] = new aiNode();
+                    blend_controller_node->mParent = blend_controllers_node;
+
+                    aiMetadata *md = blend_controller_node->mMetaData = aiMetadata::Alloc(3);
+                    md->Set(0, "Start", pseqdesc->blendstart[j]);
+                    md->Set(1, "End", pseqdesc->blendend[j]);
+                    md->Set(2, "MotionFlags", pseqdesc->blendtype[j]);
+                }
+            }
+        }
+
+        if (import_settings_.read_animation_events && pseqdesc->numevents) {
+            // Read animation events.
+
+            if (pseqdesc->numevents > AI_MDL_HL1_MAX_EVENTS) {
+                log_warning_limit_exceeded<AI_MDL_HL1_MAX_EVENTS>(
+                        "Sequence " + std::string(pseqdesc->label),
+                        pseqdesc->numevents, "animation events");
+            }
+
+            const AnimEvent_HL1 *pevent = (const AnimEvent_HL1 *)((uint8_t *)header_ + pseqdesc->eventindex);
+
+            aiNode *pEventsNode = new aiNode(AI_MDL_HL1_NODE_ANIMATION_EVENTS);
+            sequence_info_node_children.push_back(pEventsNode);
+            pEventsNode->mParent = sequence_info_node;
+            pEventsNode->mNumChildren = static_cast<unsigned int>(pseqdesc->numevents);
+            pEventsNode->mChildren = new aiNode *[pEventsNode->mNumChildren];
+
+            for (unsigned int j = 0; j < pEventsNode->mNumChildren; ++j, ++pevent) {
+                aiNode *pEvent = pEventsNode->mChildren[j] = new aiNode();
+                pEvent->mParent = pEventsNode;
+
+                aiMetadata *md = pEvent->mMetaData = aiMetadata::Alloc(3);
+                md->Set(0, "Frame", pevent->frame);
+                md->Set(1, "ScriptEvent", pevent->event);
+                md->Set(2, "Options", aiString(pevent->options));
+            }
+        }
+
+        if (sequence_info_node_children.size()) {
+            sequence_info_node->addChildren(
+                    static_cast<unsigned int>(sequence_info_node_children.size()),
+                    sequence_info_node_children.data());
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void HL1MDLLoader::read_sequence_transitions() {
+    if (!header_->numtransitions)
+        return;
+
+    // Read sequence transition graph.
+    aiNode *transition_graph_node = new aiNode(AI_MDL_HL1_NODE_SEQUENCE_TRANSITION_GRAPH);
+    rootnode_children_.push_back(transition_graph_node);
+
+    uint8_t *ptransitions = ((uint8_t *)header_ + header_->transitionindex);
+    aiMetadata *md = transition_graph_node->mMetaData = aiMetadata::Alloc(header_->numtransitions * header_->numtransitions);
+    for (unsigned int i = 0; i < md->mNumProperties; ++i)
+        md->Set(i, std::to_string(i), static_cast<int>(ptransitions[i]));
+}
+
+void HL1MDLLoader::read_attachments() {
+    if (!header_->numattachments)
+        return;
+
+    const Attachment_HL1 *pattach = (const Attachment_HL1 *)((uint8_t *)header_ + header_->attachmentindex);
+
+    aiNode *attachments_node = new aiNode(AI_MDL_HL1_NODE_ATTACHMENTS);
+    rootnode_children_.push_back(attachments_node);
+    attachments_node->mNumChildren = static_cast<unsigned int>(header_->numattachments);
+    attachments_node->mChildren = new aiNode *[attachments_node->mNumChildren];
+
+    for (int i = 0; i < header_->numattachments; ++i, ++pattach) {
+        aiNode *attachment_node = attachments_node->mChildren[i] = new aiNode();
+        attachment_node->mParent = attachments_node;
+        attachment_node->mMetaData = aiMetadata::Alloc(2);
+        attachment_node->mMetaData->Set(0, "Position", aiVector3D(pattach->org[0], pattach->org[1], pattach->org[2]));
+        // Reference the bone by name. This allows us to search a particular
+        // bone by name using aiNode(s).
+        attachment_node->mMetaData->Set(1, "Bone", temp_bones_[pattach->bone].node->mName);
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void HL1MDLLoader::read_hitboxes() {
+    if (!header_->numhitboxes)
+        return;
+
+    const Hitbox_HL1 *phitbox = (const Hitbox_HL1 *)((uint8_t *)header_ + header_->hitboxindex);
+
+    aiNode *hitboxes_node = new aiNode(AI_MDL_HL1_NODE_HITBOXES);
+    rootnode_children_.push_back(hitboxes_node);
+    hitboxes_node->mNumChildren = static_cast<unsigned int>(header_->numhitboxes);
+    hitboxes_node->mChildren = new aiNode *[hitboxes_node->mNumChildren];
+
+    for (int i = 0; i < header_->numhitboxes; ++i, ++phitbox) {
+        aiNode *hitbox_node = hitboxes_node->mChildren[i] = new aiNode();
+        hitbox_node->mParent = hitboxes_node;
+
+        aiMetadata *md = hitbox_node->mMetaData = aiMetadata::Alloc(4);
+        // Reference the bone by name. This allows us to search a particular
+        // bone by name using aiNode(s).
+        md->Set(0, "Bone", temp_bones_[phitbox->bone].node->mName);
+        md->Set(1, "HitGroup", phitbox->group);
+        md->Set(2, "BBMin", aiVector3D(phitbox->bbmin[0], phitbox->bbmin[1], phitbox->bbmin[2]));
+        md->Set(3, "BBMax", aiVector3D(phitbox->bbmax[0], phitbox->bbmax[1], phitbox->bbmax[2]));
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void HL1MDLLoader::read_bone_controllers() {
+    if (!header_->numbonecontrollers)
+        return;
+
+    const BoneController_HL1 *pbonecontroller = (const BoneController_HL1 *)((uint8_t *)header_ + header_->bonecontrollerindex);
+
+    aiNode *bones_controller_node = new aiNode(AI_MDL_HL1_NODE_BONE_CONTROLLERS);
+    rootnode_children_.push_back(bones_controller_node);
+    bones_controller_node->mNumChildren = static_cast<unsigned int>(header_->numbonecontrollers);
+    bones_controller_node->mChildren = new aiNode *[bones_controller_node->mNumChildren];
+
+    for (int i = 0; i < header_->numbonecontrollers; ++i, ++pbonecontroller) {
+        aiNode *bone_controller_node = bones_controller_node->mChildren[i] = new aiNode();
+        bone_controller_node->mParent = bones_controller_node;
+
+        aiMetadata *md = bone_controller_node->mMetaData = aiMetadata::Alloc(5);
+        // Reference the bone by name. This allows us to search a particular
+        // bone by name using aiNode(s).
+        md->Set(0, "Bone", temp_bones_[pbonecontroller->bone].node->mName);
+        md->Set(1, "MotionFlags", pbonecontroller->type);
+        md->Set(2, "Start", pbonecontroller->start);
+        md->Set(3, "End", pbonecontroller->end);
+        md->Set(4, "Channel", pbonecontroller->index);
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void HL1MDLLoader::read_global_info() {
+    aiNode *global_info_node = new aiNode(AI_MDL_HL1_NODE_GLOBAL_INFO);
+    rootnode_children_.push_back(global_info_node);
+
+    aiMetadata *md = global_info_node->mMetaData = aiMetadata::Alloc(import_settings_.read_misc_global_info ? 16 : 11);
+    md->Set(0, "Version", AI_MDL_HL1_VERSION);
+    md->Set(1, "NumBodyparts", header_->numbodyparts);
+    md->Set(2, "NumModels", total_models_);
+    md->Set(3, "NumBones", header_->numbones);
+    md->Set(4, "NumAttachments", import_settings_.read_attachments ? header_->numattachments : 0);
+    md->Set(5, "NumSkinFamilies", texture_header_->numskinfamilies);
+    md->Set(6, "NumHitboxes", import_settings_.read_hitboxes ? header_->numhitboxes : 0);
+    md->Set(7, "NumBoneControllers", import_settings_.read_bone_controllers ? header_->numbonecontrollers : 0);
+    md->Set(8, "NumSequences", import_settings_.read_animations ? header_->numseq : 0);
+    md->Set(9, "NumBlendControllers", import_settings_.read_blend_controllers ? num_blend_controllers_ : 0);
+    md->Set(10, "NumTransitionNodes", import_settings_.read_sequence_transitions ? header_->numtransitions : 0);
+
+    if (import_settings_.read_misc_global_info) {
+        md->Set(11, "EyePosition", aiVector3D(header_->eyeposition[0], header_->eyeposition[1], header_->eyeposition[2]));
+        md->Set(12, "HullMin", aiVector3D(header_->min[0], header_->min[1], header_->min[2]));
+        md->Set(13, "HullMax", aiVector3D(header_->max[0], header_->max[1], header_->max[2]));
+        md->Set(14, "CollisionMin", aiVector3D(header_->bbmin[0], header_->bbmin[1], header_->bbmin[2]));
+        md->Set(15, "CollisionMax", aiVector3D(header_->bbmax[0], header_->bbmax[1], header_->bbmax[2]));
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+/** @brief This method reads a compressed anim value.
+*
+*   @note The structure of this method is taken from HL2 source code.
+*   Although this is from HL2, it's implementation is almost identical
+*   to code found in HL1 SDK. See HL1 and HL2 SDKs for more info.
+*   
+*   source:
+*       HL1 source code.
+*           file: studio_render.cpp
+*           function(s): CalcBoneQuaternion and CalcBonePosition
+*
+*       HL2 source code.
+*           file: bone_setup.cpp
+*           function(s): ExtractAnimValue
+*/
+void HL1MDLLoader::extract_anim_value(
+        const AnimValue_HL1 *panimvalue,
+        int frame, float bone_scale, float &value) {
+    int k = frame;
+
+    // find span of values that includes the frame we want
+    while (panimvalue->num.total <= k) {
+        k -= panimvalue->num.total;
+        panimvalue += panimvalue->num.valid + 1;
+    }
+
+    // Bah, missing blend!
+    if (panimvalue->num.valid > k)
+        value = panimvalue[k + 1].value * bone_scale;
+    else
+        value = panimvalue[panimvalue->num.valid].value * bone_scale;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Get the number of blend controllers.
+bool HL1MDLLoader::get_num_blend_controllers(const int num_blend_animations, int &num_blend_controllers) {
+
+    switch (num_blend_animations) {
+        case SequenceBlendMode_HL1::NoBlend:
+            num_blend_controllers = 0;
+            return true;
+        case SequenceBlendMode_HL1::TwoWayBlending:
+            num_blend_controllers = 1;
+            return true;
+        case SequenceBlendMode_HL1::FourWayBlending:
+            num_blend_controllers = 2;
+            return true;
+        default:
+            num_blend_controllers = 0;
+            ASSIMP_LOG_WARN(MDL_HALFLIFE_LOG_HEADER "Unsupported number of blend animations (" + std::to_string(num_blend_animations) + ")");
+            return false;
+    }
+}
+
+} // namespace HalfLife
+} // namespace MDL
+} // namespace Assimp

+ 235 - 0
code/MDL/HalfLife/HL1MDLLoader.h

@@ -0,0 +1,235 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+
+/** @file HL1MDLLoader.h
+ *  @brief Declaration of the Half-Life 1 MDL loader.
+ */
+
+#ifndef AI_HL1MDLLOADER_INCLUDED
+#define AI_HL1MDLLOADER_INCLUDED
+
+#include "HL1FileData.h"
+#include "HL1ImportSettings.h"
+#include "UniqueNameGenerator.h"
+
+#include <assimp/scene.h>
+#include <assimp/texture.h>
+#include <assimp/IOSystem.hpp>
+
+namespace Assimp {
+namespace MDL {
+namespace HalfLife {
+
+class HL1MDLLoader {
+public:
+    HL1MDLLoader() = delete;
+    HL1MDLLoader(const HL1MDLLoader &) = delete;
+
+    /** See variables descriptions at the end for more details. */
+    HL1MDLLoader(
+        aiScene *scene,
+        IOSystem *io,
+        const unsigned char *buffer,
+        const std::string &file_path,
+        const HL1ImportSettings &import_settings);
+
+    ~HL1MDLLoader();
+
+    void load_file();
+
+protected:
+    /** \brief Validate the header data structure of a Half-Life 1 MDL file.
+     * \param[in] header Input header to be validated.
+     * \param[in] is_texture_header Whether or not we are reading an MDL
+     *   texture file.
+     */
+    void validate_header(const Header_HL1 *header, bool is_texture_header);
+
+    void load_texture_file();
+    void load_sequence_groups_files();
+    void read_textures();
+    void read_skins();
+    void read_bones();
+    void read_meshes();
+    void read_animations();
+    void read_sequence_groups_info();
+    void read_sequence_infos();
+    void read_sequence_transitions();
+    void read_attachments();
+    void read_hitboxes();
+    void read_bone_controllers();
+    void read_global_info();
+
+private:
+    void release_resources();
+
+    /** \brief Load a file and copy it's content to a buffer.
+     * \param file_path The path to the file to be loaded.
+     * \param buffer A pointer to a buffer to receive the data.
+     */
+    template <typename MDLFileHeader>
+    void load_file_into_buffer(const std::string &file_path, unsigned char *&buffer);
+
+    /** \brief Read an MDL texture.
+     * \param[in] ptexture A pointer to an MDL texture.
+     * \param[in] data A pointer to the data from \p ptexture.
+     * \param[in] pal A pointer to the texture palette from \p ptexture.
+     * \param[in,out] pResult A pointer to the output resulting Assimp texture.
+     * \param[in,out] last_palette_color The last color from the image palette.
+     */
+    void read_texture(const Texture_HL1 *ptexture,
+            uint8_t *data, uint8_t *pal, aiTexture *pResult,
+            aiColor3D &last_palette_color);
+
+    /** \brief This method reads a compressed anim value.
+    * \param[in] panimvalue A pointer to the animation data.
+    * \param[in] frame The frame to look for.
+    * \param[in] bone_scale The current bone scale to apply to the compressed value.
+    * \param[in,out] value The decompressed anim value at \p frame.
+    */
+    void extract_anim_value(const AnimValue_HL1 *panimvalue,
+            int frame, float bone_scale, float &value);
+
+    /**
+     *  \brief Given the number of blend animations, determine the number of blend controllers.
+     *
+     * \param[in] num_blend_animations The number of blend animations.
+     * \param[out] num_blend_controllers The number of blend controllers.
+     * \return True if the number of blend controllers was determined. False otherwise.
+     */
+    static bool get_num_blend_controllers(const int num_blend_animations, int &num_blend_controllers);
+
+    /** Output scene to be filled */
+    aiScene *scene_;
+
+    /** Output I/O handler. Required for additional IO operations. */
+    IOSystem *io_;
+
+    /** Buffer from MDLLoader class. */
+    const unsigned char *buffer_;
+
+    /** The full file path to the MDL file we are trying to load.
+     * Used to locate other MDL files since MDL may store resources
+     * in external MDL files. */
+    const std::string &file_path_;
+
+    /** Configuration for HL1 MDL */
+    const HL1ImportSettings &import_settings_;
+
+    /** Main MDL header. */
+    const Header_HL1 *header_;
+
+    /** External MDL texture header. */
+    const Header_HL1 *texture_header_;
+
+    /** External MDL animation headers.
+     * One for each loaded animation file. */
+    SequenceHeader_HL1 **anim_headers_;
+
+    /** Texture file data. */
+    unsigned char *texture_buffer_;
+
+    /** Animation files data. */
+    unsigned char **anim_buffers_;
+
+    /** The number of sequence groups. */
+    int num_sequence_groups_;
+
+    /** The list of children to be appended to the scene's root node. */
+    std::vector<aiNode *> rootnode_children_;
+
+    /** A unique name generator. Used to generate names for MDL values
+     * that may have empty/duplicate names. */
+    UniqueNameGenerator unique_name_generator_;
+
+    /** The list of unique sequence names. */
+    std::vector<std::string> unique_sequence_names_;
+
+    /** The list of unique sequence groups names. */
+    std::vector<std::string> unique_sequence_groups_names_;
+
+    /** Structure to store temporary bone information. */
+    struct TempBone {
+
+        TempBone() :
+            node(nullptr),
+            absolute_transform(),
+            offset_matrix() {}
+
+        aiNode *node;
+        aiMatrix4x4 absolute_transform;
+        aiMatrix4x4 offset_matrix;
+    };
+
+    std::vector<TempBone> temp_bones_;
+
+    /** The number of available bone controllers in the model. */
+    int num_blend_controllers_;
+
+    /** Self explanatory. */
+    int total_models_;
+};
+
+// ------------------------------------------------------------------------------------------------
+template <typename MDLFileHeader>
+void HL1MDLLoader::load_file_into_buffer(const std::string &file_path, unsigned char *&buffer) {
+    if (!io_->Exists(file_path))
+        throw DeadlyImportError("Missing file " + DefaultIOSystem::fileName(file_path) + ".");
+
+    std::unique_ptr<IOStream> file(io_->Open(file_path));
+
+    if (file.get() == NULL)
+        throw DeadlyImportError("Failed to open MDL file " + DefaultIOSystem::fileName(file_path) + ".");
+
+    const size_t file_size = file->FileSize();
+    if (file_size < sizeof(MDLFileHeader))
+        throw DeadlyImportError("MDL file is too small.");
+
+    buffer = new unsigned char[1 + file_size];
+    file->Read((void *)buffer, 1, file_size);
+    buffer[file_size] = '\0';
+}
+
+} // namespace HalfLife
+} // namespace MDL
+} // namespace Assimp
+
+#endif // AI_HL1MDLLOADER_INCLUDED

+ 127 - 0
code/MDL/HalfLife/HL1MeshTrivert.h

@@ -0,0 +1,127 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+
+/** @file HL1MeshTrivert.h
+ *  @brief This file contains the class declaration for the
+ *         HL1 mesh trivert class.
+ */
+
+#ifndef AI_HL1MESHTRIVERT_INCLUDED
+#define AI_HL1MESHTRIVERT_INCLUDED
+
+#include "HL1FileData.h"
+
+namespace Assimp {
+namespace MDL {
+namespace HalfLife {
+
+/* A class to help map model triverts to mesh triverts. */
+struct HL1MeshTrivert {
+    HL1MeshTrivert() :
+        vertindex(-1),
+        normindex(-1),
+        s(0),
+        t(0),
+        localindex(-1) {
+    }
+
+    HL1MeshTrivert(short vertindex, short normindex, short s, short t, short localindex) :
+        vertindex(vertindex),
+        normindex(normindex),
+        s(s),
+        t(t),
+        localindex() {
+    }
+
+    HL1MeshTrivert(const Trivert &a) :
+        vertindex(a.vertindex),
+        normindex(a.normindex),
+        s(a.s),
+        t(a.t),
+        localindex(-1) {
+    }
+
+    inline bool operator==(const Trivert &a) const {
+        return vertindex == a.vertindex &&
+               normindex == a.normindex &&
+               s == a.s &&
+               t == a.t;
+    }
+
+    inline bool operator!=(const Trivert &a) const {
+        return !(*this == a);
+    }
+
+    inline bool operator==(const HL1MeshTrivert &a) const {
+        return localindex == a.localindex &&
+               vertindex == a.vertindex &&
+               normindex == a.normindex &&
+               s == a.s &&
+               t == a.t;
+    }
+
+    inline bool operator!=(const HL1MeshTrivert &a) const {
+        return !(*this == a);
+    }
+
+    inline HL1MeshTrivert &operator=(const Trivert &other) {
+        vertindex = other.vertindex;
+        normindex = other.normindex;
+        s = other.s;
+        t = other.t;
+        return *this;
+    }
+
+    short vertindex;
+    short normindex;
+    short s, t;
+    short localindex;
+};
+
+struct HL1MeshFace {
+    short v0, v1, v2;
+};
+
+} // namespace HalfLife
+} // namespace MDL
+} // namespace Assimp
+
+#endif // AI_HL1MESHTRIVERT_INCLUDED

+ 64 - 0
code/MDL/HalfLife/HalfLifeMDLBaseHeader.h

@@ -0,0 +1,64 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+
+/** @file HalfLifeMDLBaseHeader.h */
+
+#ifndef AI_HALFLIFEMDLBASEHEADER_INCLUDED
+#define AI_HALFLIFEMDLBASEHEADER_INCLUDED
+
+#include <assimp/types.h>
+
+namespace Assimp {
+namespace MDL {
+namespace HalfLife {
+
+/** Used to interface different Valve MDL formats. */
+struct HalfLifeMDLBaseHeader
+{
+    char ident[4];
+    int32_t version;
+};
+
+}
+}
+}
+
+#endif // AI_HALFLIFEMDLBASEHEADER_INCLUDED

+ 95 - 0
code/MDL/HalfLife/LogFunctions.h

@@ -0,0 +1,95 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+
+/** @file LogFunctions.h */
+
+#ifndef AI_MDL_HALFLIFE_LOGFUNCTIONS_INCLUDED
+#define AI_MDL_HALFLIFE_LOGFUNCTIONS_INCLUDED
+
+#include <assimp/Logger.hpp>
+#include <string>
+
+namespace Assimp {
+namespace MDL {
+namespace HalfLife {
+
+/**
+ * \brief A function to log precise messages regarding limits exceeded.
+ *
+ * \param[in] subject Subject.
+ * \param[in] current_amount Current amount.
+ * \param[in] direct_object Direct object.
+ *            LIMIT Limit constant.
+ *
+ * Example: Model has 100 textures, which exceeds the limit (50)
+ *
+ *          where \p subject is 'Model'
+ *                \p current_amount is '100'
+ *                \p direct_object is 'textures'
+ *                LIMIT is '50'
+ */
+template <int LIMIT>
+static inline void log_warning_limit_exceeded(
+    const std::string &subject, int current_amount,
+    const std::string &direct_object) {
+
+    ASSIMP_LOG_WARN(MDL_HALFLIFE_LOG_HEADER
+        + subject
+        + " has "
+        + std::to_string(current_amount) + " "
+        + direct_object
+        + ", which exceeds the limit ("
+        + std::to_string(LIMIT)
+        + ")");
+}
+
+/** \brief Same as above, but uses 'Model' as the subject. */
+template <int LIMIT>
+static inline void log_warning_limit_exceeded(int current_amount,
+    const std::string &direct_object) {
+    log_warning_limit_exceeded<LIMIT>("Model", current_amount, direct_object);
+}
+
+} // namespace HalfLife
+} // namespace MDL
+} // namespace Assimp
+
+#endif // AI_MDL_HALFLIFE_LOGFUNCTIONS_INCLUDED

+ 180 - 0
code/MDL/HalfLife/UniqueNameGenerator.cpp

@@ -0,0 +1,180 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+
+/** @file UniqueNameGenerator.cpp
+ *  @brief Implementation for the unique name generator.
+ */
+
+#include "UniqueNameGenerator.h"
+#include <algorithm>
+#include <list>
+#include <map>
+#include <numeric>
+
+namespace Assimp {
+namespace MDL {
+namespace HalfLife {
+
+UniqueNameGenerator::UniqueNameGenerator() :
+    template_name_("unnamed"),
+    separator_("_") {
+}
+
+UniqueNameGenerator::UniqueNameGenerator(const char *template_name) :
+    template_name_(template_name),
+    separator_("_") {
+}
+
+UniqueNameGenerator::UniqueNameGenerator(const char *template_name, const char *separator) :
+    template_name_(template_name),
+    separator_(separator) {
+}
+
+UniqueNameGenerator::~UniqueNameGenerator() {
+}
+
+void UniqueNameGenerator::make_unique(std::vector<std::string> &names) {
+    struct DuplicateInfo {
+        DuplicateInfo() :
+            indices(),
+            next_id(0) {
+        }
+
+        std::list<size_t> indices;
+        size_t next_id;
+    };
+
+    std::vector<size_t> empty_names_indices;
+    std::vector<size_t> template_name_duplicates;
+    std::map<std::string, DuplicateInfo> names_to_duplicates;
+
+    const std::string template_name_with_separator(template_name_ + separator_);
+
+    auto format_name = [&](const std::string &base_name, size_t id) -> std::string {
+        return base_name + separator_ + std::to_string(id);
+    };
+
+    auto generate_unique_name = [&](const std::string &base_name) -> std::string {
+        auto *duplicate_info = &names_to_duplicates[base_name];
+
+        std::string new_name = "";
+
+        bool found_identical_name;
+        bool tried_with_base_name_only = false;
+        do {
+            // Assume that no identical name exists.
+            found_identical_name = false;
+
+            if (!tried_with_base_name_only) {
+                // First try with only the base name.
+                new_name = base_name;
+            } else {
+                // Create the name expected to be unique.
+                new_name = format_name(base_name, duplicate_info->next_id);
+            }
+
+            // Check in the list of duplicates for an identical name.
+            for (size_t i = 0;
+                    i < names.size() &&
+                    !found_identical_name;
+                    ++i) {
+                if (new_name == names[i])
+                    found_identical_name = true;
+            }
+
+            if (tried_with_base_name_only)
+                ++duplicate_info->next_id;
+
+            tried_with_base_name_only = true;
+
+        } while (found_identical_name);
+
+        return new_name;
+    };
+
+    for (size_t i = 0; i < names.size(); ++i) {
+        // Check for empty names.
+        if (names[i].find_first_not_of(' ') == std::string::npos) {
+            empty_names_indices.push_back(i);
+            continue;
+        }
+
+        /* Check for potential duplicate.
+        a) Either if this name is the same as the template name or
+        b) <template name><separator> is found at the beginning. */
+        if (names[i] == template_name_ ||
+                names[i].substr(0, template_name_with_separator.length()) == template_name_with_separator)
+            template_name_duplicates.push_back(i);
+
+        // Map each unique name to it's duplicate.
+        if (names_to_duplicates.count(names[i]) == 0)
+            names_to_duplicates.insert_or_assign(names[i], DuplicateInfo());
+        else
+            names_to_duplicates[names[i]].indices.push_back(i);
+    }
+
+    // Make every non-empty name unique.
+    for (auto it = names_to_duplicates.begin();
+            it != names_to_duplicates.end(); ++it) {
+        for (auto it2 = it->second.indices.begin();
+                it2 != it->second.indices.end();
+                ++it2)
+            names[*it2] = generate_unique_name(it->first);
+    }
+
+    // Generate a unique name for every empty string.
+    if (template_name_duplicates.size()) {
+        // At least one string ressembles to <template name>.
+        for (auto it = empty_names_indices.begin();
+                it != empty_names_indices.end(); ++it)
+            names[*it] = generate_unique_name(template_name_);
+    } else {
+        // No string alike <template name> exists.
+        size_t i = 0;
+        for (auto it = empty_names_indices.begin();
+                it != empty_names_indices.end(); ++it, ++i)
+            names[*it] = format_name(template_name_, i);
+    }
+}
+
+} // namespace HalfLife
+} // namespace MDL
+} // namespace Assimp

+ 81 - 0
code/MDL/HalfLife/UniqueNameGenerator.h

@@ -0,0 +1,81 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+
+/** @file UniqueNameGenerator.h
+ *  @brief Declaration of the unique name generator.
+ */
+
+#ifndef AI_UNIQUENAMEGENERATOR_INCLUDED
+#define AI_UNIQUENAMEGENERATOR_INCLUDED
+
+#include <string>
+#include <vector>
+
+namespace Assimp {
+namespace MDL {
+namespace HalfLife {
+
+class UniqueNameGenerator {
+public:
+    UniqueNameGenerator();
+    UniqueNameGenerator(const char *template_name);
+    UniqueNameGenerator(const char *template_name, const char *separator);
+    ~UniqueNameGenerator();
+
+    inline void set_template_name(const char *template_name) {
+        template_name_ = template_name;
+    }
+    inline void set_separator(const char *separator) {
+        separator_ = separator;
+    }
+
+    void make_unique(std::vector<std::string> &names);
+
+private:
+    std::string template_name_;
+    std::string separator_;
+};
+
+} // namespace HalfLife
+} // namespace MDL
+} // namespace Assimp
+
+#endif // AI_UNIQUENAMEGENERATOR_INCLUDED

+ 42 - 2
code/MDL/MDLLoader.cpp

@@ -51,6 +51,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "MDL/MDLLoader.h"
 #include "MDL/MDLLoader.h"
 #include "MDL/MDLDefaultColorMap.h"
 #include "MDL/MDLDefaultColorMap.h"
 #include "MD2/MD2FileData.h"
 #include "MD2/MD2FileData.h"
+#include "MDL/HalfLife/HL1MDLLoader.h"
 
 
 #include <assimp/qnan.h>
 #include <assimp/qnan.h>
 #include <assimp/StringUtils.h>
 #include <assimp/StringUtils.h>
@@ -142,6 +143,18 @@ void MDLImporter::SetupProperties(const Importer* pImp)
 
 
     // AI_CONFIG_IMPORT_MDL_COLORMAP - palette file
     // AI_CONFIG_IMPORT_MDL_COLORMAP - palette file
     configPalette =  pImp->GetPropertyString(AI_CONFIG_IMPORT_MDL_COLORMAP,"colormap.lmp");
     configPalette =  pImp->GetPropertyString(AI_CONFIG_IMPORT_MDL_COLORMAP,"colormap.lmp");
+
+    // Read configuration specific to MDL (Half-Life 1).
+    mHL1ImportSettings.read_animations = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_ANIMATIONS, true);
+    if (mHL1ImportSettings.read_animations) {
+        mHL1ImportSettings.read_animation_events = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_ANIMATION_EVENTS, true);
+        mHL1ImportSettings.read_blend_controllers = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_BLEND_CONTROLLERS, true);
+        mHL1ImportSettings.read_sequence_transitions = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_SEQUENCE_TRANSITIONS, true);
+    }
+    mHL1ImportSettings.read_attachments = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_ATTACHMENTS, true);
+    mHL1ImportSettings.read_bone_controllers = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_BONE_CONTROLLERS, true);
+    mHL1ImportSettings.read_hitboxes = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_HITBOXES, true);
+    mHL1ImportSettings.read_misc_global_info = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_MISC_GLOBAL_INFO, true);
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
@@ -224,9 +237,19 @@ void MDLImporter::InternReadFile( const std::string& pFile,
     else if (AI_MDL_MAGIC_NUMBER_BE_HL2a == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_HL2a == iMagicWord ||
     else if (AI_MDL_MAGIC_NUMBER_BE_HL2a == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_HL2a == iMagicWord ||
         AI_MDL_MAGIC_NUMBER_BE_HL2b == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_HL2b == iMagicWord)
         AI_MDL_MAGIC_NUMBER_BE_HL2b == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_HL2b == iMagicWord)
     {
     {
-        ASSIMP_LOG_DEBUG("MDL subtype: Source(tm) Engine, magic word is IDST/IDSQ");
         iGSFileVersion = 0;
         iGSFileVersion = 0;
-        InternReadFile_HL2();
+
+        HalfLife::HalfLifeMDLBaseHeader *pHeader = (HalfLife::HalfLifeMDLBaseHeader *)mBuffer;
+        if (pHeader->version == AI_MDL_HL1_VERSION)
+        {
+            ASSIMP_LOG_DEBUG("MDL subtype: Half-Life 1/Goldsrc Engine, magic word is IDST/IDSQ");
+            InternReadFile_HL1(pFile, iMagicWord);
+        }
+        else
+        {
+            ASSIMP_LOG_DEBUG("MDL subtype: Source(tm) Engine, magic word is IDST/IDSQ");
+            InternReadFile_HL2();
+        }
     }
     }
     else    {
     else    {
         // print the magic word to the log file
         // print the magic word to the log file
@@ -1955,6 +1978,23 @@ void MDLImporter::JoinSkins_3DGS_MDL7(
     }
     }
 }
 }
 
 
+// ------------------------------------------------------------------------------------------------
+// Read a Half-life 1 MDL
+void MDLImporter::InternReadFile_HL1(const std::string& pFile, const uint32_t iMagicWord)
+{
+    // We can't correctly load an MDL from a MDL "sequence" file.
+    if (iMagicWord == AI_MDL_MAGIC_NUMBER_BE_HL2b || iMagicWord == AI_MDL_MAGIC_NUMBER_LE_HL2b)
+        throw DeadlyImportError("Impossible to properly load a model from an MDL sequence file.");
+
+    // Read the MDL file.
+    HalfLife::HL1MDLLoader loader(
+        pScene,
+        pIOHandler,
+        mBuffer,
+        pFile,
+        mHL1ImportSettings);
+}
+
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Read a half-life 2 MDL
 // Read a half-life 2 MDL
 void MDLImporter::InternReadFile_HL2( )
 void MDLImporter::InternReadFile_HL2( )

+ 10 - 0
code/MDL/MDLLoader.h

@@ -51,6 +51,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/BaseImporter.h>
 #include <assimp/BaseImporter.h>
 #include "MDLFileData.h"
 #include "MDLFileData.h"
 #include "HMP/HalfLifeFileData.h"
 #include "HMP/HalfLifeFileData.h"
+#include "HalfLife/HL1ImportSettings.h"
 
 
 struct aiNode;
 struct aiNode;
 struct aiTexture;
 struct aiTexture;
@@ -77,6 +78,7 @@ using namespace MDL;
  *      <li>3D Game Studio MDL3, MDL4</li>
  *      <li>3D Game Studio MDL3, MDL4</li>
  *      <li>3D Game Studio MDL5</li>
  *      <li>3D Game Studio MDL5</li>
  *      <li>3D Game Studio MDL7</li>
  *      <li>3D Game Studio MDL7</li>
+ *      <li>Halflife 1</li>
  *      <li>Halflife 2</li>
  *      <li>Halflife 2</li>
  *   </ul>
  *   </ul>
  *  These formats are partially identical and it would be possible to load
  *  These formats are partially identical and it would be possible to load
@@ -131,6 +133,11 @@ protected:
     */
     */
     void InternReadFile_3DGS_MDL7( );
     void InternReadFile_3DGS_MDL7( );
 
 
+    // -------------------------------------------------------------------
+    /** Import a Half-Life 1 MDL file
+    */
+    void InternReadFile_HL1(const std::string& pFile, const uint32_t iMagicWord);
+
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Import a CS:S/HL2 MDL file (not fully implemented)
     /** Import a CS:S/HL2 MDL file (not fully implemented)
     */
     */
@@ -436,6 +443,9 @@ protected:
 
 
     /** Size of the input file in bytes */
     /** Size of the input file in bytes */
     unsigned int iFileSize;
     unsigned int iFileSize;
+
+    /* Configuration for HL1 MDL */
+    HalfLife::HL1ImportSettings mHL1ImportSettings;
 };
 };
 
 
 } // end of namespace Assimp
 } // end of namespace Assimp

+ 67 - 0
include/assimp/config.h.in

@@ -695,6 +695,73 @@ enum aiComponent
 #define AI_CONFIG_IMPORT_SMD_KEYFRAME       "IMPORT_SMD_KEYFRAME"
 #define AI_CONFIG_IMPORT_SMD_KEYFRAME       "IMPORT_SMD_KEYFRAME"
 #define AI_CONFIG_IMPORT_UNREAL_KEYFRAME    "IMPORT_UNREAL_KEYFRAME"
 #define AI_CONFIG_IMPORT_UNREAL_KEYFRAME    "IMPORT_UNREAL_KEYFRAME"
 
 
+// ---------------------------------------------------------------------------
+/** @brief Set whether the MDL (HL1) importer will read animations.
+ *
+ * The default value is true (1)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_MDL_HL1_READ_ANIMATIONS "IMPORT_MDL_HL1_READ_ANIMATIONS"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the MDL (HL1) importer will read animation events.
+ * \note This property requires AI_CONFIG_IMPORT_MDL_HL1_READ_ANIMATIONS to be set to true.
+ *
+ * The default value is true (1)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_MDL_HL1_READ_ANIMATION_EVENTS "IMPORT_MDL_HL1_READ_ANIMATION_EVENTS"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the MDL (HL1) importer will read blend controllers.
+ * \note This property requires AI_CONFIG_IMPORT_MDL_HL1_READ_ANIMATIONS to be set to true.
+ *
+ * The default value is true (1)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_MDL_HL1_READ_BLEND_CONTROLLERS "IMPORT_MDL_HL1_READ_BLEND_CONTROLLERS"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the MDL (HL1) importer will read sequence transition graph.
+ * \note This property requires AI_CONFIG_IMPORT_MDL_HL1_READ_ANIMATIONS to be set to true.
+ *
+ * The default value is true (1)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_MDL_HL1_READ_SEQUENCE_TRANSITIONS "IMPORT_MDL_HL1_READ_SEQUENCE_TRANSITIONS"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the MDL (HL1) importer will read attachments info.
+ *
+ * The default value is true (1)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_MDL_HL1_READ_ATTACHMENTS "IMPORT_MDL_HL1_READ_ATTACHMENTS"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the MDL (HL1) importer will read bone controllers info.
+ *
+ * The default value is true (1)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_MDL_HL1_READ_BONE_CONTROLLERS "IMPORT_MDL_HL1_READ_BONE_CONTROLLERS"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the MDL (HL1) importer will read hitboxes info.
+ *
+ * The default value is true (1)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_MDL_HL1_READ_HITBOXES "IMPORT_MDL_HL1_READ_HITBOXES"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the MDL (HL1) importer will read miscellaneous global model info.
+ *
+ * The default value is true (1)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_MDL_HL1_READ_MISC_GLOBAL_INFO "IMPORT_MDL_HL1_READ_MISC_GLOBAL_INFO"
+
 // ---------------------------------------------------------------------------
 // ---------------------------------------------------------------------------
 /** Smd load multiple animations
 /** Smd load multiple animations
  *
  *