diff options
Diffstat (limited to 'libs/assimp/code/AssetLib/MDL')
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 |