Explorar el Código

Merge branch 'master' into collada_modeller_metadata

RichardTea hace 5 años
padre
commit
ae7a0aa6ef
Se han modificado 70 ficheros con 4435 adiciones y 382 borrados
  1. 1 0
      Build.md
  2. 15 1
      CMakeLists.txt
  3. 10 0
      code/CMakeLists.txt
  4. 2 2
      code/Common/Exporter.cpp
  5. 38 13
      code/M3D/M3DExporter.cpp
  6. 66 38
      code/M3D/M3DImporter.cpp
  7. 3 3
      code/M3D/M3DMaterials.h
  8. 21 21
      code/M3D/M3DWrapper.cpp
  9. 4 0
      code/M3D/M3DWrapper.h
  10. 173 160
      code/M3D/m3d.h
  11. 1 1
      code/MD3/MD3Loader.cpp
  12. 1 1
      code/MDC/MDCLoader.cpp
  13. 600 0
      code/MDL/HalfLife/HL1FileData.h
  14. 64 0
      code/MDL/HalfLife/HL1ImportDefinitions.h
  15. 85 0
      code/MDL/HalfLife/HL1ImportSettings.h
  16. 1338 0
      code/MDL/HalfLife/HL1MDLLoader.cpp
  17. 241 0
      code/MDL/HalfLife/HL1MDLLoader.h
  18. 127 0
      code/MDL/HalfLife/HL1MeshTrivert.h
  19. 67 0
      code/MDL/HalfLife/HalfLifeMDLBaseHeader.h
  20. 95 0
      code/MDL/HalfLife/LogFunctions.h
  21. 180 0
      code/MDL/HalfLife/UniqueNameGenerator.cpp
  22. 81 0
      code/MDL/HalfLife/UniqueNameGenerator.h
  23. 42 2
      code/MDL/MDLLoader.cpp
  24. 10 0
      code/MDL/MDLLoader.h
  25. 1 1
      code/SMD/SMDLoader.cpp
  26. 1 1
      code/glTF/glTFAsset.h
  27. 1 1
      code/glTF/glTFAsset.inl
  28. 1 1
      code/glTF2/glTF2Asset.h
  29. 1 1
      code/glTF2/glTF2Asset.inl
  30. 67 0
      include/assimp/config.h.in
  31. 138 135
      port/PyAssimp/pyassimp/structs.py
  32. 5 0
      test/CMakeLists.txt
  33. BIN
      test/models/MDL/MDL (HL1)/alpha_test.mdl
  34. BIN
      test/models/MDL/MDL (HL1)/blend_additive.mdl
  35. BIN
      test/models/MDL/MDL (HL1)/chrome_sphere.mdl
  36. BIN
      test/models/MDL/MDL (HL1)/duplicate_bodyparts.mdl
  37. BIN
      test/models/MDL/MDL (HL1)/duplicate_sequence_groups/duplicate_sequence_groups.mdl
  38. BIN
      test/models/MDL/MDL (HL1)/duplicate_sequence_groups/duplicate_sequence_groups01.mdl
  39. BIN
      test/models/MDL/MDL (HL1)/duplicate_sequence_groups/duplicate_sequence_groups02.mdl
  40. BIN
      test/models/MDL/MDL (HL1)/duplicate_sequence_groups/duplicate_sequence_groups03.mdl
  41. BIN
      test/models/MDL/MDL (HL1)/duplicate_sequence_groups/duplicate_sequence_groups04.mdl
  42. BIN
      test/models/MDL/MDL (HL1)/duplicate_sequence_groups/duplicate_sequence_groups05.mdl
  43. BIN
      test/models/MDL/MDL (HL1)/duplicate_sequence_groups/duplicate_sequence_groups06.mdl
  44. BIN
      test/models/MDL/MDL (HL1)/duplicate_sequence_groups/duplicate_sequence_groups07.mdl
  45. BIN
      test/models/MDL/MDL (HL1)/duplicate_sequence_groups/duplicate_sequence_groups08.mdl
  46. BIN
      test/models/MDL/MDL (HL1)/duplicate_sequence_groups/duplicate_sequence_groups09.mdl
  47. BIN
      test/models/MDL/MDL (HL1)/duplicate_sequences.mdl
  48. BIN
      test/models/MDL/MDL (HL1)/duplicate_submodels.mdl
  49. BIN
      test/models/MDL/MDL (HL1)/man.mdl
  50. BIN
      test/models/MDL/MDL (HL1)/man01.mdl
  51. BIN
      test/models/MDL/MDL (HL1)/manT.mdl
  52. BIN
      test/models/MDL/MDL (HL1)/sequence_transitions.mdl
  53. BIN
      test/models/MDL/MDL (HL1)/unnamed_bodyparts.mdl
  54. BIN
      test/models/MDL/MDL (HL1)/unnamed_bones.mdl
  55. BIN
      test/models/MDL/MDL (HL1)/unnamed_sequence_groups/unnamed_sequence_groups.mdl
  56. BIN
      test/models/MDL/MDL (HL1)/unnamed_sequence_groups/unnamed_sequence_groups01.mdl
  57. BIN
      test/models/MDL/MDL (HL1)/unnamed_sequence_groups/unnamed_sequence_groups02.mdl
  58. BIN
      test/models/MDL/MDL (HL1)/unnamed_sequence_groups/unnamed_sequence_groups03.mdl
  59. BIN
      test/models/MDL/MDL (HL1)/unnamed_sequence_groups/unnamed_sequence_groups04.mdl
  60. BIN
      test/models/MDL/MDL (HL1)/unnamed_sequence_groups/unnamed_sequence_groups05.mdl
  61. BIN
      test/models/MDL/MDL (HL1)/unnamed_sequence_groups/unnamed_sequence_groups06.mdl
  62. BIN
      test/models/MDL/MDL (HL1)/unnamed_sequence_groups/unnamed_sequence_groups07.mdl
  63. BIN
      test/models/MDL/MDL (HL1)/unnamed_sequence_groups/unnamed_sequence_groups08.mdl
  64. BIN
      test/models/MDL/MDL (HL1)/unnamed_sequence_groups/unnamed_sequence_groups09.mdl
  65. BIN
      test/models/MDL/MDL (HL1)/unnamed_sequences.mdl
  66. 59 0
      test/unit/ImportExport/MDL/MDLHL1TestFiles.h
  67. 232 0
      test/unit/ImportExport/MDL/utMDLImporter_HL1_ImportSettings.cpp
  68. 136 0
      test/unit/ImportExport/MDL/utMDLImporter_HL1_Materials.cpp
  69. 457 0
      test/unit/ImportExport/MDL/utMDLImporter_HL1_Nodes.cpp
  70. 71 0
      test/unit/ImportExport/utMDLImporter.cpp

+ 1 - 0
Build.md

@@ -77,6 +77,7 @@ The cmake-build-environment provides options to configure the build. The followi
 - **ASSIMP_BUILD_SAMPLES ( default OFF )**: If the official samples are built as well (needs Glut).
 - **ASSIMP_BUILD_TESTS ( default ON )**: If the test suite for Assimp is built in addition to the library.
 - **ASSIMP_COVERALLS ( default OFF )**: Enable this to measure test coverage.
+- **ASSIMP_ERROR_MAX( default OFF)**: Enable all warnings.
 - **ASSIMP_WERROR( default OFF )**: Treat warnings as errors.
 - **ASSIMP_ASAN ( default OFF )**: Enable AddressSanitizer.
 - **ASSIMP_UBSAN ( default OFF )**: Enable Undefined Behavior sanitizer.

+ 15 - 1
CMakeLists.txt

@@ -100,6 +100,10 @@ OPTION ( ASSIMP_COVERALLS
   "Enable this to measure test coverage."
   OFF
 )
