summaryrefslogtreecommitdiff
path: root/libs/assimp/code/AssetLib/MDL
diff options
context:
space:
mode:
Diffstat (limited to 'libs/assimp/code/AssetLib/MDL')
-rw-r--r--libs/assimp/code/AssetLib/MDL/HalfLife/HL1FileData.h600
-rw-r--r--libs/assimp/code/AssetLib/MDL/HalfLife/HL1ImportDefinitions.h64
-rw-r--r--libs/assimp/code/AssetLib/MDL/HalfLife/HL1ImportSettings.h85
-rw-r--r--libs/assimp/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp1353
-rw-r--r--libs/assimp/code/AssetLib/MDL/HalfLife/HL1MDLLoader.h243
-rw-r--r--libs/assimp/code/AssetLib/MDL/HalfLife/HL1MeshTrivert.h127
-rw-r--r--libs/assimp/code/AssetLib/MDL/HalfLife/HalfLifeMDLBaseHeader.h67
-rw-r--r--libs/assimp/code/AssetLib/MDL/HalfLife/LogFunctions.h95
-rw-r--r--libs/assimp/code/AssetLib/MDL/HalfLife/UniqueNameGenerator.cpp180
-rw-r--r--libs/assimp/code/AssetLib/MDL/HalfLife/UniqueNameGenerator.h81
-rw-r--r--libs/assimp/code/AssetLib/MDL/MDLDefaultColorMap.h120
-rw-r--r--libs/assimp/code/AssetLib/MDL/MDLFileData.h945
-rw-r--r--libs/assimp/code/AssetLib/MDL/MDLLoader.cpp1976
-rw-r--r--libs/assimp/code/AssetLib/MDL/MDLLoader.h451
-rw-r--r--libs/assimp/code/AssetLib/MDL/MDLMaterialLoader.cpp765
15 files changed, 7152 insertions, 0 deletions
diff --git a/libs/assimp/code/AssetLib/MDL/HalfLife/HL1FileData.h b/libs/assimp/code/AssetLib/MDL/HalfLife/HL1FileData.h
new file mode 100644
index 0000000..28b1b28
--- /dev/null
+++ b/libs/assimp/code/AssetLib/MDL/HalfLife/HL1FileData.h
@@ -0,0 +1,600 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2022, 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
+ * \brief Data structure for the HL1 MDL file header.
+ */
+struct Header_HL1 : HalfLifeMDLBaseHeader {
+ //! The model name.
+ char name[64];
+
+ //! The total file size in bytes.
+ int32_t length;
+
+ //! Ideal eye position.
+ vec3_t eyeposition;
+
+ //! Ideal movement hull size.
+ vec3_t min;
+ vec3_t max;
+
+ //! Clipping bounding box.
+ vec3_t bbmin;
+ vec3_t bbmax;
+
+ //! Was "flags".
+ int32_t unused;
+
+ //! The number of bones.
+ int32_t numbones;
+
+ //! Offset to the first bone chunk.
+ int32_t boneindex;
+
+ //! The number of bone controllers.
+ int32_t numbonecontrollers;
+
+ //! Offset to the first bone controller chunk.
+ int32_t bonecontrollerindex;
+
+ //! The number of hitboxes.
+ int32_t numhitboxes;
+
+ //! Offset to the first hitbox chunk.
+ int32_t hitboxindex;
+
+ //! The number of sequences.
+ int32_t numseq;
+
+ //! Offset to the first sequence description chunk.
+ int32_t seqindex;
+
+ //! The number of sequence groups.
+ int32_t numseqgroups;
+
+ //! Offset to the first sequence group chunk.
+ int32_t seqgroupindex;
+
+ //! The number of textures.
+ int32_t numtextures;
+
+ //! Offset to the first texture chunk.
+ int32_t textureindex;
+
+ //! Offset to the first texture's image data.
+ int32_t texturedataindex;
+
+ //! The number of replaceable textures.
+ int32_t numskinref;
+
+ //! The number of skin families.
+ int32_t numskinfamilies;
+
+ //! Offset to the first replaceable texture.
+ int32_t skinindex;
+
+ //! The number of bodyparts.
+ int32_t numbodyparts;
+
+ //! Offset the the first bodypart.
+ int32_t bodypartindex;
+
+ //! The number of attachments.
+ int32_t numattachments;
+
+ //! Offset the the first attachment chunk.
+ int32_t attachmentindex;
+
+ //! Was "soundtable".
+ int32_t unused2;
+
+ //! Was "soundindex".
+ int32_t unused3;
+
+ //! Was "soundgroups".
+ int32_t unused4;
+
+ //! Was "soundgroupindex".
+ int32_t unused5;
+
+ //! The number of nodes in the sequence transition graph.
+ int32_t numtransitions;
+
+ //! Offset the the first sequence transition.
+ int32_t transitionindex;
+} PACK_STRUCT;
+
+/** \struct SequenceHeader_HL1
+ * \brief Data structure for the file header of a demand loaded
+ * HL1 MDL sequence group file.
+ */
+struct SequenceHeader_HL1 : HalfLifeMDLBaseHeader {
+ //! The sequence group file name.
+ char name[64];
+
+ //! The total file size in bytes.
+ int32_t length;
+} PACK_STRUCT;
+
+/** \struct Bone_HL1
+ * \brief Data structure for a bone in HL1 MDL files.
+ */
+struct Bone_HL1 {
+ //! The bone name.
+ char name[32];
+
+ //! The parent bone index. (-1) If it has no parent.
+ int32_t parent;
+
+ //! Was "flags".
+ int32_t unused;
+
+ //! Available bone controller per motion type.
+ //! (-1) if no controller is available.
+ int32_t bonecontroller[6];
+
+ /*! Default position and rotation values where
+ * scale[0] = position.X
+ * scale[1] = position.Y
+ * scale[2] = position.Z
+ * scale[3] = rotation.X
+ * scale[4] = rotation.Y
+ * scale[5] = rotation.Z
+ */
+ float value[6];
+
+ /*! Compressed scale values where
+ * scale[0] = position.X scale
+ * scale[1] = position.Y scale
+ * scale[2] = position.Z scale
+ * scale[3] = rotation.X scale
+ * scale[4] = rotation.Y scale
+ * scale[5] = rotation.Z scale
+ */
+ float scale[6];
+} PACK_STRUCT;
+
+/** \struct BoneController_HL1
+ * \brief Data structure for a bone controller in HL1 MDL files.
+ */
+struct BoneController_HL1 {
+ //! Bone affected by this controller.
+ int32_t bone;
+
+ //! The motion type.
+ int32_t type;
+
+ //! The minimum and maximum values.
+ float start;
+ float end;
+
+ // Was "rest".
+ int32_t unused;
+
+ // The bone controller channel.
+ int32_t index;
+} PACK_STRUCT;
+
+/** \struct Hitbox_HL1
+ * \brief Data structure for a hitbox in HL1 MDL files.
+ */
+struct Hitbox_HL1 {
+ //! The bone this hitbox follows.
+ int32_t bone;
+
+ //! The hit group.
+ int32_t group;
+
+ //! The hitbox minimum and maximum extents.
+ vec3_t bbmin;
+ vec3_t bbmax;
+} PACK_STRUCT;
+
+/** \struct SequenceGroup_HL1
+ * \brief Data structure for a sequence group in HL1 MDL files.
+ */
+struct SequenceGroup_HL1 {
+ //! A textual name for this sequence group.
+ char label[32];
+
+ //! The file name.
+ char name[64];
+
+ //! Was "cache".
+ int32_t unused;
+
+ //! Was "data".
+ int32_t unused2;
+} PACK_STRUCT;
+
+//! The type of blending for a sequence.
+enum SequenceBlendMode_HL1 {
+ NoBlend = 1,
+ TwoWayBlending = 2,
+ FourWayBlending = 4,
+};
+
+/** \struct SequenceDesc_HL1
+ * \brief Data structure for a sequence description in HL1 MDL files.
+ */
+struct SequenceDesc_HL1 {
+ //! The sequence name.
+ char label[32];
+
+ //! Frames per second.
+ float fps;
+
+ //! looping/non-looping flags.
+ int32_t flags;
+
+ //! The sequence activity.
+ int32_t activity;
+
+ //! The sequence activity weight.
+ int32_t actweight;
+
+ //! The number of animation events.
+ int32_t numevents;
+
+ //! Offset the the first animation event chunk.
+ int32_t eventindex;
+
+ //! The number of frames in the sequence.
+ int32_t numframes;
+
+ //! Was "numpivots".
+ int32_t unused;
+
+ //! Was "pivotindex".
+ int32_t unused2;
+
+ //! Linear motion type.
+ int32_t motiontype;
+
+ //! Linear motion bone.
+ int32_t motionbone;
+
+ //! Linear motion.
+ vec3_t linearmovement;
+
+ //! Was "automoveposindex".
+ int32_t unused3;
+
+ //! Was "automoveangleindex".
+ int32_t unused4;
+
+ //! The sequence minimum and maximum extents.
+ vec3_t bbmin;
+ vec3_t bbmax;
+
+ //! The number of blend animations.
+ int32_t numblends;
+
+ //! Offset to first the AnimValueOffset_HL1 chunk.
+ //! This offset is relative to the SequenceHeader_HL1 of the file
+ //! that contains the animation data.
+ int32_t animindex;
+
+ //! The motion type of each blend controller.
+ int32_t blendtype[2];
+
+ //! The starting value of each blend controller.
+ float blendstart[2];
+
+ //! The ending value of each blend controller.
+ float blendend[2];
+
+ //! Was "blendparent".
+ int32_t unused5;
+
+ //! The sequence group.
+ int32_t seqgroup;
+
+ //! The node at entry in the sequence transition graph.
+ int32_t entrynode;
+
+ //! The node at exit in the sequence transition graph.
+ int32_t exitnode;
+
+ //! Transition rules.
+ int32_t nodeflags;
+
+ //! Was "nextseq"
+ int32_t unused6;
+} PACK_STRUCT;
+
+/** \struct AnimEvent_HL1
+ * \brief Data structure for an animation event in HL1 MDL files.
+ */
+struct AnimEvent_HL1 {
+ //! The frame at which this animation event occurs.
+ int32_t frame;
+
+ //! The script event type.
+ int32_t event;
+
+ //! was "type"
+ int32_t unused;
+
+ //! Options. Could be path to sound WAVE files.
+ char options[64];
+} PACK_STRUCT;
+
+/** \struct Attachment_HL1
+ * \brief Data structure for an attachment in HL1 MDL files.
+ */
+struct Attachment_HL1 {
+ //! Was "name".
+ char unused[32];
+
+ //! Was "type".
+ int32_t unused2;
+
+ //! The bone this attachment follows.
+ int32_t bone;
+
+ //! The attachment origin.
+ vec3_t org;
+
+ //! Was "vectors"
+ vec3_t unused3[3];
+} PACK_STRUCT;
+
+/** \struct AnimValueOffset_HL1
+ * \brief Data structure to hold offsets (one per motion type)
+ * to the first animation frame value for a single bone
+ * in HL1 MDL files.
+ */
+struct AnimValueOffset_HL1 {
+ unsigned short offset[6];
+} PACK_STRUCT;
+
+/** \struct AnimValue_HL1
+ * \brief Data structure for an animation frame in HL1 MDL files.
+ */
+union AnimValue_HL1 {
+ struct {
+ uint8_t valid;
+ uint8_t total;
+ } num;
+ short value;
+} PACK_STRUCT;
+
+/** \struct Bodypart_HL1
+ * \brief Data structure for a bodypart in HL1 MDL files.
+ */
+struct Bodypart_HL1 {
+ //! The bodypart name.
+ char name[64];
+
+ //! The number of available models for this bodypart.
+ int32_t nummodels;
+
+ //! Used to convert from a global model index
+ //! to a local bodypart model index.
+ int32_t base;
+
+ //! The offset to the first model chunk.
+ int32_t modelindex;
+} PACK_STRUCT;
+
+/** \struct Texture_HL1
+ * \brief Data structure for a texture in HL1 MDL files.
+ */
+struct Texture_HL1 {
+ //! Texture file name.
+ char name[64];
+
+ //! Texture flags.
+ int32_t flags;
+
+ //! Texture width in pixels.
+ int32_t width;
+
+ //! Texture height in pixels.
+ int32_t height;
+
+ //! Offset to the image data.
+ //! This offset is relative to the texture file header.
+ int32_t index;
+} PACK_STRUCT;
+
+/** \struct Model_HL1
+ * \brief Data structure for a model in HL1 MDL files.
+ */
+struct Model_HL1 {
+ //! Model name.
+ char name[64];
+
+ //! Was "type".
+ int32_t unused;
+
+ //! Was "boundingradius".
+ float unused2;
+
+ //! The number of meshes in the model.
+ int32_t nummesh;
+
+ //! Offset to the first mesh chunk.
+ int32_t meshindex;
+
+ //! The number of unique vertices.
+ int32_t numverts;
+
+ //! Offset to the vertex bone array.
+ int32_t vertinfoindex;
+
+ //! Offset to the vertex array.
+ int32_t vertindex;
+
+ //! The number of unique normals.
+ int32_t numnorms;
+
+ //! Offset to the normal bone array.
+ int32_t norminfoindex;
+
+ //! Offset to the normal array.
+ int32_t normindex;
+
+ //! Was "numgroups".
+ int32_t unused3;
+
+ //! Was "groupindex".
+ int32_t unused4;
+} PACK_STRUCT;
+
+/** \struct Mesh_HL1
+ * \brief Data structure for a mesh in HL1 MDL files.
+ */
+struct Mesh_HL1 {
+ //! Can be interpreted as the number of triangles in the mesh.
+ int32_t numtris;
+
+ //! Offset to the start of the tris sequence.
+ int32_t triindex;
+
+ //! The skin index.
+ int32_t skinref;
+
+ //! The number of normals in the mesh.
+ int32_t numnorms;
+
+ //! Was "normindex".
+ int32_t unused;
+} PACK_STRUCT;
+
+/** \struct Trivert
+ * \brief Data structure for a trivert in HL1 MDL files.
+ */
+struct Trivert {
+ //! Index into Model_HL1 vertex array.
+ short vertindex;
+
+ //! Index into Model_HL1 normal array.
+ short normindex;
+
+ //! Texture coordinates in absolute space (unnormalized).
+ short s, t;
+} 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
diff --git a/libs/assimp/code/AssetLib/MDL/HalfLife/HL1ImportDefinitions.h b/libs/assimp/code/AssetLib/MDL/HalfLife/HL1ImportDefinitions.h
new file mode 100644
index 0000000..d412aee
--- /dev/null
+++ b/libs/assimp/code/AssetLib/MDL/HalfLife/HL1ImportDefinitions.h
@@ -0,0 +1,64 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2022, 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
diff --git a/libs/assimp/code/AssetLib/MDL/HalfLife/HL1ImportSettings.h b/libs/assimp/code/AssetLib/MDL/HalfLife/HL1ImportSettings.h
new file mode 100644
index 0000000..340ba2d
--- /dev/null
+++ b/libs/assimp/code/AssetLib/MDL/HalfLife/HL1ImportSettings.h
@@ -0,0 +1,85 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2022, 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
diff --git a/libs/assimp/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp b/libs/assimp/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp
new file mode 100644
index 0000000..93d3753
--- /dev/null
+++ b/libs/assimp/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp
@@ -0,0 +1,1353 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2022, 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/StringUtils.h>
+#include <assimp/ai_assert.h>
+#include <assimp/qnan.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/Importer.hpp>
+
+#include <iomanip>
+#include <sstream>
+#include <map>
+
+#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 {
+
+#ifdef _MSC_VER
+# pragma warning(disable : 4706)
+#endif // _MSC_VER
+
+// ------------------------------------------------------------------------------------------------
+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),
+ header_(nullptr),
+ texture_header_(nullptr),
+ anim_headers_(nullptr),
+ texture_buffer_(nullptr),
+ anim_buffers_(nullptr),
+ num_sequence_groups_(0),
+ rootnode_children_(),
+ unique_name_generator_(),
+ unique_sequence_names_(),
+ unique_sequence_groups_names_(),
+ 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;
+ }
+
+ // Root has some children nodes. so let's proceed them
+ if (!rootnode_children_.empty()) {
+ // Here, it means that the nodes were not added to the
+ // scene root node. We still have to delete them.
+ for (auto it = rootnode_children_.begin(); it != rootnode_children_.end(); ++it) {
+ if (*it) {
+ delete *it;
+ }
+ }
+ // Ensure this happens only once.
+ rootnode_children_.clear();
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+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();
+
+ if (!header_->numbodyparts) {
+ // This could be an MDL external texture file. In this case,
+ // add this flag to allow the scene to be loaded even if it
+ // has no meshes.
+ scene_->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
+ }
+
+ // Append children to root node.
+ if (rootnode_children_.size()) {
+ scene_->mRootNode->addChildren(
+ static_cast<unsigned int>(rootnode_children_.size()),
+ rootnode_children_.data());
+
+ // Clear the list of nodes so they will not be destroyed
+ // when resources are released.
+ rootnode_children_.clear();
+ }
+
+ release_resources();
+
+ } catch (...) {
+ release_resources();
+ throw;
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+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 {
+
+ 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] = nullptr;
+ anim_headers_[i] = nullptr;
+ }
+
+ 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];
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Read an MDL texture.
+void HL1MDLLoader::read_texture(const Texture_HL1 *ptexture,
+ uint8_t *data, uint8_t *pal, aiTexture *pResult,
+ aiColor3D &last_palette_color) {
+ pResult->mFilename = ptexture->name;
+ pResult->mWidth = static_cast<unsigned int>(ptexture->width);
+ pResult->mHeight = static_cast<unsigned int>(ptexture->height);
+ pResult->achFormatHint[0] = 'r';
+ pResult->achFormatHint[1] = 'g';
+ pResult->achFormatHint[2] = 'b';
+ pResult->achFormatHint[3] = 'a';
+ pResult->achFormatHint[4] = '8';
+ pResult->achFormatHint[5] = '8';
+ pResult->achFormatHint[6] = '8';
+ pResult->achFormatHint[7] = '8';
+ pResult->achFormatHint[8] = '\0';
+
+ const size_t num_pixels = pResult->mWidth * pResult->mHeight;
+ aiTexel *out = pResult->pcData = new aiTexel[num_pixels];
+
+ // Convert indexed 8 bit to 32 bit RGBA.
+ for (size_t i = 0; i < num_pixels; ++i, ++out) {
+ out->r = pal[data[i] * 3];
+ out->g = pal[data[i] * 3 + 1];
+ out->b = pal[data[i] * 3 + 2];
+ out->a = 255;
+ }
+
+ // 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() {
+ if (!header_->numbones) {
+ return;
+ }
+
+ 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() {
+ if (!header_->numbodyparts) {
+ return;
+ }
+
+ 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({ 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({ 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 faceIdx = 0; faceIdx < num_faces; ++faceIdx) {
+ mesh_faces.push_back(HL1MeshFace{
+ tricmds[0],
+ tricmds[faceIdx + 1],
+ tricmds[faceIdx + 2] });
+ }
+ } else {
+ for (int faceIdx = 0; faceIdx < num_faces; ++faceIdx) {
+ if (faceIdx & 1) {
+ // Preserve winding order.
+ mesh_faces.push_back(HL1MeshFace{
+ tricmds[faceIdx + 1],
+ tricmds[faceIdx],
+ tricmds[faceIdx + 2] });
+ } else {
+ mesh_faces.push_back(HL1MeshFace{
+ tricmds[faceIdx],
+ tricmds[faceIdx + 1],
+ tricmds[faceIdx + 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].v2;
+ face->mIndices[1] = mesh_faces[f].v1;
+ face->mIndices[2] = mesh_faces[f].v0;
+ }
+
+ // 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() {
+ if (!header_->numseqgroups) {
+ return;
+ }
+
+ 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 *metaData = blend_controller_node->mMetaData = aiMetadata::Alloc(3);
+ metaData->Set(0, "Start", pseqdesc->blendstart[j]);
+ metaData->Set(1, "End", pseqdesc->blendend[j]);
+ metaData->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 *metaData = pEvent->mMetaData = aiMetadata::Alloc(3);
+ metaData->Set(0, "Frame", pevent->frame);
+ metaData->Set(1, "ScriptEvent", pevent->event);
+ metaData->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, ai_real &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 (", num_blend_animations, ")");
+ return false;
+ }
+}
+
+} // namespace HalfLife
+} // namespace MDL
+} // namespace Assimp
diff --git a/libs/assimp/code/AssetLib/MDL/HalfLife/HL1MDLLoader.h b/libs/assimp/code/AssetLib/MDL/HalfLife/HL1MDLLoader.h
new file mode 100644
index 0000000..d87d6d3
--- /dev/null
+++ b/libs/assimp/code/AssetLib/MDL/HalfLife/HL1MDLLoader.h
@@ -0,0 +1,243 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2022, 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 <memory>
+#include <string>
+
+#include <assimp/types.h>
+#include <assimp/scene.h>
+#include <assimp/texture.h>
+#include <assimp/IOSystem.hpp>
+#include <assimp/DefaultIOSystem.h>
+#include <assimp/Exceptional.h>
+
+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, ai_real &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() == nullptr) {
+ 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
diff --git a/libs/assimp/code/AssetLib/MDL/HalfLife/HL1MeshTrivert.h b/libs/assimp/code/AssetLib/MDL/HalfLife/HL1MeshTrivert.h
new file mode 100644
index 0000000..4ef8a13
--- /dev/null
+++ b/libs/assimp/code/AssetLib/MDL/HalfLife/HL1MeshTrivert.h
@@ -0,0 +1,127 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2022, 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(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
diff --git a/libs/assimp/code/AssetLib/MDL/HalfLife/HalfLifeMDLBaseHeader.h b/libs/assimp/code/AssetLib/MDL/HalfLife/HalfLifeMDLBaseHeader.h
new file mode 100644
index 0000000..c7808c4
--- /dev/null
+++ b/libs/assimp/code/AssetLib/MDL/HalfLife/HalfLifeMDLBaseHeader.h
@@ -0,0 +1,67 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2022, 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
+{
+ //! Magic number: "IDST"/"IDSQ"
+ char ident[4];
+
+ //! The file format version.
+ int32_t version;
+};
+
+}
+}
+}
+
+#endif // AI_HALFLIFEMDLBASEHEADER_INCLUDED
diff --git a/libs/assimp/code/AssetLib/MDL/HalfLife/LogFunctions.h b/libs/assimp/code/AssetLib/MDL/HalfLife/LogFunctions.h
new file mode 100644
index 0000000..003774d
--- /dev/null
+++ b/libs/assimp/code/AssetLib/MDL/HalfLife/LogFunctions.h
@@ -0,0 +1,95 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2022, 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
diff --git a/libs/assimp/code/AssetLib/MDL/HalfLife/UniqueNameGenerator.cpp b/libs/assimp/code/AssetLib/MDL/HalfLife/UniqueNameGenerator.cpp
new file mode 100644
index 0000000..6fc8b11
--- /dev/null
+++ b/libs/assimp/code/AssetLib/MDL/HalfLife/UniqueNameGenerator.cpp
@@ -0,0 +1,180 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2022, 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({ 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
diff --git a/libs/assimp/code/AssetLib/MDL/HalfLife/UniqueNameGenerator.h b/libs/assimp/code/AssetLib/MDL/HalfLife/UniqueNameGenerator.h
new file mode 100644
index 0000000..73b6f9e
--- /dev/null
+++ b/libs/assimp/code/AssetLib/MDL/HalfLife/UniqueNameGenerator.h
@@ -0,0 +1,81 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2022, 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
diff --git a/libs/assimp/code/AssetLib/MDL/MDLDefaultColorMap.h b/libs/assimp/code/AssetLib/MDL/MDLDefaultColorMap.h
new file mode 100644
index 0000000..2eecac2
--- /dev/null
+++ b/libs/assimp/code/AssetLib/MDL/MDLDefaultColorMap.h
@@ -0,0 +1,120 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2022, 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 Defines the default color map used for Quake 1 model textures
+ *
+ * The lib tries to load colormap.lmp from the model's directory.
+ * This table is only used when required.
+ */
+
+#ifndef AI_MDL_DEFAULTLMP_H_INC
+#define AI_MDL_DEFAULTLMP_H_INC
+
+const unsigned char g_aclrDefaultColorMap[256][3] = {
+{ 0, 0, 0}, { 15, 15, 15}, { 31, 31, 31}, { 47, 47, 47},
+{ 63, 63, 63}, { 75, 75, 75}, { 91, 91, 91}, {107, 107, 107},
+{123, 123, 123}, {139, 139, 139}, {155, 155, 155}, {171, 171, 171},
+{187, 187, 187}, {203, 203, 203}, {219, 219, 219}, {235, 235, 235},
+{ 15, 11, 7}, { 23, 15, 11}, { 31, 23, 11}, { 39, 27, 15},
+{ 47, 35, 19}, { 55, 43, 23}, { 63, 47, 23}, { 75, 55, 27},
+{ 83, 59, 27}, { 91, 67, 31}, { 99, 75, 31}, {107, 83, 31},
+{115, 87, 31}, {123, 95, 35}, {131, 103, 35}, {143, 111, 35},
+{ 11, 11, 15}, { 19, 19, 27}, { 27, 27, 39}, { 39, 39, 51},
+{ 47, 47, 63}, { 55, 55, 75}, { 63, 63, 87}, { 71, 71, 103},
+{ 79, 79, 115}, { 91, 91, 127}, { 99, 99, 139}, {107, 107, 151},
+{115, 115, 163}, {123, 123, 175}, {131, 131, 187}, {139, 139, 203},
+{ 0, 0, 0}, { 7, 7, 0}, { 11, 11, 0}, { 19, 19, 0},
+{ 27, 27, 0}, { 35, 35, 0}, { 43, 43, 7}, { 47, 47, 7},
+{ 55, 55, 7}, { 63, 63, 7}, { 71, 71, 7}, { 75, 75, 11},
+{ 83, 83, 11}, { 91, 91, 11}, { 99, 99, 11}, {107, 107, 15},
+{ 7, 0, 0}, { 15, 0, 0}, { 23, 0, 0}, { 31, 0, 0},
+{ 39, 0, 0}, { 47, 0, 0}, { 55, 0, 0}, { 63, 0, 0},
+{ 71, 0, 0}, { 79, 0, 0}, { 87, 0, 0}, { 95, 0, 0},
+{103, 0, 0}, {111, 0, 0}, {119, 0, 0}, {127, 0, 0},
+{ 19, 19, 0}, { 27, 27, 0}, { 35, 35, 0}, { 47, 43, 0},
+{ 55, 47, 0}, { 67, 55, 0}, { 75, 59, 7}, { 87, 67, 7},
+{ 95, 71, 7}, {107, 75, 11}, {119, 83, 15}, {131, 87, 19},
+{139, 91, 19}, {151, 95, 27}, {163, 99, 31}, {175, 103, 35},
+{ 35, 19, 7}, { 47, 23, 11}, { 59, 31, 15}, { 75, 35, 19},
+{ 87, 43, 23}, { 99, 47, 31}, {115, 55, 35}, {127, 59, 43},
+{143, 67, 51}, {159, 79, 51}, {175, 99, 47}, {191, 119, 47},
+{207, 143, 43}, {223, 171, 39}, {239, 203, 31}, {255, 243, 27},
+{ 11, 7, 0}, { 27, 19, 0}, { 43, 35, 15}, { 55, 43, 19},
+{ 71, 51, 27}, { 83, 55, 35}, { 99, 63, 43}, {111, 71, 51},
+{127, 83, 63}, {139, 95, 71}, {155, 107, 83}, {167, 123, 95},
+{183, 135, 107}, {195, 147, 123}, {211, 163, 139}, {227, 179, 151},
+{171, 139, 163}, {159, 127, 151}, {147, 115, 135}, {139, 103, 123},
+{127, 91, 111}, {119, 83, 99}, {107, 75, 87}, { 95, 63, 75},
+{ 87, 55, 67}, { 75, 47, 55}, { 67, 39, 47}, { 55, 31, 35},
+{ 43, 23, 27}, { 35, 19, 19}, { 23, 11, 11}, { 15, 7, 7},
+{187, 115, 159}, {175, 107, 143}, {163, 95, 131}, {151, 87, 119},
+{139, 79, 107}, {127, 75, 95}, {115, 67, 83}, {107, 59, 75},
+{ 95, 51, 63}, { 83, 43, 55}, { 71, 35, 43}, { 59, 31, 35},
+{ 47, 23, 27}, { 35, 19, 19}, { 23, 11, 11}, { 15, 7, 7},
+{219, 195, 187}, {203, 179, 167}, {191, 163, 155}, {175, 151, 139},
+{163, 135, 123}, {151, 123, 111}, {135, 111, 95}, {123, 99, 83},
+{107, 87, 71}, { 95, 75, 59}, { 83, 63, 51}, { 67, 51, 39},
+{ 55, 43, 31}, { 39, 31, 23}, { 27, 19, 15}, { 15, 11, 7},
+{111, 131, 123}, {103, 123, 111}, { 95, 115, 103}, { 87, 107, 95},
+{ 79, 99, 87}, { 71, 91, 79}, { 63, 83, 71}, { 55, 75, 63},
+{ 47, 67, 55}, { 43, 59, 47}, { 35, 51, 39}, { 31, 43, 31},
+{ 23, 35, 23}, { 15, 27, 19}, { 11, 19, 11}, { 7, 11, 7},
+{255, 243, 27}, {239, 223, 23}, {219, 203, 19}, {203, 183, 15},
+{187, 167, 15}, {171, 151, 11}, {155, 131, 7}, {139, 115, 7},
+{123, 99, 7}, {107, 83, 0}, { 91, 71, 0}, { 75, 55, 0},
+{ 59, 43, 0}, { 43, 31, 0}, { 27, 15, 0}, { 11, 7, 0},
+{ 0, 0, 255}, { 11, 11, 239}, { 19, 19, 223}, { 27, 27, 207},
+{ 35, 35, 191}, { 43, 43, 175}, { 47, 47, 159}, { 47, 47, 143},
+{ 47, 47, 127}, { 47, 47, 111}, { 47, 47, 95}, { 43, 43, 79},
+{ 35, 35, 63}, { 27, 27, 47}, { 19, 19, 31}, { 11, 11, 15},
+{ 43, 0, 0}, { 59, 0, 0}, { 75, 7, 0}, { 95, 7, 0},
+{111, 15, 0}, {127, 23, 7}, {147, 31, 7}, {163, 39, 11},
+{183, 51, 15}, {195, 75, 27}, {207, 99, 43}, {219, 127, 59},
+{227, 151, 79}, {231, 171, 95}, {239, 191, 119}, {247, 211, 139},
+{167, 123, 59}, {183, 155, 55}, {199, 195, 55}, {231, 227, 87},
+{127, 191, 255}, {171, 231, 255}, {215, 255, 255}, {103, 0, 0},
+{139, 0, 0}, {179, 0, 0}, {215, 0, 0}, {255, 0, 0},
+{255, 243, 147}, {255, 247, 199}, {255, 255, 255}, {159, 91, 83} };
+
+
+#endif // !! AI_MDL_DEFAULTLMP_H_INC
diff --git a/libs/assimp/code/AssetLib/MDL/MDLFileData.h b/libs/assimp/code/AssetLib/MDL/MDLFileData.h
new file mode 100644
index 0000000..7ec2afe
--- /dev/null
+++ b/libs/assimp/code/AssetLib/MDL/MDLFileData.h
@@ -0,0 +1,945 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2022, 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 MDLFileData.h
+ * @brief Definition of in-memory structures for the MDL file format.
+ *
+ * The specification has been taken from various sources on the internet.
+ * - http://tfc.duke.free.fr/coding/mdl-specs-en.html
+ * - Conitec's MED SDK
+ * - Many quite long HEX-editor sessions
+ */
+
+#ifndef AI_MDLFILEHELPER_H_INC
+#define AI_MDLFILEHELPER_H_INC
+
+#include <assimp/anim.h>
+#include <assimp/mesh.h>
+#include <assimp/Compiler/pushpack1.h>
+#include <assimp/ByteSwapper.h>
+#include <stdint.h>
+#include <vector>
+
+struct aiMaterial;
+
+namespace Assimp {
+namespace MDL {
+
+// -------------------------------------------------------------------------------------
+// to make it easier for us, we test the magic word against both "endianesses"
+
+// magic bytes used in Quake 1 MDL meshes
+#define AI_MDL_MAGIC_NUMBER_BE AI_MAKE_MAGIC("IDPO")
+#define AI_MDL_MAGIC_NUMBER_LE AI_MAKE_MAGIC("OPDI")
+
+// magic bytes used in GameStudio A<very low> MDL meshes
+#define AI_MDL_MAGIC_NUMBER_BE_GS3 AI_MAKE_MAGIC("MDL2")
+#define AI_MDL_MAGIC_NUMBER_LE_GS3 AI_MAKE_MAGIC("2LDM")
+
+// magic bytes used in GameStudio A4 MDL meshes
+#define AI_MDL_MAGIC_NUMBER_BE_GS4 AI_MAKE_MAGIC("MDL3")
+#define AI_MDL_MAGIC_NUMBER_LE_GS4 AI_MAKE_MAGIC("3LDM")
+
+// magic bytes used in GameStudio A5+ MDL meshes
+#define AI_MDL_MAGIC_NUMBER_BE_GS5a AI_MAKE_MAGIC("MDL4")
+#define AI_MDL_MAGIC_NUMBER_LE_GS5a AI_MAKE_MAGIC("4LDM")
+#define AI_MDL_MAGIC_NUMBER_BE_GS5b AI_MAKE_MAGIC("MDL5")
+#define AI_MDL_MAGIC_NUMBER_LE_GS5b AI_MAKE_MAGIC("5LDM")
+
+// magic bytes used in GameStudio A7+ MDL meshes
+#define AI_MDL_MAGIC_NUMBER_BE_GS7 AI_MAKE_MAGIC("MDL7")
+#define AI_MDL_MAGIC_NUMBER_LE_GS7 AI_MAKE_MAGIC("7LDM")
+
+// common limitations for Quake1 meshes. The loader does not check them,
+// (however it warns) but models should not exceed these limits.
+#if (!defined AI_MDL_VERSION)
+# define AI_MDL_VERSION 6
+#endif
+#if (!defined AI_MDL_MAX_FRAMES)
+# define AI_MDL_MAX_FRAMES 256
+#endif
+#if (!defined AI_MDL_MAX_UVS)
+# define AI_MDL_MAX_UVS 1024
+#endif
+#if (!defined AI_MDL_MAX_VERTS)
+# define AI_MDL_MAX_VERTS 1024
+#endif
+#if (!defined AI_MDL_MAX_TRIANGLES)
+# define AI_MDL_MAX_TRIANGLES 2048
+#endif
+
+// material key that is set for dummy materials that are
+// just referencing another material
+#if (!defined AI_MDL7_REFERRER_MATERIAL)
+# define AI_MDL7_REFERRER_MATERIAL "&&&referrer&&&",0,0
+#endif
+
+// -------------------------------------------------------------------------------------
+/** \struct Header
+ * \brief Data structure for the MDL main header
+ */
+struct Header {
+ //! magic number: "IDPO"
+ uint32_t ident;
+
+ //! version number: 6
+ int32_t version;
+
+ //! scale factors for each axis
+ ai_real scale[3];
+
+ //! translation factors for each axis
+ ai_real translate[3];
+
+ //! bounding radius of the mesh
+ float boundingradius;
+
+ //! Position of the viewer's exe. Ignored
+ ai_real vEyePos[3];
+
+ //! Number of textures
+ int32_t num_skins;
+
+ //! Texture width in pixels
+ int32_t skinwidth;
+
+ //! Texture height in pixels
+ int32_t skinheight;
+
+ //! Number of vertices contained in the file
+ int32_t num_verts;
+
+ //! Number of triangles contained in the file
+ int32_t num_tris;
+
+ //! Number of frames contained in the file
+ int32_t num_frames;
+
+ //! 0 = synchron, 1 = random . Ignored
+ //! (MDLn formats: number of texture coordinates)
+ int32_t synctype;
+
+ //! State flag
+ int32_t flags;
+
+ //! Could be the total size of the file (and not a float)
+ float size;
+} PACK_STRUCT;
+
+
+// -------------------------------------------------------------------------------------
+/** \struct Header_MDL7
+ * \brief Data structure for the MDL 7 main header
+ */
+struct Header_MDL7 {
+ //! magic number: "MDL7"
+ char ident[4];
+
+ //! Version number. Ignored
+ int32_t version;
+
+ //! Number of bones in file
+ uint32_t bones_num;
+
+ //! Number of groups in file
+ uint32_t groups_num;
+
+ //! Size of data in the file
+ uint32_t data_size;
+
+ //! Ignored. Used to store entity specific information
+ int32_t entlump_size;
+
+ //! Ignored. Used to store MED related data
+ int32_t medlump_size;
+
+ //! Size of the Bone_MDL7 data structure used in the file
+ uint16_t bone_stc_size;
+
+ //! Size of the Skin_MDL 7 data structure used in the file
+ uint16_t skin_stc_size;
+
+ //! Size of a single color (e.g. in a material)
+ uint16_t colorvalue_stc_size;
+
+ //! Size of the Material_MDL7 data structure used in the file
+ uint16_t material_stc_size;
+
+ //! Size of a texture coordinate set in the file
+ uint16_t skinpoint_stc_size;
+
+ //! Size of a triangle in the file
+ uint16_t triangle_stc_size;
+
+ //! Size of a normal vertex in the file
+ uint16_t mainvertex_stc_size;
+
+ //! Size of a per-frame animated vertex in the file
+ //! (this is not supported)
+ uint16_t framevertex_stc_size;
+
+ //! Size of a bone animation matrix
+ uint16_t bonetrans_stc_size;
+
+ //! Size of the Frame_MDL7 data structure used in the file
+ uint16_t frame_stc_size;
+} PACK_STRUCT;
+
+
+// -------------------------------------------------------------------------------------
+/** \struct Bone_MDL7
+ * \brief Data structure for a bone in a MDL7 file
+ */
+struct Bone_MDL7 {
+ //! Index of the parent bone of *this* bone. 0xffff means:
+ //! "hey, I have no parent, I'm an orphan"
+ uint16_t parent_index;
+ uint8_t _unused_[2];
+
+ //! Relative position of the bone (relative to the
+ //! parent bone)
+ float x,y,z;
+
+ //! Optional name of the bone
+ char name[1 /* DUMMY SIZE */];
+} PACK_STRUCT;
+
+#if (!defined AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_20_CHARS)
+# define AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_20_CHARS (16 + 20)
+#endif
+
+#if (!defined AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_32_CHARS)
+# define AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_32_CHARS (16 + 32)
+#endif
+
+#if (!defined AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_NOT_THERE)
+# define AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_NOT_THERE (16)
+#endif
+
+#if (!defined AI_MDL7_MAX_GROUPNAMESIZE)
+# define AI_MDL7_MAX_GROUPNAMESIZE 16
+#endif // ! AI_MDL7_MAX_GROUPNAMESIZE
+
+// -------------------------------------------------------------------------------------
+/** \struct Group_MDL7
+ * \brief Group in a MDL7 file
+ */
+struct Group_MDL7 {
+ //! = '1' -> triangle based Mesh
+ unsigned char typ;
+
+ int8_t deformers;
+ int8_t max_weights;
+ int8_t _unused_;
+
+ //! size of data for this group in bytes ( MD7_GROUP stc. included).
+ int32_t groupdata_size;
+ char name[AI_MDL7_MAX_GROUPNAMESIZE];
+
+ //! Number of skins
+ int32_t numskins;
+
+ //! Number of texture coordinates
+ int32_t num_stpts;
+
+ //! Number of triangles
+ int32_t numtris;
+
+ //! Number of vertices
+ int32_t numverts;
+
+ //! Number of frames
+ int32_t numframes;
+} PACK_STRUCT;
+
+#define AI_MDL7_SKINTYPE_MIPFLAG 0x08
+#define AI_MDL7_SKINTYPE_MATERIAL 0x10
+#define AI_MDL7_SKINTYPE_MATERIAL_ASCDEF 0x20
+#define AI_MDL7_SKINTYPE_RGBFLAG 0x80
+
+#if (!defined AI_MDL7_MAX_BONENAMESIZE)
+# define AI_MDL7_MAX_BONENAMESIZE 20
+#endif // !! AI_MDL7_MAX_BONENAMESIZE
+
+// -------------------------------------------------------------------------------------
+/** \struct Deformer_MDL7
+ * \brief Deformer in a MDL7 file
+ */
+struct Deformer_MDL7 {
+ int8_t deformer_version; // 0
+ int8_t deformer_typ; // 0 - bones
+ int8_t _unused_[2];
+ int32_t group_index;
+ int32_t elements;
+ int32_t deformerdata_size;
+} PACK_STRUCT;
+
+
+// -------------------------------------------------------------------------------------
+/** \struct DeformerElement_MDL7
+ * \brief Deformer element in a MDL7 file
+ */
+struct DeformerElement_MDL7 {
+ //! bei deformer_typ==0 (==bones) element_index == bone index
+ int32_t element_index;
+ char element_name[AI_MDL7_MAX_BONENAMESIZE];
+ int32_t weights;
+} PACK_STRUCT;
+
+// -------------------------------------------------------------------------------------
+/** \struct DeformerWeight_MDL7
+ * \brief Deformer weight in a MDL7 file
+ */
+struct DeformerWeight_MDL7 {
+ //! for deformer_typ==0 (==bones) index == vertex index
+ int32_t index;
+ float weight;
+} PACK_STRUCT;
+
+// don't know why this was in the original headers ...
+typedef int32_t MD7_MATERIAL_ASCDEFSIZE;
+
+// -------------------------------------------------------------------------------------
+/** \struct ColorValue_MDL7
+ * \brief Data structure for a color value in a MDL7 file
+ */
+struct ColorValue_MDL7 {
+ float r,g,b,a;
+} PACK_STRUCT;
+
+// -------------------------------------------------------------------------------------
+/** \struct Material_MDL7
+ * \brief Data structure for a Material in a MDL7 file
+ */
+struct Material_MDL7 {
+ //! Diffuse base color of the material
+ ColorValue_MDL7 Diffuse;
+
+ //! Ambient base color of the material
+ ColorValue_MDL7 Ambient;
+
+ //! Specular base color of the material
+ ColorValue_MDL7 Specular;
+
+ //! Emissive base color of the material
+ ColorValue_MDL7 Emissive;
+
+ //! Phong power
+ float Power;
+} PACK_STRUCT;
+
+// -------------------------------------------------------------------------------------
+/** \struct Skin
+ * \brief Skin data structure #1 - used by Quake1, MDL2, MDL3 and MDL4
+ */
+struct Skin {
+ //! 0 = single (Skin), 1 = group (GroupSkin)
+ //! For MDL3-5: Defines the type of the skin and there
+ //! fore the size of the data to skip:
+ //-------------------------------------------------------
+ //! 2 for 565 RGB,
+ //! 3 for 4444 ARGB,
+ //! 10 for 565 mipmapped,
+ //! 11 for 4444 mipmapped (bpp = 2),
+ //! 12 for 888 RGB mipmapped (bpp = 3),
+ //! 13 for 8888 ARGB mipmapped (bpp = 4)
+ //-------------------------------------------------------
+ int32_t group;
+
+ //! Texture data
+ uint8_t *data;
+} PACK_STRUCT;
+
+
+// -------------------------------------------------------------------------------------
+/** \struct Skin
+ * \brief Skin data structure #2 - used by MDL5, MDL6 and MDL7
+ * \see Skin
+ */
+struct Skin_MDL5 {
+ int32_t size, width, height;
+ uint8_t *data;
+} PACK_STRUCT;
+
+// maximum length of texture file name
+#if (!defined AI_MDL7_MAX_TEXNAMESIZE)
+# define AI_MDL7_MAX_TEXNAMESIZE 0x10
+#endif
+
+// ---------------------------------------------------------------------------
+/** \struct Skin_MDL7
+ * \brief Skin data structure #3 - used by MDL7 and HMP7
+ */
+struct Skin_MDL7 {
+ uint8_t typ;
+ int8_t _unused_[3];
+ int32_t width;
+ int32_t height;
+ char texture_name[AI_MDL7_MAX_TEXNAMESIZE];
+} PACK_STRUCT;
+
+// -------------------------------------------------------------------------------------
+/** \struct RGB565
+ * \brief Data structure for a RGB565 pixel in a texture
+ */
+struct RGB565 {
+ uint16_t r : 5;
+ uint16_t g : 6;
+ uint16_t b : 5;
+} PACK_STRUCT;
+
+// -------------------------------------------------------------------------------------
+/** \struct ARGB4
+ * \brief Data structure for a ARGB4444 pixel in a texture
+ */
+struct ARGB4 {
+ uint16_t a : 4;
+ uint16_t r : 4;
+ uint16_t g : 4;
+ uint16_t b : 4;
+} /*PACK_STRUCT*/;
+
+// -------------------------------------------------------------------------------------
+/** \struct GroupSkin
+ * \brief Skin data structure #2 (group of pictures)
+ */
+struct GroupSkin {
+ //! 0 = single (Skin), 1 = group (GroupSkin)
+ int32_t group;
+
+ //! Number of images
+ int32_t nb;
+
+ //! Time for each image
+ float *time;
+
+ //! Data of each image
+ uint8_t **data;
+} PACK_STRUCT;
+
+// -------------------------------------------------------------------------------------
+/** \struct TexCoord
+ * \brief Texture coordinate data structure used by the Quake1 MDL format
+ */
+struct TexCoord {
+ //! Is the vertex on the noundary between front and back piece?
+ int32_t onseam;
+
+ //! Texture coordinate in the tx direction
+ int32_t s;
+
+ //! Texture coordinate in the ty direction
+ int32_t t;
+} PACK_STRUCT;
+
+// -------------------------------------------------------------------------------------
+/** \struct TexCoord_MDL3
+ * \brief Data structure for an UV coordinate in the 3DGS MDL3 format
+ */
+struct TexCoord_MDL3 {
+ //! position, horizontally in range 0..skinwidth-1
+ int16_t u;
+
+ //! position, vertically in range 0..skinheight-1
+ int16_t v;
+} PACK_STRUCT;
+
+// -------------------------------------------------------------------------------------
+/** \struct TexCoord_MDL7
+ * \brief Data structure for an UV coordinate in the 3DGS MDL7 format
+ */
+struct TexCoord_MDL7 {
+ //! position, horizontally in range 0..1
+ float u;
+
+ //! position, vertically in range 0..1
+ float v;
+} PACK_STRUCT;
+
+// -------------------------------------------------------------------------------------
+/** \struct SkinSet_MDL7
+ * \brief Skin set data structure for the 3DGS MDL7 format
+ * MDL7 references UV coordinates per face via an index list.
+ * This allows the use of multiple skins per face with just one
+ * UV coordinate set.
+ */
+struct SkinSet_MDL7
+{
+ //! Index into the UV coordinate list
+ uint16_t st_index[3]; // size 6
+
+ //! Material index
+ int32_t material; // size 4
+} PACK_STRUCT;
+
+// -------------------------------------------------------------------------------------
+/** \struct Triangle
+ * \brief Triangle data structure for the Quake1 MDL format
+ */
+struct Triangle
+{
+ //! 0 = backface, 1 = frontface
+ int32_t facesfront;
+
+ //! Vertex indices
+ int32_t vertex[3];
+} PACK_STRUCT;
+
+// -------------------------------------------------------------------------------------
+/** \struct Triangle_MDL3
+ * \brief Triangle data structure for the 3DGS MDL3 format
+ */
+struct Triangle_MDL3
+{
+ //! Index of 3 3D vertices in range 0..numverts
+ uint16_t index_xyz[3];
+
+ //! Index of 3 skin vertices in range 0..numskinverts
+ uint16_t index_uv[3];
+} PACK_STRUCT;
+
+// -------------------------------------------------------------------------------------
+/** \struct Triangle_MDL7
+ * \brief Triangle data structure for the 3DGS MDL7 format
+ */
+struct Triangle_MDL7
+{
+ //! Vertex indices
+ uint16_t v_index[3]; // size 6
+
+ //! Two skinsets. The second will be used for multi-texturing
+ SkinSet_MDL7 skinsets[2];
+} PACK_STRUCT;
+
+#if (!defined AI_MDL7_TRIANGLE_STD_SIZE_ONE_UV)
+# define AI_MDL7_TRIANGLE_STD_SIZE_ONE_UV (6+sizeof(SkinSet_MDL7)-sizeof(uint32_t))
+#endif
+#if (!defined AI_MDL7_TRIANGLE_STD_SIZE_ONE_UV_WITH_MATINDEX)
+# define AI_MDL7_TRIANGLE_STD_SIZE_ONE_UV_WITH_MATINDEX (6+sizeof(SkinSet_MDL7))
+#endif
+#if (!defined AI_MDL7_TRIANGLE_STD_SIZE_TWO_UV)
+# define AI_MDL7_TRIANGLE_STD_SIZE_TWO_UV (6+2*sizeof(SkinSet_MDL7))
+#endif
+
+// Helper constants for Triangle::facesfront
+#if (!defined AI_MDL_BACKFACE)
+# define AI_MDL_BACKFACE 0x0
+#endif
+#if (!defined AI_MDL_FRONTFACE)
+# define AI_MDL_FRONTFACE 0x1
+#endif
+
+// -------------------------------------------------------------------------------------
+/** \struct Vertex
+ * \brief Vertex data structure
+ */
+struct Vertex
+{
+ uint8_t v[3];
+ uint8_t normalIndex;
+} PACK_STRUCT;
+
+
+// -------------------------------------------------------------------------------------
+struct Vertex_MDL4
+{
+ uint16_t v[3];
+ uint8_t normalIndex;
+ uint8_t unused;
+} PACK_STRUCT;
+
+#define AI_MDL7_FRAMEVERTEX120503_STCSIZE 16
+#define AI_MDL7_FRAMEVERTEX030305_STCSIZE 26
+
+// -------------------------------------------------------------------------------------
+/** \struct Vertex_MDL7
+ * \brief Vertex data structure used in MDL7 files
+ */
+struct Vertex_MDL7
+{
+ float x,y,z;
+ uint16_t vertindex; // = bone index
+ union {
+ uint8_t norm162index;
+ float norm[3];
+ };
+} PACK_STRUCT;
+
+// -------------------------------------------------------------------------------------
+/** \struct BoneTransform_MDL7
+ * \brief bone transformation matrix structure used in MDL7 files
+ */
+struct BoneTransform_MDL7
+{
+ //! 4*3
+ float m [4*4];
+
+ //! the index of this vertex, 0.. header::bones_num - 1
+ uint16_t bone_index;
+
+ //! I HATE 3DGS AND THE SILLY DEVELOPER WHO DESIGNED
+ //! THIS STUPID FILE FORMAT!
+ int8_t _unused_[2];
+} PACK_STRUCT;
+
+
+#define AI_MDL7_MAX_FRAMENAMESIZE 16
+
+// -------------------------------------------------------------------------------------
+/** \struct Frame_MDL7
+ * \brief Frame data structure used by MDL7 files
+ */
+struct Frame_MDL7
+{
+ char frame_name[AI_MDL7_MAX_FRAMENAMESIZE];
+ uint32_t vertices_count;
+ uint32_t transmatrix_count;
+};
+
+
+// -------------------------------------------------------------------------------------
+/** \struct SimpleFrame
+ * \brief Data structure for a simple frame
+ */
+struct SimpleFrame
+{
+ //! Minimum vertex of the bounding box
+ Vertex bboxmin;
+
+ //! Maximum vertex of the bounding box
+ Vertex bboxmax;
+
+ //! Name of the frame
+ char name[16];
+
+ //! Vertex list of the frame
+ Vertex *verts;
+} PACK_STRUCT;
+
+// -------------------------------------------------------------------------------------
+/** \struct Frame
+ * \brief Model frame data structure
+ */
+struct Frame
+{
+ //! 0 = simple frame, !0 = group frame
+ int32_t type;
+
+ //! Frame data
+ SimpleFrame frame;
+} PACK_STRUCT;
+
+
+// -------------------------------------------------------------------------------------
+struct SimpleFrame_MDLn_SP
+{
+ //! Minimum vertex of the bounding box
+ Vertex_MDL4 bboxmin;
+
+ //! Maximum vertex of the bounding box
+ Vertex_MDL4 bboxmax;
+
+ //! Name of the frame
+ char name[16];
+
+ //! Vertex list of the frame
+ Vertex_MDL4 *verts;
+} PACK_STRUCT;
+
+// -------------------------------------------------------------------------------------
+/** \struct GroupFrame
+ * \brief Data structure for a group of frames
+ */
+struct GroupFrame
+{
+ //! 0 = simple frame, !0 = group frame
+ int32_t type;
+
+ int32_t numframes;
+
+ //! Minimum vertex for all single frames
+ Vertex min;
+
+ //! Maximum vertex for all single frames
+ Vertex max;
+
+ //! List of times for all single frames
+ float *times;
+
+ //! List of single frames
+ SimpleFrame *frames;
+} PACK_STRUCT;
+
+#include <assimp/Compiler/poppack1.h>
+
+// -------------------------------------------------------------------------------------
+/** \struct IntFace_MDL7
+ * \brief Internal data structure to temporarily represent a face
+ */
+struct IntFace_MDL7 {
+ // provide a constructor for our own convenience
+ IntFace_MDL7() AI_NO_EXCEPT {
+ ::memset( mIndices, 0, sizeof(uint32_t) *3);
+ ::memset( iMatIndex, 0, sizeof( unsigned int) *2);
+ }
+
+ //! Vertex indices
+ uint32_t mIndices[3];
+
+ //! Material index (maximally two channels, which are joined later)
+ unsigned int iMatIndex[2];
+};
+
+// -------------------------------------------------------------------------------------
+/** \struct IntMaterial_MDL7
+ * \brief Internal data structure to temporarily represent a material
+ * which has been created from two single materials along with the
+ * original material indices.
+ */
+struct IntMaterial_MDL7 {
+ // provide a constructor for our own convenience
+ IntMaterial_MDL7() AI_NO_EXCEPT
+ : pcMat( nullptr ) {
+ ::memset( iOldMatIndices, 0, sizeof(unsigned int) *2);
+ }
+
+ //! Material instance
+ aiMaterial* pcMat;
+
+ //! Old material indices
+ unsigned int iOldMatIndices[2];
+};
+
+// -------------------------------------------------------------------------------------
+/** \struct IntBone_MDL7
+ * \brief Internal data structure to represent a bone in a MDL7 file with
+ * all of its animation channels assigned to it.
+ */
+struct IntBone_MDL7 : aiBone
+{
+ //! Default constructor
+ IntBone_MDL7() AI_NO_EXCEPT : iParent (0xffff)
+ {
+ pkeyPositions.reserve(30);
+ pkeyScalings.reserve(30);
+ pkeyRotations.reserve(30);
+ }
+
+ //! Parent bone of the bone
+ uint64_t iParent;
+
+ //! Relative position of the bone
+ aiVector3D vPosition;
+
+ //! Array of position keys
+ std::vector<aiVectorKey> pkeyPositions;
+
+ //! Array of scaling keys
+ std::vector<aiVectorKey> pkeyScalings;
+
+ //! Array of rotation keys
+ std::vector<aiQuatKey> pkeyRotations;
+};
+
+// -------------------------------------------------------------------------------------
+//! Describes a MDL7 frame
+struct IntFrameInfo_MDL7
+{
+ //! Construction from an existing frame header
+ IntFrameInfo_MDL7(BE_NCONST MDL::Frame_MDL7* _pcFrame,unsigned int _iIndex)
+ : iIndex(_iIndex)
+ , pcFrame(_pcFrame)
+ {}
+
+ //! Index of the frame
+ unsigned int iIndex;
+
+ //! Points to the header of the frame
+ BE_NCONST MDL::Frame_MDL7* pcFrame;
+};
+
+// -------------------------------------------------------------------------------------
+//! Describes a MDL7 mesh group
+struct IntGroupInfo_MDL7
+{
+ //! Default constructor
+ IntGroupInfo_MDL7() AI_NO_EXCEPT
+ : iIndex(0)
+ , pcGroup(nullptr)
+ , pcGroupUVs(nullptr)
+ , pcGroupTris(nullptr)
+ , pcGroupVerts(nullptr)
+ {}
+
+ //! Construction from an existing group header
+ IntGroupInfo_MDL7(BE_NCONST MDL::Group_MDL7* _pcGroup, unsigned int _iIndex)
+ : iIndex(_iIndex)
+ , pcGroup(_pcGroup)
+ , pcGroupUVs()
+ , pcGroupTris()
+ , pcGroupVerts()
+ {}
+
+ //! Index of the group
+ unsigned int iIndex;
+
+ //! Points to the header of the group
+ BE_NCONST MDL::Group_MDL7* pcGroup;
+
+ //! Points to the beginning of the uv coordinate section
+ BE_NCONST MDL::TexCoord_MDL7* pcGroupUVs;
+
+ //! Points to the beginning of the triangle section
+ MDL::Triangle_MDL7* pcGroupTris;
+
+ //! Points to the beginning of the vertex section
+ BE_NCONST MDL::Vertex_MDL7* pcGroupVerts;
+};
+
+// -------------------------------------------------------------------------------------
+//! Holds the data that belongs to a MDL7 mesh group
+struct IntGroupData_MDL7
+{
+ IntGroupData_MDL7() AI_NO_EXCEPT
+ : bNeed2UV(false)
+ {}
+
+ //! Array of faces that belong to the group
+ std::vector<MDL::IntFace_MDL7> pcFaces;
+
+ //! Array of vertex positions
+ std::vector<aiVector3D> vPositions;
+
+ //! Array of vertex normals
+ std::vector<aiVector3D> vNormals;
+
+ //! Array of bones indices
+ std::vector<unsigned int> aiBones;
+
+ //! First UV coordinate set
+ std::vector<aiVector3D> vTextureCoords1;
+
+ //! Optional second UV coordinate set
+ std::vector<aiVector3D> vTextureCoords2;
+
+ //! Specifies whether there are two texture
+ //! coordinate sets required
+ bool bNeed2UV;
+};
+
+// -------------------------------------------------------------------------------------
+//! Holds data from an MDL7 file that is shared by all mesh groups
+struct IntSharedData_MDL7 {
+ //! Default constructor
+ IntSharedData_MDL7() AI_NO_EXCEPT
+ : apcOutBones(),
+ iNum()
+ {
+ abNeedMaterials.reserve(10);
+ }
+
+ //! Destruction: properly delete all allocated resources
+ ~IntSharedData_MDL7()
+ {
+ // kill all bones
+ if (this->apcOutBones)
+ {
+ for (unsigned int m = 0; m < iNum;++m)
+ delete this->apcOutBones[m];
+ delete[] this->apcOutBones;
+ }
+ }
+
+ //! Specifies which materials are used
+ std::vector<bool> abNeedMaterials;
+
+ //! List of all materials
+ std::vector<aiMaterial*> pcMats;
+
+ //! List of all bones
+ IntBone_MDL7** apcOutBones;
+
+ //! number of bones
+ unsigned int iNum;
+};
+
+// -------------------------------------------------------------------------------------
+//! Contains input data for GenerateOutputMeshes_3DGS_MDL7
+struct IntSplitGroupData_MDL7
+{
+ //! Construction from a given shared data set
+ IntSplitGroupData_MDL7(IntSharedData_MDL7& _shared,
+ std::vector<aiMesh*>& _avOutList)
+
+ : aiSplit(), shared(_shared), avOutList(_avOutList)
+ {
+ }
+
+ //! Destruction: properly delete all allocated resources
+ ~IntSplitGroupData_MDL7()
+ {
+ // kill all face lists
+ if(this->aiSplit)
+ {
+ for (unsigned int m = 0; m < shared.pcMats.size();++m)
+ delete this->aiSplit[m];
+ delete[] this->aiSplit;
+ }
+ }
+
+ //! Contains a list of all faces per material
+ std::vector<unsigned int>** aiSplit;
+
+ //! Shared data for all groups of the model
+ IntSharedData_MDL7& shared;
+
+ //! List of meshes
+ std::vector<aiMesh*>& avOutList;
+};
+
+
+}
+} // end namespaces
+
+#endif // !! AI_MDLFILEHELPER_H_INC
diff --git a/libs/assimp/code/AssetLib/MDL/MDLLoader.cpp b/libs/assimp/code/AssetLib/MDL/MDLLoader.cpp
new file mode 100644
index 0000000..1e90c8e
--- /dev/null
+++ b/libs/assimp/code/AssetLib/MDL/MDLLoader.cpp
@@ -0,0 +1,1976 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2022, 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 MDLLoader.cpp
+ * @brief Implementation of the main parts of the MDL importer class
+ * *TODO* Cleanup and further testing of some parts necessary
+ */
+
+// internal headers
+
+#ifndef ASSIMP_BUILD_NO_MDL_IMPORTER
+
+#include "AssetLib/MDL/MDLLoader.h"
+#include "AssetLib/MD2/MD2FileData.h"
+#include "AssetLib/MDL/HalfLife/HL1MDLLoader.h"
+#include "AssetLib/MDL/MDLDefaultColorMap.h"
+
+#include <assimp/StringUtils.h>
+#include <assimp/importerdesc.h>
+#include <assimp/qnan.h>
+#include <assimp/scene.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/IOSystem.hpp>
+#include <assimp/Importer.hpp>
+
+#include <memory>
+
+using namespace Assimp;
+
+static const aiImporterDesc desc = {
+ "Quake Mesh / 3D GameStudio Mesh Importer",
+ "",
+ "",
+ "",
+ aiImporterFlags_SupportBinaryFlavour,
+ 0,
+ 0,
+ 7,
+ 0,
+ "mdl"
+};
+
+// ------------------------------------------------------------------------------------------------
+// Ugly stuff ... nevermind
+#define _AI_MDL7_ACCESS(_data, _index, _limit, _type) \
+ (*((const _type *)(((const char *)_data) + _index * _limit)))
+
+#define _AI_MDL7_ACCESS_PTR(_data, _index, _limit, _type) \
+ ((BE_NCONST _type *)(((const char *)_data) + _index * _limit))
+
+#define _AI_MDL7_ACCESS_VERT(_data, _index, _limit) \
+ _AI_MDL7_ACCESS(_data, _index, _limit, MDL::Vertex_MDL7)
+
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by Importer
+MDLImporter::MDLImporter() :
+ configFrameID(), mBuffer(), iGSFileVersion(), mIOHandler(nullptr), pScene(), iFileSize() {
+ // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+MDLImporter::~MDLImporter() {
+ // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+// Returns whether the class can handle the format of the given file.
+bool MDLImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
+ static const uint32_t tokens[] = {
+ AI_MDL_MAGIC_NUMBER_LE_HL2a,
+ AI_MDL_MAGIC_NUMBER_LE_HL2b,
+ AI_MDL_MAGIC_NUMBER_LE_GS7,
+ AI_MDL_MAGIC_NUMBER_LE_GS5b,
+ AI_MDL_MAGIC_NUMBER_LE_GS5a,
+ AI_MDL_MAGIC_NUMBER_LE_GS4,
+ AI_MDL_MAGIC_NUMBER_LE_GS3,
+ AI_MDL_MAGIC_NUMBER_LE
+ };
+ return CheckMagicToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
+}
+
+// ------------------------------------------------------------------------------------------------
+// Setup configuration properties
+void MDLImporter::SetupProperties(const Importer *pImp) {
+ configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MDL_KEYFRAME, -1);
+
+ // The
+ // AI_CONFIG_IMPORT_MDL_KEYFRAME option overrides the
+ // AI_CONFIG_IMPORT_GLOBAL_KEYFRAME option.
+ if (static_cast<unsigned int>(-1) == configFrameID) {
+ configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_GLOBAL_KEYFRAME, 0);
+ }
+
+ // AI_CONFIG_IMPORT_MDL_COLORMAP - palette file
+ 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);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Get a list of all supported extensions
+const aiImporterDesc *MDLImporter::GetInfo() const {
+ return &desc;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Imports the given file into the given scene structure.
+void MDLImporter::InternReadFile(const std::string &pFile,
+ aiScene *_pScene, IOSystem *pIOHandler) {
+ pScene = _pScene;
+ mIOHandler = pIOHandler;
+ std::unique_ptr<IOStream> file(pIOHandler->Open(pFile));
+
+ // Check whether we can read from the file
+ if (file.get() == nullptr) {
+ throw DeadlyImportError("Failed to open MDL file ", pFile, ".");
+ }
+
+ // This should work for all other types of MDL files, too ...
+ // the HL1 sequence group header is one of the smallest, afaik
+ iFileSize = (unsigned int)file->FileSize();
+ if (iFileSize < sizeof(MDL::HalfLife::SequenceHeader_HL1)) {
+ throw DeadlyImportError("MDL File is too small.");
+ }
+
+ // delete the file buffer and cleanup.
+ auto DeleteBufferAndCleanup = [&]() {
+ if (mBuffer) {
+ delete[] mBuffer;
+ mBuffer = nullptr;
+ }
+ AI_DEBUG_INVALIDATE_PTR(mIOHandler);
+ AI_DEBUG_INVALIDATE_PTR(pScene);
+ };
+
+ try {
+ // Allocate storage and copy the contents of the file to a memory buffer
+ mBuffer = new unsigned char[iFileSize + 1];
+ file->Read((void *)mBuffer, 1, iFileSize);
+
+ // Append a binary zero to the end of the buffer.
+ // this is just for safety that string parsing routines
+ // find the end of the buffer ...
+ mBuffer[iFileSize] = '\0';
+ const uint32_t iMagicWord = *((uint32_t *)mBuffer);
+
+ // Determine the file subtype and call the appropriate member function
+ bool is_half_life = false;
+
+ // Original Quake1 format
+ if (AI_MDL_MAGIC_NUMBER_BE == iMagicWord || AI_MDL_MAGIC_NUMBER_LE == iMagicWord) {
+ ASSIMP_LOG_DEBUG("MDL subtype: Quake 1, magic word is IDPO");
+ iGSFileVersion = 0;
+ InternReadFile_Quake1();
+ }
+ // GameStudio A<old> MDL2 format - used by some test models that come with 3DGS
+ else if (AI_MDL_MAGIC_NUMBER_BE_GS3 == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_GS3 == iMagicWord) {
+ ASSIMP_LOG_DEBUG("MDL subtype: 3D GameStudio A2, magic word is MDL2");
+ iGSFileVersion = 2;
+ InternReadFile_Quake1();
+ }
+ // GameStudio A4 MDL3 format
+ else if (AI_MDL_MAGIC_NUMBER_BE_GS4 == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_GS4 == iMagicWord) {
+ ASSIMP_LOG_DEBUG("MDL subtype: 3D GameStudio A4, magic word is MDL3");
+ iGSFileVersion = 3;
+ InternReadFile_3DGS_MDL345();
+ }
+ // GameStudio A5+ MDL4 format
+ else if (AI_MDL_MAGIC_NUMBER_BE_GS5a == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_GS5a == iMagicWord) {
+ ASSIMP_LOG_DEBUG("MDL subtype: 3D GameStudio A4, magic word is MDL4");
+ iGSFileVersion = 4;
+ InternReadFile_3DGS_MDL345();
+ }
+ // GameStudio A5+ MDL5 format
+ else if (AI_MDL_MAGIC_NUMBER_BE_GS5b == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_GS5b == iMagicWord) {
+ ASSIMP_LOG_DEBUG("MDL subtype: 3D GameStudio A5, magic word is MDL5");
+ iGSFileVersion = 5;
+ InternReadFile_3DGS_MDL345();
+ }
+ // GameStudio A7 MDL7 format
+ else if (AI_MDL_MAGIC_NUMBER_BE_GS7 == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_GS7 == iMagicWord) {
+ ASSIMP_LOG_DEBUG("MDL subtype: 3D GameStudio A7, magic word is MDL7");
+ iGSFileVersion = 7;
+ InternReadFile_3DGS_MDL7();
+ }
+ // IDST/IDSQ Format (CS:S/HL^2, etc ...)
+ 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) {
+ iGSFileVersion = 0;
+ is_half_life = true;
+
+ 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 {
+ // print the magic word to the log file
+ throw DeadlyImportError("Unknown MDL subformat ", pFile,
+ ". Magic word (", ai_str_toprintable((const char *)&iMagicWord, sizeof(iMagicWord)), ") is not known");
+ }
+
+ if (is_half_life){
+ // Now rotate the whole scene 90 degrees around the z and x axes to convert to internal coordinate system
+ pScene->mRootNode->mTransformation = aiMatrix4x4(
+ 0.f, -1.f, 0.f, 0.f,
+ 0.f, 0.f, 1.f, 0.f,
+ -1.f, 0.f, 0.f, 0.f,
+ 0.f, 0.f, 0.f, 1.f);
+ }
+ else {
+ // Now rotate the whole scene 90 degrees around the x axis to convert to internal coordinate system
+ pScene->mRootNode->mTransformation = aiMatrix4x4(1.f, 0.f, 0.f, 0.f,
+ 0.f, 0.f, 1.f, 0.f, 0.f, -1.f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f);
+ }
+
+ DeleteBufferAndCleanup();
+ } catch (...) {
+ DeleteBufferAndCleanup();
+ throw;
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Check whether we're still inside the valid file range
+void MDLImporter::SizeCheck(const void *szPos) {
+ if (!szPos || (const unsigned char *)szPos > this->mBuffer + this->iFileSize) {
+ throw DeadlyImportError("Invalid MDL file. The file is too small "
+ "or contains invalid data.");
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Just for debugging purposes
+void MDLImporter::SizeCheck(const void *szPos, const char *szFile, unsigned int iLine) {
+ ai_assert(nullptr != szFile);
+ if (!szPos || (const unsigned char *)szPos > mBuffer + iFileSize) {
+ // remove a directory if there is one
+ const char *szFilePtr = ::strrchr(szFile, '\\');
+ if (!szFilePtr) {
+ szFilePtr = ::strrchr(szFile, '/');
+ if (nullptr == szFilePtr) {
+ szFilePtr = szFile;
+ }
+ }
+ if (szFilePtr) {
+ ++szFilePtr;
+ }
+
+ char szBuffer[1024];
+ ::sprintf(szBuffer, "Invalid MDL file. The file is too small "
+ "or contains invalid data (File: %s Line: %u)",
+ szFilePtr, iLine);
+
+ throw DeadlyImportError(szBuffer);
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Validate a quake file header
+void MDLImporter::ValidateHeader_Quake1(const MDL::Header *pcHeader) {
+ // some values may not be nullptr
+ if (!pcHeader->num_frames)
+ throw DeadlyImportError("[Quake 1 MDL] There are no frames in the file");
+
+ if (!pcHeader->num_verts)
+ throw DeadlyImportError("[Quake 1 MDL] There are no vertices in the file");
+
+ if (!pcHeader->num_tris)
+ throw DeadlyImportError("[Quake 1 MDL] There are no triangles in the file");
+
+ // check whether the maxima are exceeded ...however, this applies for Quake 1 MDLs only
+ if (!this->iGSFileVersion) {
+ if (pcHeader->num_verts > AI_MDL_MAX_VERTS)
+ ASSIMP_LOG_WARN("Quake 1 MDL model has more than AI_MDL_MAX_VERTS vertices");
+
+ if (pcHeader->num_tris > AI_MDL_MAX_TRIANGLES)
+ ASSIMP_LOG_WARN("Quake 1 MDL model has more than AI_MDL_MAX_TRIANGLES triangles");
+
+ if (pcHeader->num_frames > AI_MDL_MAX_FRAMES)
+ ASSIMP_LOG_WARN("Quake 1 MDL model has more than AI_MDL_MAX_FRAMES frames");
+
+ // (this does not apply for 3DGS MDLs)
+ if (!this->iGSFileVersion && pcHeader->version != AI_MDL_VERSION)
+ ASSIMP_LOG_WARN("Quake 1 MDL model has an unknown version: AI_MDL_VERSION (=6) is "
+ "the expected file format version");
+ if (pcHeader->num_skins && (!pcHeader->skinwidth || !pcHeader->skinheight))
+ ASSIMP_LOG_WARN("Skin width or height are 0");
+ }
+}
+
+#ifdef AI_BUILD_BIG_ENDIAN
+// ------------------------------------------------------------------------------------------------
+void FlipQuakeHeader(BE_NCONST MDL::Header *pcHeader) {
+ AI_SWAP4(pcHeader->ident);
+ AI_SWAP4(pcHeader->version);
+ AI_SWAP4(pcHeader->boundingradius);
+ AI_SWAP4(pcHeader->flags);
+ AI_SWAP4(pcHeader->num_frames);
+ AI_SWAP4(pcHeader->num_skins);
+ AI_SWAP4(pcHeader->num_tris);
+ AI_SWAP4(pcHeader->num_verts);
+ for (unsigned int i = 0; i < 3; ++i) {
+ AI_SWAP4(pcHeader->scale[i]);
+ AI_SWAP4(pcHeader->translate[i]);
+ }
+ AI_SWAP4(pcHeader->size);
+ AI_SWAP4(pcHeader->skinheight);
+ AI_SWAP4(pcHeader->skinwidth);
+ AI_SWAP4(pcHeader->synctype);
+}
+#endif
+
+// ------------------------------------------------------------------------------------------------
+// Read a Quake 1 file
+void MDLImporter::InternReadFile_Quake1() {
+ ai_assert(nullptr != pScene);
+
+ BE_NCONST MDL::Header *pcHeader = (BE_NCONST MDL::Header *)this->mBuffer;
+
+#ifdef AI_BUILD_BIG_ENDIAN
+ FlipQuakeHeader(pcHeader);
+#endif
+
+ ValidateHeader_Quake1(pcHeader);
+
+ // current cursor position in the file
+ const unsigned char *szCurrent = (const unsigned char *)(pcHeader + 1);
+
+ // need to read all textures
+ for (unsigned int i = 0; i < (unsigned int)pcHeader->num_skins; ++i) {
+ union {
+ BE_NCONST MDL::Skin *pcSkin;
+ BE_NCONST MDL::GroupSkin *pcGroupSkin;
+ };
+ if (szCurrent + sizeof(MDL::Skin) > this->mBuffer + this->iFileSize) {
+ throw DeadlyImportError("[Quake 1 MDL] Unexpected EOF");
+ }
+ pcSkin = (BE_NCONST MDL::Skin *)szCurrent;
+
+ AI_SWAP4(pcSkin->group);
+
+ // Quake 1 group-skins
+ if (1 == pcSkin->group) {
+ AI_SWAP4(pcGroupSkin->nb);
+
+ // need to skip multiple images
+ const unsigned int iNumImages = (unsigned int)pcGroupSkin->nb;
+ szCurrent += sizeof(uint32_t) * 2;
+
+ if (0 != iNumImages) {
+ if (!i) {
+ // however, create only one output image (the first)
+ this->CreateTextureARGB8_3DGS_MDL3(szCurrent + iNumImages * sizeof(float));
+ }
+ // go to the end of the skin section / the beginning of the next skin
+ szCurrent += pcHeader->skinheight * pcHeader->skinwidth +
+ sizeof(float) * iNumImages;
+ }
+ } else {
+ szCurrent += sizeof(uint32_t);
+ unsigned int iSkip = i ? UINT_MAX : 0;
+ CreateTexture_3DGS_MDL4(szCurrent, pcSkin->group, &iSkip);
+ szCurrent += iSkip;
+ }
+ }
+ // get a pointer to the texture coordinates
+ BE_NCONST MDL::TexCoord *pcTexCoords = (BE_NCONST MDL::TexCoord *)szCurrent;
+ szCurrent += sizeof(MDL::TexCoord) * pcHeader->num_verts;
+
+ // get a pointer to the triangles
+ BE_NCONST MDL::Triangle *pcTriangles = (BE_NCONST MDL::Triangle *)szCurrent;
+ szCurrent += sizeof(MDL::Triangle) * pcHeader->num_tris;
+ VALIDATE_FILE_SIZE(szCurrent);
+
+ // now get a pointer to the first frame in the file
+ BE_NCONST MDL::Frame *pcFrames = (BE_NCONST MDL::Frame *)szCurrent;
+ MDL::SimpleFrame *pcFirstFrame;
+
+ if (0 == pcFrames->type) {
+ // get address of single frame
+ pcFirstFrame = (MDL::SimpleFrame *)&pcFrames->frame;
+ } else {
+ // get the first frame in the group
+ BE_NCONST MDL::GroupFrame *pcFrames2 = (BE_NCONST MDL::GroupFrame *)szCurrent;
+ pcFirstFrame = (MDL::SimpleFrame *)( szCurrent + sizeof(MDL::GroupFrame::type) + sizeof(MDL::GroupFrame::numframes)
+ + sizeof(MDL::GroupFrame::min) + sizeof(MDL::GroupFrame::max) + sizeof(*MDL::GroupFrame::times) * pcFrames2->numframes );
+ }
+ BE_NCONST MDL::Vertex *pcVertices = (BE_NCONST MDL::Vertex *)((pcFirstFrame->name) + sizeof(pcFirstFrame->name));
+ VALIDATE_FILE_SIZE((const unsigned char *)(pcVertices + pcHeader->num_verts));
+
+#ifdef AI_BUILD_BIG_ENDIAN
+ for (int i = 0; i < pcHeader->num_verts; ++i) {
+ AI_SWAP4(pcTexCoords[i].onseam);
+ AI_SWAP4(pcTexCoords[i].s);
+ AI_SWAP4(pcTexCoords[i].t);
+ }
+
+ for (int i = 0; i < pcHeader->num_tris; ++i) {
+ AI_SWAP4(pcTriangles[i].facesfront);
+ AI_SWAP4(pcTriangles[i].vertex[0]);
+ AI_SWAP4(pcTriangles[i].vertex[1]);
+ AI_SWAP4(pcTriangles[i].vertex[2]);
+ }
+#endif
+
+ // setup materials
+ SetupMaterialProperties_3DGS_MDL5_Quake1();
+
+ // allocate enough storage to hold all vertices and triangles
+ aiMesh *pcMesh = new aiMesh();
+
+ pcMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
+ pcMesh->mNumVertices = pcHeader->num_tris * 3;
+ pcMesh->mNumFaces = pcHeader->num_tris;
+ pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices];
+ pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices];
+ pcMesh->mFaces = new aiFace[pcMesh->mNumFaces];
+ pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices];
+ pcMesh->mNumUVComponents[0] = 2;
+
+ // there won't be more than one mesh inside the file
+ pScene->mRootNode = new aiNode();
+ pScene->mRootNode->mNumMeshes = 1;
+ pScene->mRootNode->mMeshes = new unsigned int[1];
+ pScene->mRootNode->mMeshes[0] = 0;
+ pScene->mNumMeshes = 1;
+ pScene->mMeshes = new aiMesh *[1];
+ pScene->mMeshes[0] = pcMesh;
+
+ // now iterate through all triangles
+ unsigned int iCurrent = 0;
+ for (unsigned int i = 0; i < (unsigned int)pcHeader->num_tris; ++i) {
+ pcMesh->mFaces[i].mIndices = new unsigned int[3];
+ pcMesh->mFaces[i].mNumIndices = 3;
+
+ unsigned int iTemp = iCurrent;
+ for (unsigned int c = 0; c < 3; ++c, ++iCurrent) {
+ pcMesh->mFaces[i].mIndices[c] = iCurrent;
+
+ // read vertices
+ unsigned int iIndex = pcTriangles->vertex[c];
+ if (iIndex >= (unsigned int)pcHeader->num_verts) {
+ iIndex = pcHeader->num_verts - 1;
+ ASSIMP_LOG_WARN("Index overflow in Q1-MDL vertex list.");
+ }
+
+ aiVector3D &vec = pcMesh->mVertices[iCurrent];
+ vec.x = (float)pcVertices[iIndex].v[0] * pcHeader->scale[0];
+ vec.x += pcHeader->translate[0];
+
+ vec.y = (float)pcVertices[iIndex].v[1] * pcHeader->scale[1];
+ vec.y += pcHeader->translate[1];
+ //vec.y *= -1.0f;
+
+ vec.z = (float)pcVertices[iIndex].v[2] * pcHeader->scale[2];
+ vec.z += pcHeader->translate[2];
+
+ // read the normal vector from the precalculated normal table
+ MD2::LookupNormalIndex(pcVertices[iIndex].normalIndex, pcMesh->mNormals[iCurrent]);
+ //pcMesh->mNormals[iCurrent].y *= -1.0f;
+
+ // read texture coordinates
+ float s = (float)pcTexCoords[iIndex].s;
+ float t = (float)pcTexCoords[iIndex].t;
+
+ // translate texture coordinates
+ if (0 == pcTriangles->facesfront && 0 != pcTexCoords[iIndex].onseam) {
+ s += pcHeader->skinwidth * 0.5f;
+ }
+
+ // Scale s and t to range from 0.0 to 1.0
+ pcMesh->mTextureCoords[0][iCurrent].x = (s + 0.5f) / pcHeader->skinwidth;
+ pcMesh->mTextureCoords[0][iCurrent].y = 1.0f - (t + 0.5f) / pcHeader->skinheight;
+ }
+ pcMesh->mFaces[i].mIndices[0] = iTemp + 2;
+ pcMesh->mFaces[i].mIndices[1] = iTemp + 1;
+ pcMesh->mFaces[i].mIndices[2] = iTemp + 0;
+ pcTriangles++;
+ }
+ return;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Setup material properties for Quake and older GameStudio files
+void MDLImporter::SetupMaterialProperties_3DGS_MDL5_Quake1() {
+ const MDL::Header *const pcHeader = (const MDL::Header *)this->mBuffer;
+
+ // allocate ONE material
+ pScene->mMaterials = new aiMaterial *[1];
+ pScene->mMaterials[0] = new aiMaterial();
+ pScene->mNumMaterials = 1;
+
+ // setup the material's properties
+ const int iMode = (int)aiShadingMode_Gouraud;
+ aiMaterial *const pcHelper = (aiMaterial *)pScene->mMaterials[0];
+ pcHelper->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL);
+
+ aiColor4D clr;
+ if (0 != pcHeader->num_skins && pScene->mNumTextures) {
+ // can we replace the texture with a single color?
+ clr = this->ReplaceTextureWithColor(pScene->mTextures[0]);
+ if (is_not_qnan(clr.r)) {
+ delete pScene->mTextures[0];
+ delete[] pScene->mTextures;
+
+ pScene->mTextures = nullptr;
+ pScene->mNumTextures = 0;
+ } else {
+ clr.b = clr.a = clr.g = clr.r = 1.0f;
+ aiString szString;
+ ::memcpy(szString.data, AI_MAKE_EMBEDDED_TEXNAME(0), 3);
+ szString.length = 2;
+ pcHelper->AddProperty(&szString, AI_MATKEY_TEXTURE_DIFFUSE(0));
+ }
+ }
+
+ pcHelper->AddProperty<aiColor4D>(&clr, 1, AI_MATKEY_COLOR_DIFFUSE);
+ pcHelper->AddProperty<aiColor4D>(&clr, 1, AI_MATKEY_COLOR_SPECULAR);
+
+ clr.r *= 0.05f;
+ clr.g *= 0.05f;
+ clr.b *= 0.05f;
+ clr.a = 1.0f;
+ pcHelper->AddProperty<aiColor4D>(&clr, 1, AI_MATKEY_COLOR_AMBIENT);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Read a MDL 3,4,5 file
+void MDLImporter::InternReadFile_3DGS_MDL345() {
+ ai_assert(nullptr != pScene);
+
+ // the header of MDL 3/4/5 is nearly identical to the original Quake1 header
+ BE_NCONST MDL::Header *pcHeader = (BE_NCONST MDL::Header *)this->mBuffer;
+#ifdef AI_BUILD_BIG_ENDIAN
+ FlipQuakeHeader(pcHeader);
+#endif
+ ValidateHeader_Quake1(pcHeader);
+
+ // current cursor position in the file
+ const unsigned char *szCurrent = (const unsigned char *)(pcHeader + 1);
+ const unsigned char *szEnd = mBuffer + iFileSize;
+
+ // need to read all textures
+ for (unsigned int i = 0; i < (unsigned int)pcHeader->num_skins; ++i) {
+ if (szCurrent + sizeof(uint32_t) > szEnd) {
+ throw DeadlyImportError("Texture data past end of file.");
+ }
+ BE_NCONST MDL::Skin *pcSkin;
+ pcSkin = (BE_NCONST MDL::Skin *)szCurrent;
+ AI_SWAP4(pcSkin->group);
+ // create one output image
+ unsigned int iSkip = i ? UINT_MAX : 0;
+ if (5 <= iGSFileVersion) {
+ // MDL5 format could contain MIPmaps
+ CreateTexture_3DGS_MDL5((unsigned char *)pcSkin + sizeof(uint32_t),
+ pcSkin->group, &iSkip);
+ } else {
+ CreateTexture_3DGS_MDL4((unsigned char *)pcSkin + sizeof(uint32_t),
+ pcSkin->group, &iSkip);
+ }
+ // need to skip one image
+ szCurrent += iSkip + sizeof(uint32_t);
+ }
+ // get a pointer to the texture coordinates
+ BE_NCONST MDL::TexCoord_MDL3 *pcTexCoords = (BE_NCONST MDL::TexCoord_MDL3 *)szCurrent;
+ szCurrent += sizeof(MDL::TexCoord_MDL3) * pcHeader->synctype;
+
+ // NOTE: for MDLn formats "synctype" corresponds to the number of UV coords
+
+ // get a pointer to the triangles
+ BE_NCONST MDL::Triangle_MDL3 *pcTriangles = (BE_NCONST MDL::Triangle_MDL3 *)szCurrent;
+ szCurrent += sizeof(MDL::Triangle_MDL3) * pcHeader->num_tris;
+
+#ifdef AI_BUILD_BIG_ENDIAN
+
+ for (int i = 0; i < pcHeader->synctype; ++i) {
+ AI_SWAP2(pcTexCoords[i].u);
+ AI_SWAP2(pcTexCoords[i].v);
+ }
+
+ for (int i = 0; i < pcHeader->num_tris; ++i) {
+ AI_SWAP2(pcTriangles[i].index_xyz[0]);
+ AI_SWAP2(pcTriangles[i].index_xyz[1]);
+ AI_SWAP2(pcTriangles[i].index_xyz[2]);
+ AI_SWAP2(pcTriangles[i].index_uv[0]);
+ AI_SWAP2(pcTriangles[i].index_uv[1]);
+ AI_SWAP2(pcTriangles[i].index_uv[2]);
+ }
+
+#endif
+
+ VALIDATE_FILE_SIZE(szCurrent);
+
+ // setup materials
+ SetupMaterialProperties_3DGS_MDL5_Quake1();
+
+ // allocate enough storage to hold all vertices and triangles
+ aiMesh *pcMesh = new aiMesh();
+ pcMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
+
+ pcMesh->mNumVertices = pcHeader->num_tris * 3;
+ pcMesh->mNumFaces = pcHeader->num_tris;
+ pcMesh->mFaces = new aiFace[pcMesh->mNumFaces];
+
+ // there won't be more than one mesh inside the file
+ pScene->mRootNode = new aiNode();
+ pScene->mRootNode->mNumMeshes = 1;
+ pScene->mRootNode->mMeshes = new unsigned int[1];
+ pScene->mRootNode->mMeshes[0] = 0;
+ pScene->mNumMeshes = 1;
+ pScene->mMeshes = new aiMesh *[1];
+ pScene->mMeshes[0] = pcMesh;
+
+ // allocate output storage
+ pcMesh->mNumVertices = (unsigned int)pcHeader->num_tris * 3;
+ pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices];
+ pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices];
+
+ if (pcHeader->synctype) {
+ pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices];
+ pcMesh->mNumUVComponents[0] = 2;
+ }
+
+ // now get a pointer to the first frame in the file
+ BE_NCONST MDL::Frame *pcFrames = (BE_NCONST MDL::Frame *)szCurrent;
+ AI_SWAP4(pcFrames->type);
+
+ // byte packed vertices
+ // FIXME: these two snippets below are almost identical ... join them?
+ /////////////////////////////////////////////////////////////////////////////////////
+ if (0 == pcFrames->type || 3 >= this->iGSFileVersion) {
+
+ const MDL::SimpleFrame *pcFirstFrame = (const MDL::SimpleFrame *)(szCurrent + sizeof(uint32_t));
+ const MDL::Vertex *pcVertices = (const MDL::Vertex *)((pcFirstFrame->name) + sizeof(pcFirstFrame->name));
+
+ VALIDATE_FILE_SIZE(pcVertices + pcHeader->num_verts);
+
+ // now iterate through all triangles
+ unsigned int iCurrent = 0;
+ for (unsigned int i = 0; i < (unsigned int)pcHeader->num_tris; ++i) {
+ pcMesh->mFaces[i].mIndices = new unsigned int[3];
+ pcMesh->mFaces[i].mNumIndices = 3;
+
+ unsigned int iTemp = iCurrent;
+ for (unsigned int c = 0; c < 3; ++c, ++iCurrent) {
+ // read vertices
+ unsigned int iIndex = pcTriangles->index_xyz[c];
+ if (iIndex >= (unsigned int)pcHeader->num_verts) {
+ iIndex = pcHeader->num_verts - 1;
+ ASSIMP_LOG_WARN("Index overflow in MDLn vertex list");
+ }
+
+ aiVector3D &vec = pcMesh->mVertices[iCurrent];
+ vec.x = (float)pcVertices[iIndex].v[0] * pcHeader->scale[0];
+ vec.x += pcHeader->translate[0];
+
+ vec.y = (float)pcVertices[iIndex].v[1] * pcHeader->scale[1];
+ vec.y += pcHeader->translate[1];
+ // vec.y *= -1.0f;
+
+ vec.z = (float)pcVertices[iIndex].v[2] * pcHeader->scale[2];
+ vec.z += pcHeader->translate[2];
+
+ // read the normal vector from the precalculated normal table
+ MD2::LookupNormalIndex(pcVertices[iIndex].normalIndex, pcMesh->mNormals[iCurrent]);
+ // pcMesh->mNormals[iCurrent].y *= -1.0f;
+
+ // read texture coordinates
+ if (pcHeader->synctype) {
+ ImportUVCoordinate_3DGS_MDL345(pcMesh->mTextureCoords[0][iCurrent],
+ pcTexCoords, pcTriangles->index_uv[c]);
+ }
+ }
+ pcMesh->mFaces[i].mIndices[0] = iTemp + 2;
+ pcMesh->mFaces[i].mIndices[1] = iTemp + 1;
+ pcMesh->mFaces[i].mIndices[2] = iTemp + 0;
+ pcTriangles++;
+ }
+
+ }
+ // short packed vertices
+ /////////////////////////////////////////////////////////////////////////////////////
+ else {
+ // now get a pointer to the first frame in the file
+ const MDL::SimpleFrame_MDLn_SP *pcFirstFrame = (const MDL::SimpleFrame_MDLn_SP *)(szCurrent + sizeof(uint32_t));
+
+ // get a pointer to the vertices
+ const MDL::Vertex_MDL4 *pcVertices = (const MDL::Vertex_MDL4 *)((pcFirstFrame->name) +
+ sizeof(pcFirstFrame->name));
+
+ VALIDATE_FILE_SIZE(pcVertices + pcHeader->num_verts);
+
+ // now iterate through all triangles
+ unsigned int iCurrent = 0;
+ for (unsigned int i = 0; i < (unsigned int)pcHeader->num_tris; ++i) {
+ pcMesh->mFaces[i].mIndices = new unsigned int[3];
+ pcMesh->mFaces[i].mNumIndices = 3;
+
+ unsigned int iTemp = iCurrent;
+ for (unsigned int c = 0; c < 3; ++c, ++iCurrent) {
+ // read vertices
+ unsigned int iIndex = pcTriangles->index_xyz[c];
+ if (iIndex >= (unsigned int)pcHeader->num_verts) {
+ iIndex = pcHeader->num_verts - 1;
+ ASSIMP_LOG_WARN("Index overflow in MDLn vertex list");
+ }
+
+ aiVector3D &vec = pcMesh->mVertices[iCurrent];
+ vec.x = (float)pcVertices[iIndex].v[0] * pcHeader->scale[0];
+ vec.x += pcHeader->translate[0];
+
+ vec.y = (float)pcVertices[iIndex].v[1] * pcHeader->scale[1];
+ vec.y += pcHeader->translate[1];
+ // vec.y *= -1.0f;
+
+ vec.z = (float)pcVertices[iIndex].v[2] * pcHeader->scale[2];
+ vec.z += pcHeader->translate[2];
+
+ // read the normal vector from the precalculated normal table
+ MD2::LookupNormalIndex(pcVertices[iIndex].normalIndex, pcMesh->mNormals[iCurrent]);
+ // pcMesh->mNormals[iCurrent].y *= -1.0f;
+
+ // read texture coordinates
+ if (pcHeader->synctype) {
+ ImportUVCoordinate_3DGS_MDL345(pcMesh->mTextureCoords[0][iCurrent],
+ pcTexCoords, pcTriangles->index_uv[c]);
+ }
+ }
+ pcMesh->mFaces[i].mIndices[0] = iTemp + 2;
+ pcMesh->mFaces[i].mIndices[1] = iTemp + 1;
+ pcMesh->mFaces[i].mIndices[2] = iTemp + 0;
+ pcTriangles++;
+ }
+ }
+
+ // For MDL5 we will need to build valid texture coordinates
+ // basing upon the file loaded (only support one file as skin)
+ if (0x5 == iGSFileVersion)
+ CalculateUVCoordinates_MDL5();
+ return;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Get a single UV coordinate for Quake and older GameStudio files
+void MDLImporter::ImportUVCoordinate_3DGS_MDL345(
+ aiVector3D &vOut,
+ const MDL::TexCoord_MDL3 *pcSrc,
+ unsigned int iIndex) {
+ ai_assert(nullptr != pcSrc);
+ const MDL::Header *const pcHeader = (const MDL::Header *)this->mBuffer;
+
+ // validate UV indices
+ if (iIndex >= (unsigned int)pcHeader->synctype) {
+ iIndex = pcHeader->synctype - 1;
+ ASSIMP_LOG_WARN("Index overflow in MDLn UV coord list");
+ }
+
+ float s = (float)pcSrc[iIndex].u;
+ float t = (float)pcSrc[iIndex].v;
+
+ // Scale s and t to range from 0.0 to 1.0
+ if (0x5 != iGSFileVersion) {
+ s = (s + 0.5f) / pcHeader->skinwidth;
+ t = 1.0f - (t + 0.5f) / pcHeader->skinheight;
+ }
+
+ vOut.x = s;
+ vOut.y = t;
+ vOut.z = 0.0f;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Compute UV coordinates for a MDL5 file
+void MDLImporter::CalculateUVCoordinates_MDL5() {
+ const MDL::Header *const pcHeader = (const MDL::Header *)this->mBuffer;
+ if (pcHeader->num_skins && this->pScene->mNumTextures) {
+ const aiTexture *pcTex = this->pScene->mTextures[0];
+
+ // if the file is loaded in DDS format: get the size of the
+ // texture from the header of the DDS file
+ // skip three DWORDs and read first height, then the width
+ unsigned int iWidth, iHeight;
+ if (!pcTex->mHeight) {
+ const uint32_t *piPtr = (uint32_t *)pcTex->pcData;
+
+ piPtr += 3;
+ iHeight = (unsigned int)*piPtr++;
+ iWidth = (unsigned int)*piPtr;
+ if (!iHeight || !iWidth) {
+ ASSIMP_LOG_WARN("Either the width or the height of the "
+ "embedded DDS texture is zero. Unable to compute final texture "
+ "coordinates. The texture coordinates remain in their original "
+ "0-x/0-y (x,y = texture size) range.");
+ iWidth = 1;
+ iHeight = 1;
+ }
+ } else {
+ iWidth = pcTex->mWidth;
+ iHeight = pcTex->mHeight;
+ }
+
+ if (1 != iWidth || 1 != iHeight) {
+ const float fWidth = (float)iWidth;
+ const float fHeight = (float)iHeight;
+ aiMesh *pcMesh = this->pScene->mMeshes[0];
+ for (unsigned int i = 0; i < pcMesh->mNumVertices; ++i) {
+ pcMesh->mTextureCoords[0][i].x /= fWidth;
+ pcMesh->mTextureCoords[0][i].y /= fHeight;
+ pcMesh->mTextureCoords[0][i].y = 1.0f - pcMesh->mTextureCoords[0][i].y; // DX to OGL
+ }
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Validate the header of a MDL7 file
+void MDLImporter::ValidateHeader_3DGS_MDL7(const MDL::Header_MDL7 *pcHeader) {
+ ai_assert(nullptr != pcHeader);
+
+ // There are some fixed sizes ...
+ if (sizeof(MDL::ColorValue_MDL7) != pcHeader->colorvalue_stc_size) {
+ throw DeadlyImportError(
+ "[3DGS MDL7] sizeof(MDL::ColorValue_MDL7) != pcHeader->colorvalue_stc_size");
+ }
+ if (sizeof(MDL::TexCoord_MDL7) != pcHeader->skinpoint_stc_size) {
+ throw DeadlyImportError(
+ "[3DGS MDL7] sizeof(MDL::TexCoord_MDL7) != pcHeader->skinpoint_stc_size");
+ }
+ if (sizeof(MDL::Skin_MDL7) != pcHeader->skin_stc_size) {
+ throw DeadlyImportError(
+ "sizeof(MDL::Skin_MDL7) != pcHeader->skin_stc_size");
+ }
+
+ // if there are no groups ... how should we load such a file?
+ if (!pcHeader->groups_num) {
+ throw DeadlyImportError("[3DGS MDL7] No frames found");
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// resolve bone animation matrices
+void MDLImporter::CalcAbsBoneMatrices_3DGS_MDL7(MDL::IntBone_MDL7 **apcOutBones) {
+ const MDL::Header_MDL7 *pcHeader = (const MDL::Header_MDL7 *)this->mBuffer;
+ const MDL::Bone_MDL7 *pcBones = (const MDL::Bone_MDL7 *)(pcHeader + 1);
+ ai_assert(nullptr != apcOutBones);
+
+ // first find the bone that has NO parent, calculate the
+ // animation matrix for it, then go on and search for the next parent
+ // index (0) and so on until we can't find a new node.
+ uint16_t iParent = 0xffff;
+ uint32_t iIterations = 0;
+ while (iIterations++ < pcHeader->bones_num) {
+ for (uint32_t iBone = 0; iBone < pcHeader->bones_num; ++iBone) {
+ BE_NCONST MDL::Bone_MDL7 *pcBone = _AI_MDL7_ACCESS_PTR(pcBones, iBone,
+ pcHeader->bone_stc_size, MDL::Bone_MDL7);
+
+ AI_SWAP2(pcBone->parent_index);
+ AI_SWAP4(pcBone->x);
+ AI_SWAP4(pcBone->y);
+ AI_SWAP4(pcBone->z);
+
+ if (iParent == pcBone->parent_index) {
+ // MDL7 readme
+ ////////////////////////////////////////////////////////////////
+ /*
+ The animation matrix is then calculated the following way:
+
+ vector3 bPos = <absolute bone position>
+ matrix44 laM; // local animation matrix
+ sphrvector key_rotate = <bone rotation>
+
+ matrix44 m1,m2;
+ create_trans_matrix(m1, -bPos.x, -bPos.y, -bPos.z);
+ create_trans_matrix(m2, -bPos.x, -bPos.y, -bPos.z);
+
+ create_rotation_matrix(laM,key_rotate);
+
+ laM = sm1 * laM;
+ laM = laM * sm2;
+ */
+ /////////////////////////////////////////////////////////////////
+
+ MDL::IntBone_MDL7 *const pcOutBone = apcOutBones[iBone];
+
+ // store the parent index of the bone
+ pcOutBone->iParent = pcBone->parent_index;
+ if (0xffff != iParent) {
+ const MDL::IntBone_MDL7 *pcParentBone = apcOutBones[iParent];
+ pcOutBone->mOffsetMatrix.a4 = -pcParentBone->vPosition.x;
+ pcOutBone->mOffsetMatrix.b4 = -pcParentBone->vPosition.y;
+ pcOutBone->mOffsetMatrix.c4 = -pcParentBone->vPosition.z;
+ }
+ pcOutBone->vPosition.x = pcBone->x;
+ pcOutBone->vPosition.y = pcBone->y;
+ pcOutBone->vPosition.z = pcBone->z;
+ pcOutBone->mOffsetMatrix.a4 -= pcBone->x;
+ pcOutBone->mOffsetMatrix.b4 -= pcBone->y;
+ pcOutBone->mOffsetMatrix.c4 -= pcBone->z;
+
+ if (AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_NOT_THERE == pcHeader->bone_stc_size) {
+ // no real name for our poor bone is specified :-(
+ pcOutBone->mName.length = ai_snprintf(pcOutBone->mName.data, MAXLEN,
+ "UnnamedBone_%i", iBone);
+ } else {
+ // Make sure we won't run over the buffer's end if there is no
+ // terminal 0 character (however the documentation says there
+ // should be one)
+ uint32_t iMaxLen = pcHeader->bone_stc_size - 16;
+ for (uint32_t qq = 0; qq < iMaxLen; ++qq) {
+ if (!pcBone->name[qq]) {
+ iMaxLen = qq;
+ break;
+ }
+ }
+
+ // store the name of the bone
+ pcOutBone->mName.length = (size_t)iMaxLen;
+ ::memcpy(pcOutBone->mName.data, pcBone->name, pcOutBone->mName.length);
+ pcOutBone->mName.data[pcOutBone->mName.length] = '\0';
+ }
+ }
+ }
+ ++iParent;
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// read bones from a MDL7 file
+MDL::IntBone_MDL7 **MDLImporter::LoadBones_3DGS_MDL7() {
+ const MDL::Header_MDL7 *pcHeader = (const MDL::Header_MDL7 *)this->mBuffer;
+ if (pcHeader->bones_num) {
+ // validate the size of the bone data structure in the file
+ if (AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_20_CHARS != pcHeader->bone_stc_size &&
+ AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_32_CHARS != pcHeader->bone_stc_size &&
+ AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_NOT_THERE != pcHeader->bone_stc_size) {
+ ASSIMP_LOG_WARN("Unknown size of bone data structure");
+ return nullptr;
+ }
+
+ MDL::IntBone_MDL7 **apcBonesOut = new MDL::IntBone_MDL7 *[pcHeader->bones_num];
+ for (uint32_t crank = 0; crank < pcHeader->bones_num; ++crank)
+ apcBonesOut[crank] = new MDL::IntBone_MDL7();
+
+ // and calculate absolute bone offset matrices ...
+ CalcAbsBoneMatrices_3DGS_MDL7(apcBonesOut);
+ return apcBonesOut;
+ }
+ return nullptr;
+}
+
+// ------------------------------------------------------------------------------------------------
+// read faces from a MDL7 file
+void MDLImporter::ReadFaces_3DGS_MDL7(const MDL::IntGroupInfo_MDL7 &groupInfo,
+ MDL::IntGroupData_MDL7 &groupData) {
+ const MDL::Header_MDL7 *pcHeader = (const MDL::Header_MDL7 *)this->mBuffer;
+ MDL::Triangle_MDL7 *pcGroupTris = groupInfo.pcGroupTris;
+
+ // iterate through all triangles and build valid display lists
+ unsigned int iOutIndex = 0;
+ for (unsigned int iTriangle = 0; iTriangle < (unsigned int)groupInfo.pcGroup->numtris; ++iTriangle) {
+ AI_SWAP2(pcGroupTris->v_index[0]);
+ AI_SWAP2(pcGroupTris->v_index[1]);
+ AI_SWAP2(pcGroupTris->v_index[2]);
+
+ // iterate through all indices of the current triangle
+ for (unsigned int c = 0; c < 3; ++c, ++iOutIndex) {
+
+ // validate the vertex index
+ unsigned int iIndex = pcGroupTris->v_index[c];
+ if (iIndex > (unsigned int)groupInfo.pcGroup->numverts) {
+ // (we might need to read this section a second time - to process frame vertices correctly)
+ pcGroupTris->v_index[c] = (uint16_t)(iIndex = groupInfo.pcGroup->numverts - 1);
+ ASSIMP_LOG_WARN("Index overflow in MDL7 vertex list");
+ }
+
+ // write the output face index
+ groupData.pcFaces[iTriangle].mIndices[2 - c] = iOutIndex;
+
+ aiVector3D &vPosition = groupData.vPositions[iOutIndex];
+ vPosition.x = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts, iIndex, pcHeader->mainvertex_stc_size).x;
+ vPosition.y = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts, iIndex, pcHeader->mainvertex_stc_size).y;
+ vPosition.z = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts, iIndex, pcHeader->mainvertex_stc_size).z;
+
+ // if we have bones, save the index
+ if (!groupData.aiBones.empty()) {
+ groupData.aiBones[iOutIndex] = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts,
+ iIndex, pcHeader->mainvertex_stc_size)
+ .vertindex;
+ }
+
+ // now read the normal vector
+ if (AI_MDL7_FRAMEVERTEX030305_STCSIZE <= pcHeader->mainvertex_stc_size) {
+ // read the full normal vector
+ aiVector3D &vNormal = groupData.vNormals[iOutIndex];
+ vNormal.x = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts, iIndex, pcHeader->mainvertex_stc_size).norm[0];
+ AI_SWAP4(vNormal.x);
+ vNormal.y = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts, iIndex, pcHeader->mainvertex_stc_size).norm[1];
+ AI_SWAP4(vNormal.y);
+ vNormal.z = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts, iIndex, pcHeader->mainvertex_stc_size).norm[2];
+ AI_SWAP4(vNormal.z);
+ } else if (AI_MDL7_FRAMEVERTEX120503_STCSIZE <= pcHeader->mainvertex_stc_size) {
+ // read the normal vector from Quake2's smart table
+ aiVector3D &vNormal = groupData.vNormals[iOutIndex];
+ MD2::LookupNormalIndex(_AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts, iIndex,
+ pcHeader->mainvertex_stc_size)
+ .norm162index,
+ vNormal);
+ }
+ // validate and process the first uv coordinate set
+ if (pcHeader->triangle_stc_size >= AI_MDL7_TRIANGLE_STD_SIZE_ONE_UV) {
+
+ if (groupInfo.pcGroup->num_stpts) {
+ AI_SWAP2(pcGroupTris->skinsets[0].st_index[0]);
+ AI_SWAP2(pcGroupTris->skinsets[0].st_index[1]);
+ AI_SWAP2(pcGroupTris->skinsets[0].st_index[2]);
+
+ iIndex = pcGroupTris->skinsets[0].st_index[c];
+ if (iIndex > (unsigned int)groupInfo.pcGroup->num_stpts) {
+ iIndex = groupInfo.pcGroup->num_stpts - 1;
+ ASSIMP_LOG_WARN("Index overflow in MDL7 UV coordinate list (#1)");
+ }
+
+ float u = groupInfo.pcGroupUVs[iIndex].u;
+ float v = 1.0f - groupInfo.pcGroupUVs[iIndex].v; // DX to OGL
+
+ groupData.vTextureCoords1[iOutIndex].x = u;
+ groupData.vTextureCoords1[iOutIndex].y = v;
+ }
+ // assign the material index, but only if it is existing
+ if (pcHeader->triangle_stc_size >= AI_MDL7_TRIANGLE_STD_SIZE_ONE_UV_WITH_MATINDEX) {
+ AI_SWAP4(pcGroupTris->skinsets[0].material);
+ groupData.pcFaces[iTriangle].iMatIndex[0] = pcGroupTris->skinsets[0].material;
+ }
+ }
+ // validate and process the second uv coordinate set
+ if (pcHeader->triangle_stc_size >= AI_MDL7_TRIANGLE_STD_SIZE_TWO_UV) {
+
+ if (groupInfo.pcGroup->num_stpts) {
+ AI_SWAP2(pcGroupTris->skinsets[1].st_index[0]);
+ AI_SWAP2(pcGroupTris->skinsets[1].st_index[1]);
+ AI_SWAP2(pcGroupTris->skinsets[1].st_index[2]);
+ AI_SWAP4(pcGroupTris->skinsets[1].material);
+
+ iIndex = pcGroupTris->skinsets[1].st_index[c];
+ if (iIndex > (unsigned int)groupInfo.pcGroup->num_stpts) {
+ iIndex = groupInfo.pcGroup->num_stpts - 1;
+ ASSIMP_LOG_WARN("Index overflow in MDL7 UV coordinate list (#2)");
+ }
+
+ float u = groupInfo.pcGroupUVs[iIndex].u;
+ float v = 1.0f - groupInfo.pcGroupUVs[iIndex].v;
+
+ groupData.vTextureCoords2[iOutIndex].x = u;
+ groupData.vTextureCoords2[iOutIndex].y = v; // DX to OGL
+
+ // check whether we do really need the second texture
+ // coordinate set ... wastes memory and loading time
+ if (0 != iIndex && (u != groupData.vTextureCoords1[iOutIndex].x ||
+ v != groupData.vTextureCoords1[iOutIndex].y))
+ groupData.bNeed2UV = true;
+
+ // if the material differs, we need a second skin, too
+ if (pcGroupTris->skinsets[1].material != pcGroupTris->skinsets[0].material)
+ groupData.bNeed2UV = true;
+ }
+ // assign the material index
+ groupData.pcFaces[iTriangle].iMatIndex[1] = pcGroupTris->skinsets[1].material;
+ }
+ }
+ // get the next triangle in the list
+ pcGroupTris = (MDL::Triangle_MDL7 *)((const char *)pcGroupTris + pcHeader->triangle_stc_size);
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// handle frames in a MDL7 file
+bool MDLImporter::ProcessFrames_3DGS_MDL7(const MDL::IntGroupInfo_MDL7 &groupInfo,
+ MDL::IntGroupData_MDL7 &groupData,
+ MDL::IntSharedData_MDL7 &shared,
+ const unsigned char *szCurrent,
+ const unsigned char **szCurrentOut) {
+ ai_assert(nullptr != szCurrent);
+ ai_assert(nullptr != szCurrentOut);
+
+ const MDL::Header_MDL7 *pcHeader = (const MDL::Header_MDL7 *)mBuffer;
+
+ // if we have no bones we can simply skip all frames,
+ // otherwise we'll need to process them.
+ // FIX: If we need another frame than the first we must apply frame vertex replacements ...
+ for (unsigned int iFrame = 0; iFrame < (unsigned int)groupInfo.pcGroup->numframes; ++iFrame) {
+ MDL::IntFrameInfo_MDL7 frame((BE_NCONST MDL::Frame_MDL7 *)szCurrent, iFrame);
+
+ AI_SWAP4(frame.pcFrame->vertices_count);
+ AI_SWAP4(frame.pcFrame->transmatrix_count);
+
+ const unsigned int iAdd = pcHeader->frame_stc_size +
+ frame.pcFrame->vertices_count * pcHeader->framevertex_stc_size +
+ frame.pcFrame->transmatrix_count * pcHeader->bonetrans_stc_size;
+
+ if (((const char *)szCurrent - (const char *)pcHeader) + iAdd > (unsigned int)pcHeader->data_size) {
+ ASSIMP_LOG_WARN("Index overflow in frame area. "
+ "Ignoring all frames and all further mesh groups, too.");
+
+ // don't parse more groups if we can't even read one
+ // FIXME: sometimes this seems to occur even for valid files ...
+ *szCurrentOut = szCurrent;
+ return false;
+ }
+ // our output frame?
+ if (configFrameID == iFrame) {
+ BE_NCONST MDL::Vertex_MDL7 *pcFrameVertices = (BE_NCONST MDL::Vertex_MDL7 *)(szCurrent + pcHeader->frame_stc_size);
+
+ for (unsigned int qq = 0; qq < frame.pcFrame->vertices_count; ++qq) {
+ // I assume this are simple replacements for normal vertices, the bone index serving
+ // as the index of the vertex to be replaced.
+ uint16_t iIndex = _AI_MDL7_ACCESS(pcFrameVertices, qq, pcHeader->framevertex_stc_size, MDL::Vertex_MDL7).vertindex;
+ AI_SWAP2(iIndex);
+ if (iIndex >= groupInfo.pcGroup->numverts) {
+ ASSIMP_LOG_WARN("Invalid vertex index in frame vertex section");
+ continue;
+ }
+
+ aiVector3D vPosition, vNormal;
+
+ vPosition.x = _AI_MDL7_ACCESS_VERT(pcFrameVertices, qq, pcHeader->framevertex_stc_size).x;
+ AI_SWAP4(vPosition.x);
+ vPosition.y = _AI_MDL7_ACCESS_VERT(pcFrameVertices, qq, pcHeader->framevertex_stc_size).y;
+ AI_SWAP4(vPosition.y);
+ vPosition.z = _AI_MDL7_ACCESS_VERT(pcFrameVertices, qq, pcHeader->framevertex_stc_size).z;
+ AI_SWAP4(vPosition.z);
+
+ // now read the normal vector
+ if (AI_MDL7_FRAMEVERTEX030305_STCSIZE <= pcHeader->mainvertex_stc_size) {
+ // read the full normal vector
+ vNormal.x = _AI_MDL7_ACCESS_VERT(pcFrameVertices, qq, pcHeader->framevertex_stc_size).norm[0];
+ AI_SWAP4(vNormal.x);
+ vNormal.y = _AI_MDL7_ACCESS_VERT(pcFrameVertices, qq, pcHeader->framevertex_stc_size).norm[1];
+ AI_SWAP4(vNormal.y);
+ vNormal.z = _AI_MDL7_ACCESS_VERT(pcFrameVertices, qq, pcHeader->framevertex_stc_size).norm[2];
+ AI_SWAP4(vNormal.z);
+ } else if (AI_MDL7_FRAMEVERTEX120503_STCSIZE <= pcHeader->mainvertex_stc_size) {
+ // read the normal vector from Quake2's smart table
+ MD2::LookupNormalIndex(_AI_MDL7_ACCESS_VERT(pcFrameVertices, qq,
+ pcHeader->framevertex_stc_size)
+ .norm162index,
+ vNormal);
+ }
+
+ // FIXME: O(n^2) at the moment ...
+ BE_NCONST MDL::Triangle_MDL7 *pcGroupTris = groupInfo.pcGroupTris;
+ unsigned int iOutIndex = 0;
+ for (unsigned int iTriangle = 0; iTriangle < (unsigned int)groupInfo.pcGroup->numtris; ++iTriangle) {
+ // iterate through all indices of the current triangle
+ for (unsigned int c = 0; c < 3; ++c, ++iOutIndex) {
+ // replace the vertex with the new data
+ const unsigned int iCurIndex = pcGroupTris->v_index[c];
+ if (iCurIndex == iIndex) {
+ groupData.vPositions[iOutIndex] = vPosition;
+ groupData.vNormals[iOutIndex] = vNormal;
+ }
+ }
+ // get the next triangle in the list
+ pcGroupTris = (BE_NCONST MDL::Triangle_MDL7 *)((const char *)
+ pcGroupTris +
+ pcHeader->triangle_stc_size);
+ }
+ }
+ }
+ // parse bone trafo matrix keys (only if there are bones ...)
+ if (shared.apcOutBones) {
+ ParseBoneTrafoKeys_3DGS_MDL7(groupInfo, frame, shared);
+ }
+ szCurrent += iAdd;
+ }
+ *szCurrentOut = szCurrent;
+ return true;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Sort faces by material, handle multiple UVs correctly
+void MDLImporter::SortByMaterials_3DGS_MDL7(
+ const MDL::IntGroupInfo_MDL7 &groupInfo,
+ MDL::IntGroupData_MDL7 &groupData,
+ MDL::IntSplitGroupData_MDL7 &splitGroupData) {
+ const unsigned int iNumMaterials = (unsigned int)splitGroupData.shared.pcMats.size();
+ if (!groupData.bNeed2UV) {
+ // if we don't need a second set of texture coordinates there is no reason to keep it in memory ...
+ groupData.vTextureCoords2.clear();
+
+ // allocate the array
+ splitGroupData.aiSplit = new std::vector<unsigned int> *[iNumMaterials];
+
+ for (unsigned int m = 0; m < iNumMaterials; ++m)
+ splitGroupData.aiSplit[m] = new std::vector<unsigned int>();
+
+ // iterate through all faces and sort by material
+ for (unsigned int iFace = 0; iFace < (unsigned int)groupInfo.pcGroup->numtris; ++iFace) {
+ // check range
+ if (groupData.pcFaces[iFace].iMatIndex[0] >= iNumMaterials) {
+ // use the last material instead
+ splitGroupData.aiSplit[iNumMaterials - 1]->push_back(iFace);
+
+ // sometimes MED writes -1, but normally only if there is only
+ // one skin assigned. No warning in this case
+ if (0xFFFFFFFF != groupData.pcFaces[iFace].iMatIndex[0])
+ ASSIMP_LOG_WARN("Index overflow in MDL7 material list [#0]");
+ } else
+ splitGroupData.aiSplit[groupData.pcFaces[iFace].iMatIndex[0]]->push_back(iFace);
+ }
+ } else {
+ // we need to build combined materials for each combination of
+ std::vector<MDL::IntMaterial_MDL7> avMats;
+ avMats.reserve(iNumMaterials * 2);
+
+ // fixme: why on the heap?
+ std::vector<std::vector<unsigned int> *> aiTempSplit(iNumMaterials * 2);
+ for (unsigned int m = 0; m < iNumMaterials; ++m)
+ aiTempSplit[m] = new std::vector<unsigned int>();
+
+ // iterate through all faces and sort by material
+ for (unsigned int iFace = 0; iFace < (unsigned int)groupInfo.pcGroup->numtris; ++iFace) {
+ // check range
+ unsigned int iMatIndex = groupData.pcFaces[iFace].iMatIndex[0];
+ if (iMatIndex >= iNumMaterials) {
+ // sometimes MED writes -1, but normally only if there is only
+ // one skin assigned. No warning in this case
+ if (UINT_MAX != iMatIndex)
+ ASSIMP_LOG_WARN("Index overflow in MDL7 material list [#1]");
+ iMatIndex = iNumMaterials - 1;
+ }
+ unsigned int iMatIndex2 = groupData.pcFaces[iFace].iMatIndex[1];
+
+ unsigned int iNum = iMatIndex;
+ if (UINT_MAX != iMatIndex2 && iMatIndex != iMatIndex2) {
+ if (iMatIndex2 >= iNumMaterials) {
+ // sometimes MED writes -1, but normally only if there is only
+ // one skin assigned. No warning in this case
+ ASSIMP_LOG_WARN("Index overflow in MDL7 material list [#2]");
+ iMatIndex2 = iNumMaterials - 1;
+ }
+
+ // do a slow search in the list ...
+ iNum = 0;
+ bool bFound = false;
+ for (std::vector<MDL::IntMaterial_MDL7>::iterator i = avMats.begin(); i != avMats.end(); ++i, ++iNum) {
+ if ((*i).iOldMatIndices[0] == iMatIndex && (*i).iOldMatIndices[1] == iMatIndex2) {
+ // reuse this material
+ bFound = true;
+ break;
+ }
+ }
+ if (!bFound) {
+ // build a new material ...
+ MDL::IntMaterial_MDL7 sHelper;
+ sHelper.pcMat = new aiMaterial();
+ sHelper.iOldMatIndices[0] = iMatIndex;
+ sHelper.iOldMatIndices[1] = iMatIndex2;
+ JoinSkins_3DGS_MDL7(splitGroupData.shared.pcMats[iMatIndex],
+ splitGroupData.shared.pcMats[iMatIndex2], sHelper.pcMat);
+
+ // and add it to the list
+ avMats.push_back(sHelper);
+ iNum = (unsigned int)avMats.size() - 1;
+ }
+ // adjust the size of the file array
+ if (iNum == aiTempSplit.size()) {
+ aiTempSplit.push_back(new std::vector<unsigned int>());
+ }
+ }
+ aiTempSplit[iNum]->push_back(iFace);
+ }
+
+ // now add the newly created materials to the old list
+ if (0 == groupInfo.iIndex) {
+ splitGroupData.shared.pcMats.resize(avMats.size());
+ for (unsigned int o = 0; o < avMats.size(); ++o)
+ splitGroupData.shared.pcMats[o] = avMats[o].pcMat;
+ } else {
+ // This might result in redundant materials ...
+ splitGroupData.shared.pcMats.resize(iNumMaterials + avMats.size());
+ for (unsigned int o = iNumMaterials; o < avMats.size(); ++o)
+ splitGroupData.shared.pcMats[o] = avMats[o].pcMat;
+ }
+
+ // and build the final face-to-material array
+ splitGroupData.aiSplit = new std::vector<unsigned int> *[aiTempSplit.size()];
+ for (unsigned int m = 0; m < iNumMaterials; ++m)
+ splitGroupData.aiSplit[m] = aiTempSplit[m];
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Read a MDL7 file
+void MDLImporter::InternReadFile_3DGS_MDL7() {
+ ai_assert(nullptr != pScene);
+
+ MDL::IntSharedData_MDL7 sharedData;
+
+ // current cursor position in the file
+ BE_NCONST MDL::Header_MDL7 *pcHeader = (BE_NCONST MDL::Header_MDL7 *)this->mBuffer;
+ const unsigned char *szCurrent = (const unsigned char *)(pcHeader + 1);
+
+ AI_SWAP4(pcHeader->version);
+ AI_SWAP4(pcHeader->bones_num);
+ AI_SWAP4(pcHeader->groups_num);
+ AI_SWAP4(pcHeader->data_size);
+ AI_SWAP4(pcHeader->entlump_size);
+ AI_SWAP4(pcHeader->medlump_size);
+ AI_SWAP2(pcHeader->bone_stc_size);
+ AI_SWAP2(pcHeader->skin_stc_size);
+ AI_SWAP2(pcHeader->colorvalue_stc_size);
+ AI_SWAP2(pcHeader->material_stc_size);
+ AI_SWAP2(pcHeader->skinpoint_stc_size);
+ AI_SWAP2(pcHeader->triangle_stc_size);
+ AI_SWAP2(pcHeader->mainvertex_stc_size);
+ AI_SWAP2(pcHeader->framevertex_stc_size);
+ AI_SWAP2(pcHeader->bonetrans_stc_size);
+ AI_SWAP2(pcHeader->frame_stc_size);
+
+ // validate the header of the file. There are some structure
+ // sizes that are expected by the loader to be constant
+ this->ValidateHeader_3DGS_MDL7(pcHeader);
+
+ // load all bones (they are shared by all groups, so
+ // we'll need to add them to all groups/meshes later)
+ // apcBonesOut is a list of all bones or nullptr if they could not been loaded
+ szCurrent += pcHeader->bones_num * pcHeader->bone_stc_size;
+ sharedData.apcOutBones = this->LoadBones_3DGS_MDL7();
+
+ // vector to held all created meshes
+ std::vector<aiMesh *> *avOutList;
+
+ // 3 meshes per group - that should be OK for most models
+ avOutList = new std::vector<aiMesh *>[pcHeader->groups_num];
+ for (uint32_t i = 0; i < pcHeader->groups_num; ++i)
+ avOutList[i].reserve(3);
+
+ // buffer to held the names of all groups in the file
+ const size_t buffersize(AI_MDL7_MAX_GROUPNAMESIZE * pcHeader->groups_num);
+ char *aszGroupNameBuffer = new char[buffersize];
+
+ // read all groups
+ for (unsigned int iGroup = 0; iGroup < (unsigned int)pcHeader->groups_num; ++iGroup) {
+ MDL::IntGroupInfo_MDL7 groupInfo((BE_NCONST MDL::Group_MDL7 *)szCurrent, iGroup);
+ szCurrent = (const unsigned char *)(groupInfo.pcGroup + 1);
+
+ VALIDATE_FILE_SIZE(szCurrent);
+
+ AI_SWAP4(groupInfo.pcGroup->groupdata_size);
+ AI_SWAP4(groupInfo.pcGroup->numskins);
+ AI_SWAP4(groupInfo.pcGroup->num_stpts);
+ AI_SWAP4(groupInfo.pcGroup->numtris);
+ AI_SWAP4(groupInfo.pcGroup->numverts);
+ AI_SWAP4(groupInfo.pcGroup->numframes);
+
+ if (1 != groupInfo.pcGroup->typ) {
+ // Not a triangle-based mesh
+ ASSIMP_LOG_WARN("[3DGS MDL7] Not a triangle mesh group. Continuing happily");
+ }
+
+ // store the name of the group
+ const unsigned int ofs = iGroup * AI_MDL7_MAX_GROUPNAMESIZE;
+ ::memcpy(&aszGroupNameBuffer[ofs],
+ groupInfo.pcGroup->name, AI_MDL7_MAX_GROUPNAMESIZE);
+
+ // make sure '\0' is at the end
+ aszGroupNameBuffer[ofs + AI_MDL7_MAX_GROUPNAMESIZE - 1] = '\0';
+
+ // read all skins
+ sharedData.pcMats.reserve(sharedData.pcMats.size() + groupInfo.pcGroup->numskins);
+ sharedData.abNeedMaterials.resize(sharedData.abNeedMaterials.size() +
+ groupInfo.pcGroup->numskins,
+ false);
+
+ for (unsigned int iSkin = 0; iSkin < (unsigned int)groupInfo.pcGroup->numskins; ++iSkin) {
+ ParseSkinLump_3DGS_MDL7(szCurrent, &szCurrent, sharedData.pcMats);
+ }
+ // if we have absolutely no skin loaded we need to generate a default material
+ if (sharedData.pcMats.empty()) {
+ const int iMode = (int)aiShadingMode_Gouraud;
+ sharedData.pcMats.push_back(new aiMaterial());
+ aiMaterial *pcHelper = (aiMaterial *)sharedData.pcMats[0];
+ pcHelper->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL);
+
+ aiColor3D clr;
+ clr.b = clr.g = clr.r = 0.6f;
+ pcHelper->AddProperty<aiColor3D>(&clr, 1, AI_MATKEY_COLOR_DIFFUSE);
+ pcHelper->AddProperty<aiColor3D>(&clr, 1, AI_MATKEY_COLOR_SPECULAR);
+
+ clr.b = clr.g = clr.r = 0.05f;
+ pcHelper->AddProperty<aiColor3D>(&clr, 1, AI_MATKEY_COLOR_AMBIENT);
+
+ aiString szName;
+ szName.Set(AI_DEFAULT_MATERIAL_NAME);
+ pcHelper->AddProperty(&szName, AI_MATKEY_NAME);
+
+ sharedData.abNeedMaterials.resize(1, false);
+ }
+
+ // now get a pointer to all texture coords in the group
+ groupInfo.pcGroupUVs = (BE_NCONST MDL::TexCoord_MDL7 *)szCurrent;
+ for (int i = 0; i < groupInfo.pcGroup->num_stpts; ++i) {
+ AI_SWAP4(groupInfo.pcGroupUVs[i].u);
+ AI_SWAP4(groupInfo.pcGroupUVs[i].v);
+ }
+ szCurrent += pcHeader->skinpoint_stc_size * groupInfo.pcGroup->num_stpts;
+
+ // now get a pointer to all triangle in the group
+ groupInfo.pcGroupTris = (Triangle_MDL7 *)szCurrent;
+ szCurrent += pcHeader->triangle_stc_size * groupInfo.pcGroup->numtris;
+
+ // now get a pointer to all vertices in the group
+ groupInfo.pcGroupVerts = (BE_NCONST MDL::Vertex_MDL7 *)szCurrent;
+ for (int i = 0; i < groupInfo.pcGroup->numverts; ++i) {
+ AI_SWAP4(groupInfo.pcGroupVerts[i].x);
+ AI_SWAP4(groupInfo.pcGroupVerts[i].y);
+ AI_SWAP4(groupInfo.pcGroupVerts[i].z);
+
+ AI_SWAP2(groupInfo.pcGroupVerts[i].vertindex);
+ //We can not swap the normal information now as we don't know which of the two kinds it is
+ }
+ szCurrent += pcHeader->mainvertex_stc_size * groupInfo.pcGroup->numverts;
+ VALIDATE_FILE_SIZE(szCurrent);
+
+ MDL::IntSplitGroupData_MDL7 splitGroupData(sharedData, avOutList[iGroup]);
+ MDL::IntGroupData_MDL7 groupData;
+ if (groupInfo.pcGroup->numtris && groupInfo.pcGroup->numverts) {
+ // build output vectors
+ const unsigned int iNumVertices = groupInfo.pcGroup->numtris * 3;
+ groupData.vPositions.resize(iNumVertices);
+ groupData.vNormals.resize(iNumVertices);
+
+ if (sharedData.apcOutBones) groupData.aiBones.resize(iNumVertices, UINT_MAX);
+
+ // it is also possible that there are 0 UV coordinate sets
+ if (groupInfo.pcGroup->num_stpts) {
+ groupData.vTextureCoords1.resize(iNumVertices, aiVector3D());
+
+ // check whether the triangle data structure is large enough
+ // to contain a second UV coordinate set
+ if (pcHeader->triangle_stc_size >= AI_MDL7_TRIANGLE_STD_SIZE_TWO_UV) {
+ groupData.vTextureCoords2.resize(iNumVertices, aiVector3D());
+ groupData.bNeed2UV = true;
+ }
+ }
+ groupData.pcFaces.resize(groupInfo.pcGroup->numtris);
+
+ // read all faces into the preallocated arrays
+ ReadFaces_3DGS_MDL7(groupInfo, groupData);
+
+ // sort by materials
+ SortByMaterials_3DGS_MDL7(groupInfo, groupData,
+ splitGroupData);
+
+ for (unsigned int qq = 0; qq < sharedData.pcMats.size(); ++qq) {
+ if (!splitGroupData.aiSplit[qq]->empty())
+ sharedData.abNeedMaterials[qq] = true;
+ }
+ } else
+ ASSIMP_LOG_WARN("[3DGS MDL7] Mesh group consists of 0 "
+ "vertices or faces. It will be skipped.");
+
+ // process all frames and generate output meshes
+ ProcessFrames_3DGS_MDL7(groupInfo, groupData, sharedData, szCurrent, &szCurrent);
+ GenerateOutputMeshes_3DGS_MDL7(groupData, splitGroupData);
+ }
+
+ // generate a nodegraph and subnodes for each group
+ pScene->mRootNode = new aiNode();
+
+ // now we need to build a final mesh list
+ for (uint32_t i = 0; i < pcHeader->groups_num; ++i)
+ pScene->mNumMeshes += (unsigned int)avOutList[i].size();
+
+ pScene->mMeshes = new aiMesh *[pScene->mNumMeshes];
+ {
+ unsigned int p = 0, q = 0;
+ for (uint32_t i = 0; i < pcHeader->groups_num; ++i) {
+ for (unsigned int a = 0; a < avOutList[i].size(); ++a) {
+ pScene->mMeshes[p++] = avOutList[i][a];
+ }
+ if (!avOutList[i].empty()) ++pScene->mRootNode->mNumChildren;
+ }
+ // we will later need an extra node to serve as parent for all bones
+ if (sharedData.apcOutBones) ++pScene->mRootNode->mNumChildren;
+ this->pScene->mRootNode->mChildren = new aiNode *[pScene->mRootNode->mNumChildren];
+ p = 0;
+ for (uint32_t i = 0; i < pcHeader->groups_num; ++i) {
+ if (avOutList[i].empty()) continue;
+
+ aiNode *const pcNode = pScene->mRootNode->mChildren[p] = new aiNode();
+ pcNode->mNumMeshes = (unsigned int)avOutList[i].size();
+ pcNode->mMeshes = new unsigned int[pcNode->mNumMeshes];
+ pcNode->mParent = this->pScene->mRootNode;
+ for (unsigned int a = 0; a < pcNode->mNumMeshes; ++a)
+ pcNode->mMeshes[a] = q + a;
+ q += (unsigned int)avOutList[i].size();
+
+ // setup the name of the node
+ char *const szBuffer = &aszGroupNameBuffer[i * AI_MDL7_MAX_GROUPNAMESIZE];
+ if ('\0' == *szBuffer) {
+ const size_t maxSize(buffersize - (i * AI_MDL7_MAX_GROUPNAMESIZE));
+ pcNode->mName.length = ai_snprintf(szBuffer, maxSize, "Group_%u", p);
+ } else {
+ pcNode->mName.length = (ai_uint32)::strlen(szBuffer);
+ }
+ ::strncpy(pcNode->mName.data, szBuffer, MAXLEN - 1);
+ ++p;
+ }
+ }
+
+ // if there is only one root node with a single child we can optimize it a bit ...
+ if (1 == pScene->mRootNode->mNumChildren && !sharedData.apcOutBones) {
+ aiNode *pcOldRoot = this->pScene->mRootNode;
+ pScene->mRootNode = pcOldRoot->mChildren[0];
+ pcOldRoot->mChildren[0] = nullptr;
+ delete pcOldRoot;
+ pScene->mRootNode->mParent = nullptr;
+ } else
+ pScene->mRootNode->mName.Set("<mesh_root>");
+
+ delete[] avOutList;
+ delete[] aszGroupNameBuffer;
+ AI_DEBUG_INVALIDATE_PTR(avOutList);
+ AI_DEBUG_INVALIDATE_PTR(aszGroupNameBuffer);
+
+ // build a final material list.
+ CopyMaterials_3DGS_MDL7(sharedData);
+ HandleMaterialReferences_3DGS_MDL7();
+
+ // generate output bone animations and add all bones to the scenegraph
+ if (sharedData.apcOutBones) {
+ // this step adds empty dummy bones to the nodegraph
+ // insert another dummy node to avoid name conflicts
+ aiNode *const pc = pScene->mRootNode->mChildren[pScene->mRootNode->mNumChildren - 1] = new aiNode();
+
+ pc->mName.Set("<skeleton_root>");
+
+ // add bones to the nodegraph
+ AddBonesToNodeGraph_3DGS_MDL7((const Assimp::MDL::IntBone_MDL7 **)
+ sharedData.apcOutBones,
+ pc, 0xffff);
+
+ // this steps build a valid output animation
+ BuildOutputAnims_3DGS_MDL7((const Assimp::MDL::IntBone_MDL7 **)
+ sharedData.apcOutBones);
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Copy materials
+void MDLImporter::CopyMaterials_3DGS_MDL7(MDL::IntSharedData_MDL7 &shared) {
+ pScene->mNumMaterials = (unsigned int)shared.pcMats.size();
+ pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials];
+ for (unsigned int i = 0; i < pScene->mNumMaterials; ++i)
+ pScene->mMaterials[i] = shared.pcMats[i];
+}
+
+// ------------------------------------------------------------------------------------------------
+// Process material references
+void MDLImporter::HandleMaterialReferences_3DGS_MDL7() {
+ // search for referrer materials
+ for (unsigned int i = 0; i < pScene->mNumMaterials; ++i) {
+ int iIndex = 0;
+ if (AI_SUCCESS == aiGetMaterialInteger(pScene->mMaterials[i], AI_MDL7_REFERRER_MATERIAL, &iIndex)) {
+ for (unsigned int a = 0; a < pScene->mNumMeshes; ++a) {
+ aiMesh *const pcMesh = pScene->mMeshes[a];
+ if (i == pcMesh->mMaterialIndex) {
+ pcMesh->mMaterialIndex = iIndex;
+ }
+ }
+ // collapse the rest of the array
+ delete pScene->mMaterials[i];
+ for (unsigned int pp = i; pp < pScene->mNumMaterials - 1; ++pp) {
+
+ pScene->mMaterials[pp] = pScene->mMaterials[pp + 1];
+ for (unsigned int a = 0; a < pScene->mNumMeshes; ++a) {
+ aiMesh *const pcMesh = pScene->mMeshes[a];
+ if (pcMesh->mMaterialIndex > i) --pcMesh->mMaterialIndex;
+ }
+ }
+ --pScene->mNumMaterials;
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Read bone transformation keys
+void MDLImporter::ParseBoneTrafoKeys_3DGS_MDL7(
+ const MDL::IntGroupInfo_MDL7 &groupInfo,
+ IntFrameInfo_MDL7 &frame,
+ MDL::IntSharedData_MDL7 &shared) {
+ const MDL::Header_MDL7 *const pcHeader = (const MDL::Header_MDL7 *)this->mBuffer;
+
+ // only the first group contains bone animation keys
+ if (frame.pcFrame->transmatrix_count) {
+ if (!groupInfo.iIndex) {
+ // skip all frames vertices. We can't support them
+ const MDL::BoneTransform_MDL7 *pcBoneTransforms = (const MDL::BoneTransform_MDL7 *)(((const char *)frame.pcFrame) + pcHeader->frame_stc_size +
+ frame.pcFrame->vertices_count * pcHeader->framevertex_stc_size);
+
+ // read all transformation matrices
+ for (unsigned int iTrafo = 0; iTrafo < frame.pcFrame->transmatrix_count; ++iTrafo) {
+ if (pcBoneTransforms->bone_index >= pcHeader->bones_num) {
+ ASSIMP_LOG_WARN("Index overflow in frame area. "
+ "Unable to parse this bone transformation");
+ } else {
+ AddAnimationBoneTrafoKey_3DGS_MDL7(frame.iIndex,
+ pcBoneTransforms, shared.apcOutBones);
+ }
+ pcBoneTransforms = (const MDL::BoneTransform_MDL7 *)((const char *)pcBoneTransforms + pcHeader->bonetrans_stc_size);
+ }
+ } else {
+ ASSIMP_LOG_WARN("Ignoring animation keyframes in groups != 0");
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Attach bones to the output nodegraph
+void MDLImporter::AddBonesToNodeGraph_3DGS_MDL7(const MDL::IntBone_MDL7 **apcBones,
+ aiNode *pcParent, uint16_t iParentIndex) {
+ ai_assert(nullptr != apcBones);
+ ai_assert(nullptr != pcParent);
+
+ // get a pointer to the header ...
+ const MDL::Header_MDL7 *const pcHeader = (const MDL::Header_MDL7 *)this->mBuffer;
+
+ const MDL::IntBone_MDL7 **apcBones2 = apcBones;
+ for (uint32_t i = 0; i < pcHeader->bones_num; ++i) {
+
+ const MDL::IntBone_MDL7 *const pcBone = *apcBones2++;
+ if (pcBone->iParent == iParentIndex) {
+ ++pcParent->mNumChildren;
+ }
+ }
+ pcParent->mChildren = new aiNode *[pcParent->mNumChildren];
+ unsigned int qq = 0;
+ for (uint32_t i = 0; i < pcHeader->bones_num; ++i) {
+
+ const MDL::IntBone_MDL7 *const pcBone = *apcBones++;
+ if (pcBone->iParent != iParentIndex) continue;
+
+ aiNode *pcNode = pcParent->mChildren[qq++] = new aiNode();
+ pcNode->mName = aiString(pcBone->mName);
+
+ AddBonesToNodeGraph_3DGS_MDL7(apcBones, pcNode, (uint16_t)i);
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Build output animations
+void MDLImporter::BuildOutputAnims_3DGS_MDL7(
+ const MDL::IntBone_MDL7 **apcBonesOut) {
+ ai_assert(nullptr != apcBonesOut);
+ const MDL::Header_MDL7 *const pcHeader = (const MDL::Header_MDL7 *)mBuffer;
+
+ // one animation ...
+ aiAnimation *pcAnim = new aiAnimation();
+ for (uint32_t i = 0; i < pcHeader->bones_num; ++i) {
+ if (!apcBonesOut[i]->pkeyPositions.empty()) {
+
+ // get the last frame ... (needn't be equal to pcHeader->frames_num)
+ for (size_t qq = 0; qq < apcBonesOut[i]->pkeyPositions.size(); ++qq) {
+ pcAnim->mDuration = std::max(pcAnim->mDuration, (double)
+ apcBonesOut[i]
+ ->pkeyPositions[qq]
+ .mTime);
+ }
+ ++pcAnim->mNumChannels;
+ }
+ }
+ if (pcAnim->mDuration) {
+ pcAnim->mChannels = new aiNodeAnim *[pcAnim->mNumChannels];
+
+ unsigned int iCnt = 0;
+ for (uint32_t i = 0; i < pcHeader->bones_num; ++i) {
+ if (!apcBonesOut[i]->pkeyPositions.empty()) {
+ const MDL::IntBone_MDL7 *const intBone = apcBonesOut[i];
+
+ aiNodeAnim *const pcNodeAnim = pcAnim->mChannels[iCnt++] = new aiNodeAnim();
+ pcNodeAnim->mNodeName = aiString(intBone->mName);
+
+ // allocate enough storage for all keys
+ pcNodeAnim->mNumPositionKeys = (unsigned int)intBone->pkeyPositions.size();
+ pcNodeAnim->mNumScalingKeys = (unsigned int)intBone->pkeyPositions.size();
+ pcNodeAnim->mNumRotationKeys = (unsigned int)intBone->pkeyPositions.size();
+
+ pcNodeAnim->mPositionKeys = new aiVectorKey[pcNodeAnim->mNumPositionKeys];
+ pcNodeAnim->mScalingKeys = new aiVectorKey[pcNodeAnim->mNumPositionKeys];
+ pcNodeAnim->mRotationKeys = new aiQuatKey[pcNodeAnim->mNumPositionKeys];
+
+ // copy all keys
+ for (unsigned int qq = 0; qq < pcNodeAnim->mNumPositionKeys; ++qq) {
+ pcNodeAnim->mPositionKeys[qq] = intBone->pkeyPositions[qq];
+ pcNodeAnim->mScalingKeys[qq] = intBone->pkeyScalings[qq];
+ pcNodeAnim->mRotationKeys[qq] = intBone->pkeyRotations[qq];
+ }
+ }
+ }
+
+ // store the output animation
+ pScene->mNumAnimations = 1;
+ pScene->mAnimations = new aiAnimation *[1];
+ pScene->mAnimations[0] = pcAnim;
+ } else
+ delete pcAnim;
+}
+
+// ------------------------------------------------------------------------------------------------
+void MDLImporter::AddAnimationBoneTrafoKey_3DGS_MDL7(unsigned int iTrafo,
+ const MDL::BoneTransform_MDL7 *pcBoneTransforms,
+ MDL::IntBone_MDL7 **apcBonesOut) {
+ ai_assert(nullptr != pcBoneTransforms);
+ ai_assert(nullptr != apcBonesOut);
+
+ // first .. get the transformation matrix
+ aiMatrix4x4 mTransform;
+ mTransform.a1 = pcBoneTransforms->m[0];
+ mTransform.b1 = pcBoneTransforms->m[1];
+ mTransform.c1 = pcBoneTransforms->m[2];
+ mTransform.d1 = pcBoneTransforms->m[3];
+
+ mTransform.a2 = pcBoneTransforms->m[4];
+ mTransform.b2 = pcBoneTransforms->m[5];
+ mTransform.c2 = pcBoneTransforms->m[6];
+ mTransform.d2 = pcBoneTransforms->m[7];
+
+ mTransform.a3 = pcBoneTransforms->m[8];
+ mTransform.b3 = pcBoneTransforms->m[9];
+ mTransform.c3 = pcBoneTransforms->m[10];
+ mTransform.d3 = pcBoneTransforms->m[11];
+
+ // now decompose the transformation matrix into separate
+ // scaling, rotation and translation
+ aiVectorKey vScaling, vPosition;
+ aiQuatKey qRotation;
+
+ // FIXME: Decompose will assert in debug builds if the matrix is invalid ...
+ mTransform.Decompose(vScaling.mValue, qRotation.mValue, vPosition.mValue);
+
+ // now generate keys
+ vScaling.mTime = qRotation.mTime = vPosition.mTime = (double)iTrafo;
+
+ // add the keys to the bone
+ MDL::IntBone_MDL7 *const pcBoneOut = apcBonesOut[pcBoneTransforms->bone_index];
+ pcBoneOut->pkeyPositions.push_back(vPosition);
+ pcBoneOut->pkeyScalings.push_back(vScaling);
+ pcBoneOut->pkeyRotations.push_back(qRotation);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Construct output meshes
+void MDLImporter::GenerateOutputMeshes_3DGS_MDL7(
+ MDL::IntGroupData_MDL7 &groupData,
+ MDL::IntSplitGroupData_MDL7 &splitGroupData) {
+ const MDL::IntSharedData_MDL7 &shared = splitGroupData.shared;
+
+ // get a pointer to the header ...
+ const MDL::Header_MDL7 *const pcHeader = (const MDL::Header_MDL7 *)this->mBuffer;
+ const unsigned int iNumOutBones = pcHeader->bones_num;
+
+ for (std::vector<aiMaterial *>::size_type i = 0; i < shared.pcMats.size(); ++i) {
+ if (!splitGroupData.aiSplit[i]->empty()) {
+
+ // allocate the output mesh
+ aiMesh *pcMesh = new aiMesh();
+
+ pcMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
+ pcMesh->mMaterialIndex = (unsigned int)i;
+
+ // allocate output storage
+ pcMesh->mNumFaces = (unsigned int)splitGroupData.aiSplit[i]->size();
+ pcMesh->mFaces = new aiFace[pcMesh->mNumFaces];
+
+ pcMesh->mNumVertices = pcMesh->mNumFaces * 3;
+ pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices];
+ pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices];
+
+ if (!groupData.vTextureCoords1.empty()) {
+ pcMesh->mNumUVComponents[0] = 2;
+ pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices];
+ if (!groupData.vTextureCoords2.empty()) {
+ pcMesh->mNumUVComponents[1] = 2;
+ pcMesh->mTextureCoords[1] = new aiVector3D[pcMesh->mNumVertices];
+ }
+ }
+
+ // iterate through all faces and build an unique set of vertices
+ unsigned int iCurrent = 0;
+ for (unsigned int iFace = 0; iFace < pcMesh->mNumFaces; ++iFace) {
+ pcMesh->mFaces[iFace].mNumIndices = 3;
+ pcMesh->mFaces[iFace].mIndices = new unsigned int[3];
+
+ unsigned int iSrcFace = splitGroupData.aiSplit[i]->operator[](iFace);
+ const MDL::IntFace_MDL7 &oldFace = groupData.pcFaces[iSrcFace];
+
+ // iterate through all face indices
+ for (unsigned int c = 0; c < 3; ++c) {
+ const uint32_t iIndex = oldFace.mIndices[c];
+ pcMesh->mVertices[iCurrent] = groupData.vPositions[iIndex];
+ pcMesh->mNormals[iCurrent] = groupData.vNormals[iIndex];
+
+ if (!groupData.vTextureCoords1.empty()) {
+
+ pcMesh->mTextureCoords[0][iCurrent] = groupData.vTextureCoords1[iIndex];
+ if (!groupData.vTextureCoords2.empty()) {
+ pcMesh->mTextureCoords[1][iCurrent] = groupData.vTextureCoords2[iIndex];
+ }
+ }
+ pcMesh->mFaces[iFace].mIndices[c] = iCurrent++;
+ }
+ }
+
+ // if we have bones in the mesh we'll need to generate
+ // proper vertex weights for them
+ if (!groupData.aiBones.empty()) {
+ std::vector<std::vector<unsigned int>> aaiVWeightList;
+ aaiVWeightList.resize(iNumOutBones);
+
+ int iCurrentWeight = 0;
+ for (unsigned int iFace = 0; iFace < pcMesh->mNumFaces; ++iFace) {
+ unsigned int iSrcFace = splitGroupData.aiSplit[i]->operator[](iFace);
+ const MDL::IntFace_MDL7 &oldFace = groupData.pcFaces[iSrcFace];
+
+ // iterate through all face indices
+ for (unsigned int c = 0; c < 3; ++c) {
+ unsigned int iBone = groupData.aiBones[oldFace.mIndices[c]];
+ if (UINT_MAX != iBone) {
+ if (iBone >= iNumOutBones) {
+ ASSIMP_LOG_ERROR("Bone index overflow. "
+ "The bone index of a vertex exceeds the allowed range. ");
+ iBone = iNumOutBones - 1;
+ }
+ aaiVWeightList[iBone].push_back(iCurrentWeight);
+ }
+ ++iCurrentWeight;
+ }
+ }
+ // now check which bones are required ...
+ for (std::vector<std::vector<unsigned int>>::const_iterator k = aaiVWeightList.begin(); k != aaiVWeightList.end(); ++k) {
+ if (!(*k).empty()) {
+ ++pcMesh->mNumBones;
+ }
+ }
+ pcMesh->mBones = new aiBone *[pcMesh->mNumBones];
+ iCurrent = 0;
+ for (std::vector<std::vector<unsigned int>>::const_iterator k = aaiVWeightList.begin(); k != aaiVWeightList.end(); ++k, ++iCurrent) {
+ if ((*k).empty())
+ continue;
+
+ // seems we'll need this node
+ aiBone *pcBone = pcMesh->mBones[iCurrent] = new aiBone();
+ pcBone->mName = aiString(shared.apcOutBones[iCurrent]->mName);
+ pcBone->mOffsetMatrix = shared.apcOutBones[iCurrent]->mOffsetMatrix;
+
+ // setup vertex weights
+ pcBone->mNumWeights = (unsigned int)(*k).size();
+ pcBone->mWeights = new aiVertexWeight[pcBone->mNumWeights];
+
+ for (unsigned int weight = 0; weight < pcBone->mNumWeights; ++weight) {
+ pcBone->mWeights[weight].mVertexId = (*k)[weight];
+ pcBone->mWeights[weight].mWeight = 1.0f;
+ }
+ }
+ }
+ // add the mesh to the list of output meshes
+ splitGroupData.avOutList.push_back(pcMesh);
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Join to materials
+void MDLImporter::JoinSkins_3DGS_MDL7(
+ aiMaterial *pcMat1,
+ aiMaterial *pcMat2,
+ aiMaterial *pcMatOut) {
+ ai_assert(nullptr != pcMat1);
+ ai_assert(nullptr != pcMat2);
+ ai_assert(nullptr != pcMatOut);
+
+ // first create a full copy of the first skin property set
+ // and assign it to the output material
+ aiMaterial::CopyPropertyList(pcMatOut, pcMat1);
+
+ int iVal = 0;
+ pcMatOut->AddProperty<int>(&iVal, 1, AI_MATKEY_UVWSRC_DIFFUSE(0));
+
+ // then extract the diffuse texture from the second skin,
+ // setup 1 as UV source and we have it
+ aiString sString;
+ if (AI_SUCCESS == aiGetMaterialString(pcMat2, AI_MATKEY_TEXTURE_DIFFUSE(0), &sString)) {
+ iVal = 1;
+ pcMatOut->AddProperty<int>(&iVal, 1, AI_MATKEY_UVWSRC_DIFFUSE(1));
+ pcMatOut->AddProperty(&sString, AI_MATKEY_TEXTURE_DIFFUSE(1));
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// 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,
+ mIOHandler,
+ mBuffer,
+ pFile,
+ mHL1ImportSettings);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Read a half-life 2 MDL
+void MDLImporter::InternReadFile_HL2() {
+ //const MDL::Header_HL2* pcHeader = (const MDL::Header_HL2*)this->mBuffer;
+ throw DeadlyImportError("HL2 MDLs are not implemented");
+}
+
+#endif // !! ASSIMP_BUILD_NO_MDL_IMPORTER
diff --git a/libs/assimp/code/AssetLib/MDL/MDLLoader.h b/libs/assimp/code/AssetLib/MDL/MDLLoader.h
new file mode 100644
index 0000000..b7d87e8
--- /dev/null
+++ b/libs/assimp/code/AssetLib/MDL/MDLLoader.h
@@ -0,0 +1,451 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2022, 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 MDLLoader.h
+ * @brief Declaration of the loader for MDL files
+ */
+#pragma once
+#ifndef AI_MDLLOADER_H_INCLUDED
+#define AI_MDLLOADER_H_INCLUDED
+
+#include <assimp/BaseImporter.h>
+#include "MDLFileData.h"
+#include "AssetLib/HMP/HalfLifeFileData.h"
+#include "AssetLib/MDL/HalfLife/HL1ImportSettings.h"
+
+struct aiNode;
+struct aiTexture;
+
+namespace Assimp {
+
+using namespace MDL;
+
+// --------------------------------------------------------------------------------------
+// Include file/line information in debug builds
+#ifdef ASSIMP_BUILD_DEBUG
+# define VALIDATE_FILE_SIZE(msg) SizeCheck(msg,__FILE__,__LINE__)
+#else
+# define VALIDATE_FILE_SIZE(msg) SizeCheck(msg)
+#endif
+
+// --------------------------------------------------------------------------------------
+/** @brief Class to load MDL files.
+ *
+ * Several subformats exist:
+ * <ul>
+ * <li>Quake I</li>
+ * <li>3D Game Studio MDL3, MDL4</li>
+ * <li>3D Game Studio MDL5</li>
+ * <li>3D Game Studio MDL7</li>
+ * <li>Halflife 1</li>
+ * <li>Halflife 2</li>
+ * </ul>
+ * These formats are partially identical and it would be possible to load
+ * them all with a single 1000-line function-beast. However, it has been
+ * split into several code paths to make the code easier to read and maintain.
+*/
+class MDLImporter : public BaseImporter
+{
+public:
+ MDLImporter();
+ ~MDLImporter() override;
+
+ // -------------------------------------------------------------------
+ /** Returns whether the class can handle the format of the given file.
+ * See BaseImporter::CanRead() for details. */
+ bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
+ bool checkSig) const override;
+
+ // -------------------------------------------------------------------
+ /** Called prior to ReadFile().
+ * The function is a request to the importer to update its configuration
+ * basing on the Importer's configuration property list.
+ */
+ void SetupProperties(const Importer* pImp) override;
+
+protected:
+ // -------------------------------------------------------------------
+ /** Return importer meta information.
+ * See #BaseImporter::GetInfo for the details
+ */
+ const aiImporterDesc* GetInfo () const override;
+
+ // -------------------------------------------------------------------
+ /** Imports the given file into the given scene structure.
+ * See BaseImporter::InternReadFile() for details
+ */
+ void InternReadFile( const std::string& pFile, aiScene* pScene,
+ IOSystem* pIOHandler) override;
+
+ // -------------------------------------------------------------------
+ /** Import a quake 1 MDL file (IDPO)
+ */
+ void InternReadFile_Quake1( );
+
+ // -------------------------------------------------------------------
+ /** Import a GameStudio A4/A5 file (MDL 3,4,5)
+ */
+ void InternReadFile_3DGS_MDL345( );
+
+ // -------------------------------------------------------------------
+ /** Import a GameStudio A7 file (MDL 7)
+ */
+ 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)
+ */
+ void InternReadFile_HL2( );
+
+ // -------------------------------------------------------------------
+ /** Check whether a given position is inside the valid range
+ * Throw a DeadlyImportError if it is not
+ * \param szPos Cursor position
+ * \param szFile Name of the source file from which the function was called
+ * \param iLine Source code line from which the function was called
+ */
+ void SizeCheck(const void* szPos);
+ void SizeCheck(const void* szPos, const char* szFile, unsigned int iLine);
+
+ // -------------------------------------------------------------------
+ /** Validate the header data structure of a game studio MDL7 file
+ * \param pcHeader Input header to be validated
+ */
+ void ValidateHeader_3DGS_MDL7(const MDL::Header_MDL7* pcHeader);
+
+ // -------------------------------------------------------------------
+ /** Validate the header data structure of a Quake 1 model
+ * \param pcHeader Input header to be validated
+ */
+ void ValidateHeader_Quake1(const MDL::Header* pcHeader);
+
+ // -------------------------------------------------------------------
+ /** Try to load a palette from the current directory (colormap.lmp)
+ * If it is not found the default palette of Quake1 is returned
+ */
+ void SearchPalette(const unsigned char** pszColorMap);
+
+ // -------------------------------------------------------------------
+ /** Free a palette created with a previous call to SearchPalette()
+ */
+ void FreePalette(const unsigned char* pszColorMap);
+
+ // -------------------------------------------------------------------
+ /** Load a palletized texture from the file and convert it to 32bpp
+ */
+ void CreateTextureARGB8_3DGS_MDL3(const unsigned char* szData);
+
+ // -------------------------------------------------------------------
+ /** Used to load textures from MDL3/4
+ * \param szData Input data
+ * \param iType Color data type
+ * \param piSkip Receive: Size to skip, in bytes
+ */
+ void CreateTexture_3DGS_MDL4(const unsigned char* szData,
+ unsigned int iType,
+ unsigned int* piSkip);
+
+ // -------------------------------------------------------------------
+ /** Used to load textures from MDL5
+ * \param szData Input data
+ * \param iType Color data type
+ * \param piSkip Receive: Size to skip, in bytes
+ */
+ void CreateTexture_3DGS_MDL5(const unsigned char* szData,
+ unsigned int iType,
+ unsigned int* piSkip);
+
+ // -------------------------------------------------------------------
+ /** Checks whether a texture can be replaced with a single color
+ * This is useful for all file formats before MDL7 (all those
+ * that are not containing material colors separate from textures).
+ * MED seems to write dummy 8x8 monochrome images instead.
+ * \param pcTexture Input texture
+ * \return aiColor.r is set to qnan if the function fails and no
+ * color can be found.
+ */
+ aiColor4D ReplaceTextureWithColor(const aiTexture* pcTexture);
+
+ // -------------------------------------------------------------------
+ /** Converts the absolute texture coordinates in MDL5 files to
+ * relative in a range between 0 and 1
+ */
+ void CalculateUVCoordinates_MDL5();
+
+ // -------------------------------------------------------------------
+ /** Read an UV coordinate from the file. If the file format is not
+ * MDL5, the function calculates relative texture coordinates
+ * \param vOut Receives the output UV coord
+ * \param pcSrc UV coordinate buffer
+ * \param UV coordinate index
+ */
+ void ImportUVCoordinate_3DGS_MDL345( aiVector3D& vOut,
+ const MDL::TexCoord_MDL3* pcSrc,
+ unsigned int iIndex);
+
+ // -------------------------------------------------------------------
+ /** Setup the material properties for Quake and MDL<7 models.
+ * These formats don't support more than one material per mesh,
+ * therefore the method processes only ONE skin and removes
+ * all others.
+ */
+ void SetupMaterialProperties_3DGS_MDL5_Quake1( );
+
+ // -------------------------------------------------------------------
+ /** Parse a skin lump in a MDL7/HMP7 file with all of its features
+ * variant 1: Current cursor position is the beginning of the skin header
+ * \param szCurrent Current data pointer
+ * \param szCurrentOut Output data pointer
+ * \param pcMats Material list for this group. To be filled ...
+ */
+ void ParseSkinLump_3DGS_MDL7(
+ const unsigned char* szCurrent,
+ const unsigned char** szCurrentOut,
+ std::vector<aiMaterial*>& pcMats);
+
+ // -------------------------------------------------------------------
+ /** Parse a skin lump in a MDL7/HMP7 file with all of its features
+ * variant 2: Current cursor position is the beginning of the skin data
+ * \param szCurrent Current data pointer
+ * \param szCurrentOut Output data pointer
+ * \param pcMatOut Output material
+ * \param iType header.typ
+ * \param iWidth header.width
+ * \param iHeight header.height
+ */
+ void ParseSkinLump_3DGS_MDL7(
+ const unsigned char* szCurrent,
+ const unsigned char** szCurrentOut,
+ aiMaterial* pcMatOut,
+ unsigned int iType,
+ unsigned int iWidth,
+ unsigned int iHeight);
+
+ // -------------------------------------------------------------------
+ /** Skip a skin lump in a MDL7/HMP7 file
+ * \param szCurrent Current data pointer
+ * \param szCurrentOut Output data pointer. Points to the byte just
+ * behind the last byte of the skin.
+ * \param iType header.typ
+ * \param iWidth header.width
+ * \param iHeight header.height
+ */
+ void SkipSkinLump_3DGS_MDL7(const unsigned char* szCurrent,
+ const unsigned char** szCurrentOut,
+ unsigned int iType,
+ unsigned int iWidth,
+ unsigned int iHeight);
+
+ // -------------------------------------------------------------------
+ /** Parse texture color data for MDL5, MDL6 and MDL7 formats
+ * \param szData Current data pointer
+ * \param iType type of the texture data. No DDS or external
+ * \param piSkip Receive the number of bytes to skip
+ * \param pcNew Must point to fully initialized data. Width and
+ * height must be set. If pcNew->pcData is set to UINT_MAX,
+ * piSkip will receive the size of the texture, in bytes, but no
+ * color data will be read.
+ */
+ void ParseTextureColorData(const unsigned char* szData,
+ unsigned int iType,
+ unsigned int* piSkip,
+ aiTexture* pcNew);
+
+ // -------------------------------------------------------------------
+ /** Join two materials / skins. Setup UV source ... etc
+ * \param pcMat1 First input material
+ * \param pcMat2 Second input material
+ * \param pcMatOut Output material instance to be filled. Must be empty
+ */
+ void JoinSkins_3DGS_MDL7(aiMaterial* pcMat1,
+ aiMaterial* pcMat2,
+ aiMaterial* pcMatOut);
+
+ // -------------------------------------------------------------------
+ /** Add a bone transformation key to an animation
+ * \param iTrafo Index of the transformation (always==frame index?)
+ * No need to validate this index, it is always valid.
+ * \param pcBoneTransforms Bone transformation for this index
+ * \param apcOutBones Output bones array
+ */
+ void AddAnimationBoneTrafoKey_3DGS_MDL7(unsigned int iTrafo,
+ const MDL::BoneTransform_MDL7* pcBoneTransforms,
+ MDL::IntBone_MDL7** apcBonesOut);
+
+ // -------------------------------------------------------------------
+ /** Load the bone list of a MDL7 file
+ * \return If the bones could be loaded successfully, a valid
+ * array containing pointers to a temporary bone
+ * representation. nullptr if the bones could not be loaded.
+ */
+ MDL::IntBone_MDL7** LoadBones_3DGS_MDL7();
+
+ // -------------------------------------------------------------------
+ /** Load bone transformation keyframes from a file chunk
+ * \param groupInfo -> doc of data structure
+ * \param frame -> doc of data structure
+ * \param shared -> doc of data structure
+ */
+ void ParseBoneTrafoKeys_3DGS_MDL7(
+ const MDL::IntGroupInfo_MDL7& groupInfo,
+ IntFrameInfo_MDL7& frame,
+ MDL::IntSharedData_MDL7& shared);
+
+ // -------------------------------------------------------------------
+ /** Calculate absolute bone animation matrices for each bone
+ * \param apcOutBones Output bones array
+ */
+ void CalcAbsBoneMatrices_3DGS_MDL7(MDL::IntBone_MDL7** apcOutBones);
+
+ // -------------------------------------------------------------------
+ /** Add all bones to the nodegraph (as children of the root node)
+ * \param apcBonesOut List of bones
+ * \param pcParent Parent node. New nodes will be added to this node
+ * \param iParentIndex Index of the parent bone
+ */
+ void AddBonesToNodeGraph_3DGS_MDL7(const MDL::IntBone_MDL7** apcBonesOut,
+ aiNode* pcParent,uint16_t iParentIndex);
+
+ // -------------------------------------------------------------------
+ /** Build output animations
+ * \param apcBonesOut List of bones
+ */
+ void BuildOutputAnims_3DGS_MDL7(const MDL::IntBone_MDL7** apcBonesOut);
+
+ // -------------------------------------------------------------------
+ /** Handles materials that are just referencing another material
+ * There is no test file for this feature, but Conitec's doc
+ * say it is used.
+ */
+ void HandleMaterialReferences_3DGS_MDL7();
+
+ // -------------------------------------------------------------------
+ /** Copies only the material that are referenced by at least one
+ * mesh to the final output material list. All other materials
+ * will be discarded.
+ * \param shared -> doc of data structure
+ */
+ void CopyMaterials_3DGS_MDL7(MDL::IntSharedData_MDL7 &shared);
+
+ // -------------------------------------------------------------------
+ /** Process the frame section at the end of a group
+ * \param groupInfo -> doc of data structure
+ * \param shared -> doc of data structure
+ * \param szCurrent Pointer to the start of the frame section
+ * \param szCurrentOut Receives a pointer to the first byte of the
+ * next data section.
+ * \return false to read no further groups (a small workaround for
+ * some tiny and unsolved problems ... )
+ */
+ bool ProcessFrames_3DGS_MDL7(const MDL::IntGroupInfo_MDL7& groupInfo,
+ MDL::IntGroupData_MDL7& groupData,
+ MDL::IntSharedData_MDL7& shared,
+ const unsigned char* szCurrent,
+ const unsigned char** szCurrentOut);
+
+ // -------------------------------------------------------------------
+ /** Sort all faces by their materials. If the mesh is using
+ * multiple materials per face (that are blended together) the function
+ * might create new materials.
+ * \param groupInfo -> doc of data structure
+ * \param groupData -> doc of data structure
+ * \param splitGroupData -> doc of data structure
+ */
+ void SortByMaterials_3DGS_MDL7(
+ const MDL::IntGroupInfo_MDL7& groupInfo,
+ MDL::IntGroupData_MDL7& groupData,
+ MDL::IntSplitGroupData_MDL7& splitGroupData);
+
+ // -------------------------------------------------------------------
+ /** Read all faces and vertices from a MDL7 group. The function fills
+ * preallocated memory buffers.
+ * \param groupInfo -> doc of data structure
+ * \param groupData -> doc of data structure
+ */
+ void ReadFaces_3DGS_MDL7(const MDL::IntGroupInfo_MDL7& groupInfo,
+ MDL::IntGroupData_MDL7& groupData);
+
+ // -------------------------------------------------------------------
+ /** Generate the final output meshes for a7 models
+ * \param groupData -> doc of data structure
+ * \param splitGroupData -> doc of data structure
+ */
+ void GenerateOutputMeshes_3DGS_MDL7(
+ MDL::IntGroupData_MDL7& groupData,
+ MDL::IntSplitGroupData_MDL7& splitGroupData);
+
+protected:
+
+ /** Configuration option: frame to be loaded */
+ unsigned int configFrameID;
+
+ /** Configuration option: palette to be used to decode palletized images*/
+ std::string configPalette;
+
+ /** Buffer to hold the loaded file */
+ unsigned char* mBuffer;
+
+ /** For GameStudio MDL files: The number in the magic word, either 3,4 or 5
+ * (MDL7 doesn't need this, the format has a separate loader) */
+ unsigned int iGSFileVersion;
+
+ /** Output I/O handler. used to load external lmp files */
+ IOSystem* mIOHandler;
+
+ /** Output scene to be filled */
+ aiScene* pScene;
+
+ /** Size of the input file in bytes */
+ unsigned int iFileSize;
+
+ /* Configuration for HL1 MDL */
+ HalfLife::HL1ImportSettings mHL1ImportSettings;
+};
+
+} // end of namespace Assimp
+
+#endif // AI_3DSIMPORTER_H_INC
diff --git a/libs/assimp/code/AssetLib/MDL/MDLMaterialLoader.cpp b/libs/assimp/code/AssetLib/MDL/MDLMaterialLoader.cpp
new file mode 100644
index 0000000..eebb9d1
--- /dev/null
+++ b/libs/assimp/code/AssetLib/MDL/MDLMaterialLoader.cpp
@@ -0,0 +1,765 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2022, 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 Implementation of the material part of the MDL importer class */
+
+#ifndef ASSIMP_BUILD_NO_MDL_IMPORTER
+
+#include "MDLDefaultColorMap.h"
+#include "MDLLoader.h"
+
+#include <assimp/StringUtils.h>
+#include <assimp/qnan.h>
+#include <assimp/scene.h>
+#include <assimp/texture.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/IOSystem.hpp>
+
+#include <memory>
+
+using namespace Assimp;
+
+static aiTexel *const bad_texel = reinterpret_cast<aiTexel *>(SIZE_MAX);
+
+// ------------------------------------------------------------------------------------------------
+// Find a suitable palette file or take the default one
+void MDLImporter::SearchPalette(const unsigned char **pszColorMap) {
+ // now try to find the color map in the current directory
+ IOStream *pcStream = mIOHandler->Open(configPalette, "rb");
+
+ const unsigned char *szColorMap = (const unsigned char *)::g_aclrDefaultColorMap;
+ if (pcStream) {
+ if (pcStream->FileSize() >= 768) {
+ size_t len = 256 * 3;
+ unsigned char *colorMap = new unsigned char[len];
+ szColorMap = colorMap;
+ pcStream->Read(colorMap, len, 1);
+ ASSIMP_LOG_INFO("Found valid colormap.lmp in directory. "
+ "It will be used to decode embedded textures in palletized formats.");
+ }
+ delete pcStream;
+ pcStream = nullptr;
+ }
+ *pszColorMap = szColorMap;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Free the palette again
+void MDLImporter::FreePalette(const unsigned char *szColorMap) {
+ if (szColorMap != (const unsigned char *)::g_aclrDefaultColorMap) {
+ delete[] szColorMap;
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Check whether we can replace a texture with a single color
+aiColor4D MDLImporter::ReplaceTextureWithColor(const aiTexture *pcTexture) {
+ ai_assert(nullptr != pcTexture);
+
+ aiColor4D clrOut;
+ clrOut.r = get_qnan();
+ if (!pcTexture->mHeight || !pcTexture->mWidth)
+ return clrOut;
+
+ const unsigned int iNumPixels = pcTexture->mHeight * pcTexture->mWidth;
+ const aiTexel *pcTexel = pcTexture->pcData + 1;
+ const aiTexel *const pcTexelEnd = &pcTexture->pcData[iNumPixels];
+
+ while (pcTexel != pcTexelEnd) {
+ if (*pcTexel != *(pcTexel - 1)) {
+ pcTexel = nullptr;
+ break;
+ }
+ ++pcTexel;
+ }
+ if (pcTexel) {
+ clrOut.r = pcTexture->pcData->r / 255.0f;
+ clrOut.g = pcTexture->pcData->g / 255.0f;
+ clrOut.b = pcTexture->pcData->b / 255.0f;
+ clrOut.a = pcTexture->pcData->a / 255.0f;
+ }
+ return clrOut;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Read a texture from a MDL3 file
+void MDLImporter::CreateTextureARGB8_3DGS_MDL3(const unsigned char *szData) {
+ const MDL::Header *pcHeader = (const MDL::Header *)mBuffer; //the endianness is already corrected in the InternReadFile_3DGS_MDL345 function
+
+ VALIDATE_FILE_SIZE(szData + pcHeader->skinwidth *
+ pcHeader->skinheight);
+
+ // allocate a new texture object
+ aiTexture *pcNew = new aiTexture();
+ pcNew->mWidth = pcHeader->skinwidth;
+ pcNew->mHeight = pcHeader->skinheight;
+
+ if(pcNew->mWidth != 0 && pcNew->mHeight > UINT_MAX/pcNew->mWidth) {
+ throw DeadlyImportError("Invalid MDL file. A texture is too big.");
+ }
+ pcNew->pcData = new aiTexel[pcNew->mWidth * pcNew->mHeight];
+
+ const unsigned char *szColorMap;
+ this->SearchPalette(&szColorMap);
+
+ // copy texture data
+ for (unsigned int i = 0; i < pcNew->mWidth * pcNew->mHeight; ++i) {
+ const unsigned char val = szData[i];
+ const unsigned char *sz = &szColorMap[val * 3];
+
+ pcNew->pcData[i].a = 0xFF;
+ pcNew->pcData[i].r = *sz++;
+ pcNew->pcData[i].g = *sz++;
+ pcNew->pcData[i].b = *sz;
+ }
+
+ FreePalette(szColorMap);
+
+ // store the texture
+ aiTexture **pc = this->pScene->mTextures;
+ this->pScene->mTextures = new aiTexture *[pScene->mNumTextures + 1];
+ for (unsigned int i = 0; i < pScene->mNumTextures; ++i)
+ pScene->mTextures[i] = pc[i];
+
+ pScene->mTextures[this->pScene->mNumTextures] = pcNew;
+ pScene->mNumTextures++;
+ delete[] pc;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Read a texture from a MDL4 file
+void MDLImporter::CreateTexture_3DGS_MDL4(const unsigned char *szData,
+ unsigned int iType,
+ unsigned int *piSkip) {
+ ai_assert(nullptr != piSkip);
+
+ const MDL::Header *pcHeader = (const MDL::Header *)mBuffer; //the endianness is already corrected in the InternReadFile_3DGS_MDL345 function
+
+ if (iType == 1 || iType > 3) {
+ ASSIMP_LOG_ERROR("Unsupported texture file format");
+ return;
+ }
+
+ const bool bNoRead = *piSkip == UINT_MAX;
+
+ // allocate a new texture object
+ aiTexture *pcNew = new aiTexture();
+ pcNew->mWidth = pcHeader->skinwidth;
+ pcNew->mHeight = pcHeader->skinheight;
+
+ if (bNoRead) pcNew->pcData = bad_texel;
+ ParseTextureColorData(szData, iType, piSkip, pcNew);
+
+ // store the texture
+ if (!bNoRead) {
+ if (!this->pScene->mNumTextures) {
+ pScene->mNumTextures = 1;
+ pScene->mTextures = new aiTexture *[1];
+ pScene->mTextures[0] = pcNew;
+ } else {
+ aiTexture **pc = pScene->mTextures;
+ pScene->mTextures = new aiTexture *[pScene->mNumTextures + 1];
+ for (unsigned int i = 0; i < this->pScene->mNumTextures; ++i)
+ pScene->mTextures[i] = pc[i];
+ pScene->mTextures[pScene->mNumTextures] = pcNew;
+ pScene->mNumTextures++;
+ delete[] pc;
+ }
+ } else {
+ pcNew->pcData = nullptr;
+ delete pcNew;
+ }
+ return;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Load color data of a texture and convert it to our output format
+void MDLImporter::ParseTextureColorData(const unsigned char *szData,
+ unsigned int iType,
+ unsigned int *piSkip,
+ aiTexture *pcNew) {
+ const bool do_read = bad_texel != pcNew->pcData;
+
+ // allocate storage for the texture image
+ if (do_read) {
+ if(pcNew->mWidth != 0 && pcNew->mHeight > UINT_MAX/pcNew->mWidth) {
+ throw DeadlyImportError("Invalid MDL file. A texture is too big.");
+ }
+ pcNew->pcData = new aiTexel[pcNew->mWidth * pcNew->mHeight];
+ }
+
+ // R5G6B5 format (with or without MIPs)
+ // ****************************************************************
+ if (2 == iType || 10 == iType) {
+ VALIDATE_FILE_SIZE(szData + pcNew->mWidth * pcNew->mHeight * 2);
+
+ // copy texture data
+ unsigned int i;
+ if (do_read) {
+ for (i = 0; i < pcNew->mWidth * pcNew->mHeight; ++i) {
+ MDL::RGB565 val = ((MDL::RGB565 *)szData)[i];
+ AI_SWAP2(val);
+
+ pcNew->pcData[i].a = 0xFF;
+ pcNew->pcData[i].r = (unsigned char)val.b << 3;
+ pcNew->pcData[i].g = (unsigned char)val.g << 2;
+ pcNew->pcData[i].b = (unsigned char)val.r << 3;
+ }
+ } else {
+ i = pcNew->mWidth * pcNew->mHeight;
+ }
+ *piSkip = i * 2;
+
+ // apply MIP maps
+ if (10 == iType) {
+ *piSkip += ((i >> 2) + (i >> 4) + (i >> 6)) << 1;
+ VALIDATE_FILE_SIZE(szData + *piSkip);
+ }
+ }
+ // ARGB4 format (with or without MIPs)
+ // ****************************************************************
+ else if (3 == iType || 11 == iType) {
+ VALIDATE_FILE_SIZE(szData + pcNew->mWidth * pcNew->mHeight * 4);
+
+ // copy texture data
+ unsigned int i;
+ if (do_read) {
+ for (i = 0; i < pcNew->mWidth * pcNew->mHeight; ++i) {
+ MDL::ARGB4 val = ((MDL::ARGB4 *)szData)[i];
+ AI_SWAP2(val);
+
+ pcNew->pcData[i].a = (unsigned char)val.a << 4;
+ pcNew->pcData[i].r = (unsigned char)val.r << 4;
+ pcNew->pcData[i].g = (unsigned char)val.g << 4;
+ pcNew->pcData[i].b = (unsigned char)val.b << 4;
+ }
+ } else
+ i = pcNew->mWidth * pcNew->mHeight;
+ *piSkip = i * 2;
+
+ // apply MIP maps
+ if (11 == iType) {
+ *piSkip += ((i >> 2) + (i >> 4) + (i >> 6)) << 1;
+ VALIDATE_FILE_SIZE(szData + *piSkip);
+ }
+ }
+ // RGB8 format (with or without MIPs)
+ // ****************************************************************
+ else if (4 == iType || 12 == iType) {
+ VALIDATE_FILE_SIZE(szData + pcNew->mWidth * pcNew->mHeight * 3);
+
+ // copy texture data
+ unsigned int i;
+ if (do_read) {
+ for (i = 0; i < pcNew->mWidth * pcNew->mHeight; ++i) {
+ const unsigned char *_szData = &szData[i * 3];
+
+ pcNew->pcData[i].a = 0xFF;
+ pcNew->pcData[i].b = *_szData++;
+ pcNew->pcData[i].g = *_szData++;
+ pcNew->pcData[i].r = *_szData;
+ }
+ } else
+ i = pcNew->mWidth * pcNew->mHeight;
+
+ // apply MIP maps
+ *piSkip = i * 3;
+ if (12 == iType) {
+ *piSkip += ((i >> 2) + (i >> 4) + (i >> 6)) * 3;
+ VALIDATE_FILE_SIZE(szData + *piSkip);
+ }
+ }
+ // ARGB8 format (with ir without MIPs)
+ // ****************************************************************
+ else if (5 == iType || 13 == iType) {
+ VALIDATE_FILE_SIZE(szData + pcNew->mWidth * pcNew->mHeight * 4);
+
+ // copy texture data
+ unsigned int i;
+ if (do_read) {
+ for (i = 0; i < pcNew->mWidth * pcNew->mHeight; ++i) {
+ const unsigned char *_szData = &szData[i * 4];
+
+ pcNew->pcData[i].b = *_szData++;
+ pcNew->pcData[i].g = *_szData++;
+ pcNew->pcData[i].r = *_szData++;
+ pcNew->pcData[i].a = *_szData;
+ }
+ } else {
+ i = pcNew->mWidth * pcNew->mHeight;
+ }
+
+ // apply MIP maps
+ *piSkip = i << 2;
+ if (13 == iType) {
+ *piSkip += ((i >> 2) + (i >> 4) + (i >> 6)) << 2;
+ }
+ }
+ // palletized 8 bit texture. As for Quake 1
+ // ****************************************************************
+ else if (0 == iType) {
+ VALIDATE_FILE_SIZE(szData + pcNew->mWidth * pcNew->mHeight);
+
+ // copy texture data
+ unsigned int i;
+ if (do_read) {
+
+ const unsigned char *szColorMap;
+ SearchPalette(&szColorMap);
+
+ for (i = 0; i < pcNew->mWidth * pcNew->mHeight; ++i) {
+ const unsigned char val = szData[i];
+ const unsigned char *sz = &szColorMap[val * 3];
+
+ pcNew->pcData[i].a = 0xFF;
+ pcNew->pcData[i].r = *sz++;
+ pcNew->pcData[i].g = *sz++;
+ pcNew->pcData[i].b = *sz;
+ }
+ this->FreePalette(szColorMap);
+
+ } else
+ i = pcNew->mWidth * pcNew->mHeight;
+ *piSkip = i;
+
+ // FIXME: Also support for MIP maps?
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Get a texture from a MDL5 file
+void MDLImporter::CreateTexture_3DGS_MDL5(const unsigned char *szData,
+ unsigned int iType,
+ unsigned int *piSkip) {
+ ai_assert(nullptr != piSkip);
+ bool bNoRead = *piSkip == UINT_MAX;
+
+ // allocate a new texture object
+ aiTexture *pcNew = new aiTexture();
+
+ VALIDATE_FILE_SIZE(szData + 8);
+
+ // first read the size of the texture
+ pcNew->mWidth = *((uint32_t *)szData);
+ AI_SWAP4(pcNew->mWidth);
+ szData += sizeof(uint32_t);
+
+ pcNew->mHeight = *((uint32_t *)szData);
+ AI_SWAP4(pcNew->mHeight);
+ szData += sizeof(uint32_t);
+
+ if (bNoRead) {
+ pcNew->pcData = bad_texel;
+ }
+
+ // this should not occur - at least the docs say it shouldn't.
+ // however, one can easily try out what MED does if you have
+ // a model with a DDS texture and export it to MDL5 ...
+ // yeah, it embeds the DDS file.
+ if (6 == iType) {
+ // this is a compressed texture in DDS format
+ *piSkip = pcNew->mWidth;
+ VALIDATE_FILE_SIZE(szData + *piSkip);
+
+ if (!bNoRead) {
+ // place a hint and let the application know that this is a DDS file
+ pcNew->mHeight = 0;
+ pcNew->achFormatHint[0] = 'd';
+ pcNew->achFormatHint[1] = 'd';
+ pcNew->achFormatHint[2] = 's';
+ pcNew->achFormatHint[3] = '\0';
+
+ pcNew->pcData = (aiTexel *)new unsigned char[pcNew->mWidth];
+ ::memcpy(pcNew->pcData, szData, pcNew->mWidth);
+ }
+ } else {
+ // parse the color data of the texture
+ ParseTextureColorData(szData, iType, piSkip, pcNew);
+ }
+ *piSkip += sizeof(uint32_t) * 2;
+
+ if (!bNoRead) {
+ // store the texture
+ if (!this->pScene->mNumTextures) {
+ pScene->mNumTextures = 1;
+ pScene->mTextures = new aiTexture *[1];
+ pScene->mTextures[0] = pcNew;
+ } else {
+ aiTexture **pc = pScene->mTextures;
+ pScene->mTextures = new aiTexture *[pScene->mNumTextures + 1];
+ for (unsigned int i = 0; i < pScene->mNumTextures; ++i)
+ this->pScene->mTextures[i] = pc[i];
+
+ pScene->mTextures[pScene->mNumTextures] = pcNew;
+ pScene->mNumTextures++;
+ delete[] pc;
+ }
+ } else {
+ pcNew->pcData = nullptr;
+ delete pcNew;
+ }
+ return;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Get a skin from a MDL7 file - more complex than all other subformats
+void MDLImporter::ParseSkinLump_3DGS_MDL7(
+ const unsigned char *szCurrent,
+ const unsigned char **szCurrentOut,
+ aiMaterial *pcMatOut,
+ unsigned int iType,
+ unsigned int iWidth,
+ unsigned int iHeight) {
+ std::unique_ptr<aiTexture> pcNew;
+
+ // get the type of the skin
+ unsigned int iMasked = (unsigned int)(iType & 0xF);
+
+ if (0x1 == iMasked) {
+ // ***** REFERENCE TO ANOTHER SKIN INDEX *****
+ int referrer = (int)iWidth;
+ pcMatOut->AddProperty<int>(&referrer, 1, AI_MDL7_REFERRER_MATERIAL);
+ } else if (0x6 == iMasked) {
+ // ***** EMBEDDED DDS FILE *****
+ if (1 != iHeight) {
+ ASSIMP_LOG_WARN("Found a reference to an embedded DDS texture, "
+ "but texture height is not equal to 1, which is not supported by MED");
+ }
+ if (iWidth == 0) {
+ ASSIMP_LOG_ERROR("Found a reference to an embedded DDS texture, but texture width is zero, aborting import.");
+ return;
+ }
+
+ pcNew.reset(new aiTexture);
+ pcNew->mHeight = 0;
+ pcNew->mWidth = iWidth;
+
+ // place a proper format hint
+ pcNew->achFormatHint[0] = 'd';
+ pcNew->achFormatHint[1] = 'd';
+ pcNew->achFormatHint[2] = 's';
+ pcNew->achFormatHint[3] = '\0';
+
+ pcNew->pcData = (aiTexel *)new unsigned char[pcNew->mWidth];
+ memcpy(pcNew->pcData, szCurrent, pcNew->mWidth);
+ szCurrent += iWidth;
+ } else if (0x7 == iMasked) {
+ // ***** REFERENCE TO EXTERNAL FILE *****
+ if (1 != iHeight) {
+ ASSIMP_LOG_WARN("Found a reference to an external texture, "
+ "but texture height is not equal to 1, which is not supported by MED");
+ }
+
+ aiString szFile;
+ const size_t iLen = strlen((const char *)szCurrent);
+ size_t iLen2 = iLen + 1;
+ iLen2 = iLen2 > MAXLEN ? MAXLEN : iLen2;
+ memcpy(szFile.data, (const char *)szCurrent, iLen2);
+ szFile.length = (ai_uint32)iLen;
+
+ szCurrent += iLen2;
+
+ // place this as diffuse texture
+ pcMatOut->AddProperty(&szFile, AI_MATKEY_TEXTURE_DIFFUSE(0));
+ } else if (iMasked || !iType || (iType && iWidth && iHeight)) {
+ pcNew.reset(new aiTexture());
+ if (!iHeight || !iWidth) {
+ ASSIMP_LOG_WARN("Found embedded texture, but its width "
+ "an height are both 0. Is this a joke?");
+
+ // generate an empty chess pattern
+ pcNew->mWidth = pcNew->mHeight = 8;
+ pcNew->pcData = new aiTexel[64];
+ for (unsigned int x = 0; x < 8; ++x) {
+ for (unsigned int y = 0; y < 8; ++y) {
+ const bool bSet = ((0 == x % 2 && 0 != y % 2) ||
+ (0 != x % 2 && 0 == y % 2));
+
+ aiTexel *pc = &pcNew->pcData[y * 8 + x];
+ pc->r = pc->b = pc->g = (bSet ? 0xFF : 0);
+ pc->a = 0xFF;
+ }
+ }
+ } else {
+ // it is a standard color texture. Fill in width and height
+ // and call the same function we used for loading MDL5 files
+
+ pcNew->mWidth = iWidth;
+ pcNew->mHeight = iHeight;
+
+ unsigned int iSkip = 0;
+ ParseTextureColorData(szCurrent, iMasked, &iSkip, pcNew.get());
+
+ // skip length of texture data
+ szCurrent += iSkip;
+ }
+ }
+
+ // sometimes there are MDL7 files which have a monochrome
+ // texture instead of material colors ... possible they have
+ // been converted to MDL7 from other formats, such as MDL5
+ aiColor4D clrTexture;
+ if (pcNew)
+ clrTexture = ReplaceTextureWithColor(pcNew.get());
+ else
+ clrTexture.r = get_qnan();
+
+ // check whether a material definition is contained in the skin
+ if (iType & AI_MDL7_SKINTYPE_MATERIAL) {
+ BE_NCONST MDL::Material_MDL7 *pcMatIn = (BE_NCONST MDL::Material_MDL7 *)szCurrent;
+ szCurrent = (unsigned char *)(pcMatIn + 1);
+ VALIDATE_FILE_SIZE(szCurrent);
+
+ aiColor3D clrTemp;
+
+#define COLOR_MULTIPLY_RGB() \
+ if (is_not_qnan(clrTexture.r)) { \
+ clrTemp.r *= clrTexture.r; \
+ clrTemp.g *= clrTexture.g; \
+ clrTemp.b *= clrTexture.b; \
+ }
+
+ // read diffuse color
+ clrTemp.r = pcMatIn->Diffuse.r;
+ AI_SWAP4(clrTemp.r);
+ clrTemp.g = pcMatIn->Diffuse.g;
+ AI_SWAP4(clrTemp.g);
+ clrTemp.b = pcMatIn->Diffuse.b;
+ AI_SWAP4(clrTemp.b);
+ COLOR_MULTIPLY_RGB();
+ pcMatOut->AddProperty<aiColor3D>(&clrTemp, 1, AI_MATKEY_COLOR_DIFFUSE);
+
+ // read specular color
+ clrTemp.r = pcMatIn->Specular.r;
+ AI_SWAP4(clrTemp.r);
+ clrTemp.g = pcMatIn->Specular.g;
+ AI_SWAP4(clrTemp.g);
+ clrTemp.b = pcMatIn->Specular.b;
+ AI_SWAP4(clrTemp.b);
+ COLOR_MULTIPLY_RGB();
+ pcMatOut->AddProperty<aiColor3D>(&clrTemp, 1, AI_MATKEY_COLOR_SPECULAR);
+
+ // read ambient color
+ clrTemp.r = pcMatIn->Ambient.r;
+ AI_SWAP4(clrTemp.r);
+ clrTemp.g = pcMatIn->Ambient.g;
+ AI_SWAP4(clrTemp.g);
+ clrTemp.b = pcMatIn->Ambient.b;
+ AI_SWAP4(clrTemp.b);
+ COLOR_MULTIPLY_RGB();
+ pcMatOut->AddProperty<aiColor3D>(&clrTemp, 1, AI_MATKEY_COLOR_AMBIENT);
+
+ // read emissive color
+ clrTemp.r = pcMatIn->Emissive.r;
+ AI_SWAP4(clrTemp.r);
+ clrTemp.g = pcMatIn->Emissive.g;
+ AI_SWAP4(clrTemp.g);
+ clrTemp.b = pcMatIn->Emissive.b;
+ AI_SWAP4(clrTemp.b);
+ pcMatOut->AddProperty<aiColor3D>(&clrTemp, 1, AI_MATKEY_COLOR_EMISSIVE);
+
+#undef COLOR_MULITPLY_RGB
+
+ // FIX: Take the opacity from the ambient color.
+ // The doc say something else, but it is fact that MED exports the
+ // opacity like this .... oh well.
+ clrTemp.r = pcMatIn->Ambient.a;
+ AI_SWAP4(clrTemp.r);
+ if (is_not_qnan(clrTexture.r)) {
+ clrTemp.r *= clrTexture.a;
+ }
+ pcMatOut->AddProperty<ai_real>(&clrTemp.r, 1, AI_MATKEY_OPACITY);
+
+ // read phong power
+ int iShadingMode = (int)aiShadingMode_Gouraud;
+ AI_SWAP4(pcMatIn->Power);
+ if (0.0f != pcMatIn->Power) {
+ iShadingMode = (int)aiShadingMode_Phong;
+ // pcMatIn is packed, we can't form pointers to its members
+ float power = pcMatIn->Power;
+ pcMatOut->AddProperty<float>(&power, 1, AI_MATKEY_SHININESS);
+ }
+ pcMatOut->AddProperty<int>(&iShadingMode, 1, AI_MATKEY_SHADING_MODEL);
+ } else if (is_not_qnan(clrTexture.r)) {
+ pcMatOut->AddProperty<aiColor4D>(&clrTexture, 1, AI_MATKEY_COLOR_DIFFUSE);
+ pcMatOut->AddProperty<aiColor4D>(&clrTexture, 1, AI_MATKEY_COLOR_SPECULAR);
+ }
+ // if the texture could be replaced by a single material color
+ // we don't need the texture anymore
+ if (is_not_qnan(clrTexture.r)) {
+ pcNew.reset();
+ }
+
+ // If an ASCII effect description (HLSL?) is contained in the file,
+ // we can simply ignore it ...
+ if (iType & AI_MDL7_SKINTYPE_MATERIAL_ASCDEF) {
+ VALIDATE_FILE_SIZE(szCurrent);
+ int32_t iMe = *((int32_t *)szCurrent);
+ AI_SWAP4(iMe);
+ szCurrent += sizeof(char) * iMe + sizeof(int32_t);
+ VALIDATE_FILE_SIZE(szCurrent);
+ }
+
+ // If an embedded texture has been loaded setup the corresponding
+ // data structures in the aiScene instance
+ if (pcNew && pScene->mNumTextures <= 999) {
+ // place this as diffuse texture
+ char current[5];
+ ai_snprintf(current, 5, "*%i", this->pScene->mNumTextures);
+
+ aiString szFile;
+ const size_t iLen = strlen((const char *)current);
+ ::memcpy(szFile.data, (const char *)current, iLen + 1);
+ szFile.length = (ai_uint32)iLen;
+
+ pcMatOut->AddProperty(&szFile, AI_MATKEY_TEXTURE_DIFFUSE(0));
+
+ // store the texture
+ if (!pScene->mNumTextures) {
+ pScene->mNumTextures = 1;
+ pScene->mTextures = new aiTexture *[1];
+ pScene->mTextures[0] = pcNew.release();
+ } else {
+ aiTexture **pc = pScene->mTextures;
+ pScene->mTextures = new aiTexture *[pScene->mNumTextures + 1];
+ for (unsigned int i = 0; i < pScene->mNumTextures; ++i) {
+ pScene->mTextures[i] = pc[i];
+ }
+
+ pScene->mTextures[pScene->mNumTextures] = pcNew.release();
+ pScene->mNumTextures++;
+ delete[] pc;
+ }
+ }
+ VALIDATE_FILE_SIZE(szCurrent);
+ *szCurrentOut = szCurrent;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Skip a skin lump
+void MDLImporter::SkipSkinLump_3DGS_MDL7(
+ const unsigned char *szCurrent,
+ const unsigned char **szCurrentOut,
+ unsigned int iType,
+ unsigned int iWidth,
+ unsigned int iHeight) {
+ // get the type of the skin
+ const unsigned int iMasked = (unsigned int)(iType & 0xF);
+
+ if (0x6 == iMasked) {
+ szCurrent += iWidth;
+ }
+ if (0x7 == iMasked) {
+ const size_t iLen = std::strlen((const char *)szCurrent);
+ szCurrent += iLen + 1;
+ } else if (iMasked || !iType) {
+ if (iMasked || !iType || (iType && iWidth && iHeight)) {
+ // ParseTextureColorData(..., aiTexture::pcData == bad_texel) will simply
+ // return the size of the color data in bytes in iSkip
+ unsigned int iSkip = 0;
+
+ aiTexture tex;
+ tex.pcData = bad_texel;
+ tex.mHeight = iHeight;
+ tex.mWidth = iWidth;
+ ParseTextureColorData(szCurrent, iMasked, &iSkip, &tex);
+
+ // FIX: Important, otherwise the destructor will crash
+ tex.pcData = nullptr;
+
+ // skip length of texture data
+ szCurrent += iSkip;
+ }
+ }
+
+ // check whether a material definition is contained in the skin
+ if (iType & AI_MDL7_SKINTYPE_MATERIAL) {
+ BE_NCONST MDL::Material_MDL7 *pcMatIn = (BE_NCONST MDL::Material_MDL7 *)szCurrent;
+ szCurrent = (unsigned char *)(pcMatIn + 1);
+ }
+
+ // if an ASCII effect description (HLSL?) is contained in the file,
+ // we can simply ignore it ...
+ if (iType & AI_MDL7_SKINTYPE_MATERIAL_ASCDEF) {
+ int32_t iMe = *((int32_t *)szCurrent);
+ AI_SWAP4(iMe);
+ szCurrent += sizeof(char) * iMe + sizeof(int32_t);
+ }
+ *szCurrentOut = szCurrent;
+}
+
+// ------------------------------------------------------------------------------------------------
+void MDLImporter::ParseSkinLump_3DGS_MDL7(
+ const unsigned char *szCurrent,
+ const unsigned char **szCurrentOut,
+ std::vector<aiMaterial *> &pcMats) {
+ ai_assert(nullptr != szCurrent);
+ ai_assert(nullptr != szCurrentOut);
+
+ *szCurrentOut = szCurrent;
+ BE_NCONST MDL::Skin_MDL7 *pcSkin = (BE_NCONST MDL::Skin_MDL7 *)szCurrent;
+ AI_SWAP4(pcSkin->width);
+ AI_SWAP4(pcSkin->height);
+ szCurrent += 12;
+
+ // allocate an output material
+ aiMaterial *pcMatOut = new aiMaterial();
+ pcMats.push_back(pcMatOut);
+
+ // skip length of file name
+ szCurrent += AI_MDL7_MAX_TEXNAMESIZE;
+
+ ParseSkinLump_3DGS_MDL7(szCurrent, szCurrentOut, pcMatOut,
+ pcSkin->typ, pcSkin->width, pcSkin->height);
+
+ // place the name of the skin in the material
+ if (pcSkin->texture_name[0]) {
+ // the 0 termination could be there or not - we can't know
+ aiString szFile;
+ ::memcpy(szFile.data, pcSkin->texture_name, sizeof(pcSkin->texture_name));
+ szFile.data[sizeof(pcSkin->texture_name)] = '\0';
+ szFile.length = (ai_uint32)::strlen(szFile.data);
+
+ pcMatOut->AddProperty(&szFile, AI_MATKEY_NAME);
+ }
+}
+
+#endif // !! ASSIMP_BUILD_NO_MDL_IMPORTER