diff --git a/Build.md b/Build.md index 2db47798d..4b7513313 100644 --- a/Build.md +++ b/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. diff --git a/CMakeLists.txt b/CMakeLists.txt index 7544d9255..23b6f6d61 100644 --- a/CMakeLists.txt +++ b/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) diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 85aa620d9..342433e46 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -454,6 +454,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 diff --git a/code/M3D/M3DExporter.cpp b/code/M3D/M3DExporter.cpp index 1fe6e7f76..fbb03a591 100644 --- a/code/M3D/M3DExporter.cpp +++ b/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 #include +#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, @@ -275,11 +296,15 @@ void ExportSceneM3DA( 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]); diff --git a/code/M3D/M3DImporter.cpp b/code/M3D/M3DImporter.cpp index 5218dd9ed..a77e75a27 100644 --- a/code/M3D/M3DImporter.cpp +++ b/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 #include +#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 *meshes = new std::vector(); std::vector *faces = nullptr; std::vector *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++) { diff --git a/code/M3D/M3DMaterials.h b/code/M3D/M3DMaterials.h index 3d6fe246d..4aeba59e4 100644 --- a/code/M3D/M3DMaterials.h +++ b/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 }, diff --git a/code/M3D/M3DWrapper.cpp b/code/M3D/M3DWrapper.cpp index 0060c894e..28eda845f 100644 --- a/code/M3D/M3DWrapper.cpp +++ b/code/M3D/M3DWrapper.cpp @@ -48,24 +48,22 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -#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 AI_M3D_USE_STDMUTEX -#include -std::mutex file_mutex; -#endif - -// 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) +# 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 extern "C" { -void *m3dimporter_pIOHandler; + +// workaround: the M3D SDK expects a C callback, but we want to use Assimp::IOSystem to implement that +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(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 &buffer) { -#if AI_M3D_USE_STDMUTEX - // M3D is NOT thread-safe, so lock the global mutex - const std::lock_guard 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(buffer.data()), m3dimporter_readfile, free, nullptr); // Clear the C callback m3dimporter_pIOHandler = nullptr; +#else + m3d_ = m3d_load(const_cast(buffer.data()), nullptr, nullptr, nullptr); +#endif } M3DWrapper::~M3DWrapper() { diff --git a/code/M3D/M3DWrapper.h b/code/M3D/M3DWrapper.h index f404d3d4b..b4f90ff1f 100644 --- a/code/M3D/M3DWrapper.h +++ b/code/M3D/M3DWrapper.h @@ -52,6 +52,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +// Assimp specific M3D configuration. Comment out these defines to remove functionality +//#define ASSIMP_USE_M3D_READFILECB +//#define M3D_ASCII + #include "m3d.h" namespace Assimp { diff --git a/code/M3D/m3d.h b/code/M3D/m3d.h index c1d690bcc..d09348497 100644 --- a/code/M3D/m3d.h +++ b/code/M3D/m3d.h @@ -70,11 +70,14 @@ typedef double M3D_FLOAT; #endif #if !defined(M3D_SMALLINDEX) typedef uint32_t M3D_INDEX; +#define M3D_UNDEF 0xffffffff #define M3D_INDEXMAX 0xfffffffe #else typedef uint16_t M3D_INDEX; +#define M3D_UNDEF 0xffff #define M3D_INDEXMAX 0xfffe #endif +#define M3D_NOTDEFINED 0xffffffff #ifndef M3D_NUMBONE #define M3D_NUMBONE 4 #endif @@ -88,7 +91,7 @@ typedef uint16_t M3D_INDEX; #else #define _inline #define _pack -#define _unused +#define _unused __pragma(warning(suppress:4100)) #endif #ifndef __cplusplus #define _register register @@ -253,7 +256,7 @@ enum { m3dp_map_Tf, m3dp_map_Km, /* bump map */ m3dp_map_D, - m3dp_map_il, /* reflection map */ + m3dp_map_N, /* normal map */ m3dp_map_Pr = 192, /* textured physical map properties */ m3dp_map_Pm, @@ -263,6 +266,7 @@ enum { }; enum { /* aliases */ m3dp_bump = m3dp_map_Km, + m3dp_map_il = m3dp_map_N, m3dp_refl = m3dp_map_Pm }; @@ -557,6 +561,7 @@ static m3dpd_t m3d_propertytypes[] = { /* aliases, note that "map_*" aliases are handled automatically */ M3D_PROPERTYDEF(m3dpf_map, m3dp_map_Km, "bump"), + M3D_PROPERTYDEF(m3dpf_map, m3dp_map_N, "map_N"),/* as normal map has no scalar version, it's counterpart is 'il' */ M3D_PROPERTYDEF(m3dpf_map, m3dp_map_Pm, "refl") }; /* shape command definitions. if more commands start with the same string, the longer must come first */ @@ -2057,7 +2062,7 @@ static char *_m3d_getfloat(char *s, M3D_FLOAT *ret) return _m3d_findarg(e); } #endif -#if !defined(M3D_NODUP) && (defined(M3D_ASCII) || defined(M3D_EXPORTER)) +#if !defined(M3D_NODUP) && (!defined(M3D_NOIMPORTER) || defined(M3D_ASCII) || defined(M3D_EXPORTER)) /* helper function to create safe strings */ char *_m3d_safestr(char *in, int morelines) { @@ -2123,54 +2128,57 @@ M3D_INDEX _m3d_gettx(m3d_t *model, m3dread_t readfilecb, m3dfree_t freecb, char } /* try to load from external source */ if(!buff && readfilecb) { - i = strlen(fn); + i = (unsigned int)strlen(fn); if(i < 5 || fn[i - 4] != '.') { fn2 = (char*)M3D_MALLOC(i + 5); - if(!fn2) { model->errcode = M3D_ERR_ALLOC; return (M3D_INDEX)-1U; } + if(!fn2) { model->errcode = M3D_ERR_ALLOC; return M3D_UNDEF; } memcpy(fn2, fn, i); memcpy(fn2+i, ".png", 5); buff = (*readfilecb)(fn2, &len); M3D_FREE(fn2); } - if(!buff) + if(!buff) { buff = (*readfilecb)(fn, &len); + if(!buff) return M3D_UNDEF; + } } - if(!buff) return (M3D_INDEX)-1U; /* add to textures array */ i = model->numtexture++; model->texture = (m3dtx_t*)M3D_REALLOC(model->texture, model->numtexture * sizeof(m3dtx_t)); if(!model->texture) { - if(freecb) (*freecb)(buff); + if(buff && freecb) (*freecb)(buff); model->errcode = M3D_ERR_ALLOC; - return (M3D_INDEX)-1U; + return M3D_UNDEF; } model->texture[i].name = fn; model->texture[i].w = model->texture[i].h = 0; model->texture[i].d = NULL; - if(buff[0] == 0x89 && buff[1] == 'P' && buff[2] == 'N' && buff[3] == 'G') { + if(buff) { + if(buff[0] == 0x89 && buff[1] == 'P' && buff[2] == 'N' && buff[3] == 'G') { #ifdef STBI__PNG_TYPE - s.read_from_callbacks = 0; - s.img_buffer = s.img_buffer_original = (unsigned char *) buff; - s.img_buffer_end = s.img_buffer_original_end = (unsigned char *) buff+len; - /* don't use model->texture[i].w directly, it's a uint16_t */ - w = h = len = 0; - ri.bits_per_channel = 8; - model->texture[i].d = (uint8_t*)stbi__png_load(&s, (int*)&w, (int*)&h, (int*)&len, 0, &ri); - model->texture[i].w = w; - model->texture[i].h = h; - model->texture[i].f = (uint8_t)len; + s.read_from_callbacks = 0; + s.img_buffer = s.img_buffer_original = (unsigned char *) buff; + s.img_buffer_end = s.img_buffer_original_end = (unsigned char *) buff+len; + /* don't use model->texture[i].w directly, it's a uint16_t */ + w = h = len = 0; + ri.bits_per_channel = 8; + model->texture[i].d = (uint8_t*)stbi__png_load(&s, (int*)&w, (int*)&h, (int*)&len, 0, &ri); + model->texture[i].w = w; + model->texture[i].h = h; + model->texture[i].f = (uint8_t)len; #endif - } else { + } else { #ifdef M3D_TX_INTERP - if((model->errcode = M3D_TX_INTERP(fn, buff, len, &model->texture[i])) != M3D_SUCCESS) { - M3D_LOG("Unable to generate texture"); - M3D_LOG(fn); - } + if((model->errcode = M3D_TX_INTERP(fn, buff, len, &model->texture[i])) != M3D_SUCCESS) { + M3D_LOG("Unable to generate texture"); + M3D_LOG(fn); + } #else - M3D_LOG("Unimplemented interpreter"); - M3D_LOG(fn); + M3D_LOG("Unimplemented interpreter"); + M3D_LOG(fn); #endif + } + if(freecb) (*freecb)(buff); } - if(freecb) (*freecb)(buff); if(!model->texture[i].d) model->errcode = M3D_ERR_UNKIMG; return i; @@ -2199,6 +2207,9 @@ void _m3d_getpr(m3d_t *model, _unused m3dread_t readfilecb, _unused m3dfree_t f } if(freecb && buff) (*freecb)(buff); #else + (void)readfilecb; + (void)freecb; + (void)fn; M3D_LOG("Unimplemented interpreter"); M3D_LOG(fn); model->errcode = M3D_ERR_UNIMPL; @@ -2291,7 +2302,7 @@ void _m3d_mat(M3D_FLOAT *r, m3dv_t *p, m3dv_t *q) } #endif #if !defined(M3D_NOANIMATION) || !defined(M3D_NONORMALS) -/* fast inverse square root calculation. returns 1/sqrt(x) */ +/* portable fast inverse square root calculation. returns 1/sqrt(x) */ static M3D_FLOAT _m3d_rsq(M3D_FLOAT x) { #ifdef M3D_DOUBLE @@ -2434,7 +2445,7 @@ m3d_t *m3d_load(unsigned char *data, m3dread_t readfilecb, m3dfree_t freecb, m3d model->vertex = (m3dv_t*)M3D_REALLOC(model->vertex, model->numvertex * sizeof(m3dv_t)); if(!model->vertex) goto memerr; memset(&model->vertex[i], 0, sizeof(m3dv_t)); - model->vertex[i].skinid = (M3D_INDEX)-1U; + model->vertex[i].skinid = M3D_UNDEF; model->vertex[i].color = 0; model->vertex[i].w = (M3D_FLOAT)1.0; ptr = _m3d_getfloat(ptr, &model->vertex[i].x); @@ -2464,16 +2475,16 @@ m3d_t *m3d_load(unsigned char *data, m3dread_t readfilecb, m3dfree_t freecb, m3d s.weight[j] = (M3D_FLOAT)1.0; if(!*ptr) goto asciiend; } - if(s.boneid[0] != (M3D_INDEX)-1U && s.weight[0] > (M3D_FLOAT)0.0) { + if(s.boneid[0] != M3D_UNDEF && s.weight[0] > (M3D_FLOAT)0.0) { if(w != (M3D_FLOAT)1.0 && w != (M3D_FLOAT)0.0) for(j = 0; j < M3D_NUMBONE && s.weight[j] > (M3D_FLOAT)0.0; j++) s.weight[j] /= w; - k = -1U; + k = M3D_NOTDEFINED; if(model->skin) { for(j = 0; j < model->numskin; j++) if(!memcmp(&model->skin[j], &s, sizeof(m3ds_t))) { k = j; break; } } - if(k == -1U) { + if(k == M3D_NOTDEFINED) { k = model->numskin++; model->skin = (m3ds_t*)M3D_REALLOC(model->skin, model->numskin * sizeof(m3ds_t)); memcpy(&model->skin[k], &s, sizeof(m3ds_t)); @@ -2486,7 +2497,7 @@ m3d_t *m3d_load(unsigned char *data, m3dread_t readfilecb, m3dfree_t freecb, m3d /* Skeleton, bone hierarchy */ if(!memcmp(pe, "Bones", 5)) { if(model->bone) { M3D_LOG("More bones chunks, should be unique"); goto asciiend; } - bi[0] = (M3D_INDEX)-1U; + bi[0] = M3D_UNDEF; while(*ptr && *ptr != '\r' && *ptr != '\n') { i = model->numbone++; model->bone = (m3db_t*)M3D_REALLOC(model->bone, model->numbone * sizeof(m3db_t)); @@ -2505,7 +2516,7 @@ m3d_t *m3d_load(unsigned char *data, m3dread_t readfilecb, m3dfree_t freecb, m3d ptr = _m3d_findarg(ptr); if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; model->bone[i].ori = (M3D_INDEX)k; - model->vertex[k].skinid = (M3D_INDEX)-2U; + model->vertex[k].skinid = M3D_INDEXMAX; pe = _m3d_safestr(ptr, 0); if(!pe || !*pe) goto asciiend; model->bone[i].name = pe; @@ -2579,7 +2590,8 @@ m3d_t *m3d_load(unsigned char *data, m3dread_t readfilecb, m3dfree_t freecb, m3d if(!pe || !*pe) goto asciiend; m->prop[j].value.textureid = _m3d_gettx(model, readfilecb, freecb, pe); if(model->errcode == M3D_ERR_ALLOC) { M3D_FREE(pe); goto memerr; } - if(m->prop[j].value.textureid == (M3D_INDEX)-1U) { + /* this error code only returned if readfilecb was specified */ + if(m->prop[j].value.textureid == M3D_UNDEF) { M3D_LOG("Texture not found"); M3D_LOG(pe); m->numprop--; @@ -2605,18 +2617,18 @@ m3d_t *m3d_load(unsigned char *data, m3dread_t readfilecb, m3dfree_t freecb, m3d } else /* mesh */ if(!memcmp(pe, "Mesh", 4)) { - mi = (M3D_INDEX)-1U; + mi = M3D_UNDEF; while(*ptr && *ptr != '\r' && *ptr != '\n') { if(*ptr == 'u') { ptr = _m3d_findarg(ptr); if(!*ptr) goto asciiend; - mi = (M3D_INDEX)-1U; + mi = M3D_UNDEF; if(*ptr != '\r' && *ptr != '\n') { pe = _m3d_safestr(ptr, 0); if(!pe || !*pe) goto asciiend; for(j = 0; j < model->nummaterial; j++) if(!strcmp(pe, model->material[j].name)) { mi = (M3D_INDEX)j; break; } - if(mi == (M3D_INDEX)-1U && !(model->flags & M3D_FLG_MTLLIB)) { + if(mi == M3D_UNDEF && !(model->flags & M3D_FLG_MTLLIB)) { mi = model->nummaterial++; model->material = (m3dm_t*)M3D_REALLOC(model->material, model->nummaterial * sizeof(m3dm_t)); if(!model->material) goto memerr; @@ -2655,7 +2667,7 @@ m3d_t *m3d_load(unsigned char *data, m3dread_t readfilecb, m3dfree_t freecb, m3d } } #ifndef M3D_NONORMALS - if(model->face[i].normal[j] == (M3D_INDEX)-1U) neednorm = 1; + if(model->face[i].normal[j] == M3D_UNDEF) neednorm = 1; #endif ptr = _m3d_findarg(ptr); } @@ -2674,7 +2686,7 @@ m3d_t *m3d_load(unsigned char *data, m3dread_t readfilecb, m3dfree_t freecb, m3d if(!model->shape) goto memerr; h = &model->shape[i]; h->name = pe; - h->group = (M3D_INDEX)-1U; + h->group = M3D_UNDEF; h->numcmd = 0; h->cmd = NULL; while(*ptr && *ptr != '\r' && *ptr != '\n') { @@ -2682,16 +2694,16 @@ m3d_t *m3d_load(unsigned char *data, m3dread_t readfilecb, m3dfree_t freecb, m3d ptr = _m3d_findarg(ptr); ptr = _m3d_getint(ptr, &h->group); ptr = _m3d_findnl(ptr); - if(h->group != (M3D_INDEX)-1U && h->group >= model->numbone) { + if(h->group != M3D_UNDEF && h->group >= model->numbone) { M3D_LOG("Unknown bone id as shape group in shape"); M3D_LOG(pe); - h->group = (M3D_INDEX)-1U; + h->group = M3D_UNDEF; model->errcode = M3D_ERR_SHPE; } continue; } for(cd = NULL, k = 0; k < (unsigned int)(sizeof(m3d_commandtypes)/sizeof(m3d_commandtypes[0])); k++) { - j = strlen(m3d_commandtypes[k].key); + j = (unsigned int)strlen(m3d_commandtypes[k].key); if(!memcmp(ptr, m3d_commandtypes[k].key, j) && (ptr[j] == ' ' || ptr[j] == '\r' || ptr[j] == '\n')) { cd = &m3d_commandtypes[k]; break; } } @@ -2713,13 +2725,13 @@ m3d_t *m3d_load(unsigned char *data, m3dread_t readfilecb, m3dfree_t freecb, m3d if(*ptr == ']' || *ptr == '\r' || *ptr == '\n') break; switch(cd->a[((k - n) % (cd->p - n)) + n]) { case m3dcp_mi_t: - mi = (M3D_INDEX)-1U; + mi = M3D_UNDEF; if(*ptr != '\r' && *ptr != '\n') { pe = _m3d_safestr(ptr, 0); if(!pe || !*pe) goto asciiend; for(n = 0; n < model->nummaterial; n++) if(!strcmp(pe, model->material[n].name)) { mi = (M3D_INDEX)n; break; } - if(mi == (M3D_INDEX)-1U && !(model->flags & M3D_FLG_MTLLIB)) { + if(mi == M3D_UNDEF && !(model->flags & M3D_FLG_MTLLIB)) { mi = model->nummaterial++; model->material = (m3dm_t*)M3D_REALLOC(model->material, model->nummaterial * sizeof(m3dm_t)); @@ -2745,7 +2757,7 @@ m3d_t *m3d_load(unsigned char *data, m3dread_t readfilecb, m3dfree_t freecb, m3d break; case m3dcp_qi_t: ptr = _m3d_getint(ptr, &h->cmd[j].arg[k]); - model->vertex[h->cmd[i].arg[k]].skinid = (M3D_INDEX)-2U; + model->vertex[h->cmd[i].arg[k]].skinid = M3D_INDEXMAX; break; default: ptr = _m3d_getint(ptr, &h->cmd[j].arg[k]); @@ -2844,7 +2856,7 @@ m3d_t *m3d_load(unsigned char *data, m3dread_t readfilecb, m3dfree_t freecb, m3d ptr = _m3d_getint(ptr, &k); if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; a->frame[i].transform[j].ori = (M3D_INDEX)k; - model->vertex[k].skinid = (M3D_INDEX)-2U; + model->vertex[k].skinid = M3D_INDEXMAX; } ptr = _m3d_findnl(ptr); } @@ -2988,11 +3000,11 @@ asciiend: while(buff < end && !M3D_CHUNKMAGIC(buff, 'O','M','D','3')) { data = buff; len = ((m3dchunk_t*)data)->length; - if(len < sizeof(m3dchunk_t)) { + buff += len; + if(len < sizeof(m3dchunk_t) || buff >= end) { M3D_LOG("Invalid chunk size"); break; } - buff += len; len -= sizeof(m3dchunk_t) + model->si_s; /* inlined assets */ @@ -3018,11 +3030,11 @@ memerr: M3D_LOG("Out of memory"); while(chunk < end && !M3D_CHUNKMAGIC(chunk, 'O','M','D','3')) { data = chunk; len = ((m3dchunk_t*)chunk)->length; - if(len < sizeof(m3dchunk_t)) { + chunk += len; + if(len < sizeof(m3dchunk_t) || chunk >= end) { M3D_LOG("Invalid chunk size"); break; } - chunk += len; len -= sizeof(m3dchunk_t); /* preview chunk */ @@ -3050,12 +3062,12 @@ memerr: M3D_LOG("Out of memory"); for(i = 0, data += sizeof(m3dchunk_t); data < chunk; i++) { switch(model->vc_s) { case 1: - model->tmap[i].u = (M3D_FLOAT)(data[0]) / 255; - model->tmap[i].v = (M3D_FLOAT)(data[1]) / 255; + model->tmap[i].u = (M3D_FLOAT)(data[0]) / (M3D_FLOAT)255.0; + model->tmap[i].v = (M3D_FLOAT)(data[1]) / (M3D_FLOAT)255.0; break; case 2: - model->tmap[i].u = (M3D_FLOAT)(*((int16_t*)(data+0))) / 65535; - model->tmap[i].v = (M3D_FLOAT)(*((int16_t*)(data+2))) / 65535; + model->tmap[i].u = (M3D_FLOAT)(*((int16_t*)(data+0))) / (M3D_FLOAT)65535.0; + model->tmap[i].v = (M3D_FLOAT)(*((int16_t*)(data+2))) / (M3D_FLOAT)65535.0; break; case 4: model->tmap[i].u = (M3D_FLOAT)(*((float*)(data+0))); @@ -3082,17 +3094,17 @@ memerr: M3D_LOG("Out of memory"); for(i = 0, data += sizeof(m3dchunk_t); data < chunk && i < model->numvertex; i++) { switch(model->vc_s) { case 1: - model->vertex[i].x = (M3D_FLOAT)((int8_t)data[0]) / 127; - model->vertex[i].y = (M3D_FLOAT)((int8_t)data[1]) / 127; - model->vertex[i].z = (M3D_FLOAT)((int8_t)data[2]) / 127; - model->vertex[i].w = (M3D_FLOAT)((int8_t)data[3]) / 127; + model->vertex[i].x = (M3D_FLOAT)((int8_t)data[0]) / (M3D_FLOAT)127.0; + model->vertex[i].y = (M3D_FLOAT)((int8_t)data[1]) / (M3D_FLOAT)127.0; + model->vertex[i].z = (M3D_FLOAT)((int8_t)data[2]) / (M3D_FLOAT)127.0; + model->vertex[i].w = (M3D_FLOAT)((int8_t)data[3]) / (M3D_FLOAT)127.0; data += 4; break; case 2: - model->vertex[i].x = (M3D_FLOAT)(*((int16_t*)(data+0))) / 32767; - model->vertex[i].y = (M3D_FLOAT)(*((int16_t*)(data+2))) / 32767; - model->vertex[i].z = (M3D_FLOAT)(*((int16_t*)(data+4))) / 32767; - model->vertex[i].w = (M3D_FLOAT)(*((int16_t*)(data+6))) / 32767; + model->vertex[i].x = (M3D_FLOAT)(*((int16_t*)(data+0))) / (M3D_FLOAT)32767.0; + model->vertex[i].y = (M3D_FLOAT)(*((int16_t*)(data+2))) / (M3D_FLOAT)32767.0; + model->vertex[i].z = (M3D_FLOAT)(*((int16_t*)(data+4))) / (M3D_FLOAT)32767.0; + model->vertex[i].w = (M3D_FLOAT)(*((int16_t*)(data+6))) / (M3D_FLOAT)32767.0; data += 8; break; case 4: @@ -3116,7 +3128,7 @@ memerr: M3D_LOG("Out of memory"); case 4: model->vertex[i].color = *((uint32_t*)data); data += 4; break; /* case 8: break; */ } - model->vertex[i].skinid = (M3D_INDEX)-1U; + model->vertex[i].skinid = M3D_UNDEF; data = _m3d_getidx(data, model->sk_s, &model->vertex[i].skinid); } } else @@ -3150,7 +3162,7 @@ memerr: M3D_LOG("Out of memory"); if(!model->skin) goto memerr; for(i = 0; data < chunk && i < model->numskin; i++) { for(j = 0; j < M3D_NUMBONE; j++) { - model->skin[i].boneid[j] = (M3D_INDEX)-1U; + model->skin[i].boneid[j] = M3D_UNDEF; model->skin[i].weight[j] = (M3D_FLOAT)0.0; } memset(&weights, 0, sizeof(weights)); @@ -3164,7 +3176,7 @@ memerr: M3D_LOG("Out of memory"); if(j >= M3D_NUMBONE) data += model->bi_s; else { - model->skin[i].weight[j] = (M3D_FLOAT)(weights[j]) / 255; + model->skin[i].weight[j] = (M3D_FLOAT)(weights[j]) / (M3D_FLOAT)255.0; w += model->skin[i].weight[j]; data = _m3d_getidx(data, model->bi_s, &model->skin[i].boneid[j]); } @@ -3244,7 +3256,8 @@ memerr: M3D_LOG("Out of memory"); M3D_GETSTR(name); m->prop[i].value.textureid = _m3d_gettx(model, readfilecb, freecb, name); if(model->errcode == M3D_ERR_ALLOC) goto memerr; - if(m->prop[i].value.textureid == (M3D_INDEX)-1U) { + /* this error code only returned if readfilecb was specified */ + if(m->prop[i].value.textureid == M3D_UNDEF) { M3D_LOG("Texture not found"); M3D_LOG(m->name); m->numprop--; @@ -3275,7 +3288,7 @@ memerr: M3D_LOG("Out of memory"); M3D_LOG("Mesh data"); /* mesh */ data += sizeof(m3dchunk_t); - mi = (M3D_INDEX)-1U; + mi = M3D_UNDEF; am = model->numface; while(data < chunk) { k = *data++; @@ -3283,7 +3296,7 @@ memerr: M3D_LOG("Out of memory"); k &= 15; if(!n) { /* use material */ - mi = (M3D_INDEX)-1U; + mi = M3D_UNDEF; M3D_GETSTR(name); if(name) { for(j = 0; j < model->nummaterial; j++) @@ -3291,7 +3304,7 @@ memerr: M3D_LOG("Out of memory"); mi = (M3D_INDEX)j; break; } - if(mi == (M3D_INDEX)-1U) model->errcode = M3D_ERR_MTRL; + if(mi == M3D_UNDEF) model->errcode = M3D_ERR_MTRL; } continue; } @@ -3314,7 +3327,7 @@ memerr: M3D_LOG("Out of memory"); if(k & 2) data = _m3d_getidx(data, model->vi_s, &model->face[i].normal[j]); #ifndef M3D_NONORMALS - if(model->face[i].normal[j] == (M3D_INDEX)-1U) neednorm = 1; + if(model->face[i].normal[j] == M3D_UNDEF) neednorm = 1; #endif } } @@ -3333,12 +3346,12 @@ memerr: M3D_LOG("Out of memory"); h->numcmd = 0; h->cmd = NULL; h->name = name; - h->group = (M3D_INDEX)-1U; + h->group = M3D_UNDEF; data = _m3d_getidx(data, model->bi_s, &h->group); - if(h->group != (M3D_INDEX)-1U && h->group >= model->numbone) { + if(h->group != M3D_UNDEF && h->group >= model->numbone) { M3D_LOG("Unknown bone id as shape group in shape"); M3D_LOG(name); - h->group = (M3D_INDEX)-1U; + h->group = M3D_UNDEF; model->errcode = M3D_ERR_SHPE; } while(data < chunk) { @@ -3363,7 +3376,7 @@ memerr: M3D_LOG("Out of memory"); for(k = n = 0, l = cd->p; k < l; k++) switch(cd->a[((k - n) % (cd->p - n)) + n]) { case m3dcp_mi_t: - h->cmd[i].arg[k] = -1U; + h->cmd[i].arg[k] = M3D_NOTDEFINED; M3D_GETSTR(name); if(name) { for(n = 0; n < model->nummaterial; n++) @@ -3371,7 +3384,7 @@ memerr: M3D_LOG("Out of memory"); h->cmd[i].arg[k] = n; break; } - if(h->cmd[i].arg[k] == -1U) model->errcode = M3D_ERR_MTRL; + if(h->cmd[i].arg[k] == M3D_NOTDEFINED) model->errcode = M3D_ERR_MTRL; } break; case m3dcp_vc_t: @@ -3488,7 +3501,7 @@ postprocess: norm = (m3dv_t*)M3D_MALLOC(model->numface * sizeof(m3dv_t)); if(!norm) goto memerr; for(i = 0, n = model->numvertex; i < model->numface; i++) - if(model->face[i].normal[0] == -1U) { + if(model->face[i].normal[0] == M3D_UNDEF) { v0 = &model->vertex[model->face[i].vertex[0]]; v1 = &model->vertex[model->face[i].vertex[1]]; v2 = &model->vertex[model->face[i].vertex[2]]; @@ -3522,7 +3535,7 @@ postprocess: for(i = 0, v0 = &model->vertex[n]; i < n; i++, v0++) { w = _m3d_rsq((v0->x * v0->x) + (v0->y * v0->y) + (v0->z * v0->z)); v0->x *= w; v0->y *= w; v0->z *= w; - v0->skinid = -1U; + v0->skinid = M3D_UNDEF; } M3D_FREE(norm); } @@ -3534,9 +3547,9 @@ postprocess: if(model->vertex[i].skinid < model->numskin) { sk = &model->skin[model->vertex[i].skinid]; w = (M3D_FLOAT)0.0; - for(j = 0; j < M3D_NUMBONE && sk->boneid[j] != (M3D_INDEX)-1U && sk->weight[j] > (M3D_FLOAT)0.0; j++) + for(j = 0; j < M3D_NUMBONE && sk->boneid[j] != M3D_UNDEF && sk->weight[j] > (M3D_FLOAT)0.0; j++) w += sk->weight[j]; - for(j = 0; j < M3D_NUMBONE && sk->boneid[j] != (M3D_INDEX)-1U && sk->weight[j] > (M3D_FLOAT)0.0; j++) { + for(j = 0; j < M3D_NUMBONE && sk->boneid[j] != M3D_UNDEF && sk->weight[j] > (M3D_FLOAT)0.0; j++) { sk->weight[j] /= w; b = &model->bone[sk->boneid[j]]; k = b->numweight++; @@ -3552,7 +3565,7 @@ postprocess: M3D_LOG("Calculating bone transformation matrices"); for(i = 0; i < model->numbone; i++) { b = &model->bone[i]; - if(model->bone[i].parent == (M3D_INDEX)-1U) { + if(model->bone[i].parent == M3D_UNDEF) { _m3d_mat((M3D_FLOAT*)&b->mat4, &model->vertex[b->pos], &model->vertex[b->ori]); } else { _m3d_mat((M3D_FLOAT*)&r, &model->vertex[b->pos], &model->vertex[b->ori]); @@ -3583,7 +3596,7 @@ m3dtr_t *m3d_frame(m3d_t *model, M3D_INDEX actionid, M3D_INDEX frameid, m3dtr_t M3D_INDEX s = frameid; m3dfr_t *fr; - if(!model || !model->numbone || !model->bone || (actionid != (M3D_INDEX)-1U && (!model->action || + if(!model || !model->numbone || !model->bone || (actionid != M3D_UNDEF && (!model->action || actionid >= model->numaction || frameid >= model->action[actionid].numframe))) { model->errcode = M3D_ERR_UNKFRAME; return skeleton; @@ -3597,7 +3610,7 @@ m3dtr_t *m3d_frame(m3d_t *model, M3D_INDEX actionid, M3D_INDEX frameid, m3dtr_t } goto gen; } - if(actionid == (M3D_INDEX)-1U || !frameid) { + if(actionid == M3D_UNDEF || !frameid) { gen: s = 0; for(i = 0; i < model->numbone; i++) { skeleton[i].boneid = i; @@ -3721,7 +3734,7 @@ m3db_t *m3d_pose(m3d_t *model, M3D_INDEX actionid, uint32_t msec) } } for(i = 0; i < model->numbone; i++) { - if(ret[i].parent == (M3D_INDEX)-1U) { + if(ret[i].parent == M3D_UNDEF) { _m3d_mat((M3D_FLOAT*)&ret[i].mat4, &model->vertex[ret[i].pos], &model->vertex[ret[i].ori]); } else { _m3d_mat((M3D_FLOAT*)&r, &model->vertex[ret[i].pos], &model->vertex[ret[i].ori]); @@ -3904,7 +3917,7 @@ m3dhdr_t *_m3d_addhdr(m3dhdr_t *h, m3dstr_t *s) { int i; char *safe = _m3d_safestr(s->str, 0); - i = strlen(safe); + i = (int)strlen(safe); h = (m3dhdr_t*)M3D_REALLOC(h, h->length + i+1); if(!h) { M3D_FREE(safe); return NULL; } memcpy((uint8_t*)h + h->length, safe, i+1); @@ -4033,16 +4046,16 @@ static void _m3d_round(int quality, m3dv_t *src, m3dv_t *dst) /* round according to quality */ switch(quality) { case M3D_EXP_INT8: - t = src->x * 127 + (src->x >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5); dst->x = (M3D_FLOAT)t / 127; - t = src->y * 127 + (src->y >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5); dst->y = (M3D_FLOAT)t / 127; - t = src->z * 127 + (src->z >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5); dst->z = (M3D_FLOAT)t / 127; - t = src->w * 127 + (src->w >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5); dst->w = (M3D_FLOAT)t / 127; + t = (int)(src->x * 127 + (src->x >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5)); dst->x = (M3D_FLOAT)t / (M3D_FLOAT)127.0; + t = (int)(src->y * 127 + (src->y >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5)); dst->y = (M3D_FLOAT)t / (M3D_FLOAT)127.0; + t = (int)(src->z * 127 + (src->z >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5)); dst->z = (M3D_FLOAT)t / (M3D_FLOAT)127.0; + t = (int)(src->w * 127 + (src->w >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5)); dst->w = (M3D_FLOAT)t / (M3D_FLOAT)127.0; break; case M3D_EXP_INT16: - t = src->x * 32767 + (src->x >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5); dst->x = (M3D_FLOAT)t / 32767; - t = src->y * 32767 + (src->y >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5); dst->y = (M3D_FLOAT)t / 32767; - t = src->z * 32767 + (src->z >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5); dst->z = (M3D_FLOAT)t / 32767; - t = src->w * 32767 + (src->w >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5); dst->w = (M3D_FLOAT)t / 32767; + t = (int)(src->x * 32767 + (src->x >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5)); dst->x = (M3D_FLOAT)t / (M3D_FLOAT)32767.0; + t = (int)(src->y * 32767 + (src->y >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5)); dst->y = (M3D_FLOAT)t / (M3D_FLOAT)32767.0; + t = (int)(src->z * 32767 + (src->z >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5)); dst->z = (M3D_FLOAT)t / (M3D_FLOAT)32767.0; + t = (int)(src->w * 32767 + (src->w >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5)); dst->w = (M3D_FLOAT)t / (M3D_FLOAT)32767.0; break; } if(dst->x == (M3D_FLOAT)-0.0) dst->x = (M3D_FLOAT)0.0; @@ -4160,7 +4173,7 @@ unsigned char *m3d_save(m3d_t *model, int quality, int flags, unsigned int *size } face[i].opacity = opa[model->face[i].materialid * 2 + 1]; } else - face[i].data.materialid = (M3D_INDEX)-1U; + face[i].data.materialid = M3D_UNDEF; } for(j = 0; j < 3; j++) { k = model->face[i].vertex[j]; @@ -4311,7 +4324,7 @@ unsigned char *m3d_save(m3d_t *model, int quality, int flags, unsigned int *size if(!(flags & M3D_EXP_NOMATERIAL)) { M3D_LOG("Processing materials"); for(i = k = 0; i < model->nummaterial; i++) { - if(mtrlidx[i] == (M3D_INDEX)-1U || !model->material[i].numprop) continue; + if(mtrlidx[i] == M3D_UNDEF || !model->material[i].numprop) continue; mtrlidx[i] = k++; m = &model->material[i]; str = _m3d_addstr(str, &numstr, m->name); @@ -4345,15 +4358,15 @@ unsigned char *m3d_save(m3d_t *model, int quality, int flags, unsigned int *size tmap = (m3dtisave_t*)M3D_MALLOC(model->numtmap * sizeof(m3dtisave_t)); if(!tmap) goto memerr; for(i = 0; i < model->numtmap; i++) { - if(tmapidx[i] == (M3D_INDEX)-1U) continue; + if(tmapidx[i] == M3D_UNDEF) continue; switch(quality) { case M3D_EXP_INT8: - l = model->tmap[i].u * 255; tcoord.data.u = (M3D_FLOAT)l / 255; - l = model->tmap[i].v * 255; tcoord.data.v = (M3D_FLOAT)l / 255; + l = (unsigned int)(model->tmap[i].u * 255); tcoord.data.u = (M3D_FLOAT)l / (M3D_FLOAT)255.0; + l = (unsigned int)(model->tmap[i].v * 255); tcoord.data.v = (M3D_FLOAT)l / (M3D_FLOAT)255.0; break; case M3D_EXP_INT16: - l = model->tmap[i].u * 65535; tcoord.data.u = (M3D_FLOAT)l / 65535; - l = model->tmap[i].v * 65535; tcoord.data.v = (M3D_FLOAT)l / 65535; + l = (unsigned int)(model->tmap[i].u * 65535); tcoord.data.u = (M3D_FLOAT)l / (M3D_FLOAT)65535.0; + l = (unsigned int)(model->tmap[i].v * 65535); tcoord.data.v = (M3D_FLOAT)l / (M3D_FLOAT)65535.0; break; default: tcoord.data.u = model->tmap[i].u; @@ -4387,13 +4400,13 @@ unsigned char *m3d_save(m3d_t *model, int quality, int flags, unsigned int *size if(!skin) goto memerr; memset(skinidx, 255, model->numskin * sizeof(M3D_INDEX)); for(i = 0; i < model->numvertex; i++) { - if(vrtxidx[i] != (M3D_INDEX)-1U && model->vertex[i].skinid < model->numskin) + if(vrtxidx[i] != M3D_UNDEF && model->vertex[i].skinid < model->numskin) skinidx[model->vertex[i].skinid] = 0; } for(i = 0; i < model->numskin; i++) { - if(skinidx[i] == (M3D_INDEX)-1U) continue; + if(skinidx[i] == M3D_UNDEF) continue; memset(&sk, 0, sizeof(m3dssave_t)); - for(j = 0, min_x = (M3D_FLOAT)0.0; j < M3D_NUMBONE && model->skin[i].boneid[j] != (M3D_INDEX)-1U && + for(j = 0, min_x = (M3D_FLOAT)0.0; j < M3D_NUMBONE && model->skin[i].boneid[j] != M3D_UNDEF && model->skin[i].weight[j] > (M3D_FLOAT)0.0; j++) { sk.data.boneid[j] = model->skin[i].boneid[j]; sk.data.weight[j] = model->skin[i].weight[j]; @@ -4428,11 +4441,11 @@ unsigned char *m3d_save(m3d_t *model, int quality, int flags, unsigned int *size vrtx = (m3dvsave_t*)M3D_MALLOC(model->numvertex * sizeof(m3dvsave_t)); if(!vrtx) goto memerr; for(i = numvrtx = 0; i < model->numvertex; i++) { - if(vrtxidx[i] == (M3D_INDEX)-1U) continue; + if(vrtxidx[i] == M3D_UNDEF) continue; _m3d_round(quality, &model->vertex[i], &vertex.data); vertex.norm = norm ? norm[i] : 0; - if(vertex.data.skinid != (M3D_INDEX)-2U && !vertex.norm) { - vertex.data.skinid = vertex.data.skinid != (M3D_INDEX)-1U && skinidx ? skinidx[vertex.data.skinid] : (M3D_INDEX)-1U; + if(vertex.data.skinid != M3D_INDEXMAX && !vertex.norm) { + vertex.data.skinid = vertex.data.skinid != M3D_UNDEF && skinidx ? skinidx[vertex.data.skinid] : M3D_UNDEF; if(vertex.data.x > max_x) max_x = vertex.data.x; if(vertex.data.x < min_x) min_x = vertex.data.x; if(vertex.data.y > max_y) max_y = vertex.data.y; @@ -4481,7 +4494,7 @@ unsigned char *m3d_save(m3d_t *model, int quality, int flags, unsigned int *size if(scale == (M3D_FLOAT)0.0) scale = (M3D_FLOAT)1.0; if(scale != (M3D_FLOAT)1.0) { for(i = 0; i < numvrtx; i++) { - if(vrtx[i].data.skinid == (M3D_INDEX)-2U) continue; + if(vrtx[i].data.skinid == M3D_INDEXMAX) continue; vrtx[i].data.x /= scale; vrtx[i].data.y /= scale; vrtx[i].data.z /= scale; @@ -4528,7 +4541,7 @@ memerr: if(vrtxidx) M3D_FREE(vrtxidx); ol = setlocale(LC_NUMERIC, NULL); setlocale(LC_NUMERIC, "C"); /* header */ - len = 64 + strlen(sn) + strlen(sl) + strlen(sa) + strlen(sd); + len = 64 + (unsigned int)(strlen(sn) + strlen(sl) + strlen(sa) + strlen(sd)); out = (unsigned char*)M3D_MALLOC(len); if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } ptr = (char*)out; @@ -4540,7 +4553,7 @@ memerr: if(vrtxidx) M3D_FREE(vrtxidx); if(model->preview.data && model->preview.length) { sl = _m3d_safestr(sn, 0); if(sl) { - ptr -= (uintptr_t)out; len = (uintptr_t)ptr + 20; + ptr -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)20); out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uintptr_t)out; if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } ptr += sprintf(ptr, "Preview\r\n%s.png\r\n\r\n", sl); @@ -4550,11 +4563,11 @@ memerr: if(vrtxidx) M3D_FREE(vrtxidx); M3D_FREE(sn); sn = NULL; /* texture map */ if(numtmap && tmap && !(flags & M3D_EXP_NOTXTCRD) && !(flags & M3D_EXP_NOFACE)) { - ptr -= (uintptr_t)out; len = (uintptr_t)ptr + maxtmap * 32 + 12; + ptr -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)(maxtmap * 32) + (uintptr_t)12); out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uintptr_t)out; if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } ptr += sprintf(ptr, "Textmap\r\n"); - last = (M3D_INDEX)-1U; + last = M3D_UNDEF; for(i = 0; i < numtmap; i++) { if(tmap[i].newidx == last) continue; last = tmap[i].newidx; @@ -4564,11 +4577,11 @@ memerr: if(vrtxidx) M3D_FREE(vrtxidx); } /* vertex chunk */ if(numvrtx && vrtx && !(flags & M3D_EXP_NOFACE)) { - ptr -= (uintptr_t)out; len = (uintptr_t)ptr + maxvrtx * 128 + 10; + ptr -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)(maxvrtx * 128) + (uintptr_t)10); out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uintptr_t)out; if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } ptr += sprintf(ptr, "Vertex\r\n"); - last = (M3D_INDEX)-1U; + last = M3D_UNDEF; for(i = 0; i < numvrtx; i++) { if(vrtx[i].newidx == last) continue; last = vrtx[i].newidx; @@ -4579,7 +4592,7 @@ memerr: if(vrtxidx) M3D_FREE(vrtxidx); if(skin[vrtx[i].data.skinid].data.weight[0] == (M3D_FLOAT)1.0) ptr += sprintf(ptr, " %d", skin[vrtx[i].data.skinid].data.boneid[0]); else - for(j = 0; j < M3D_NUMBONE && skin[vrtx[i].data.skinid].data.boneid[j] != (M3D_INDEX)-1U && + for(j = 0; j < M3D_NUMBONE && skin[vrtx[i].data.skinid].data.boneid[j] != M3D_UNDEF && skin[vrtx[i].data.skinid].data.weight[j] > (M3D_FLOAT)0.0; j++) ptr += sprintf(ptr, " %d:%g", skin[vrtx[i].data.skinid].data.boneid[j], skin[vrtx[i].data.skinid].data.weight[j]); @@ -4590,29 +4603,29 @@ memerr: if(vrtxidx) M3D_FREE(vrtxidx); } /* bones chunk */ if(model->numbone && model->bone && !(flags & M3D_EXP_NOBONE)) { - ptr -= (uintptr_t)out; len = (uintptr_t)ptr + 9; + ptr -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)9); for(i = 0; i < model->numbone; i++) { - len += strlen(model->bone[i].name) + 128; + len += (unsigned int)strlen(model->bone[i].name) + 128; } out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uintptr_t)out; if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } ptr += sprintf(ptr, "Bones\r\n"); - ptr = _m3d_prtbone(ptr, model->bone, model->numbone, (M3D_INDEX)-1U, 0, vrtxidx); + ptr = _m3d_prtbone(ptr, model->bone, model->numbone, M3D_UNDEF, 0, vrtxidx); ptr += sprintf(ptr, "\r\n"); } /* materials */ if(model->nummaterial && !(flags & M3D_EXP_NOMATERIAL)) { for(j = 0; j < model->nummaterial; j++) { - if(mtrlidx[j] == (M3D_INDEX)-1U || !model->material[j].numprop || !model->material[j].prop) continue; + if(mtrlidx[j] == M3D_UNDEF || !model->material[j].numprop || !model->material[j].prop) continue; m = &model->material[j]; sn = _m3d_safestr(m->name, 0); if(!sn) { setlocale(LC_NUMERIC, ol); goto memerr; } - ptr -= (uintptr_t)out; len = (uintptr_t)ptr + strlen(sn) + 12; + ptr -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)strlen(sn) + (uintptr_t)12); for(i = 0; i < m->numprop; i++) { if(m->prop[i].type < 128) len += 32; else if(m->prop[i].value.textureid < model->numtexture && model->texture[m->prop[i].value.textureid].name) - len += strlen(model->texture[m->prop[i].value.textureid].name) + 16; + len += (unsigned int)strlen(model->texture[m->prop[i].value.textureid].name) + 16; } out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uintptr_t)out; if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } @@ -4676,7 +4689,7 @@ memerr: if(vrtxidx) M3D_FREE(vrtxidx); if(k) continue; sn = _m3d_safestr(model->inlined[j].name, 0); if(!sn) { setlocale(LC_NUMERIC, ol); goto memerr; } - ptr -= (uintptr_t)out; len = (uintptr_t)ptr + strlen(sn) + 18; + ptr -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)strlen(sn) + (uintptr_t)18); out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uintptr_t)out; if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } ptr += sprintf(ptr, "Procedural\r\n%s\r\n\r\n", sn); @@ -4685,24 +4698,24 @@ memerr: if(vrtxidx) M3D_FREE(vrtxidx); } /* mesh face */ if(model->numface && face && !(flags & M3D_EXP_NOFACE)) { - ptr -= (uintptr_t)out; len = (uintptr_t)ptr + model->numface * 128 + 6; - last = (M3D_INDEX)-1U; + ptr -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)(model->numface * 128) + (uintptr_t)6); + last = M3D_UNDEF; if(!(flags & M3D_EXP_NOMATERIAL)) for(i = 0; i < model->numface; i++) { - j = face[i].data.materialid < model->nummaterial ? face[i].data.materialid : (M3D_INDEX)-1U; + j = face[i].data.materialid < model->nummaterial ? face[i].data.materialid : M3D_UNDEF; if(j != last) { last = j; if(last < model->nummaterial) - len += strlen(model->material[last].name); + len += (unsigned int)strlen(model->material[last].name); len += 6; } } out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uintptr_t)out; if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } ptr += sprintf(ptr, "Mesh\r\n"); - last = (M3D_INDEX)-1U; + last = M3D_UNDEF; for(i = 0; i < model->numface; i++) { - j = face[i].data.materialid < model->nummaterial ? face[i].data.materialid : (M3D_INDEX)-1U; + j = face[i].data.materialid < model->nummaterial ? face[i].data.materialid : M3D_UNDEF; if(!(flags & M3D_EXP_NOMATERIAL) && j != last) { last = j; if(last < model->nummaterial) { @@ -4716,14 +4729,14 @@ memerr: if(vrtxidx) M3D_FREE(vrtxidx); /* hardcoded triangles. Should be repeated as many times as the number of edges in polygon */ for(j = 0; j < 3; j++) { ptr += sprintf(ptr, "%s%d", j?" ":"", vrtxidx[face[i].data.vertex[j]]); - k = -1U; - if(!(flags & M3D_EXP_NOTXTCRD) && (face[i].data.texcoord[j] != (M3D_INDEX)-1U) && - (tmapidx[face[i].data.texcoord[j]] != (M3D_INDEX)-1U)) { + k = M3D_NOTDEFINED; + if(!(flags & M3D_EXP_NOTXTCRD) && (face[i].data.texcoord[j] != M3D_UNDEF) && + (tmapidx[face[i].data.texcoord[j]] != M3D_UNDEF)) { k = tmapidx[face[i].data.texcoord[j]]; ptr += sprintf(ptr, "/%d", k); } - if(!(flags & M3D_EXP_NONORMAL) && (face[i].data.normal[j] != (M3D_INDEX)-1U)) - ptr += sprintf(ptr, "%s/%d", k == -1U? "/" : "", vrtxidx[face[i].data.normal[j]]); + if(!(flags & M3D_EXP_NONORMAL) && (face[i].data.normal[j] != M3D_UNDEF)) + ptr += sprintf(ptr, "%s/%d", k == M3D_NOTDEFINED? "/" : "", vrtxidx[face[i].data.normal[j]]); } ptr += sprintf(ptr, "\r\n"); } @@ -4734,22 +4747,22 @@ memerr: if(vrtxidx) M3D_FREE(vrtxidx); for(j = 0; j < model->numshape; j++) { sn = _m3d_safestr(model->shape[j].name, 0); if(!sn) { setlocale(LC_NUMERIC, ol); goto memerr; } - ptr -= (uintptr_t)out; len = (uintptr_t)ptr + strlen(sn) + 33; + ptr -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)strlen(sn) + (uintptr_t)33); out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uintptr_t)out; if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } ptr += sprintf(ptr, "Shape %s\r\n", sn); M3D_FREE(sn); sn = NULL; - if(model->shape[j].group != (M3D_INDEX)-1U && !(flags & M3D_EXP_NOBONE)) + if(model->shape[j].group != M3D_UNDEF && !(flags & M3D_EXP_NOBONE)) ptr += sprintf(ptr, "group %d\r\n", model->shape[j].group); for(i = 0; i < model->shape[j].numcmd; i++) { cmd = &model->shape[j].cmd[i]; if(cmd->type >= (unsigned int)(sizeof(m3d_commandtypes)/sizeof(m3d_commandtypes[0])) || !cmd->arg) continue; cd = &m3d_commandtypes[cmd->type]; - ptr -= (uintptr_t)out; len = (uintptr_t)ptr + strlen(cd->key) + 3; + ptr -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)strlen(cd->key) + (uintptr_t)3); for(k = 0; k < cd->p; k++) switch(cd->a[k]) { - case m3dcp_mi_t: if(cmd->arg[k] != -1U) { len += strlen(model->material[cmd->arg[k]].name) + 1; } break; + case m3dcp_mi_t: if(cmd->arg[k] != M3D_NOTDEFINED) { len += (unsigned int)strlen(model->material[cmd->arg[k]].name) + 1; } break; case m3dcp_va_t: len += cmd->arg[k] * (cd->p - k - 1) * 16; k = cd->p; break; default: len += 16; break; } @@ -4759,7 +4772,7 @@ memerr: if(vrtxidx) M3D_FREE(vrtxidx); for(k = n = 0, l = cd->p; k < l; k++) { switch(cd->a[((k - n) % (cd->p - n)) + n]) { case m3dcp_mi_t: - if(cmd->arg[k] != -1U) { + if(cmd->arg[k] != M3D_NOTDEFINED) { sn = _m3d_safestr(model->material[cmd->arg[k]].name, 0); if(!sn) { setlocale(LC_NUMERIC, ol); goto memerr; } ptr += sprintf(ptr, " %s", sn); @@ -4781,12 +4794,12 @@ memerr: if(vrtxidx) M3D_FREE(vrtxidx); /* annotation labels */ if(model->numlabel && model->label && !(flags & M3D_EXP_NOFACE)) { for(i = 0, j = 3, length = NULL; i < model->numlabel; i++) { - if(model->label[i].name) j += strlen(model->label[i].name); - if(model->label[i].lang) j += strlen(model->label[i].lang); - if(model->label[i].text) j += strlen(model->label[i].text); + if(model->label[i].name) j += (unsigned int)strlen(model->label[i].name); + if(model->label[i].lang) j += (unsigned int)strlen(model->label[i].lang); + if(model->label[i].text) j += (unsigned int)strlen(model->label[i].text); j += 40; } - ptr -= (uintptr_t)out; len = (uintptr_t)ptr + j; + ptr -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)j); out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uintptr_t)out; if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } for(i = 0; i < model->numlabel; i++) { @@ -4821,7 +4834,7 @@ memerr: if(vrtxidx) M3D_FREE(vrtxidx); a = &model->action[j]; sn = _m3d_safestr(a->name, 0); if(!sn) { setlocale(LC_NUMERIC, ol); goto memerr; } - ptr -= (uintptr_t)out; len = (uintptr_t)ptr + strlen(sn) + 48; + ptr -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)strlen(sn) + (uintptr_t)48); for(i = 0; i < a->numframe; i++) len += a->frame[i].numtransform * 128 + 8; out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uintptr_t)out; @@ -4842,9 +4855,9 @@ memerr: if(vrtxidx) M3D_FREE(vrtxidx); if(model->numinlined && model->inlined) { for(i = j = 0; i < model->numinlined; i++) if(model->inlined[i].name) - j += strlen(model->inlined[i].name) + 6; + j += (unsigned int)strlen(model->inlined[i].name) + 6; if(j > 0) { - ptr -= (uintptr_t)out; len = (uintptr_t)ptr + j + 16; + ptr -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)j + (uintptr_t)16); out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uintptr_t)out; if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } ptr += sprintf(ptr, "Assets\r\n"); @@ -4858,7 +4871,7 @@ memerr: if(vrtxidx) M3D_FREE(vrtxidx); if(model->numextra && (flags & M3D_EXP_EXTRA)) { for(i = 0; i < model->numextra; i++) { if(model->extra[i]->length < 9) continue; - ptr -= (uintptr_t)out; len = (uintptr_t)ptr + 17 + model->extra[i]->length * 3; + ptr -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)17 + (uintptr_t)(model->extra[i]->length * 3)); out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uintptr_t)out; if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } ptr += sprintf(ptr, "Extra %c%c%c%c\r\n", @@ -4873,7 +4886,7 @@ memerr: if(vrtxidx) M3D_FREE(vrtxidx); } } setlocale(LC_NUMERIC, ol); - len = (uintptr_t)ptr - (uintptr_t)out; + len = (unsigned int)((uintptr_t)ptr - (uintptr_t)out); out = (unsigned char*)M3D_REALLOC(out, len + 1); if(!out) goto memerr; out[len] = 0; @@ -4889,10 +4902,10 @@ memerr: if(vrtxidx) M3D_FREE(vrtxidx); memcpy((uint8_t*)h, "HEAD", 4); h->length = sizeof(m3dhdr_t); h->scale = scale; - i = strlen(sn); memcpy((uint8_t*)h + h->length, sn, i+1); h->length += i+1; M3D_FREE(sn); - i = strlen(sl); memcpy((uint8_t*)h + h->length, sl, i+1); h->length += i+1; M3D_FREE(sl); - i = strlen(sa); memcpy((uint8_t*)h + h->length, sa, i+1); h->length += i+1; M3D_FREE(sa); - i = strlen(sd); memcpy((uint8_t*)h + h->length, sd, i+1); h->length += i+1; M3D_FREE(sd); + i = (unsigned int)strlen(sn); memcpy((uint8_t*)h + h->length, sn, i+1); h->length += i+1; M3D_FREE(sn); + i = (unsigned int)strlen(sl); memcpy((uint8_t*)h + h->length, sl, i+1); h->length += i+1; M3D_FREE(sl); + i = (unsigned int)strlen(sa); memcpy((uint8_t*)h + h->length, sa, i+1); h->length += i+1; M3D_FREE(sa); + i = (unsigned int)strlen(sd); memcpy((uint8_t*)h + h->length, sd, i+1); h->length += i+1; M3D_FREE(sd); sn = sl = sa = sd = NULL; if(model->inlined) for(i = 0; i < model->numinlined; i++) { @@ -4960,7 +4973,7 @@ memerr: if(vrtxidx) M3D_FREE(vrtxidx); memcpy((uint8_t*)h + len, "TMAP", 4); length = (uint32_t*)((uint8_t*)h + len + 4); out = (uint8_t*)h + len + 8; - last = (M3D_INDEX)-1U; + last = M3D_UNDEF; for(i = 0; i < numtmap; i++) { if(tmap[i].newidx == last) continue; last = tmap[i].newidx; @@ -4974,7 +4987,7 @@ memerr: if(vrtxidx) M3D_FREE(vrtxidx); case 8: *((double*)out) = tmap[i].data.u; out += 8; *((double*)out) = tmap[i].data.v; out += 8; break; } } - *length = (uintptr_t)out - (uintptr_t)((uint8_t*)h + len); + *length = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t*)h + len)); out = NULL; len += *length; } @@ -4986,7 +4999,7 @@ memerr: if(vrtxidx) M3D_FREE(vrtxidx); memcpy((uint8_t*)h + len, "VRTS", 4); length = (uint32_t*)((uint8_t*)h + len + 4); out = (uint8_t*)h + len + 8; - last = (M3D_INDEX)-1U; + last = M3D_UNDEF; for(i = 0; i < numvrtx; i++) { if(vrtx[i].newidx == last) continue; last = vrtx[i].newidx; @@ -5024,7 +5037,7 @@ memerr: if(vrtxidx) M3D_FREE(vrtxidx); } out = _m3d_addidx(out, sk_s, vrtx[i].data.skinid); } - *length = (uintptr_t)out - (uintptr_t)((uint8_t*)h + len); + *length = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t*)h + len)); out = NULL; len += *length; } @@ -5046,12 +5059,12 @@ memerr: if(vrtxidx) M3D_FREE(vrtxidx); out = _m3d_addidx(out, vi_s, vrtxidx[model->bone[i].ori]); } if(numskin && skin && sk_s) { - last = (M3D_INDEX)-1U; + last = M3D_UNDEF; for(i = 0; i < numskin; i++) { if(skin[i].newidx == last) continue; last = skin[i].newidx; memset(&weights, 0, nb_s); - for(j = 0; j < (uint32_t)nb_s && skin[i].data.boneid[j] != (M3D_INDEX)-1U && + for(j = 0; j < (uint32_t)nb_s && skin[i].data.boneid[j] != M3D_UNDEF && skin[i].data.weight[j] > (M3D_FLOAT)0.0; j++) weights[j] = (uint8_t)(skin[i].data.weight[j] * 255); switch(nb_s) { @@ -5060,20 +5073,20 @@ memerr: if(vrtxidx) M3D_FREE(vrtxidx); case 4: *((uint32_t*)out) = *((uint32_t*)&weights[0]); out += 4; break; case 8: *((uint64_t*)out) = *((uint64_t*)&weights[0]); out += 8; break; } - for(j = 0; j < (uint32_t)nb_s && skin[i].data.boneid[j] != (M3D_INDEX)-1U && weights[j]; j++) { + for(j = 0; j < (uint32_t)nb_s && skin[i].data.boneid[j] != M3D_UNDEF && weights[j]; j++) { out = _m3d_addidx(out, bi_s, skin[i].data.boneid[j]); *length += bi_s; } } } - *length = (uintptr_t)out - (uintptr_t)((uint8_t*)h + len); + *length = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t*)h + len)); out = NULL; len += *length; } /* materials */ if(model->nummaterial && !(flags & M3D_EXP_NOMATERIAL)) { for(j = 0; j < model->nummaterial; j++) { - if(mtrlidx[j] == (M3D_INDEX)-1U || !model->material[j].numprop || !model->material[j].prop) continue; + if(mtrlidx[j] == M3D_UNDEF || !model->material[j].numprop || !model->material[j].prop) continue; m = &model->material[j]; chunklen = 12 + si_s + m->numprop * 5; h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen); @@ -5115,7 +5128,7 @@ memerr: if(vrtxidx) M3D_FREE(vrtxidx); break; } } - *length = (uintptr_t)out - (uintptr_t)((uint8_t*)h + len); + *length = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t*)h + len)); len += *length; out = NULL; } @@ -5152,7 +5165,7 @@ memerr: if(vrtxidx) M3D_FREE(vrtxidx); memcpy((uint8_t*)h + len, "MESH", 4); length = (uint32_t*)((uint8_t*)h + len + 4); out = (uint8_t*)h + len + 8; - last = (M3D_INDEX)-1U; + last = M3D_UNDEF; for(i = 0; i < model->numface; i++) { if(!(flags & M3D_EXP_NOMATERIAL) && face[i].data.materialid != last) { last = face[i].data.materialid; @@ -5162,10 +5175,10 @@ memerr: if(vrtxidx) M3D_FREE(vrtxidx); } /* hardcoded triangles. */ k = (3 << 4) | - (((flags & M3D_EXP_NOTXTCRD) || !ti_s || face[i].data.texcoord[0] == (M3D_INDEX)-1U || - face[i].data.texcoord[1] == (M3D_INDEX)-1U || face[i].data.texcoord[2] == (M3D_INDEX)-1U) ? 0 : 1) | - (((flags & M3D_EXP_NONORMAL) || face[i].data.normal[0] == (M3D_INDEX)-1U || - face[i].data.normal[1] == (M3D_INDEX)-1U || face[i].data.normal[2] == (M3D_INDEX)-1U) ? 0 : 2); + (((flags & M3D_EXP_NOTXTCRD) || !ti_s || face[i].data.texcoord[0] == M3D_UNDEF || + face[i].data.texcoord[1] == M3D_UNDEF || face[i].data.texcoord[2] == M3D_UNDEF) ? 0 : 1) | + (((flags & M3D_EXP_NONORMAL) || face[i].data.normal[0] == M3D_UNDEF || + face[i].data.normal[1] == M3D_UNDEF || face[i].data.normal[2] == M3D_UNDEF) ? 0 : 2); *out++ = k; for(j = 0; j < 3; j++) { out = _m3d_addidx(out, vi_s, vrtxidx[face[i].data.vertex[j]]); @@ -5175,7 +5188,7 @@ memerr: if(vrtxidx) M3D_FREE(vrtxidx); out = _m3d_addidx(out, vi_s, vrtxidx[face[i].data.normal[j]]); } } - *length = (uintptr_t)out - (uintptr_t)((uint8_t*)h + len); + *length = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t*)h + len)); len += *length; out = NULL; } @@ -5226,7 +5239,7 @@ memerr: if(vrtxidx) M3D_FREE(vrtxidx); } } } - *length = (uintptr_t)out - (uintptr_t)((uint8_t*)h + len); + *length = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t*)h + len)); len += *length; out = NULL; } @@ -5238,7 +5251,7 @@ memerr: if(vrtxidx) M3D_FREE(vrtxidx); sl = model->label[i].lang; sn = model->label[i].name; if(length) { - *length = (uintptr_t)out - (uintptr_t)((uint8_t*)h + len); + *length = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t*)h + len)); len += *length; } chunklen = 8 + 2 * si_s + ci_s + model->numlabel * (vi_s + si_s); @@ -5260,7 +5273,7 @@ memerr: if(vrtxidx) M3D_FREE(vrtxidx); out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->label[l].text)); } if(length) { - *length = (uintptr_t)out - (uintptr_t)((uint8_t*)h + len); + *length = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t*)h + len)); len += *length; } out = NULL; @@ -5288,7 +5301,7 @@ memerr: if(vrtxidx) M3D_FREE(vrtxidx); out = _m3d_addidx(out, vi_s, vrtxidx[a->frame[i].transform[k].ori]); } } - *length = (uintptr_t)out - (uintptr_t)((uint8_t*)h + len); + *length = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t*)h + len)); len += *length; out = NULL; } diff --git a/code/MDL/HalfLife/HL1FileData.h b/code/MDL/HalfLife/HL1FileData.h new file mode 100644 index 000000000..4248b1237 --- /dev/null +++ b/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 +#include + +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 + +#if (!defined AI_MDL_HL1_VERSION) +#define AI_MDL_HL1_VERSION 10 +#endif +#if (!defined AI_MDL_HL1_MAX_TRIANGLES) +#define AI_MDL_HL1_MAX_TRIANGLES 20000 +#endif +#if (!defined AI_MDL_HL1_MAX_VERTICES) +#define AI_MDL_HL1_MAX_VERTICES 2048 +#endif +#if (!defined AI_MDL_HL1_MAX_SEQUENCES) +#define AI_MDL_HL1_MAX_SEQUENCES 2048 +#endif +#if (!defined AI_MDL_HL1_MAX_SEQUENCE_GROUPS) +#define AI_MDL_HL1_MAX_SEQUENCE_GROUPS 32 +#endif +#if (!defined AI_MDL_HL1_MAX_TEXTURES) +#define AI_MDL_HL1_MAX_TEXTURES 100 +#endif +#if (!defined AI_MDL_HL1_MAX_SKIN_FAMILIES) +#define AI_MDL_HL1_MAX_SKIN_FAMILIES 100 +#endif +#if (!defined AI_MDL_HL1_MAX_BONES) +#define AI_MDL_HL1_MAX_BONES 128 +#endif +#if (!defined AI_MDL_HL1_MAX_BODYPARTS) +#define AI_MDL_HL1_MAX_BODYPARTS 32 +#endif +#if (!defined AI_MDL_HL1_MAX_MODELS) +#define AI_MDL_HL1_MAX_MODELS 32 +#endif +#if (!defined AI_MDL_HL1_MAX_MESHES) +#define AI_MDL_HL1_MAX_MESHES 256 +#endif +#if (!defined AI_MDL_HL1_MAX_EVENTS) +#define AI_MDL_HL1_MAX_EVENTS 1024 +#endif +#if (!defined AI_MDL_HL1_MAX_BONE_CONTROLLERS) +#define AI_MDL_HL1_MAX_BONE_CONTROLLERS 8 +#endif +#if (!defined AI_MDL_HL1_MAX_ATTACHMENTS) +#define AI_MDL_HL1_MAX_ATTACHMENTS 512 +#endif + +// lighting options +#if (!defined AI_MDL_HL1_STUDIO_NF_FLATSHADE) +#define AI_MDL_HL1_STUDIO_NF_FLATSHADE 0x0001 +#endif +#if (!defined AI_MDL_HL1_STUDIO_NF_CHROME) +#define AI_MDL_HL1_STUDIO_NF_CHROME 0x0002 +#endif +#if (!defined AI_MDL_HL1_STUDIO_NF_ADDITIVE) +#define AI_MDL_HL1_STUDIO_NF_ADDITIVE 0x0020 +#endif +#if (!defined AI_MDL_HL1_STUDIO_NF_MASKED) +#define AI_MDL_HL1_STUDIO_NF_MASKED 0x0040 +#endif + +} // namespace HalfLife +} // namespace MDL +} // namespace Assimp + +#endif // AI_HL1FILEDATA_INCLUDED diff --git a/code/MDL/HalfLife/HL1ImportDefinitions.h b/code/MDL/HalfLife/HL1ImportDefinitions.h new file mode 100644 index 000000000..f70f40699 --- /dev/null +++ b/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 "" +#define AI_MDL_HL1_NODE_BODYPARTS "" +#define AI_MDL_HL1_NODE_BONES "" +#define AI_MDL_HL1_NODE_BONE_CONTROLLERS "" +#define AI_MDL_HL1_NODE_SEQUENCE_INFOS "" +#define AI_MDL_HL1_NODE_SEQUENCE_GROUPS "" +#define AI_MDL_HL1_NODE_SEQUENCE_TRANSITION_GRAPH "" +#define AI_MDL_HL1_NODE_ATTACHMENTS "" +#define AI_MDL_HL1_NODE_HITBOXES "" +#define AI_MDL_HL1_NODE_GLOBAL_INFO "" +#define AI_MDL_HL1_NODE_ANIMATION_EVENTS "AnimationEvents" +#define AI_MDL_HL1_NODE_BLEND_CONTROLLERS "BlendControllers" + +#define AI_MDL_HL1_MATKEY_CHROME(type, N) "$mat.HL1.chrome", type, N + +#endif // AI_MDL_HL1_IMPORT_DEFINITIONS_INCLUDED diff --git a/code/MDL/HalfLife/HL1ImportSettings.h b/code/MDL/HalfLife/HL1ImportSettings.h new file mode 100644 index 000000000..229303e2c --- /dev/null +++ b/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 + +namespace Assimp { +namespace MDL { +namespace HalfLife { + +struct HL1ImportSettings { + HL1ImportSettings() : + read_animations(false), + read_animation_events(false), + read_blend_controllers(false), + read_sequence_groups_info(false), + read_sequence_transitions(false), + read_attachments(false), + read_bone_controllers(false), + read_hitboxes(false), + read_textures(false), + read_misc_global_info(false) { + } + + bool read_animations; + bool read_animation_events; + bool read_blend_controllers; + bool read_sequence_groups_info; + bool read_sequence_transitions; + bool read_attachments; + bool read_bone_controllers; + bool read_hitboxes; + bool read_textures; + bool read_misc_global_info; +}; + +} // namespace HalfLife +} // namespace MDL +} // namespace Assimp + +#endif // AI_HL1IMPORTSETTINGS_INCLUDED diff --git a/code/MDL/HalfLife/HL1MDLLoader.cpp b/code/MDL/HalfLife/HL1MDLLoader.cpp new file mode 100644 index 000000000..785183950 --- /dev/null +++ b/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 +#include +#include +#include +#include +#include + +#include +#include + +#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(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(header->numtextures, "textures"); + + if (header->numskinfamilies > AI_MDL_HL1_MAX_SKIN_FAMILIES) + log_warning_limit_exceeded(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(header->numbodyparts, "bodyparts"); + + if (header->numbones > AI_MDL_HL1_MAX_BONES) + log_warning_limit_exceeded(header->numbones, "bones"); + + if (header->numbonecontrollers > AI_MDL_HL1_MAX_BONE_CONTROLLERS) + log_warning_limit_exceeded(header->numbonecontrollers, "bone controllers"); + + if (header->numseq > AI_MDL_HL1_MAX_SEQUENCES) + log_warning_limit_exceeded(header->numseq, "sequences"); + + if (header->numseqgroups > AI_MDL_HL1_MAX_SEQUENCE_GROUPS) + log_warning_limit_exceeded(header->numseqgroups, "sequence groups"); + + if (header->numattachments > AI_MDL_HL1_MAX_ATTACHMENTS) + log_warning_limit_exceeded(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: 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(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(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: 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(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 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(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 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(total_verts, "vertices"); + + if (scene_->mNumMeshes > AI_MDL_HL1_MAX_MESHES) + log_warning_limit_exceeded(scene_->mNumMeshes, "meshes"); + + if (total_models_ > AI_MDL_HL1_MAX_MODELS) + log_warning_limit_exceeded(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 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. + + + | + +-- 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(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 bind_pose_vertices; + + // Model_HL1 normals, in bind pose space. + std::vector bind_pose_normals; + + // Used to contain temporary information for building a mesh. + std::vector triverts; + + std::vector tricmds; + + // Which triverts to use for the mesh. + std::vector mesh_triverts_indices; + + std::vector 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> triverts_similars; + + // triverts per bone. + std::map> 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{ 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(mesh_triverts_indices.size()); + + short new_trivert_index = static_cast(triverts.size()); + + if (triverts_similars.count(trivert.vertindex) == 0) + triverts_similars.insert({ trivert.vertindex, std::set{ 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(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(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(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(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(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(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(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(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(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(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(pseqdesc->fps) * pseqdesc->numframes; + scene_animation->mNumChannels = static_cast(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(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(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(header_->numseq); + sequence_infos_node->mChildren = new aiNode *[sequence_infos_node->mNumChildren]; + + std::vector 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(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( + "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(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(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(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(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(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(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 diff --git a/code/MDL/HalfLife/HL1MDLLoader.h b/code/MDL/HalfLife/HL1MDLLoader.h new file mode 100644 index 000000000..8e356235f --- /dev/null +++ b/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 +#include + +#include +#include +#include +#include +#include +#include + +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 + 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 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 unique_sequence_names_; + + /** The list of unique sequence groups names. */ + std::vector 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 temp_bones_; + + /** The number of available bone controllers in the model. */ + int num_blend_controllers_; + + /** Self explanatory. */ + int total_models_; +}; + +// ------------------------------------------------------------------------------------------------ +template +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 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 diff --git a/code/MDL/HalfLife/HL1MeshTrivert.h b/code/MDL/HalfLife/HL1MeshTrivert.h new file mode 100644 index 000000000..38bd371a0 --- /dev/null +++ b/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 diff --git a/code/MDL/HalfLife/HalfLifeMDLBaseHeader.h b/code/MDL/HalfLife/HalfLifeMDLBaseHeader.h new file mode 100644 index 000000000..964ade4ae --- /dev/null +++ b/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 + +namespace Assimp { +namespace MDL { +namespace HalfLife { + +/** Used to interface different Valve MDL formats. */ +struct HalfLifeMDLBaseHeader +{ + //! Magic number: "IDST"/"IDSQ" + char ident[4]; + + //! The file format version. + int32_t version; +}; + +} +} +} + +#endif // AI_HALFLIFEMDLBASEHEADER_INCLUDED diff --git a/code/MDL/HalfLife/LogFunctions.h b/code/MDL/HalfLife/LogFunctions.h new file mode 100644 index 000000000..db82a2604 --- /dev/null +++ b/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 +#include + +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 +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 +static inline void log_warning_limit_exceeded(int current_amount, + const std::string &direct_object) { + log_warning_limit_exceeded("Model", current_amount, direct_object); +} + +} // namespace HalfLife +} // namespace MDL +} // namespace Assimp + +#endif // AI_MDL_HALFLIFE_LOGFUNCTIONS_INCLUDED diff --git a/code/MDL/HalfLife/UniqueNameGenerator.cpp b/code/MDL/HalfLife/UniqueNameGenerator.cpp new file mode 100644 index 000000000..1efc3238c --- /dev/null +++ b/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 +#include +#include +#include + +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 &names) { + struct DuplicateInfo { + DuplicateInfo() : + indices(), + next_id(0) { + } + + std::list indices; + size_t next_id; + }; + + std::vector empty_names_indices; + std::vector template_name_duplicates; + std::map 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)