+OPTION ( ASSIMP_ERROR_MAX
+  "Enable all warnings."
+  OFF
+)
 OPTION ( ASSIMP_WERROR
   "Treat warnings as errors."
   OFF
@@ -253,7 +257,7 @@ ELSEIF(MSVC)
   IF(MSVC12)
     ADD_COMPILE_OPTIONS(/wd4351)
   ENDIF()
-  SET(CMAKE_CXX_FLAGS_DEBUG "/D_DEBUG /MDd /Zi /Od")
+  SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /D_DEBUG /Zi /Od")
 ELSEIF ( "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" )
   IF(NOT HUNTER_ENABLED)
     SET(CMAKE_CXX_FLAGS "-fPIC -std=c++11 ${CMAKE_CXX_FLAGS}")
@@ -294,6 +298,16 @@ IF (ASSIMP_COVERALLS)
   SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage")
 ENDIF()
 
+IF (ASSIMP_ERROR_MAX)
+  MESSAGE(STATUS "Turning on all warnings")
+  IF (MSVC)
+    ADD_COMPILE_OPTIONS(/W4) # NB: there is a /Wall option, pedantic mode
+  ELSE()
+    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
+    SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall")
+  ENDIF()
+ENDIF()
+
 IF (ASSIMP_WERROR)
   MESSAGE(STATUS "Treating warnings as errors")
   IF (MSVC)

+ 10 - 0
code/CMakeLists.txt

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

+ 2 - 2
code/Common/Exporter.cpp

@@ -103,7 +103,7 @@ void ExportSceneFBX(const char*, IOSystem*, const aiScene*, const ExportProperti
 void ExportSceneFBXA(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportScene3MF( const char*, IOSystem*, const aiScene*, const ExportProperties* );
 void ExportSceneM3D(const char*, IOSystem*, const aiScene*, const ExportProperties*);
-void ExportSceneA3D(const char*, IOSystem*, const aiScene*, const ExportProperties*);
+void ExportSceneM3DA(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportAssimp2Json(const char* , IOSystem*, const aiScene* , const Assimp::ExportProperties*);
 
 
@@ -177,7 +177,7 @@ static void setupExporterArray(std::vector<Exporter::ExportFormatEntry> &exporte
 
 #ifndef ASSIMP_BUILD_NO_M3D_EXPORTER
 	exporters.push_back(Exporter::ExportFormatEntry("m3d", "Model 3D (binary)", "m3d", &ExportSceneM3D, 0));
-	exporters.push_back(Exporter::ExportFormatEntry("a3d", "Model 3D (ascii)", "m3d", &ExportSceneA3D, 0));
+	exporters.push_back(Exporter::ExportFormatEntry("m3da", "Model 3D (ascii)", "a3d", &ExportSceneM3DA, 0));
 #endif
 
 #ifndef ASSIMP_BUILD_NO_3MF_EXPORTER

+ 38 - 13
code/M3D/M3DExporter.cpp

@@ -45,7 +45,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #define M3D_IMPLEMENTATION
 #define M3D_NOIMPORTER
 #define M3D_EXPORTER
-#define M3D_ASCII
 #ifndef ASSIMP_BUILD_NO_M3D_IMPORTER
 #define M3D_NODUP
 #endif
@@ -65,9 +64,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/Exporter.hpp>
 #include <assimp/IOSystem.hpp>
 
+#include "M3DWrapper.h"
 #include "M3DExporter.h"
 #include "M3DMaterials.h"
-#include "M3DWrapper.h"
 
 // RESOURCES:
 // https://gitlab.com/bztsrc/model3d/blob/master/docs/m3d_format.md
@@ -131,10 +130,32 @@ void addProp(m3dm_t *m, uint8_t type, uint32_t value) {
 	m->prop[i].value.num = value;
 }
 
+// ------------------------------------------------------------------------------------------------
+// convert aiString to identifier safe C string. This is a duplication of _m3d_safestr
+char *SafeStr(aiString str, bool isStrict)
+{
+	char *s = (char *)&str.data;
+	char *d, *ret;
+	int i, len;
+
+	for(len = str.length + 1; *s && (*s == ' ' || *s == '\t'); s++, len--);
+	if(len > 255) len = 255;
+	ret = (char *)M3D_MALLOC(len + 1);
+	if (!ret) {
+		throw DeadlyExportError("memory allocation error");
+	}
+	for(i = 0, d = ret; i < len && *s && *s != '\r' && *s != '\n'; s++, d++, i++) {
+		*d = isStrict && (*s == ' ' || *s == '\t' || *s == '/' || *s == '\\') ? '_' : (*s == '\t' ? ' ' : *s);
+	}
+	for(; d > ret && (*(d-1) == ' ' || *(d-1) == '\t'); d--);
+	*d = 0;
+	return ret;
+}
+
 // ------------------------------------------------------------------------------------------------
 // add a material to the output
 M3D_INDEX addMaterial(const Assimp::M3DWrapper &m3d, const aiMaterial *mat) {
-	unsigned int mi = -1U;
+	unsigned int mi = M3D_NOTDEFINED;
 	aiColor4D c;
 	aiString name;
 	ai_real f;
@@ -150,14 +171,14 @@ M3D_INDEX addMaterial(const Assimp::M3DWrapper &m3d, const aiMaterial *mat) {
 				break;
 			}
 		// if not found, add the material to the output
-		if (mi == -1U) {
+		if (mi == M3D_NOTDEFINED) {
 			unsigned int k;
 			mi = m3d->nummaterial++;
 			m3d->material = (m3dm_t *)M3D_REALLOC(m3d->material, m3d->nummaterial * sizeof(m3dm_t));
 			if (!m3d->material) {
 				throw DeadlyExportError("memory allocation error");
 			}
-			m3d->material[mi].name = _m3d_safestr((char *)&name.data, 0);
+			m3d->material[mi].name = SafeStr(name, true);
 			m3d->material[mi].numprop = 0;
 			m3d->material[mi].prop = NULL;
 			// iterate through the material property table and see what we got
@@ -218,14 +239,14 @@ M3D_INDEX addMaterial(const Assimp::M3DWrapper &m3d, const aiMaterial *mat) {
 							(name.data[j + 1] == 'g' || name.data[j + 1] == 'G'))
 						name.data[j] = 0;
 					// do we have this texture saved already?
-					fn = _m3d_safestr((char *)&name.data, 0);
-					for (j = 0, i = -1U; j < m3d->numtexture; j++)
+					fn = SafeStr(name, true);
+					for (j = 0, i = M3D_NOTDEFINED; j < m3d->numtexture; j++)
 						if (!strcmp(fn, m3d->texture[j].name)) {
 							i = j;
 							free(fn);
 							break;
 						}
-					if (i == -1U) {
+					if (i == M3D_NOTDEFINED) {
 						i = m3d->numtexture++;
 						m3d->texture = (m3dtx_t *)M3D_REALLOC(
 								m3d->texture,
@@ -268,18 +289,22 @@ void ExportSceneM3D(
 // ---------------------------------------------------------------------
 // Worker function for exporting a scene to ASCII A3D.
 // Prototyped and registered in Exporter.cpp
-void ExportSceneA3D(
+void ExportSceneM3DA(
 		const char *pFile,
 		IOSystem *pIOSystem,
 		const aiScene *pScene,
 		const ExportProperties *pProperties
 
 ) {
+#ifdef M3D_ASCII
 	// initialize the exporter
 	M3DExporter exporter(pScene, pProperties);
 
 	// perform ascii export
 	exporter.doExport(pFile, pIOSystem, true);
+#else
+	throw DeadlyExportError("Assimp configured without M3D_ASCII support");
+#endif
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -306,7 +331,7 @@ void M3DExporter::doExport(
 	if (!m3d) {
 		throw DeadlyExportError("memory allocation error");
 	}
-	m3d->name = _m3d_safestr((char *)&mScene->mRootNode->mName.data, 2);
+	m3d->name = SafeStr(mScene->mRootNode->mName, false);
 
 	// Create a model from assimp structures
 	aiMatrix4x4 m;
@@ -335,7 +360,7 @@ void M3DExporter::NodeWalk(const M3DWrapper &m3d, const aiNode *pNode, aiMatrix4
 
 	for (unsigned int i = 0; i < pNode->mNumMeshes; i++) {
 		const aiMesh *mesh = mScene->mMeshes[pNode->mMeshes[i]];
-		unsigned int mi = (M3D_INDEX)-1U;
+		unsigned int mi = M3D_NOTDEFINED;
 		if (mScene->mMaterials) {
 			// get the material for this mesh
 			mi = addMaterial(m3d, mScene->mMaterials[mesh->mMaterialIndex]);
@@ -358,7 +383,7 @@ void M3DExporter::NodeWalk(const M3DWrapper &m3d, const aiNode *pNode, aiMatrix4
 			/* set all index to -1 by default */
 			m3d->face[n].vertex[0] = m3d->face[n].vertex[1] = m3d->face[n].vertex[2] =
 					m3d->face[n].normal[0] = m3d->face[n].normal[1] = m3d->face[n].normal[2] =
-							m3d->face[n].texcoord[0] = m3d->face[n].texcoord[1] = m3d->face[n].texcoord[2] = -1U;
+							m3d->face[n].texcoord[0] = m3d->face[n].texcoord[1] = m3d->face[n].texcoord[2] = M3D_UNDEF;
 			m3d->face[n].materialid = mi;
 			for (unsigned int k = 0; k < face->mNumIndices; k++) {
 				// get the vertex's index
@@ -374,7 +399,7 @@ void M3DExporter::NodeWalk(const M3DWrapper &m3d, const aiNode *pNode, aiMatrix4
 				vertex.z = v.z;
 				vertex.w = 1.0;
 				vertex.color = 0;
-				vertex.skinid = -1U;
+				vertex.skinid = M3D_UNDEF;
 				// add color if defined
 				if (mesh->HasVertexColors(0))
 					vertex.color = mkColor(&mesh->mColors[0][l]);

+ 66 - 38
code/M3D/M3DImporter.cpp

@@ -43,7 +43,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #ifndef ASSIMP_BUILD_NO_M3D_IMPORTER
 
 #define M3D_IMPLEMENTATION
-#define M3D_ASCII
 #define M3D_NONORMALS /* leave the post-processing to Assimp */
 #define M3D_NOWEIGHTS
 #define M3D_NOANIMATION
@@ -57,9 +56,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/Importer.hpp>
 #include <memory>
 
+#include "M3DWrapper.h"
 #include "M3DImporter.h"
 #include "M3DMaterials.h"
-#include "M3DWrapper.h"
 
 // RESOURCES:
 // https://gitlab.com/bztsrc/model3d/blob/master/docs/m3d_format.md
@@ -96,7 +95,11 @@ static const aiImporterDesc desc = {
 	0,
 	0,
 	0,
+#ifdef M3D_ASCII
 	"m3d a3d"
+#else
+	"m3d"
+#endif
 };
 
 namespace Assimp {
@@ -113,7 +116,11 @@ M3DImporter::M3DImporter() :
 bool M3DImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const {
 	const std::string extension = GetExtension(pFile);
 
-	if (extension == "m3d" || extension == "a3d")
+	if (extension == "m3d"
+#ifdef M3D_ASCII
+		|| extension == "a3d"
+#endif
+		)
 		return true;
 	else if (!extension.length() || checkSig) {
 		if (!pIOHandler) {
@@ -131,7 +138,11 @@ bool M3DImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool c
 		if (4 != pStream->Read(data, 1, 4)) {
 			return false;
 		}
-		return !memcmp(data, "3DMO", 4) /* bin */ || !memcmp(data, "3dmo", 4) /* ASCII */;
+		return !memcmp(data, "3DMO", 4) /* bin */
+#ifdef M3D_ASCII
+			|| !memcmp(data, "3dmo", 4) /* ASCII */
+#endif
+		;
 	}
 	return false;
 }
@@ -159,6 +170,16 @@ void M3DImporter::InternReadFile(const std::string &file, aiScene *pScene, IOSys
 	if (fileSize != pStream->Read(buffer.data(), 1, fileSize)) {
 		throw DeadlyImportError("Failed to read the file " + file + ".");
 	}
+	// extra check for binary format's first 8 bytes. Not done for the ASCII variant
+	if(!memcmp(buffer.data(), "3DMO", 4) && memcmp(buffer.data() + 4, &fileSize, 4)) {
+		throw DeadlyImportError("Bad binary header in file " + file + ".");
+	}
+#ifdef M3D_ASCII
+	// make sure there's a terminator zero character, as input must be ASCIIZ
+	if(!memcmp(buffer.data(), "3dmo", 4)) {
+		buffer.push_back(0);
+	}
+#endif
 
 	// Get the path for external assets
 	std::string folderName("./");
@@ -176,7 +197,6 @@ void M3DImporter::InternReadFile(const std::string &file, aiScene *pScene, IOSys
 	// let the C SDK do the hard work for us
 	M3DWrapper m3d(pIOHandler, buffer);
 
-	
 	if (!m3d) {
 		throw DeadlyImportError("Unable to parse " + file + " as M3D.");
 	}
@@ -193,7 +213,7 @@ void M3DImporter::InternReadFile(const std::string &file, aiScene *pScene, IOSys
 	// now we just have to fill up the Assimp structures in pScene
 	importMaterials(m3d);
     importTextures(m3d);
-	importBones(m3d, -1U, pScene->mRootNode);
+	importBones(m3d, M3D_NOTDEFINED, pScene->mRootNode);
 	importMeshes(m3d);
 	importAnimations(m3d);
 
@@ -306,32 +326,40 @@ void M3DImporter::importTextures(const M3DWrapper &m3d) {
 	for (i = 0; i < m3d->numtexture; i++) {
 		unsigned int j, k;
 		t = &m3d->texture[i];
-		if (!t->w || !t->h || !t->f || !t->d) continue;
 		aiTexture *tx = new aiTexture;
-		strcpy(tx->achFormatHint, formatHint[t->f - 1]);
 		tx->mFilename = aiString(std::string(t->name) + ".png");
-		tx->mWidth = t->w;
-		tx->mHeight = t->h;
-		tx->pcData = new aiTexel[tx->mWidth * tx->mHeight];
-		for (j = k = 0; j < tx->mWidth * tx->mHeight; j++) {
-			switch (t->f) {
-				case 1: tx->pcData[j].g = t->d[k++]; break;
-				case 2:
-					tx->pcData[j].g = t->d[k++];
-					tx->pcData[j].a = t->d[k++];
-					break;
-				case 3:
-					tx->pcData[j].r = t->d[k++];
-					tx->pcData[j].g = t->d[k++];
-					tx->pcData[j].b = t->d[k++];
-					tx->pcData[j].a = 255;
-					break;
-				case 4:
-					tx->pcData[j].r = t->d[k++];
-					tx->pcData[j].g = t->d[k++];
-					tx->pcData[j].b = t->d[k++];
-					tx->pcData[j].a = t->d[k++];
-					break;
+		if (!t->w || !t->h || !t->f || !t->d) {
+			/* without ASSIMP_USE_M3D_READFILECB, we only have the filename, but no texture data ever */
+			tx->mWidth = 0;
+			tx->mHeight = 0;
+			memcpy(tx->achFormatHint, "png\000", 4);
+			tx->pcData = nullptr;
+		} else {
+			/* if we have the texture loaded, set format hint and pcData too */
+			tx->mWidth = t->w;
+			tx->mHeight = t->h;
+			strcpy(tx->achFormatHint, formatHint[t->f - 1]);
+			tx->pcData = new aiTexel[tx->mWidth * tx->mHeight];
+			for (j = k = 0; j < tx->mWidth * tx->mHeight; j++) {
+				switch (t->f) {
+					case 1: tx->pcData[j].g = t->d[k++]; break;
+					case 2:
+						tx->pcData[j].g = t->d[k++];
+						tx->pcData[j].a = t->d[k++];
+						break;
+					case 3:
+						tx->pcData[j].r = t->d[k++];
+						tx->pcData[j].g = t->d[k++];
+						tx->pcData[j].b = t->d[k++];
+						tx->pcData[j].a = 255;
+						break;
+					case 4:
+						tx->pcData[j].r = t->d[k++];
+						tx->pcData[j].g = t->d[k++];
+						tx->pcData[j].b = t->d[k++];
+						tx->pcData[j].a = t->d[k++];
+						break;
+				}
 			}
 		}
 		mScene->mTextures[i] = tx;
@@ -343,7 +371,7 @@ void M3DImporter::importTextures(const M3DWrapper &m3d) {
 // individually. In assimp there're per mesh vertex and UV lists, and they must be
 // indexed simultaneously.
 void M3DImporter::importMeshes(const M3DWrapper &m3d) {
-	unsigned int i, j, k, l, numpoly = 3, lastMat = -2U;
+	unsigned int i, j, k, l, numpoly = 3, lastMat = M3D_INDEXMAX;
 	std::vector<aiMesh *> *meshes = new std::vector<aiMesh *>();
 	std::vector<aiFace> *faces = nullptr;
 	std::vector<aiVector3D> *vertices = nullptr;
@@ -398,20 +426,20 @@ void M3DImporter::importMeshes(const M3DWrapper &m3d) {
 			vertices->push_back(pos);
 			colors->push_back(mkColor(m3d->vertex[l].color));
 			// add a bone to temporary vector
-			if (m3d->vertex[l].skinid != -1U && m3d->vertex[l].skinid != -2U && m3d->skin && m3d->bone) {
+			if (m3d->vertex[l].skinid != M3D_UNDEF && m3d->vertex[l].skinid != M3D_INDEXMAX && m3d->skin && m3d->bone) {
 				// this is complicated, because M3D stores a list of bone id / weight pairs per
 				// vertex but assimp uses lists of local vertex id/weight pairs per local bone list
 				vertexids->push_back(l);
 			}
 			l = m3d->face[i].texcoord[j];
-			if (l != -1U) {
+			if (l != M3D_UNDEF) {
 				uv.x = m3d->tmap[l].u;
 				uv.y = m3d->tmap[l].v;
 				uv.z = 0.0;
 				texcoords->push_back(uv);
 			}
 			l = m3d->face[i].normal[j];
-			if (l != -1U) {
+			if (l != M3D_UNDEF) {
 				norm.x = m3d->vertex[l].x;
 				norm.y = m3d->vertex[l].y;
 				norm.z = m3d->vertex[l].z;
@@ -557,8 +585,8 @@ aiColor4D M3DImporter::mkColor(uint32_t c) {
 void M3DImporter::convertPose(const M3DWrapper &m3d, aiMatrix4x4 *m, unsigned int posid, unsigned int orientid) {
 	ai_assert(m != nullptr);
 	ai_assert(m3d);
-	ai_assert(posid != -1U && posid < m3d->numvertex);
-	ai_assert(orientid != -1U && orientid < m3d->numvertex);
+	ai_assert(posid != M3D_UNDEF && posid < m3d->numvertex);
+	ai_assert(orientid != M3D_UNDEF && orientid < m3d->numvertex);
 	m3dv_t *p = &m3d->vertex[posid];
 	m3dv_t *q = &m3d->vertex[orientid];
 
@@ -692,7 +720,7 @@ void M3DImporter::populateMesh(const M3DWrapper &m3d, aiMesh *pMesh, std::vector
 				// first count how many vertices we have per bone
 				for (i = 0; i < vertexids->size(); i++) {
 					unsigned int s = m3d->vertex[vertexids->at(i)].skinid;
-					if (s != -1U && s != -2U) {
+					if (s != M3D_UNDEF && s != M3D_INDEXMAX) {
 						for (unsigned int k = 0; k < M3D_NUMBONE && m3d->skin[s].weight[k] > 0.0; k++) {
 							aiString name = aiString(std::string(m3d->bone[m3d->skin[s].boneid[k]].name));
 							for (j = 0; j < pMesh->mNumBones; j++) {
@@ -715,7 +743,7 @@ void M3DImporter::populateMesh(const M3DWrapper &m3d, aiMesh *pMesh, std::vector
 				// fill up with data
 				for (i = 0; i < vertexids->size(); i++) {
 					unsigned int s = m3d->vertex[vertexids->at(i)].skinid;
-					if (s != -1U && s != -2U) {
+					if (s != M3D_UNDEF && s != M3D_INDEXMAX) {
 						for (unsigned int k = 0; k < M3D_NUMBONE && m3d->skin[s].weight[k] > 0.0; k++) {
 							aiString name = aiString(std::string(m3d->bone[m3d->skin[s].boneid[k]].name));
 							for (j = 0; j < pMesh->mNumBones; j++) {

+ 3 - 3
code/M3D/M3DMaterials.h

@@ -84,19 +84,19 @@ static const aiMatProp aiProps[] = {
 /* --- Texture Map Properties ---   !!!!! must match m3d_propertytypes !!!!! */
 static const aiMatProp aiTxProps[] = {
     { AI_MATKEY_TEXTURE_DIFFUSE(0) },                        /* m3dp_map_Kd */
-    { AI_MATKEY_TEXTURE_AMBIENT(0) },                        /* m3dp_map_Ka */
+    { AI_MATKEY_TEXTURE(aiTextureType_AMBIENT_OCCLUSION,0) },/* m3dp_map_Ka */
     { AI_MATKEY_TEXTURE_SPECULAR(0) },                       /* m3dp_map_Ks */
     { AI_MATKEY_TEXTURE_SHININESS(0) },                      /* m3dp_map_Ns */
     { AI_MATKEY_TEXTURE_EMISSIVE(0) },                       /* m3dp_map_Ke */
     { NULL, 0, 0 },                                          /* m3dp_map_Tf */
     { AI_MATKEY_TEXTURE_HEIGHT(0) },                         /* m3dp_bump */
     { AI_MATKEY_TEXTURE_OPACITY(0) },                        /* m3dp_map_d */
-    { AI_MATKEY_TEXTURE_REFLECTION(0) },                     /* m3dp_refl */
+    { AI_MATKEY_TEXTURE_NORMALS(0) },                        /* m3dp_map_N */
 
     { AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE_ROUGHNESS,0) },/* m3dp_map_Pr */
     { AI_MATKEY_TEXTURE(aiTextureType_METALNESS,0) },        /* m3dp_map_Pm */
     { NULL, 0, 0 },                                          /* m3dp_map_Ps */
-    { AI_MATKEY_TEXTURE(aiTextureType_AMBIENT_OCCLUSION,0) },/* m3dp_map_Ni */
+    { AI_MATKEY_TEXTURE(aiTextureType_REFLECTION,0) },       /* m3dp_map_Ni */
     { NULL, 0, 0 },                                          /* m3dp_map_Nt */
     { NULL, 0, 0 },
     { NULL, 0, 0 },

+ 21 - 21
code/M3D/M3DWrapper.cpp

@@ -48,24 +48,22 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/IOStreamBuffer.h>
 #include <assimp/ai_assert.h>
 
-#ifndef AI_M3D_USE_STDMUTEX
-#if (__cplusplus >= 201103L) || (_MSC_VER >= 1900) // C++11 and MSVC 2015 onwards
-#define AI_M3D_USE_STDMUTEX 1
-#else
-#define AI_M3D_USE_STDMUTEX 0
-#endif
-#endif
+#ifdef ASSIMP_USE_M3D_READFILECB
+
+# if (__cplusplus >= 201103L) || !defined(_MSC_VER) || (_MSC_VER >= 1900) // C++11 and MSVC 2015 onwards
+#  define threadlocal thread_local
+# else
+#  if defined(_MSC_VER) && (_MSC_VER >= 1800) // there's an alternative for MSVC 2013
+#   define threadlocal __declspec(thread)
+#  else
+#   define threadlocal
+#  endif
+# endif
 
-#if AI_M3D_USE_STDMUTEX
-#include <mutex>
-std::mutex file_mutex;
-#endif
+extern "C" {
 
 // workaround: the M3D SDK expects a C callback, but we want to use Assimp::IOSystem to implement that
-// This makes it non-rentrant so lock a mutex (requires C++11)
-
-extern "C" {
-void *m3dimporter_pIOHandler;
+threadlocal void *m3dimporter_pIOHandler;
 
 unsigned char *m3dimporter_readfile(char *fn, unsigned int *size) {
 	ai_assert(nullptr != fn);
@@ -75,7 +73,8 @@ unsigned char *m3dimporter_readfile(char *fn, unsigned int *size) {
 			(reinterpret_cast<Assimp::IOSystem *>(m3dimporter_pIOHandler))->Open(file, "rb"));
 	size_t fileSize = 0;
 	unsigned char *data = NULL;
-	// sometimes pStream is nullptr for some reason (should be an empty object returning nothing I guess)
+	// sometimes pStream is nullptr in a single-threaded scenario too for some reason
+	// (should be an empty object returning nothing I guess)
 	if (pStream) {
 		fileSize = pStream->FileSize();
 		// should be allocated with malloc(), because the library will call free() to deallocate
@@ -92,6 +91,7 @@ unsigned char *m3dimporter_readfile(char *fn, unsigned int *size) {
 	return data;
 }
 }
+#endif
 
 namespace Assimp {
 M3DWrapper::M3DWrapper() {
@@ -100,15 +100,15 @@ M3DWrapper::M3DWrapper() {
 }
 
 M3DWrapper::M3DWrapper(IOSystem *pIOHandler, const std::vector<unsigned char> &buffer) {
-#if AI_M3D_USE_STDMUTEX
-	// M3D is NOT thread-safe, so lock the global mutex
-	const std::lock_guard<std::mutex> lock(file_mutex);
-#endif
-	// pass this IOHandler to the C callback
+#ifdef ASSIMP_USE_M3D_READFILECB
+	// pass this IOHandler to the C callback in a thread-local pointer
 	m3dimporter_pIOHandler = pIOHandler;
 	m3d_ = m3d_load(const_cast<unsigned char *>(buffer.data()), m3dimporter_readfile, free, nullptr);
 	// Clear the C callback
 	m3dimporter_pIOHandler = nullptr;
+#else
+	m3d_ = m3d_load(const_cast<unsigned char *>(buffer.data()), nullptr, nullptr, nullptr);
+#endif
 }
 
 M3DWrapper::~M3DWrapper() {

+ 4 - 0
code/M3D/M3DWrapper.h

@@ -52,6 +52,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <vector>
 #include <string>
 
+// Assimp specific M3D configuration. Comment out these defines to remove functionality
+//#define ASSIMP_USE_M3D_READFILECB
+//#define M3D_ASCII
+
 #include "m3d.h"
 
 namespace Assimp {

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 173 - 160
code/M3D/m3d.h


+ 1 - 1
code/MD3/MD3Loader.cpp

@@ -973,7 +973,7 @@ void MD3Importer::InternReadFile( const std::string& pFile, aiScene* pScene, IOS
             AI_SWAP2( pcVertices[i].Z );
 
             AI_SWAP4( pcUVs[i].U );
-            AI_SWAP4( pcUVs[i].U );
+            AI_SWAP4( pcUVs[i].V );
         }
         for (uint32_t i = 0; i < pcSurfaces->NUM_TRIANGLES;++i) {
             AI_SWAP4(pcTriangles[i].INDEXES[0]);

+ 1 - 1
code/MDC/MDCLoader.cpp

@@ -355,7 +355,7 @@ void MDCImporter::InternReadFile(
         // swap all texture coordinates
         for (unsigned int i = 0; i < pcSurface->ulNumVertices;++i)
         {
-            AI_SWAP4( pcUVs->v );
+            AI_SWAP4( pcUVs->u );
             AI_SWAP4( pcUVs->v );
         }
 

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

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

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

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

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

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

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

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

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

@@ -0,0 +1,241 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+
+/** @file HL1MDLLoader.h
+ *  @brief Declaration of the Half-Life 1 MDL loader.
+ */
+
+#ifndef AI_HL1MDLLOADER_INCLUDED
+#define AI_HL1MDLLOADER_INCLUDED
+
+#include "HL1FileData.h"
+#include "HL1ImportSettings.h"
+#include "UniqueNameGenerator.h"
+
+#include <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, float &value);
+
+    /**
+     *  \brief Given the number of blend animations, determine the number of blend controllers.
+     *
+     * \param[in] num_blend_animations The number of blend animations.
+     * \param[out] num_blend_controllers The number of blend controllers.
+     * \return True if the number of blend controllers was determined. False otherwise.
+     */
+    static bool get_num_blend_controllers(const int num_blend_animations, int &num_blend_controllers);
+
+    /** Output scene to be filled */
+    aiScene *scene_;
+
+    /** Output I/O handler. Required for additional IO operations. */
+    IOSystem *io_;
+
+    /** Buffer from MDLLoader class. */
+    const unsigned char *buffer_;
+
+    /** The full file path to the MDL file we are trying to load.
+     * Used to locate other MDL files since MDL may store resources
+     * in external MDL files. */
+    const std::string &file_path_;
+
+    /** Configuration for HL1 MDL */
+    const HL1ImportSettings &import_settings_;
+
+    /** Main MDL header. */
+    const Header_HL1 *header_;
+
+    /** External MDL texture header. */
+    const Header_HL1 *texture_header_;
+
+    /** External MDL animation headers.
+     * One for each loaded animation file. */
+    SequenceHeader_HL1 **anim_headers_;
+
+    /** Texture file data. */
+    unsigned char *texture_buffer_;
+
+    /** Animation files data. */
+    unsigned char **anim_buffers_;
+
+    /** The number of sequence groups. */
+    int num_sequence_groups_;
+
+    /** The list of children to be appended to the scene's root node. */
+    std::vector<aiNode *> rootnode_children_;
+
+    /** A unique name generator. Used to generate names for MDL values
+     * that may have empty/duplicate names. */
+    UniqueNameGenerator unique_name_generator_;
+
+    /** The list of unique sequence names. */
+    std::vector<std::string> unique_sequence_names_;
+
+    /** The list of unique sequence groups names. */
+    std::vector<std::string> unique_sequence_groups_names_;
+
+    /** Structure to store temporary bone information. */
+    struct TempBone {
+
+        TempBone() :
+            node(nullptr),
+            absolute_transform(),
+            offset_matrix() {}
+
+        aiNode *node;
+        aiMatrix4x4 absolute_transform;
+        aiMatrix4x4 offset_matrix;
+    };
+
+    std::vector<TempBone> temp_bones_;
+
+    /** The number of available bone controllers in the model. */
+    int num_blend_controllers_;
+
+    /** Self explanatory. */
+    int total_models_;
+};
+
+// ------------------------------------------------------------------------------------------------
+template <typename MDLFileHeader>
+void HL1MDLLoader::load_file_into_buffer(const std::string &file_path, unsigned char *&buffer) {
+    if (!io_->Exists(file_path))
+        throw DeadlyImportError("Missing file " + DefaultIOSystem::fileName(file_path) + ".");
+
+    std::unique_ptr<IOStream> file(io_->Open(file_path));
+
+    if (file.get() == NULL)
+        throw DeadlyImportError("Failed to open MDL file " + DefaultIOSystem::fileName(file_path) + ".");
+
+    const size_t file_size = file->FileSize();
+    if (file_size < sizeof(MDLFileHeader))
+        throw DeadlyImportError("MDL file is too small.");
+
+    buffer = new unsigned char[1 + file_size];
+    file->Read((void *)buffer, 1, file_size);
+    buffer[file_size] = '\0';
+}
+
+} // namespace HalfLife
+} // namespace MDL
+} // namespace Assimp
+
+#endif // AI_HL1MDLLOADER_INCLUDED

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

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

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

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

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

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

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

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

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

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

+ 42 - 2
code/MDL/MDLLoader.cpp

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

+ 10 - 0
code/MDL/MDLLoader.h

@@ -51,6 +51,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/BaseImporter.h>
 #include "MDLFileData.h"
 #include "HMP/HalfLifeFileData.h"
+#include "HalfLife/HL1ImportSettings.h"
 
 struct aiNode;
 struct aiTexture;
@@ -77,6 +78,7 @@ using namespace MDL;
  *      <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
@@ -131,6 +133,11 @@ protected:
     */
     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)
     */
@@ -436,6 +443,9 @@ protected:
 
     /** Size of the input file in bytes */
     unsigned int iFileSize;
+
+    /* Configuration for HL1 MDL */
+    HalfLife::HL1ImportSettings mHL1ImportSettings;
 };
 
 } // end of namespace Assimp

+ 1 - 1
code/SMD/SMDLoader.cpp

@@ -60,7 +60,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 // internal headers
 #include "SMDLoader.h"
 
-#ifndef _WIN32
+#ifndef _MSC_VER
 #define strtok_s strtok_r
 #endif
 

+ 1 - 1
code/glTF/glTFAsset.h

@@ -749,7 +749,7 @@ namespace glTF
 		/// \fn void Read(Value& pJSON_Object, Asset& pAsset_Root)
 		/// Get mesh data from JSON-object and place them to root asset.
 		/// \param [in] pJSON_Object - reference to pJSON-object from which data are read.
-		/// \param [out] pAsset_Root - reference to root assed where data will be stored.
+		/// \param [out] pAsset_Root - reference to root asset where data will be stored.
 		void Read(Value& pJSON_Object, Asset& pAsset_Root);
 
 		#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC

+ 1 - 1
code/glTF/glTFAsset.inl

@@ -325,7 +325,7 @@ inline void Buffer::Read(Value& obj, Asset& r)
     }
     else { // Local file
         if (byteLength > 0) {
-            std::string dir = !r.mCurrentAssetDir.empty() ? (r.mCurrentAssetDir + "/") : "";
+            std::string dir = !r.mCurrentAssetDir.empty() ? (r.mCurrentAssetDir) : "";
 
             IOStream* file = r.OpenFile(dir + uri, "rb");
             if (file) {

+ 1 - 1
code/glTF2/glTF2Asset.h

@@ -783,7 +783,7 @@ namespace glTF2
 		/// \fn void Read(Value& pJSON_Object, Asset& pAsset_Root)
 		/// Get mesh data from JSON-object and place them to root asset.
 		/// \param [in] pJSON_Object - reference to pJSON-object from which data are read.
-		/// \param [out] pAsset_Root - reference to root assed where data will be stored.
+		/// \param [out] pAsset_Root - reference to root asset where data will be stored.
 		void Read(Value& pJSON_Object, Asset& pAsset_Root);
     };
 

+ 1 - 1
code/glTF2/glTF2Asset.inl

@@ -384,7 +384,7 @@ inline void Buffer::Read(Value& obj, Asset& r)
     }
     else { // Local file
         if (byteLength > 0) {
-            std::string dir = !r.mCurrentAssetDir.empty() ? (r.mCurrentAssetDir + "/") : "";
+            std::string dir = !r.mCurrentAssetDir.empty() ? (r.mCurrentAssetDir) : "";
 
             IOStream* file = r.OpenFile(dir + uri, "rb");
             if (file) {

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

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

+ 138 - 135
port/PyAssimp/pyassimp/structs.py

@@ -1,4 +1,4 @@
-#-*- coding: UTF-8 -*-
+#-*- coding: utf-8 -*-
 
 from ctypes import POINTER, c_void_p, c_uint, c_char, c_float, Structure, c_char_p, c_double, c_ubyte, c_size_t, c_uint32
 
@@ -6,7 +6,7 @@ from ctypes import POINTER, c_void_p, c_uint, c_char, c_float, Structure, c_char
 class Vector2D(Structure):
     """
     See 'vector2.h' for details.
-    """ 
+    """
 
 
     _fields_ = [
@@ -16,7 +16,7 @@ class Vector2D(Structure):
 class Matrix3x3(Structure):
     """
     See 'matrix3x3.h' for details.
-    """ 
+    """
 
 
     _fields_ = [
@@ -28,7 +28,7 @@ class Matrix3x3(Structure):
 class Texel(Structure):
     """
     See 'texture.h' for details.
-    """ 
+    """
 
     _fields_ = [
             ("b", c_ubyte),("g", c_ubyte),("r", c_ubyte),("a", c_ubyte),
@@ -37,7 +37,7 @@ class Texel(Structure):
 class Color4D(Structure):
     """
     See 'color4.h' for details.
-    """ 
+    """
 
 
     _fields_ = [
@@ -48,7 +48,7 @@ class Color4D(Structure):
 class Plane(Structure):
     """
     See 'types.h' for details.
-    """ 
+    """
 
     _fields_ = [
             #  Plane equation
@@ -58,7 +58,7 @@ class Plane(Structure):
 class Color3D(Structure):
     """
     See 'types.h' for details.
-    """ 
+    """
 
     _fields_ = [
             #  Red, green and blue color values
@@ -68,7 +68,7 @@ class Color3D(Structure):
 class String(Structure):
     """
     See 'types.h' for details.
-    """ 
+    """
 
     MAXLEN = 1024
 
@@ -76,8 +76,8 @@ class String(Structure):
             # Binary length of the string excluding the terminal 0. This is NOT the
             #  logical length of strings containing UTF-8 multibyte sequences! It's
             #  the number of bytes from the beginning of the string to its end.
-            ("length", c_size_t),
-            
+            ("length", c_uint32),
+
             # String buffer. Size limit is MAXLEN
             ("data", c_char*MAXLEN),
         ]
@@ -85,7 +85,7 @@ class String(Structure):
 class MaterialPropertyString(Structure):
     """
     See 'MaterialSystem.cpp' for details.
-    
+
     The size of length is truncated to 4 bytes on 64-bit platforms when used as a
     material property (see MaterialSystem.cpp aiMaterial::AddProperty() for details).
     """
@@ -97,7 +97,7 @@ class MaterialPropertyString(Structure):
             #  logical length of strings containing UTF-8 multibyte sequences! It's
             #  the number of bytes from the beginning of the string to its end.
             ("length", c_uint32),
-            
+
             # String buffer. Size limit is MAXLEN
             ("data", c_char*MAXLEN),
         ]
@@ -105,30 +105,30 @@ class MaterialPropertyString(Structure):
 class MemoryInfo(Structure):
     """
     See 'types.h' for details.
-    """ 
+    """
 
     _fields_ = [
             # Storage allocated for texture data
             ("textures", c_uint),
-            
+
             # Storage allocated for material data
             ("materials", c_uint),
-            
+
             # Storage allocated for mesh data
             ("meshes", c_uint),
-            
+
             # Storage allocated for node data
             ("nodes", c_uint),
-            
+
             # Storage allocated for animation data
             ("animations", c_uint),
-            
+
             # Storage allocated for camera data
             ("cameras", c_uint),
-            
+
             # Storage allocated for light data
             ("lights", c_uint),
-            
+
             # Total storage allocated for the full import.
             ("total", c_uint),
         ]
@@ -136,7 +136,7 @@ class MemoryInfo(Structure):
 class Quaternion(Structure):
     """
     See 'quaternion.h' for details.
-    """ 
+    """
 
 
     _fields_ = [
@@ -147,14 +147,14 @@ class Quaternion(Structure):
 class Face(Structure):
     """
     See 'mesh.h' for details.
-    """ 
+    """
 
     _fields_ = [
             #  Number of indices defining this face.
             #  The maximum value for this member is
             #AI_MAX_FACE_INDICES.
             ("mNumIndices", c_uint),
-            
+
             #  Pointer to the indices array. Size of the array is given in numIndices.
             ("mIndices", POINTER(c_uint)),
         ]
@@ -162,12 +162,12 @@ class Face(Structure):
 class VertexWeight(Structure):
     """
     See 'mesh.h' for details.
-    """ 
+    """
 
     _fields_ = [
             #  Index of the vertex which is influenced by the bone.
             ("mVertexId", c_uint),
-            
+
             #  The strength of the influence in the range (0...1).
             #  The influence from all bones at one vertex amounts to 1.
             ("mWeight", c_float),
@@ -176,7 +176,7 @@ class VertexWeight(Structure):
 class Matrix4x4(Structure):
     """
     See 'matrix4x4.h' for details.
-    """ 
+    """
 
 
     _fields_ = [
@@ -189,7 +189,7 @@ class Matrix4x4(Structure):
 class Vector3D(Structure):
     """
     See 'vector3.h' for details.
-    """ 
+    """
 
 
     _fields_ = [
@@ -199,12 +199,12 @@ class Vector3D(Structure):
 class MeshKey(Structure):
     """
     See 'anim.h' for details.
-    """ 
+    """
 
     _fields_ = [
             # The time of this key
             ("mTime", c_double),
-            
+
             # Index into the aiMesh::mAnimMeshes array of the
             #  mesh corresponding to the
             #aiMeshAnim hosting this
@@ -252,7 +252,7 @@ class Metadata(Structure):
 class Node(Structure):
     """
     See 'scene.h' for details.
-    """ 
+    """
 
 
 Node._fields_ = [
@@ -270,22 +270,22 @@ Node._fields_ = [
             # this text. You should be able to handle stuff like whitespace, tabs,
             # linefeeds, quotation marks, ampersands, ... .
             ("mName", String),
-            
+
             # The transformation relative to the node's parent.
             ("mTransformation", Matrix4x4),
-            
+
             # Parent node. NULL if this node is the root node.
             ("mParent", POINTER(Node)),
-            
+
             # The number of child nodes of this node.
             ("mNumChildren", c_uint),
-            
+
             # The child nodes of this node. NULL if mNumChildren is 0.
             ("mChildren", POINTER(POINTER(Node))),
-            
+
             # The number of meshes of this node.
             ("mNumMeshes", c_uint),
-            
+
             # The meshes of this node. Each entry is an index into the mesh
             ("mMeshes", POINTER(c_uint)),
 
@@ -297,7 +297,7 @@ Node._fields_ = [
 class Light(Structure):
     """
     See 'light.h' for details.
-    """ 
+    """
 
 
     _fields_ = [
@@ -306,22 +306,22 @@ class Light(Structure):
             #  This node specifies the position of the light in the scene
             #  hierarchy and can be animated.
             ("mName", String),
-            
+
             # The type of the light source.
             # aiLightSource_UNDEFINED is not a valid value for this member.
             ("mType", c_uint),
-            
+
             # Position of the light source in space. Relative to the
             #  transformation of the node corresponding to the light.
             #  The position is undefined for directional lights.
             ("mPosition", Vector3D),
-            
+
             # Direction of the light source in space. Relative to the
             #  transformation of the node corresponding to the light.
             #  The direction is undefined for point lights. The vector
             #  may be normalized, but it needn't.
             ("mDirection", Vector3D),
-            
+
             # Up direction of the light source in space. Relative to the
             #  transformation of the node corresponding to the light.
             #
@@ -340,7 +340,7 @@ class Light(Structure):
             #  This member corresponds to the att0 variable in the equation.
             #  Naturally undefined for directional lights.
             ("mAttenuationConstant", c_float),
-            
+
             # Linear light attenuation factor.
             #  The intensity of the light source at a given distance 'd' from
             #  the light's position is
@@ -352,7 +352,7 @@ class Light(Structure):
             #  This member corresponds to the att1 variable in the equation.
             #  Naturally undefined for directional lights.
             ("mAttenuationLinear", c_float),
-            
+
             # Quadratic light attenuation factor.
             #  The intensity of the light source at a given distance 'd' from
             #  the light's position is
@@ -364,19 +364,19 @@ class Light(Structure):
             #  This member corresponds to the att2 variable in the equation.
             #  Naturally undefined for directional lights.
             ("mAttenuationQuadratic", c_float),
-            
+
             # Diffuse color of the light source
             #  The diffuse light color is multiplied with the diffuse
             #  material color to obtain the final color that contributes
             #  to the diffuse shading term.
             ("mColorDiffuse", Color3D),
-            
+
             # Specular color of the light source
             #  The specular light color is multiplied with the specular
             #  material color to obtain the final color that contributes
             #  to the specular shading term.
             ("mColorSpecular", Color3D),
-            
+
             # Ambient color of the light source
             #  The ambient light color is multiplied with the ambient
             #  material color to obtain the final color that contributes
@@ -384,13 +384,13 @@ class Light(Structure):
             #  this value it, is just a remaining of the fixed-function pipeline
             #  that is still supported by quite many file formats.
             ("mColorAmbient", Color3D),
-            
+
             # Inner angle of a spot light's light cone.
             #  The spot light has maximum influence on objects inside this
             #  angle. The angle is given in radians. It is 2PI for point
             #  lights and undefined for directional lights.
             ("mAngleInnerCone", c_float),
-            
+
             # Outer angle of a spot light's light cone.
             #  The spot light does not affect objects outside this angle.
             #  The angle is given in radians. It is 2PI for point lights and
@@ -408,7 +408,7 @@ class Light(Structure):
 class Texture(Structure):
     """
     See 'texture.h' for details.
-    """ 
+    """
 
 
     _fields_ = [
@@ -417,15 +417,15 @@ class Texture(Structure):
             # like JPEG. In this case mWidth specifies the size of the
             # memory area pcData is pointing to, in bytes.
             ("mWidth", c_uint),
-            
+
             # Height of the texture, in pixels
             # If this value is zero, pcData points to an compressed texture
             # in any format (e.g. JPEG).
             ("mHeight", c_uint),
-            
+
             # A hint from the loader to make it easier for applications
             # to determine the type of embedded textures.
-            # 
+            #
             # If mHeight != 0 this member is show how data is packed. Hint will consist of
             # two parts: channel order and channel bitness (count of the bits for every
             # color channel). For simple parsing by the viewer it's better to not omit
@@ -443,7 +443,7 @@ class Texture(Structure):
             # E.g. 'dds\\0', 'pcx\\0', 'jpg\\0'.  All characters are lower-case.
             # The fourth character will always be '\\0'.
             ("achFormatHint", c_char*9),
-            
+
             # Data of the texture.
             # Points to an array of mWidth
             # mHeight aiTexel's.
@@ -462,7 +462,7 @@ class Texture(Structure):
 class Ray(Structure):
     """
     See 'types.h' for details.
-    """ 
+    """
 
     _fields_ = [
             #  Position and direction of the ray
@@ -472,17 +472,17 @@ class Ray(Structure):
 class UVTransform(Structure):
     """
     See 'material.h' for details.
-    """ 
+    """
 
     _fields_ = [
             # Translation on the u and v axes.
             #  The default value is (0|0).
             ("mTranslation", Vector2D),
-            
+
             # Scaling on the u and v axes.
             #  The default value is (1|1).
             ("mScaling", Vector2D),
-            
+
             # Rotation - in counter-clockwise direction.
             #  The rotation angle is specified in radians. The
             #  rotation center is 0.5f|0.5f. The default value
@@ -493,34 +493,34 @@ class UVTransform(Structure):
 class MaterialProperty(Structure):
     """
     See 'material.h' for details.
-    """ 
+    """
 
     _fields_ = [
             # Specifies the name of the property (key)
             #  Keys are generally case insensitive.
             ("mKey", String),
-            
+
             # Textures: Specifies their exact usage semantic.
             # For non-texture properties, this member is always 0
             # (or, better-said,
             #aiTextureType_NONE).
             ("mSemantic", c_uint),
-            
+
             # Textures: Specifies the index of the texture.
             #  For non-texture properties, this member is always 0.
             ("mIndex", c_uint),
-            
+
             # Size of the buffer mData is pointing to, in bytes.
             #  This value may not be 0.
             ("mDataLength", c_uint),
-            
+
             # Type information for the property.
             # Defines the data layout inside the data buffer. This is used
             # by the library internally to perform debug checks and to
             # utilize proper type conversions.
             # (It's probably a hacky solution, but it works.)
             ("mType", c_uint),
-            
+
             # Binary buffer to hold the property's value.
             # The size of the buffer is always mDataLength.
             ("mData", POINTER(c_char)),
@@ -529,15 +529,15 @@ class MaterialProperty(Structure):
 class Material(Structure):
     """
     See 'material.h' for details.
-    """ 
+    """
 
     _fields_ = [
             # List of all material properties loaded.
             ("mProperties", POINTER(POINTER(MaterialProperty))),
-            
+
             # Number of properties in the data base
             ("mNumProperties", c_uint),
-            
+
             # Storage allocated
             ("mNumAllocated", c_uint),
         ]
@@ -545,20 +545,20 @@ class Material(Structure):
 class Bone(Structure):
     """
     See 'mesh.h' for details.
-    """ 
+    """
 
     _fields_ = [
             #  The name of the bone.
             ("mName", String),
-            
+
             #  The number of vertices affected by this bone
             #  The maximum value for this member is
             #AI_MAX_BONE_WEIGHTS.
             ("mNumWeights", c_uint),
-            
+
             #  The vertices affected by this bone
             ("mWeights", POINTER(VertexWeight)),
-            
+
             #  Matrix that transforms from mesh space to bone space in bind pose
             ("mOffsetMatrix", Matrix4x4),
         ]
@@ -567,12 +567,15 @@ class Bone(Structure):
 class AnimMesh(Structure):
     """
     See 'mesh.h' for details.
-    """ 
+    """
 
     AI_MAX_NUMBER_OF_TEXTURECOORDS = 0x8
     AI_MAX_NUMBER_OF_COLOR_SETS = 0x8
 
     _fields_ = [
+            #  Anim Mesh name
+            ("mName", String),
+
             # Replacement for aiMesh::mVertices. If this array is non-NULL,
             # it *must* contain mNumVertices entries. The corresponding
             # array in the host mesh must be non-NULL as well - animation
@@ -613,7 +616,7 @@ class AnimMesh(Structure):
 class Mesh(Structure):
     """
     See 'mesh.h' for details.
-    """ 
+    """
 
     AI_MAX_FACE_INDICES = 0x7fff
     AI_MAX_BONE_WEIGHTS = 0x7fffffff
@@ -628,24 +631,24 @@ class Mesh(Structure):
             # The "SortByPrimitiveType"-Step can be used to make sure the
             # output meshes consist of one primitive type each.
             ("mPrimitiveTypes", c_uint),
-            
+
             # The number of vertices in this mesh.
             # This is also the size of all of the per-vertex data arrays.
             # The maximum value for this member is
             #AI_MAX_VERTICES.
             ("mNumVertices", c_uint),
-            
+
             # The number of primitives (triangles, polygons, lines) in this  mesh.
             # This is also the size of the mFaces array.
             # The maximum value for this member is
             #AI_MAX_FACES.
             ("mNumFaces", c_uint),
-            
+
             # Vertex positions.
             # This array is always present in a mesh. The array is
             # mNumVertices in size.
             ("mVertices", POINTER(Vector3D)),
-            
+
             # Vertex normals.
             # The array contains normalized vectors, NULL if not present.
             # The array is mNumVertices in size. Normals are undefined for
@@ -666,7 +669,7 @@ class Mesh(Structure):
             # However, this needn't apply for normals that have been taken
             #   directly from the model file.
             ("mNormals", POINTER(Vector3D)),
-            
+
             # Vertex tangents.
             # The tangent of a vertex points in the direction of the positive
             # X texture axis. The array contains normalized vectors, NULL if
@@ -681,7 +684,7 @@ class Mesh(Structure):
             # contains bitangents (the bitangent is just the cross product of
             # tangent and normal vectors).
             ("mTangents", POINTER(Vector3D)),
-            
+
             # Vertex bitangents.
             # The bitangent of a vertex points in the direction of the positive
             # Y texture axis. The array contains normalized vectors, NULL if not
@@ -689,19 +692,19 @@ class Mesh(Structure):
             # @note If the mesh contains tangents, it automatically also contains
             # bitangents.
             ("mBitangents", POINTER(Vector3D)),
-            
+
             # Vertex color sets.
             # A mesh may contain 0 to
             #AI_MAX_NUMBER_OF_COLOR_SETS vertex
             # colors per vertex. NULL if not present. Each array is
             # mNumVertices in size if present.
             ("mColors", POINTER(Color4D)*AI_MAX_NUMBER_OF_COLOR_SETS),
-            
+
             # Vertex texture coords, also known as UV channels.
             # A mesh may contain 0 to AI_MAX_NUMBER_OF_TEXTURECOORDS per
             # vertex. NULL if not present. The array is mNumVertices in size.
             ("mTextureCoords", POINTER(Vector3D)*AI_MAX_NUMBER_OF_TEXTURECOORDS),
-            
+
             # Specifies the number of components for a given UV channel.
             # Up to three channels are supported (UVW, for accessing volume
             # or cube maps). If the value is 2 for a given channel n, the
@@ -709,7 +712,7 @@ class Mesh(Structure):
             # If the value is 1 for a given channel, p.y is set to 0.0f, too.
             # @note 4D coords are not supported
             ("mNumUVComponents", c_uint*AI_MAX_NUMBER_OF_TEXTURECOORDS),
-            
+
             # The faces the mesh is constructed from.
             # Each face refers to a number of vertices by their indices.
             # This array is always present in a mesh, its size is given
@@ -717,22 +720,22 @@ class Mesh(Structure):
             #AI_SCENE_FLAGS_NON_VERBOSE_FORMAT
             # is NOT set each face references an unique set of vertices.
             ("mFaces", POINTER(Face)),
-            
+
             # The number of bones this mesh contains.
             # Can be 0, in which case the mBones array is NULL.
             ("mNumBones", c_uint),
-            
+
             # The bones of this mesh.
             # A bone consists of a name by which it can be found in the
             # frame hierarchy and a set of vertex weights.
             ("mBones", POINTER(POINTER(Bone))),
-            
+
             # The material used by this mesh.
             # A mesh does use only a single material. If an imported model uses
             # multiple materials, the import splits up the mesh. Use this value
             # as index into the scene's material list.
             ("mMaterialIndex", c_uint),
-            
+
             # Name of the mesh. Meshes can be named, but this is not a
             #  requirement and leaving this field empty is totally fine.
             #  There are mainly three uses for mesh names:
@@ -744,15 +747,15 @@ class Mesh(Structure):
             #      partitioning.
             #   - Vertex animations refer to meshes by their names.
             ("mName", String),
-            
+
             # The number of attachment meshes. Note! Currently only works with Collada loader.
             ("mNumAnimMeshes", c_uint),
-            
+
             # Attachment meshes for this mesh, for vertex-based animation.
             # Attachment meshes carry replacement data for some of the
             # mesh'es vertex components (usually positions, normals).
             # Note! Currently only works with Collada loader.
-            ("mAnimMesh", POINTER(POINTER(AnimMesh))),
+            ("mAnimMeshes", POINTER(POINTER(AnimMesh))),
 
             # Method of morphing when animeshes are specified.
             ("mMethod", c_uint),
@@ -762,7 +765,7 @@ class Mesh(Structure):
 class Camera(Structure):
     """
     See 'camera.h' for details.
-    """ 
+    """
 
 
     _fields_ = [
@@ -771,12 +774,12 @@ class Camera(Structure):
             #  This node specifies the position of the camera in the scene
             #  hierarchy and can be animated.
             ("mName", String),
-            
+
             # Position of the camera relative to the coordinate space
             #  defined by the corresponding node.
             #  The default value is 0|0|0.
             ("mPosition", Vector3D),
-            
+
             # 'Up' - vector of the camera coordinate system relative to
             #  the coordinate space defined by the corresponding node.
             #  The 'right' vector of the camera coordinate system is
@@ -784,25 +787,25 @@ class Camera(Structure):
             #  The default value is 0|1|0. The vector
             #  may be normalized, but it needn't.
             ("mUp", Vector3D),
-            
+
             # 'LookAt' - vector of the camera coordinate system relative to
             #  the coordinate space defined by the corresponding node.
             #  This is the viewing direction of the user.
             #  The default value is 0|0|1. The vector
             #  may be normalized, but it needn't.
             ("mLookAt", Vector3D),
-            
+
             # Half horizontal field of view angle, in radians.
             #  The field of view angle is the angle between the center
             #  line of the screen and the left or right border.
             #  The default value is 1/4PI.
             ("mHorizontalFOV", c_float),
-            
+
             # Distance of the near clipping plane from the camera.
             # The value may not be 0.f (for arithmetic reasons to prevent
             # a division through zero). The default value is 0.1f.
             ("mClipPlaneNear", c_float),
-            
+
             # Distance of the far clipping plane from the camera.
             # The far clipping plane must, of course, be further away than the
             # near clipping plane. The default value is 1000.f. The ratio
@@ -810,7 +813,7 @@ class Camera(Structure):
             # large (between 1000-10000 should be ok) to avoid floating-point
             # inaccuracies which could lead to z-fighting.
             ("mClipPlaneFar", c_float),
-            
+
             # Screen aspect ratio.
             # This is the ration between the width and the height of the
             # screen. Typical values are 4/3, 1/2 or 1/1. This value is
@@ -822,12 +825,12 @@ class Camera(Structure):
 class VectorKey(Structure):
     """
     See 'anim.h' for details.
-    """ 
+    """
 
     _fields_ = [
             # The time of this key
             ("mTime", c_double),
-            
+
             # The value of this key
             ("mValue", Vector3D),
         ]
@@ -835,12 +838,12 @@ class VectorKey(Structure):
 class QuatKey(Structure):
     """
     See 'anim.h' for details.
-    """ 
+    """
 
     _fields_ = [
             # The time of this key
             ("mTime", c_double),
-            
+
             # The value of this key
             ("mValue", Quaternion),
         ]
@@ -848,7 +851,7 @@ class QuatKey(Structure):
 class MeshMorphKey(Structure):
     """
     See 'anim.h' for details.
-    """ 
+    """
 
     _fields_ = [
             # The time of this key
@@ -866,47 +869,47 @@ class MeshMorphKey(Structure):
 class NodeAnim(Structure):
     """
     See 'anim.h' for details.
-    """ 
+    """
 
     _fields_ = [
             # The name of the node affected by this animation. The node
             #  must exist and it must be unique.
             ("mNodeName", String),
-            
+
             # The number of position keys
             ("mNumPositionKeys", c_uint),
-            
+
             # The position keys of this animation channel. Positions are
             # specified as 3D vector. The array is mNumPositionKeys in size.
             # If there are position keys, there will also be at least one
             # scaling and one rotation key.
             ("mPositionKeys", POINTER(VectorKey)),
-            
+
             # The number of rotation keys
             ("mNumRotationKeys", c_uint),
-            
+
             # The rotation keys of this animation channel. Rotations are
             #  given as quaternions,  which are 4D vectors. The array is
             #  mNumRotationKeys in size.
             # If there are rotation keys, there will also be at least one
             # scaling and one position key.
             ("mRotationKeys", POINTER(QuatKey)),
-            
+
             # The number of scaling keys
             ("mNumScalingKeys", c_uint),
-            
+
             # The scaling keys of this animation channel. Scalings are
             #  specified as 3D vector. The array is mNumScalingKeys in size.
             # If there are scaling keys, there will also be at least one
             # position and one rotation key.
             ("mScalingKeys", POINTER(VectorKey)),
-            
+
             # Defines how the animation behaves before the first
             #  key is encountered.
             #  The default value is aiAnimBehaviour_DEFAULT (the original
             #  transformation matrix of the affected node is used).
             ("mPreState", c_uint),
-            
+
             # Defines how the animation behaves after the last
             #  key was processed.
             #  The default value is aiAnimBehaviour_DEFAULT (the original
@@ -917,7 +920,7 @@ class NodeAnim(Structure):
 class MeshAnim(Structure):
     """
     See 'anim.h' for details.
-    """ 
+    """
 
     _fields_ = [
             # Name of the mesh to be animated. An empty string is not allowed,
@@ -936,7 +939,7 @@ class MeshAnim(Structure):
 class MeshMorphAnim(Structure):
     """
     See 'anim.h' for details.
-    """     
+    """
 
     _fields_ = [
             # Name of the mesh to be animated. An empty string is not allowed,
@@ -956,32 +959,32 @@ class MeshMorphAnim(Structure):
 class Animation(Structure):
     """
     See 'anim.h' for details.
-    """ 
+    """
 
     _fields_ = [
             # The name of the animation. If the modeling package this data was
             #  exported from does support only a single animation channel, this
             #  name is usually empty (length is zero).
             ("mName", String),
-            
+
             # Duration of the animation in ticks.
             ("mDuration", c_double),
-            
+
             # Ticks per second. 0 if not specified in the imported file
             ("mTicksPerSecond", c_double),
-            
+
             # The number of bone animation channels. Each channel affects
             #  a single node.
             ("mNumChannels", c_uint),
-            
+
             # The node animation channels. Each channel affects a single node.
             #  The array is mNumChannels in size.
             ("mChannels", POINTER(POINTER(NodeAnim))),
-            
+
             # The number of mesh animation channels. Each channel affects
             #  a single mesh and defines vertex-based animation.
             ("mNumMeshChannels", c_uint),
-            
+
             # The mesh animation channels. Each channel affects a single mesh.
             #  The array is mNumMeshChannels in size.
             ("mMeshChannels", POINTER(POINTER(MeshAnim))),
@@ -991,7 +994,7 @@ class Animation(Structure):
             ("mNumMorphMeshChannels", c_uint),
 
             # The morph mesh animation channels. Each channel affects a single mesh.
-            # The array is mNumMorphMeshChannels in size. 
+            # The array is mNumMorphMeshChannels in size.
             ("mMorphMeshChannels", POINTER(POINTER(MeshMorphAnim))),
 
         ]
@@ -1032,7 +1035,7 @@ ExportDataBlob._fields_ = [
 class Scene(Structure):
     """
     See 'aiScene.h' for details.
-    """ 
+    """
 
     AI_SCENE_FLAGS_INCOMPLETE = 0x1
     AI_SCENE_FLAGS_VALIDATED = 0x2
@@ -1047,64 +1050,64 @@ class Scene(Structure):
             # want to reject all scenes with the AI_SCENE_FLAGS_INCOMPLETE
             # bit set.
             ("mFlags", c_uint),
-            
+
             # The root node of the hierarchy.
             # There will always be at least the root node if the import
             # was successful (and no special flags have been set).
             # Presence of further nodes depends on the format and content
             # of the imported file.
             ("mRootNode", POINTER(Node)),
-            
+
             # The number of meshes in the scene.
             ("mNumMeshes", c_uint),
-            
+
             # The array of meshes.
             # Use the indices given in the aiNode structure to access
             # this array. The array is mNumMeshes in size. If the
             # AI_SCENE_FLAGS_INCOMPLETE flag is not set there will always
             # be at least ONE material.
             ("mMeshes", POINTER(POINTER(Mesh))),
-            
+
             # The number of materials in the scene.
             ("mNumMaterials", c_uint),
-            
+
             # The array of materials.
             # Use the index given in each aiMesh structure to access this
             # array. The array is mNumMaterials in size. If the
             # AI_SCENE_FLAGS_INCOMPLETE flag is not set there will always
             # be at least ONE material.
             ("mMaterials", POINTER(POINTER(Material))),
-            
+
             # The number of animations in the scene.
             ("mNumAnimations", c_uint),
-            
+
             # The array of animations.
             # All animations imported from the given file are listed here.
             # The array is mNumAnimations in size.
             ("mAnimations", POINTER(POINTER(Animation))),
-            
+
             # The number of textures embedded into the file
             ("mNumTextures", c_uint),
-            
+
             # The array of embedded textures.
             # Not many file formats embed their textures into the file.
             # An example is Quake's MDL format (which is also used by
             # some GameStudio versions)
             ("mTextures", POINTER(POINTER(Texture))),
-            
+
             # The number of light sources in the scene. Light sources
             # are fully optional, in most cases this attribute will be 0
             ("mNumLights", c_uint),
-            
+
             # The array of light sources.
             # All light sources imported from the given file are
             # listed here. The array is mNumLights in size.
             ("mLights", POINTER(POINTER(Light))),
-            
+
             # The number of cameras in the scene. Cameras
             # are fully optional, in most cases this attribute will be 0
             ("mNumCameras", c_uint),
-            
+
             # The array of cameras.
             # All cameras imported from the given file are listed here.
             # The array is mNumCameras in size. The first camera in the

+ 5 - 0
test/CMakeLists.txt

@@ -128,6 +128,11 @@ SET( IMPORTERS
   unit/ImportExport/utOFFImportExport.cpp
   unit/ImportExport/utNFFImportExport.cpp
   unit/ImportExport/utXGLImportExport.cpp
+  unit/ImportExport/utMDLImporter.cpp
+  unit/ImportExport/MDL/MDLHL1TestFiles.h
+  unit/ImportExport/MDL/utMDLImporter_HL1_ImportSettings.cpp
+  unit/ImportExport/MDL/utMDLImporter_HL1_Materials.cpp
+  unit/ImportExport/MDL/utMDLImporter_HL1_Nodes.cpp
 )
 
 SET( MATERIAL

BIN
test/models/MDL/MDL (HL1)/alpha_test.mdl


BIN
test/models/MDL/MDL (HL1)/blend_additive.mdl


BIN
test/models/MDL/MDL (HL1)/chrome_sphere.mdl


BIN
test/models/MDL/MDL (HL1)/duplicate_bodyparts.mdl


BIN
test/models/MDL/MDL (HL1)/duplicate_sequence_groups/duplicate_sequence_groups.mdl


BIN
test/models/MDL/MDL (HL1)/duplicate_sequence_groups/duplicate_sequence_groups01.mdl


BIN
test/models/MDL/MDL (HL1)/duplicate_sequence_groups/duplicate_sequence_groups02.mdl


BIN
test/models/MDL/MDL (HL1)/duplicate_sequence_groups/duplicate_sequence_groups03.mdl


BIN
test/models/MDL/MDL (HL1)/duplicate_sequence_groups/duplicate_sequence_groups04.mdl


BIN
test/models/MDL/MDL (HL1)/duplicate_sequence_groups/duplicate_sequence_groups05.mdl


BIN
test/models/MDL/MDL (HL1)/duplicate_sequence_groups/duplicate_sequence_groups06.mdl


BIN
test/models/MDL/MDL (HL1)/duplicate_sequence_groups/duplicate_sequence_groups07.mdl


BIN
test/models/MDL/MDL (HL1)/duplicate_sequence_groups/duplicate_sequence_groups08.mdl


BIN
test/models/MDL/MDL (HL1)/duplicate_sequence_groups/duplicate_sequence_groups09.mdl


BIN
test/models/MDL/MDL (HL1)/duplicate_sequences.mdl


BIN
test/models/MDL/MDL (HL1)/duplicate_submodels.mdl


BIN
test/models/MDL/MDL (HL1)/man.mdl


BIN
test/models/MDL/MDL (HL1)/man01.mdl


BIN
test/models/MDL/MDL (HL1)/manT.mdl


BIN
test/models/MDL/MDL (HL1)/sequence_transitions.mdl


BIN
test/models/MDL/MDL (HL1)/unnamed_bodyparts.mdl


BIN
test/models/MDL/MDL (HL1)/unnamed_bones.mdl


BIN
test/models/MDL/MDL (HL1)/unnamed_sequence_groups/unnamed_sequence_groups.mdl


BIN
test/models/MDL/MDL (HL1)/unnamed_sequence_groups/unnamed_sequence_groups01.mdl


BIN
test/models/MDL/MDL (HL1)/unnamed_sequence_groups/unnamed_sequence_groups02.mdl


BIN
test/models/MDL/MDL (HL1)/unnamed_sequence_groups/unnamed_sequence_groups03.mdl


BIN
test/models/MDL/MDL (HL1)/unnamed_sequence_groups/unnamed_sequence_groups04.mdl


BIN
test/models/MDL/MDL (HL1)/unnamed_sequence_groups/unnamed_sequence_groups05.mdl


BIN
test/models/MDL/MDL (HL1)/unnamed_sequence_groups/unnamed_sequence_groups06.mdl


BIN
test/models/MDL/MDL (HL1)/unnamed_sequence_groups/unnamed_sequence_groups07.mdl


BIN
test/models/MDL/MDL (HL1)/unnamed_sequence_groups/unnamed_sequence_groups08.mdl


BIN
test/models/MDL/MDL (HL1)/unnamed_sequence_groups/unnamed_sequence_groups09.mdl


BIN
test/models/MDL/MDL (HL1)/unnamed_sequences.mdl


+ 59 - 0
test/unit/ImportExport/MDL/MDLHL1TestFiles.h

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

+ 232 - 0
test/unit/ImportExport/MDL/utMDLImporter_HL1_ImportSettings.cpp

@@ -0,0 +1,232 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+
+/** @file utMDLImporter_HL1_ImportSettings.cpp
+ *  @brief Half-Life 1 MDL loader import settings tests.
+ */
+
+#include "AbstractImportExportBase.h"
+#include "MDL/HalfLife/HL1ImportDefinitions.h"
+#include "MDLHL1TestFiles.h"
+#include "UnitTestPCH.h"
+#include <assimp/postprocess.h>
+#include <assimp/scene.h>
+#include <assimp/Importer.hpp>
+#include <functional>
+#include <initializer_list>
+
+using namespace Assimp;
+
+class utMDLImporter_HL1_ImportSettings : public ::testing::Test {
+
+public:
+    // Test various import settings scenarios.
+
+    void importSettings() {
+        
+        /* Verify that animations are *NOT* imported when
+           'Read animations' is disabled. */
+        load_with_import_setting_bool(
+            MDL_HL1_FILE_MAN,
+            AI_CONFIG_IMPORT_MDL_HL1_READ_ANIMATIONS,
+            false, // Set config value to false.
+            [&](const aiScene *scene) {
+                EXPECT_EQ(0u, scene->mNumAnimations);
+                EXPECT_EQ(nullptr, scene->mRootNode->FindNode(AI_MDL_HL1_NODE_SEQUENCE_INFOS));
+                EXPECT_EQ(nullptr, scene->mRootNode->FindNode(AI_MDL_HL1_NODE_SEQUENCE_GROUPS));
+                EXPECT_EQ(nullptr, scene->mRootNode->FindNode(AI_MDL_HL1_NODE_SEQUENCE_TRANSITION_GRAPH));
+
+                expect_global_info_eq<int>(scene, {
+                    { 0, "NumSequences" },
+                    { 0, "NumTransitionNodes" }
+                });
+            });
+
+        /* Verify that blend controllers info is *NOT* imported when
+           'Read blend controllers' is disabled. */
+        load_with_import_setting_bool(
+            MDL_HL1_FILE_MAN,
+            AI_CONFIG_IMPORT_MDL_HL1_READ_BLEND_CONTROLLERS,
+            false, // Set config value to false.
+            [&](const aiScene *scene) {
+                EXPECT_NE(0u, scene->mNumAnimations);
+
+                const aiNode *sequence_infos = scene->mRootNode->FindNode(AI_MDL_HL1_NODE_SEQUENCE_INFOS);
+                EXPECT_NE(nullptr, sequence_infos);
+
+                for (unsigned int i = 0; i < sequence_infos->mNumChildren; ++i)
+                    EXPECT_EQ(nullptr, sequence_infos->mChildren[i]->FindNode(AI_MDL_HL1_NODE_BLEND_CONTROLLERS));
+                
+                expect_global_info_eq(scene, 0, "NumBlendControllers");
+            });
+
+        /* Verify that animation events are *NOT* imported when
+           'Read animation events' is disabled. */
+        load_with_import_setting_bool(
+            MDL_HL1_FILE_MAN,
+            AI_CONFIG_IMPORT_MDL_HL1_READ_ANIMATION_EVENTS,
+            false, // Set config value to false.
+            [&](const aiScene *scene) {
+                EXPECT_NE(0u, scene->mNumAnimations);
+
+                const aiNode *sequence_infos = scene->mRootNode->FindNode(AI_MDL_HL1_NODE_SEQUENCE_INFOS);
+                EXPECT_NE(nullptr, sequence_infos);
+
+                for (unsigned int i = 0; i < sequence_infos->mNumChildren; ++i)
+                    EXPECT_EQ(nullptr, sequence_infos->mChildren[i]->FindNode(AI_MDL_HL1_NODE_ANIMATION_EVENTS));
+            });
+
+        /* Verify that sequence transitions info is read when
+           'Read sequence transitions' is enabled. */
+        load_with_import_setting_bool(
+            ASSIMP_TEST_MDL_HL1_MODELS_DIR "sequence_transitions.mdl",
+            AI_CONFIG_IMPORT_MDL_HL1_READ_SEQUENCE_TRANSITIONS,
+            true, // Set config value to true.
+            [&](const aiScene *scene) {
+                EXPECT_NE(nullptr, scene->mRootNode->FindNode(AI_MDL_HL1_NODE_SEQUENCE_TRANSITION_GRAPH));
+                expect_global_info_eq(scene, 4, "NumTransitionNodes");
+            });
+
+        /* Verify that sequence transitions info is *NOT* read when
+           'Read sequence transitions' is disabled. */
+        load_with_import_setting_bool(
+            ASSIMP_TEST_MDL_HL1_MODELS_DIR "sequence_transitions.mdl",
+            AI_CONFIG_IMPORT_MDL_HL1_READ_SEQUENCE_TRANSITIONS,
+            false, // Set config value to false.
+            [&](const aiScene *scene) {
+                EXPECT_EQ(nullptr, scene->mRootNode->FindNode(AI_MDL_HL1_NODE_SEQUENCE_TRANSITION_GRAPH));
+                expect_global_info_eq(scene, 0, "NumTransitionNodes");
+            });
+
+        /* Verify that bone controllers info is *NOT* read when
+           'Read bone controllers' is disabled. */
+        load_with_import_setting_bool(
+            MDL_HL1_FILE_MAN,
+            AI_CONFIG_IMPORT_MDL_HL1_READ_BONE_CONTROLLERS,
+            false, // Set config value to false.
+            [&](const aiScene *scene) {
+                EXPECT_EQ(nullptr, scene->mRootNode->FindNode(AI_MDL_HL1_NODE_BONE_CONTROLLERS));
+                expect_global_info_eq(scene, 0, "NumBoneControllers");
+            });
+
+        /* Verify that attachments info is *NOT* read when
+           'Read attachments' is disabled. */
+        load_with_import_setting_bool(
+            MDL_HL1_FILE_MAN,
+            AI_CONFIG_IMPORT_MDL_HL1_READ_ATTACHMENTS,
+            false, // Set config value to false.
+            [&](const aiScene *scene) {
+                EXPECT_EQ(nullptr, scene->mRootNode->FindNode(AI_MDL_HL1_NODE_ATTACHMENTS));
+                expect_global_info_eq(scene, 0, "NumAttachments");
+            });
+
+        /* Verify that hitboxes info is *NOT* read when
+           'Read hitboxes' is disabled. */
+        load_with_import_setting_bool(
+            MDL_HL1_FILE_MAN,
+            AI_CONFIG_IMPORT_MDL_HL1_READ_HITBOXES,
+            false, // Set config value to false.
+            [&](const aiScene *scene) {
+                EXPECT_EQ(nullptr, scene->mRootNode->FindNode(AI_MDL_HL1_NODE_HITBOXES));
+                expect_global_info_eq(scene, 0, "NumHitboxes");
+            });
+
+        /* Verify that misc global info is *NOT* read when
+           'Read misc global info' is disabled. */
+        load_with_import_setting_bool(
+            MDL_HL1_FILE_MAN,
+            AI_CONFIG_IMPORT_MDL_HL1_READ_MISC_GLOBAL_INFO,
+            false, // Set config value to false.
+            [&](const aiScene *scene) {
+                aiNode *global_info = get_global_info(scene);
+                EXPECT_NE(nullptr, global_info);
+                aiVector3D temp;
+                EXPECT_FALSE(global_info->mMetaData->Get("EyePosition", temp));
+            });
+    }
+
+private:
+    void load_with_import_setting_bool(
+        const char *file_path,
+        const char *setting_key,
+        bool setting_value,
+        std::function<void(const aiScene *)> &&func) {
+        Assimp::Importer importer;
+        importer.SetPropertyBool(setting_key, setting_value);
+        const aiScene *scene = importer.ReadFile(file_path, aiProcess_ValidateDataStructure);
+        EXPECT_NE(nullptr, scene);
+        func(scene);
+    }
+
+    inline static aiNode *get_global_info(const aiScene *scene) {
+        return scene->mRootNode->FindNode(AI_MDL_HL1_NODE_GLOBAL_INFO);
+    }
+
+    template <typename T>
+    static void expect_global_info_eq(
+        const aiScene *scene, 
+        T expected_value, 
+        const char *key_name) {
+        aiNode *global_info = get_global_info(scene);
+        EXPECT_NE(nullptr, global_info);
+        T temp;
+        EXPECT_TRUE(global_info->mMetaData->Get(key_name, temp));
+        EXPECT_EQ(expected_value, temp);
+    }
+
+    template <typename T>
+    static void expect_global_info_eq(const aiScene *scene,
+        std::initializer_list<std::pair<T, const char *>> p_kv) {
+        aiNode *global_info = get_global_info(scene);
+        EXPECT_NE(nullptr, global_info);
+        for (auto it = p_kv.begin(); it != p_kv.end(); ++it) {
+            T temp;
+            EXPECT_TRUE(global_info->mMetaData->Get(it->second, temp));
+            EXPECT_EQ(it->first, temp);
+        }
+    }
+};
+
+TEST_F(utMDLImporter_HL1_ImportSettings, importSettings) {
+    importSettings();
+}

+ 136 - 0
test/unit/ImportExport/MDL/utMDLImporter_HL1_Materials.cpp

@@ -0,0 +1,136 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+
+/** @file utMDLImporter_HL1_Materials.cpp
+ *  @brief Half-Life 1 MDL loader materials tests.
+ */
+
+#include "UnitTestPCH.h"
+#include "AbstractImportExportBase.h"
+#include <assimp/postprocess.h>
+#include <assimp/scene.h>
+#include <assimp/Importer.hpp>
+#include "MDLHL1TestFiles.h"
+#include "MDL/HalfLife/HL1ImportDefinitions.h"
+
+using namespace Assimp;
+
+class utMDLImporter_HL1_Materials : public ::testing::Test {
+
+public:
+    /* Given an MDL model with a texture flagged as flatshade,
+       verify that the imported model has a flat shading model. */
+    void flatShadeTexture() {
+        Assimp::Importer importer;
+        const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MDL_HL1_MODELS_DIR "chrome_sphere.mdl", aiProcess_ValidateDataStructure);
+        EXPECT_NE(nullptr, scene);
+        EXPECT_NE(nullptr, scene->mMaterials);
+
+        aiShadingMode shading_mode;
+        scene->mMaterials[0]->Get(AI_MATKEY_SHADING_MODEL, shading_mode);
+        EXPECT_EQ(aiShadingMode_Flat, shading_mode);
+    }
+
+    /* Given an MDL model with a chrome texture, verify that
+       the imported model has a chrome material. */
+    void chromeTexture() {
+        Assimp::Importer importer;
+        const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MDL_HL1_MODELS_DIR "chrome_sphere.mdl", aiProcess_ValidateDataStructure);
+        EXPECT_NE(nullptr, scene);
+        EXPECT_NE(nullptr, scene->mMaterials);
+
+        int chrome;
+        scene->mMaterials[0]->Get(AI_MDL_HL1_MATKEY_CHROME(aiTextureType_DIFFUSE, 0), chrome);
+        EXPECT_EQ(1, chrome);
+    }
+
+    /* Given an MDL model with an additive texture, verify that
+       the imported model has an additive material. */
+    void additiveBlendTexture() {
+        Assimp::Importer importer;
+        const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MDL_HL1_MODELS_DIR "blend_additive.mdl", aiProcess_ValidateDataStructure);
+        EXPECT_NE(nullptr, scene);
+        EXPECT_NE(nullptr, scene->mMaterials);
+
+        aiBlendMode blend_mode;
+        scene->mMaterials[0]->Get(AI_MATKEY_BLEND_FUNC, blend_mode);
+        EXPECT_EQ(aiBlendMode_Additive, blend_mode);
+    }
+
+    /* Given an MDL model with a color masked texture, verify that
+       the imported model has a color masked material. Ensure too
+       that the transparency color is the correct one. */
+    void textureWithColorMask() {
+        Assimp::Importer importer;
+        const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MDL_HL1_MODELS_DIR "alpha_test.mdl", aiProcess_ValidateDataStructure);
+        EXPECT_NE(nullptr, scene);
+        EXPECT_NE(nullptr, scene->mMaterials);
+
+        int texture_flags;
+        scene->mMaterials[0]->Get(AI_MATKEY_TEXFLAGS_DIFFUSE(0), texture_flags);
+        EXPECT_EQ(aiTextureFlags_UseAlpha, texture_flags);
+
+        // The model has only one texture, a 256 color bitmap with
+        // a palette. Pure blue is the last color in the palette,
+        // and should be the transparency color.
+        aiColor3D transparency_color;
+        scene->mMaterials[0]->Get(AI_MATKEY_COLOR_TRANSPARENT, transparency_color);
+        EXPECT_EQ(aiColor3D(0, 0, 255), transparency_color);
+    }
+};
+
+TEST_F(utMDLImporter_HL1_Materials, flatShadeTexture) {
+    flatShadeTexture();
+}
+
+TEST_F(utMDLImporter_HL1_Materials, chromeTexture) {
+    chromeTexture();
+}
+
+TEST_F(utMDLImporter_HL1_Materials, additiveBlendTexture) {
+    additiveBlendTexture();
+}
+
+TEST_F(utMDLImporter_HL1_Materials, textureWithColorMask) {
+    textureWithColorMask();
+}

+ 457 - 0
test/unit/ImportExport/MDL/utMDLImporter_HL1_Nodes.cpp

@@ -0,0 +1,457 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+
+/** @file utMDLImporter_HL1_Nodes.cpp
+ *  @brief Half-Life 1 MDL loader nodes tests.
+ */
+
+#include "UnitTestPCH.h"
+#include "AbstractImportExportBase.h"
+#include <assimp/postprocess.h>
+#include <assimp/scene.h>
+#include <assimp/Importer.hpp>
+#include "MDLHL1TestFiles.h"
+#include "MDL/HalfLife/HL1ImportDefinitions.h"
+
+using namespace Assimp;
+
+class utMDLImporter_HL1_Nodes : public ::testing::Test {
+
+public:
+    /**
+    * @note The following tests require a basic understanding
+    * of the SMD format. For more information about SMD format,
+    * please refer to the SMD importer or go to VDC
+    * (Valve Developer Community).
+    */
+
+    /*  Given a model with bones that have empty names,
+        verify that all the bones of the imported model
+        have unique and no empty names.
+
+        ""        <----+---- empty names
+        ""        <----+
+        ""        <----+
+        "Bone_3"       |
+        ""        <----+
+        "Bone_2"       |
+        "Bone_5"       | 
+        ""        <----+
+        ""        <----+
+    */
+    void emptyBonesNames() {
+        Assimp::Importer importer;
+        const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MDL_HL1_MODELS_DIR "unnamed_bones.mdl", aiProcess_ValidateDataStructure);
+        EXPECT_NE(nullptr, scene);
+
+        const std::vector<std::string> expected_bones_names = {
+            "Bone",
+            "Bone_0",
+            "Bone_1",
+            "Bone_3",
+            "Bone_4",
+            "Bone_2",
+            "Bone_5",
+            "Bone_6",
+            "Bone_7"
+        };
+
+        expect_named_children(scene, AI_MDL_HL1_NODE_BONES, expected_bones_names);
+    }
+
+    /*  Given a model with bodyparts that have empty names,
+        verify that the imported model contains bodyparts with
+        unique and no empty names.
+
+        $body ""           <----+---- empty names
+        $body "Bodypart_1"      |
+        $body "Bodypart_5"      |
+        $body "Bodypart_6"      |
+        $body ""           <----+
+        $body "Bodypart_2"      |
+        $body ""           <----+
+        $body "Bodypart_3"      |
+        $body ""           <----+
+    */
+    void emptyBodypartsNames() {
+        Assimp::Importer importer;
+        const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MDL_HL1_MODELS_DIR "unnamed_bodyparts.mdl", aiProcess_ValidateDataStructure);
+        EXPECT_NE(nullptr, scene);
+        
+        const std::vector<std::string> expected_bodyparts_names = {
+            "Bodypart",
+            "Bodypart_1",
+            "Bodypart_5",
+            "Bodypart_6",
+            "Bodypart_0",
+            "Bodypart_2",
+            "Bodypart_4",
+            "Bodypart_3",
+            "Bodypart_7"
+        };
+
+        expect_named_children(scene, AI_MDL_HL1_NODE_BODYPARTS, expected_bodyparts_names);
+    }
+
+    /*  Given a model with bodyparts that have duplicate names,
+        verify that the imported model contains bodyparts with
+        unique and no duplicate names.
+
+        $body "Bodypart"   <-----+
+        $body "Bodypart_1" <--+  |
+        $body "Bodypart_2"    |  |
+        $body "Bodypart1"     |  |
+        $body "Bodypart"   ---|--+ 
+        $body "Bodypart_1" ---+  |
+        $body "Bodypart2"        |
+        $body "Bodypart"   ------+
+        $body "Bodypart_4"
+    */
+    void duplicateBodypartsNames() {
+        Assimp::Importer importer;
+        const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MDL_HL1_MODELS_DIR "duplicate_bodyparts.mdl", aiProcess_ValidateDataStructure);
+        EXPECT_NE(nullptr, scene);
+
+        const std::vector<std::string> expected_bodyparts_names = {
+            "Bodypart",
+            "Bodypart_1",
+            "Bodypart_2",
+            "Bodypart1",
+            "Bodypart_0",
+            "Bodypart_1_0",
+            "Bodypart2",
+            "Bodypart_3",
+            "Bodypart_4"
+        };
+
+        expect_named_children(scene, AI_MDL_HL1_NODE_BODYPARTS, expected_bodyparts_names);
+    }
+
+    /*  Given a model with several bodyparts that contains multiple
+        sub models with the same file name, verify for each bodypart
+        sub model of the imported model that they have a unique name.
+
+        $bodygroup "first_bodypart"
+        {
+            studio "triangle"   <------+ duplicate file names.
+            studio "triangle"   -------+
+        }                              |
+                                       |
+        $bodygroup "second_bodypart"   |
+        {                              |
+            studio "triangle"   -------+ same as first bodypart, but with same file.
+            studio "triangle"   -------+
+        }
+
+        $bodygroup "last_bodypart"
+        {
+            studio "triangle2"  <------+ duplicate names.
+            studio "triangle2"  -------+
+        }
+    */
+    void duplicateSubModelsNames() {
+        Assimp::Importer importer;
+        const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MDL_HL1_MODELS_DIR "duplicate_submodels.mdl", aiProcess_ValidateDataStructure);
+        EXPECT_NE(nullptr, scene);
+
+        const std::vector<std::vector<std::string>> expected_bodypart_sub_models_names = {
+            {
+                "triangle",
+                "triangle_0",
+            },
+            {
+                "triangle_1",
+                "triangle_2",
+            },
+            {
+                "triangle2",
+                "triangle2_0",
+            }
+        };
+
+        const aiNode *bodyparts_node = scene->mRootNode->FindNode(AI_MDL_HL1_NODE_BODYPARTS);
+        EXPECT_NE(nullptr, bodyparts_node);
+        EXPECT_EQ(3u, bodyparts_node->mNumChildren);
+        for (unsigned int i = 0; i < bodyparts_node->mNumChildren; ++i) {
+            expect_named_children(bodyparts_node->mChildren[i],
+                    expected_bodypart_sub_models_names[i]);
+        }
+    }
+
+    /*  Given a model with sequences that have duplicate names, verify
+        that each sequence from the imported model has a unique
+        name.
+
+        $sequence "idle_1" <-------+
+        $sequence "idle"   <----+  |
+        $sequence "idle_2"      |  |
+        $sequence "idle"   -----+  |
+        $sequence "idle_0"      |  |
+        $sequence "idle_1" -----|--+
+        $sequence "idle_3"      |
+        $sequence "idle"   -----+
+        $sequence "idle_7"
+    */
+    void duplicateSequenceNames() {
+        Assimp::Importer importer;
+        const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MDL_HL1_MODELS_DIR "duplicate_sequences.mdl", aiProcess_ValidateDataStructure);
+        EXPECT_NE(nullptr, scene);
+
+        const std::vector<std::string> expected_sequence_names = {
+            "idle_1",
+            "idle",
+            "idle_2",
+            "idle_4",
+            "idle_0",
+            "idle_1_0",
+            "idle_3",
+            "idle_5",
+            "idle_7"
+        };
+
+        expect_named_children(scene, AI_MDL_HL1_NODE_SEQUENCE_INFOS, expected_sequence_names);
+    }
+
+    /*  Given a model with sequences that have empty names, verify
+        that each sequence from the imported model has a unique
+        name.
+
+        $sequence ""            <----+---- empty names
+        $sequence "Sequence_1"       |
+        $sequence ""            <----+
+        $sequence "Sequence_4"       |
+        $sequence ""            <----+
+        $sequence "Sequence_8"       |
+        $sequence ""            <----+
+        $sequence "Sequence_2"       |
+        $sequence ""            <----+
+    */
+    void emptySequenceNames() {
+        Assimp::Importer importer;
+        const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MDL_HL1_MODELS_DIR "unnamed_sequences.mdl", aiProcess_ValidateDataStructure);
+        EXPECT_NE(nullptr, scene);
+
+        const std::vector<std::string> expected_sequence_names = {
+            "Sequence",
+            "Sequence_1",
+            "Sequence_0",
+            "Sequence_4",
+            "Sequence_3",
+            "Sequence_8",
+            "Sequence_5",
+            "Sequence_2",
+            "Sequence_6"
+        };
+
+        expect_named_children(scene, AI_MDL_HL1_NODE_SEQUENCE_INFOS, expected_sequence_names);
+    }
+
+    /*  Given a model with sequence groups that have duplicate names,
+        verify that each sequence group from the imported model has
+        a unique name.
+
+        "default"
+        $sequencegroup "SequenceGroup"    <----+
+        $sequencegroup "SequenceGroup_1"       |
+        $sequencegroup "SequenceGroup_5"  <----|--+
+        $sequencegroup "SequenceGroup"    -----+  |
+        $sequencegroup "SequenceGroup_0"       |  |
+        $sequencegroup "SequenceGroup"    -----+  |
+        $sequencegroup "SequenceGroup_5"  --------+
+        $sequencegroup "SequenceGroup_6"
+        $sequencegroup "SequenceGroup_2"
+    */
+    void duplicateSequenceGroupNames() {
+        Assimp::Importer importer;
+        const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MDL_HL1_MODELS_DIR "duplicate_sequence_groups/duplicate_sequence_groups.mdl", aiProcess_ValidateDataStructure);
+        EXPECT_NE(nullptr, scene);
+
+        const std::vector<std::string> expected_sequence_names = {
+            "default",
+            "SequenceGroup",
+            "SequenceGroup_1",
+            "SequenceGroup_5",
+            "SequenceGroup_3",
+            "SequenceGroup_0",
+            "SequenceGroup_4",
+            "SequenceGroup_5_0",
+            "SequenceGroup_6",
+            "SequenceGroup_2"
+        };
+
+        expect_named_children(scene, AI_MDL_HL1_NODE_SEQUENCE_GROUPS, expected_sequence_names);
+    }
+
+    /*  Given a model with sequence groups that have empty names,
+        verify that each sequence group from the imported model has
+        a unique name.
+
+        "default"
+        $sequencegroup ""                 <----+---- empty names
+        $sequencegroup "SequenceGroup_2"       |
+        $sequencegroup "SequenceGroup_6"       |
+        $sequencegroup ""                 <----+
+        $sequencegroup ""                 <----+
+        $sequencegroup "SequenceGroup_1"       |
+        $sequencegroup "SequenceGroup_5"       |
+        $sequencegroup ""                 <----+
+        $sequencegroup "SequenceGroup_4"
+    */
+    void emptySequenceGroupNames() {
+        Assimp::Importer importer;
+        const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MDL_HL1_MODELS_DIR "unnamed_sequence_groups/unnamed_sequence_groups.mdl", aiProcess_ValidateDataStructure);
+        EXPECT_NE(nullptr, scene);
+
+        const std::vector<std::string> expected_sequence_names = {
+            "default",
+            "SequenceGroup",
+            "SequenceGroup_2",
+            "SequenceGroup_6",
+            "SequenceGroup_0",
+            "SequenceGroup_3",
+            "SequenceGroup_1",
+            "SequenceGroup_5",
+            "SequenceGroup_7",
+            "SequenceGroup_4"
+        };
+
+        expect_named_children(scene, AI_MDL_HL1_NODE_SEQUENCE_GROUPS, expected_sequence_names);
+    }
+
+    /*  Verify that mOffsetMatrix applies the correct
+        inverse bind pose transform. */
+    void offsetMatrixUnappliesTransformations() {
+
+        const float TOLERANCE = 0.01f;
+
+        Assimp::Importer importer;
+        const aiScene *scene = importer.ReadFile(MDL_HL1_FILE_MAN, aiProcess_ValidateDataStructure);
+        EXPECT_NE(nullptr, scene);
+
+        aiNode *scene_bones_node = scene->mRootNode->FindNode(AI_MDL_HL1_NODE_BONES);
+
+        const aiMatrix4x4 identity_matrix;
+
+        for (unsigned int i = 0; i < scene->mNumMeshes; ++i) {
+            aiMesh *scene_mesh = scene->mMeshes[i];
+            for (unsigned int j = 0; j < scene_mesh->mNumBones; ++j) {
+                aiBone *scene_mesh_bone = scene_mesh->mBones[j];
+
+                // Store local node transforms.
+                aiNode *n = scene_bones_node->FindNode(scene_mesh_bone->mName);
+                std::vector<aiMatrix4x4> bone_matrices = { n->mTransformation };
+                while (n->mParent != scene->mRootNode) {
+                    n = n->mParent;
+                    bone_matrices.push_back(n->mTransformation);
+                }
+
+                // Compute absolute node transform.
+                aiMatrix4x4 transform;
+                for (auto it = bone_matrices.rbegin(); it != bone_matrices.rend(); ++it)
+                    transform *= *it;
+
+                // Unapply the transformation using the offset matrix.
+                aiMatrix4x4 unapplied_transform = scene_mesh_bone->mOffsetMatrix * transform;
+
+                // Ensure that we have, approximatively, the identity matrix.
+                expect_equal_matrices(identity_matrix, unapplied_transform, TOLERANCE);
+            }
+        }
+    }
+
+private:
+    void expect_named_children(const aiNode *parent_node, const std::vector<std::string> &expected_names) {
+        EXPECT_NE(nullptr, parent_node);
+        EXPECT_EQ(expected_names.size(), parent_node->mNumChildren);
+
+        for (unsigned int i = 0; i < parent_node->mNumChildren; ++i)
+            EXPECT_EQ(expected_names[i], parent_node->mChildren[i]->mName.C_Str());
+    }
+
+    void expect_named_children(const aiScene *scene, const char *node_name, const std::vector<std::string> &expected_names) {
+        expect_named_children(scene->mRootNode->FindNode(node_name), expected_names);
+    }
+
+    void expect_equal_matrices(const aiMatrix4x4 &expected, const aiMatrix4x4 &actual, float abs_error) {
+        for (int i = 0; i < 4; ++i) {
+            for (int j = 0; j < 4; ++j)
+                EXPECT_NEAR(expected[i][j], actual[i][j], abs_error);
+        }
+    }
+};
+
+TEST_F(utMDLImporter_HL1_Nodes, emptyBonesNames) {
+    emptyBonesNames();
+}
+
+TEST_F(utMDLImporter_HL1_Nodes, emptyBodypartsNames) {
+    emptyBodypartsNames();
+}
+
+TEST_F(utMDLImporter_HL1_Nodes, duplicateBodypartsNames) {
+    duplicateBodypartsNames();
+}
+
+TEST_F(utMDLImporter_HL1_Nodes, duplicateSubModelsNames) {
+    duplicateSubModelsNames();
+}
+
+TEST_F(utMDLImporter_HL1_Nodes, emptySequenceNames) {
+    emptySequenceNames();
+}
+
+TEST_F(utMDLImporter_HL1_Nodes, duplicateSequenceNames) {
+    duplicateSequenceNames();
+}
+
+TEST_F(utMDLImporter_HL1_Nodes, emptySequenceGroupNames) {
+    emptySequenceGroupNames();
+}
+
+TEST_F(utMDLImporter_HL1_Nodes, duplicateSequenceGroupNames) {
+    duplicateSequenceGroupNames();
+}
+
+TEST_F(utMDLImporter_HL1_Nodes, offsetMatrixUnappliesTransformations) {
+    offsetMatrixUnappliesTransformations();
+}

+ 71 - 0
test/unit/ImportExport/utMDLImporter.cpp

@@ -0,0 +1,71 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+
+#include "UnitTestPCH.h"
+
+#include "AbstractImportExportBase.h"
+#include <assimp/postprocess.h>
+#include <assimp/scene.h>
+#include <assimp/Importer.hpp>
+
+#include "MDL/MDLHL1TestFiles.h"
+
+using namespace Assimp;
+
+class utMDLImporter : public AbstractImportExportBase {
+public:
+    virtual bool importerTest() {
+
+        Assimp::Importer importer;
+        const aiScene *scene = importer.ReadFile(MDL_HL1_FILE_MAN, 0);
+        EXPECT_NE(nullptr, scene);
+
+        // Add further MDL tests...
+
+        return true;
+    }
+};
+
+TEST_F(utMDLImporter, importMDLFromFileTest) {
+    EXPECT_TRUE(importerTest());
+}

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio