1980 lines
86 KiB
C++
1980 lines
86 KiB
C++
/*
|
|
---------------------------------------------------------------------------
|
|
Open Asset Import Library (assimp)
|
|
---------------------------------------------------------------------------
|
|
|
|
Copyright (c) 2006-2022, assimp team
|
|
|
|
All rights reserved.
|
|
|
|
Redistribution and use of this software in source and binary forms,
|
|
with or without modification, are permitted provided that the following
|
|
conditions are met:
|
|
|
|
* Redistributions of source code must retain the above
|
|
copyright notice, this list of conditions and the
|
|
following disclaimer.
|
|
|
|
* Redistributions in binary form must reproduce the above
|
|
copyright notice, this list of conditions and the
|
|
following disclaimer in the documentation and/or other
|
|
materials provided with the distribution.
|
|
|
|
* Neither the name of the assimp team, nor the names of its
|
|
contributors may be used to endorse or promote products
|
|
derived from this software without specific prior
|
|
written permission of the assimp team.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
---------------------------------------------------------------------------
|
|
*/
|
|
|
|
/** @file MDLLoader.cpp
|
|
* @brief Implementation of the main parts of the MDL importer class
|
|
* *TODO* Cleanup and further testing of some parts necessary
|
|
*/
|
|
|
|
// internal headers
|
|
|
|
#ifndef ASSIMP_BUILD_NO_MDL_IMPORTER
|
|
|
|
#include "AssetLib/MDL/MDLLoader.h"
|
|
#include "AssetLib/MD2/MD2FileData.h"
|
|
#include "AssetLib/MDL/HalfLife/HL1MDLLoader.h"
|
|
#include "AssetLib/MDL/MDLDefaultColorMap.h"
|
|
|
|
#include <assimp/StringUtils.h>
|
|
#include <assimp/importerdesc.h>
|
|
#include <assimp/qnan.h>
|
|
#include <assimp/scene.h>
|
|
#include <assimp/DefaultLogger.hpp>
|
|
#include <assimp/IOSystem.hpp>
|
|
#include <assimp/Importer.hpp>
|
|
|
|
#include <memory>
|
|
|
|
using namespace Assimp;
|
|
|
|
static const aiImporterDesc desc = {
|
|
"Quake Mesh / 3D GameStudio Mesh Importer",
|
|
"",
|
|
"",
|
|
"",
|
|
aiImporterFlags_SupportBinaryFlavour,
|
|
0,
|
|
0,
|
|
7,
|
|
0,
|
|
"mdl"
|
|
};
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Ugly stuff ... nevermind
|
|
#define _AI_MDL7_ACCESS(_data, _index, _limit, _type) \
|
|
(*((const _type *)(((const char *)_data) + _index * _limit)))
|
|
|
|
#define _AI_MDL7_ACCESS_PTR(_data, _index, _limit, _type) \
|
|
((BE_NCONST _type *)(((const char *)_data) + _index * _limit))
|
|
|
|
#define _AI_MDL7_ACCESS_VERT(_data, _index, _limit) \
|
|
_AI_MDL7_ACCESS(_data, _index, _limit, MDL::Vertex_MDL7)
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Constructor to be privately used by Importer
|
|
MDLImporter::MDLImporter() :
|
|
configFrameID(), mBuffer(), iGSFileVersion(), mIOHandler(nullptr), pScene(), iFileSize() {
|
|
// empty
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Destructor, private as well
|
|
MDLImporter::~MDLImporter() {
|
|
// empty
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Returns whether the class can handle the format of the given file.
|
|
bool MDLImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
|
|
static const uint32_t tokens[] = {
|
|
AI_MDL_MAGIC_NUMBER_LE_HL2a,
|
|
AI_MDL_MAGIC_NUMBER_LE_HL2b,
|
|
AI_MDL_MAGIC_NUMBER_LE_GS7,
|
|
AI_MDL_MAGIC_NUMBER_LE_GS5b,
|
|
AI_MDL_MAGIC_NUMBER_LE_GS5a,
|
|
AI_MDL_MAGIC_NUMBER_LE_GS4,
|
|
AI_MDL_MAGIC_NUMBER_LE_GS3,
|
|
AI_MDL_MAGIC_NUMBER_LE
|
|
};
|
|
return CheckMagicToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Setup configuration properties
|
|
void MDLImporter::SetupProperties(const Importer *pImp) {
|
|
configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MDL_KEYFRAME, -1);
|
|
|
|
// The
|
|
// AI_CONFIG_IMPORT_MDL_KEYFRAME option overrides the
|
|
// AI_CONFIG_IMPORT_GLOBAL_KEYFRAME option.
|
|
if (static_cast<unsigned int>(-1) == configFrameID) {
|
|
configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_GLOBAL_KEYFRAME, 0);
|
|
}
|
|
|
|
// AI_CONFIG_IMPORT_MDL_COLORMAP - palette file
|
|
configPalette = pImp->GetPropertyString(AI_CONFIG_IMPORT_MDL_COLORMAP, "colormap.lmp");
|
|
|
|
// Read configuration specific to MDL (Half-Life 1).
|
|
mHL1ImportSettings.read_animations = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_ANIMATIONS, true);
|
|
if (mHL1ImportSettings.read_animations) {
|
|
mHL1ImportSettings.read_animation_events = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_ANIMATION_EVENTS, true);
|
|
mHL1ImportSettings.read_blend_controllers = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_BLEND_CONTROLLERS, true);
|
|
mHL1ImportSettings.read_sequence_transitions = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_SEQUENCE_TRANSITIONS, true);
|
|
}
|
|
mHL1ImportSettings.read_attachments = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_ATTACHMENTS, true);
|
|
mHL1ImportSettings.read_bone_controllers = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_BONE_CONTROLLERS, true);
|
|
mHL1ImportSettings.read_hitboxes = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_HITBOXES, true);
|
|
mHL1ImportSettings.read_misc_global_info = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_MISC_GLOBAL_INFO, true);
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Get a list of all supported extensions
|
|
const aiImporterDesc *MDLImporter::GetInfo() const {
|
|
return &desc;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Imports the given file into the given scene structure.
|
|
void MDLImporter::InternReadFile(const std::string &pFile,
|
|
aiScene *_pScene, IOSystem *pIOHandler) {
|
|
pScene = _pScene;
|
|
mIOHandler = pIOHandler;
|
|
std::unique_ptr<IOStream> file(pIOHandler->Open(pFile));
|
|
|
|
// Check whether we can read from the file
|
|
if (file.get() == nullptr) {
|
|
throw DeadlyImportError("Failed to open MDL file ", pFile, ".");
|
|
}
|
|
|
|
// This should work for all other types of MDL files, too ...
|
|
// the HL1 sequence group header is one of the smallest, afaik
|
|
iFileSize = (unsigned int)file->FileSize();
|
|
if (iFileSize < sizeof(MDL::HalfLife::SequenceHeader_HL1)) {
|
|
throw DeadlyImportError("MDL File is too small.");
|
|
}
|
|
|
|
// delete the file buffer and cleanup.
|
|
auto DeleteBufferAndCleanup = [&]() {
|
|
if (mBuffer) {
|
|
delete[] mBuffer;
|
|
mBuffer = nullptr;
|
|
}
|
|
AI_DEBUG_INVALIDATE_PTR(mIOHandler);
|
|
AI_DEBUG_INVALIDATE_PTR(pScene);
|
|
};
|
|
|
|
try {
|
|
// Allocate storage and copy the contents of the file to a memory buffer
|
|
mBuffer = new unsigned char[iFileSize + 1];
|
|
file->Read((void *)mBuffer, 1, iFileSize);
|
|
|
|
// Append a binary zero to the end of the buffer.
|
|
// this is just for safety that string parsing routines
|
|
// find the end of the buffer ...
|
|
mBuffer[iFileSize] = '\0';
|
|
const uint32_t iMagicWord = *((uint32_t *)mBuffer);
|
|
|
|
// Determine the file subtype and call the appropriate member function
|
|
bool is_half_life = false;
|
|
|
|
// Original Quake1 format
|
|
if (AI_MDL_MAGIC_NUMBER_BE == iMagicWord || AI_MDL_MAGIC_NUMBER_LE == iMagicWord) {
|
|
ASSIMP_LOG_DEBUG("MDL subtype: Quake 1, magic word is IDPO");
|
|
iGSFileVersion = 0;
|
|
InternReadFile_Quake1();
|
|
}
|
|
// GameStudio A<old> MDL2 format - used by some test models that come with 3DGS
|
|
else if (AI_MDL_MAGIC_NUMBER_BE_GS3 == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_GS3 == iMagicWord) {
|
|
ASSIMP_LOG_DEBUG("MDL subtype: 3D GameStudio A2, magic word is MDL2");
|
|
iGSFileVersion = 2;
|
|
InternReadFile_Quake1();
|
|
}
|
|
// GameStudio A4 MDL3 format
|
|
else if (AI_MDL_MAGIC_NUMBER_BE_GS4 == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_GS4 == iMagicWord) {
|
|
ASSIMP_LOG_DEBUG("MDL subtype: 3D GameStudio A4, magic word is MDL3");
|
|
iGSFileVersion = 3;
|
|
InternReadFile_3DGS_MDL345();
|
|
}
|
|
// GameStudio A5+ MDL4 format
|
|
else if (AI_MDL_MAGIC_NUMBER_BE_GS5a == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_GS5a == iMagicWord) {
|
|
ASSIMP_LOG_DEBUG("MDL subtype: 3D GameStudio A4, magic word is MDL4");
|
|
iGSFileVersion = 4;
|
|
InternReadFile_3DGS_MDL345();
|
|
}
|
|
// GameStudio A5+ MDL5 format
|
|
else if (AI_MDL_MAGIC_NUMBER_BE_GS5b == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_GS5b == iMagicWord) {
|
|
ASSIMP_LOG_DEBUG("MDL subtype: 3D GameStudio A5, magic word is MDL5");
|
|
iGSFileVersion = 5;
|
|
InternReadFile_3DGS_MDL345();
|
|
}
|
|
// GameStudio A7 MDL7 format
|
|
else if (AI_MDL_MAGIC_NUMBER_BE_GS7 == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_GS7 == iMagicWord) {
|
|
ASSIMP_LOG_DEBUG("MDL subtype: 3D GameStudio A7, magic word is MDL7");
|
|
iGSFileVersion = 7;
|
|
InternReadFile_3DGS_MDL7();
|
|
}
|
|
// IDST/IDSQ Format (CS:S/HL^2, etc ...)
|
|
else if (AI_MDL_MAGIC_NUMBER_BE_HL2a == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_HL2a == iMagicWord ||
|
|
AI_MDL_MAGIC_NUMBER_BE_HL2b == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_HL2b == iMagicWord) {
|
|
iGSFileVersion = 0;
|
|
is_half_life = true;
|
|
|
|
HalfLife::HalfLifeMDLBaseHeader *pHeader = (HalfLife::HalfLifeMDLBaseHeader *)mBuffer;
|
|
if (pHeader->version == AI_MDL_HL1_VERSION) {
|
|
ASSIMP_LOG_DEBUG("MDL subtype: Half-Life 1/Goldsrc Engine, magic word is IDST/IDSQ");
|
|
InternReadFile_HL1(pFile, iMagicWord);
|
|
} else {
|
|
ASSIMP_LOG_DEBUG("MDL subtype: Source(tm) Engine, magic word is IDST/IDSQ");
|
|
InternReadFile_HL2();
|
|
}
|
|
} else {
|
|
// print the magic word to the log file
|
|
throw DeadlyImportError("Unknown MDL subformat ", pFile,
|
|
". Magic word (", ai_str_toprintable((const char *)&iMagicWord, sizeof(iMagicWord)), ") is not known");
|
|
}
|
|
|
|
if (is_half_life){
|
|
// Now rotate the whole scene 90 degrees around the z and x axes to convert to internal coordinate system
|
|
pScene->mRootNode->mTransformation = aiMatrix4x4(
|
|
0.f, -1.f, 0.f, 0.f,
|
|
0.f, 0.f, 1.f, 0.f,
|
|
-1.f, 0.f, 0.f, 0.f,
|
|
0.f, 0.f, 0.f, 1.f);
|
|
}
|
|
else {
|
|
// Now rotate the whole scene 90 degrees around the x axis to convert to internal coordinate system
|
|
pScene->mRootNode->mTransformation = aiMatrix4x4(1.f, 0.f, 0.f, 0.f,
|
|
0.f, 0.f, 1.f, 0.f, 0.f, -1.f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f);
|
|
}
|
|
|
|
DeleteBufferAndCleanup();
|
|
} catch (...) {
|
|
DeleteBufferAndCleanup();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Check whether we're still inside the valid file range
|
|
void MDLImporter::SizeCheck(const void *szPos) {
|
|
if (!szPos || (const unsigned char *)szPos > this->mBuffer + this->iFileSize) {
|
|
throw DeadlyImportError("Invalid MDL file. The file is too small "
|
|
"or contains invalid data.");
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Just for debugging purposes
|
|
void MDLImporter::SizeCheck(const void *szPos, const char *szFile, unsigned int iLine) {
|
|
ai_assert(nullptr != szFile);
|
|
if (!szPos || (const unsigned char *)szPos > mBuffer + iFileSize) {
|
|
// remove a directory if there is one
|
|
const char *szFilePtr = ::strrchr(szFile, '\\');
|
|
if (!szFilePtr) {
|
|
szFilePtr = ::strrchr(szFile, '/');
|
|
if (nullptr == szFilePtr) {
|
|
szFilePtr = szFile;
|
|
}
|
|
}
|
|
if (szFilePtr) {
|
|
++szFilePtr;
|
|
}
|
|
|
|
char szBuffer[1024];
|
|
::sprintf(szBuffer, "Invalid MDL file. The file is too small "
|
|
"or contains invalid data (File: %s Line: %u)",
|
|
szFilePtr, iLine);
|
|
|
|
throw DeadlyImportError(szBuffer);
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Validate a quake file header
|
|
void MDLImporter::ValidateHeader_Quake1(const MDL::Header *pcHeader) {
|
|
// some values may not be nullptr
|
|
if (!pcHeader->num_frames)
|
|
throw DeadlyImportError("[Quake 1 MDL] There are no frames in the file");
|
|
|
|
if (!pcHeader->num_verts)
|
|
throw DeadlyImportError("[Quake 1 MDL] There are no vertices in the file");
|
|
|
|
if (!pcHeader->num_tris)
|
|
throw DeadlyImportError("[Quake 1 MDL] There are no triangles in the file");
|
|
|
|
// check whether the maxima are exceeded ...however, this applies for Quake 1 MDLs only
|
|
if (!this->iGSFileVersion) {
|
|
if (pcHeader->num_verts > AI_MDL_MAX_VERTS)
|
|
ASSIMP_LOG_WARN("Quake 1 MDL model has more than AI_MDL_MAX_VERTS vertices");
|
|
|
|
if (pcHeader->num_tris > AI_MDL_MAX_TRIANGLES)
|
|
ASSIMP_LOG_WARN("Quake 1 MDL model has more than AI_MDL_MAX_TRIANGLES triangles");
|
|
|
|
if (pcHeader->num_frames > AI_MDL_MAX_FRAMES)
|
|
ASSIMP_LOG_WARN("Quake 1 MDL model has more than AI_MDL_MAX_FRAMES frames");
|
|
|
|
// (this does not apply for 3DGS MDLs)
|
|
if (!this->iGSFileVersion && pcHeader->version != AI_MDL_VERSION)
|
|
ASSIMP_LOG_WARN("Quake 1 MDL model has an unknown version: AI_MDL_VERSION (=6) is "
|
|
"the expected file format version");
|
|
if (pcHeader->num_skins && (!pcHeader->skinwidth || !pcHeader->skinheight))
|
|
ASSIMP_LOG_WARN("Skin width or height are 0");
|
|
}
|
|
}
|
|
|
|
#ifdef AI_BUILD_BIG_ENDIAN
|
|
// ------------------------------------------------------------------------------------------------
|
|
void FlipQuakeHeader(BE_NCONST MDL::Header *pcHeader) {
|
|
AI_SWAP4(pcHeader->ident);
|
|
AI_SWAP4(pcHeader->version);
|
|
AI_SWAP4(pcHeader->boundingradius);
|
|
AI_SWAP4(pcHeader->flags);
|
|
AI_SWAP4(pcHeader->num_frames);
|
|
AI_SWAP4(pcHeader->num_skins);
|
|
AI_SWAP4(pcHeader->num_tris);
|
|
AI_SWAP4(pcHeader->num_verts);
|
|
for (unsigned int i = 0; i < 3; ++i) {
|
|
AI_SWAP4(pcHeader->scale[i]);
|
|
AI_SWAP4(pcHeader->translate[i]);
|
|
}
|
|
AI_SWAP4(pcHeader->size);
|
|
AI_SWAP4(pcHeader->skinheight);
|
|
AI_SWAP4(pcHeader->skinwidth);
|
|
AI_SWAP4(pcHeader->synctype);
|
|
}
|
|
#endif
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Read a Quake 1 file
|
|
void MDLImporter::InternReadFile_Quake1() {
|
|
ai_assert(nullptr != pScene);
|
|
|
|
BE_NCONST MDL::Header *pcHeader = (BE_NCONST MDL::Header *)this->mBuffer;
|
|
|
|
#ifdef AI_BUILD_BIG_ENDIAN
|
|
FlipQuakeHeader(pcHeader);
|
|
#endif
|
|
|
|
ValidateHeader_Quake1(pcHeader);
|
|
|
|
// current cursor position in the file
|
|
const unsigned char *szCurrent = (const unsigned char *)(pcHeader + 1);
|
|
|
|
// need to read all textures
|
|
for (unsigned int i = 0; i < (unsigned int)pcHeader->num_skins; ++i) {
|
|
union {
|
|
BE_NCONST MDL::Skin *pcSkin;
|
|
BE_NCONST MDL::GroupSkin *pcGroupSkin;
|
|
};
|
|
if (szCurrent + sizeof(MDL::Skin) > this->mBuffer + this->iFileSize) {
|
|
throw DeadlyImportError("[Quake 1 MDL] Unexpected EOF");
|
|
}
|
|
pcSkin = (BE_NCONST MDL::Skin *)szCurrent;
|
|
|
|
AI_SWAP4(pcSkin->group);
|
|
|
|
// Quake 1 group-skins
|
|
if (1 == pcSkin->group) {
|
|
AI_SWAP4(pcGroupSkin->nb);
|
|
|
|
// need to skip multiple images
|
|
const unsigned int iNumImages = (unsigned int)pcGroupSkin->nb;
|
|
szCurrent += sizeof(uint32_t) * 2;
|
|
|
|
if (0 != iNumImages) {
|
|
if (!i) {
|
|
// however, create only one output image (the first)
|
|
this->CreateTextureARGB8_3DGS_MDL3(szCurrent + iNumImages * sizeof(float));
|
|
}
|
|
// go to the end of the skin section / the beginning of the next skin
|
|
szCurrent += pcHeader->skinheight * pcHeader->skinwidth +
|
|
sizeof(float) * iNumImages;
|
|
}
|
|
} else {
|
|
szCurrent += sizeof(uint32_t);
|
|
unsigned int iSkip = i ? UINT_MAX : 0;
|
|
CreateTexture_3DGS_MDL4(szCurrent, pcSkin->group, &iSkip);
|
|
szCurrent += iSkip;
|
|
}
|
|
}
|
|
// get a pointer to the texture coordinates
|
|
BE_NCONST MDL::TexCoord *pcTexCoords = (BE_NCONST MDL::TexCoord *)szCurrent;
|
|
szCurrent += sizeof(MDL::TexCoord) * pcHeader->num_verts;
|
|
|
|
// get a pointer to the triangles
|
|
BE_NCONST MDL::Triangle *pcTriangles = (BE_NCONST MDL::Triangle *)szCurrent;
|
|
szCurrent += sizeof(MDL::Triangle) * pcHeader->num_tris;
|
|
VALIDATE_FILE_SIZE(szCurrent);
|
|
|
|
// now get a pointer to the first frame in the file
|
|
BE_NCONST MDL::Frame *pcFrames = (BE_NCONST MDL::Frame *)szCurrent;
|
|
MDL::SimpleFrame *pcFirstFrame;
|
|
|
|
if (0 == pcFrames->type) {
|
|
// get address of single frame
|
|
pcFirstFrame = (MDL::SimpleFrame *)&pcFrames->frame;
|
|
} else {
|
|
// get the first frame in the group
|
|
BE_NCONST MDL::GroupFrame *pcFrames2 = (BE_NCONST MDL::GroupFrame *)szCurrent;
|
|
pcFirstFrame = (MDL::SimpleFrame *)( szCurrent + sizeof(MDL::GroupFrame::type) + sizeof(MDL::GroupFrame::numframes)
|
|
+ sizeof(MDL::GroupFrame::min) + sizeof(MDL::GroupFrame::max) + sizeof(*MDL::GroupFrame::times) * pcFrames2->numframes );
|
|
}
|
|
BE_NCONST MDL::Vertex *pcVertices = (BE_NCONST MDL::Vertex *)((pcFirstFrame->name) + sizeof(pcFirstFrame->name));
|
|
VALIDATE_FILE_SIZE((const unsigned char *)(pcVertices + pcHeader->num_verts));
|
|
|
|
#ifdef AI_BUILD_BIG_ENDIAN
|
|
for (int i = 0; i < pcHeader->num_verts; ++i) {
|
|
AI_SWAP4(pcTexCoords[i].onseam);
|
|
AI_SWAP4(pcTexCoords[i].s);
|
|
AI_SWAP4(pcTexCoords[i].t);
|
|
}
|
|
|
|
for (int i = 0; i < pcHeader->num_tris; ++i) {
|
|
AI_SWAP4(pcTriangles[i].facesfront);
|
|
AI_SWAP4(pcTriangles[i].vertex[0]);
|
|
AI_SWAP4(pcTriangles[i].vertex[1]);
|
|
AI_SWAP4(pcTriangles[i].vertex[2]);
|
|
}
|
|
#endif
|
|
|
|
// setup materials
|
|
SetupMaterialProperties_3DGS_MDL5_Quake1();
|
|
|
|
// allocate enough storage to hold all vertices and triangles
|
|
aiMesh *pcMesh = new aiMesh();
|
|
|
|
pcMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
|
|
pcMesh->mNumVertices = pcHeader->num_tris * 3;
|
|
pcMesh->mNumFaces = pcHeader->num_tris;
|
|
pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices];
|
|
pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices];
|
|
pcMesh->mFaces = new aiFace[pcMesh->mNumFaces];
|
|
pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices];
|
|
pcMesh->mNumUVComponents[0] = 2;
|
|
|
|
// there won't be more than one mesh inside the file
|
|
pScene->mRootNode = new aiNode();
|
|
pScene->mRootNode->mNumMeshes = 1;
|
|
pScene->mRootNode->mMeshes = new unsigned int[1];
|
|
pScene->mRootNode->mMeshes[0] = 0;
|
|
pScene->mNumMeshes = 1;
|
|
pScene->mMeshes = new aiMesh *[1];
|
|
pScene->mMeshes[0] = pcMesh;
|
|
|
|
// now iterate through all triangles
|
|
unsigned int iCurrent = 0;
|
|
for (unsigned int i = 0; i < (unsigned int)pcHeader->num_tris; ++i) {
|
|
pcMesh->mFaces[i].mIndices = new unsigned int[3];
|
|
pcMesh->mFaces[i].mNumIndices = 3;
|
|
|
|
unsigned int iTemp = iCurrent;
|
|
for (unsigned int c = 0; c < 3; ++c, ++iCurrent) {
|
|
pcMesh->mFaces[i].mIndices[c] = iCurrent;
|
|
|
|
// read vertices
|
|
unsigned int iIndex = pcTriangles->vertex[c];
|
|
if (iIndex >= (unsigned int)pcHeader->num_verts) {
|
|
iIndex = pcHeader->num_verts - 1;
|
|
ASSIMP_LOG_WARN("Index overflow in Q1-MDL vertex list.");
|
|
}
|
|
|
|
aiVector3D &vec = pcMesh->mVertices[iCurrent];
|
|
vec.x = (float)pcVertices[iIndex].v[0] * pcHeader->scale[0];
|
|
vec.x += pcHeader->translate[0];
|
|
|
|
vec.y = (float)pcVertices[iIndex].v[1] * pcHeader->scale[1];
|
|
vec.y += pcHeader->translate[1];
|
|
//vec.y *= -1.0f;
|
|
|
|
vec.z = (float)pcVertices[iIndex].v[2] * pcHeader->scale[2];
|
|
vec.z += pcHeader->translate[2];
|
|
|
|
// read the normal vector from the precalculated normal table
|
|
MD2::LookupNormalIndex(pcVertices[iIndex].normalIndex, pcMesh->mNormals[iCurrent]);
|
|
//pcMesh->mNormals[iCurrent].y *= -1.0f;
|
|
|
|
// read texture coordinates
|
|
float s = (float)pcTexCoords[iIndex].s;
|
|
float t = (float)pcTexCoords[iIndex].t;
|
|
|
|
// translate texture coordinates
|
|
if (0 == pcTriangles->facesfront && 0 != pcTexCoords[iIndex].onseam) {
|
|
s += pcHeader->skinwidth * 0.5f;
|
|
}
|
|
|
|
// Scale s and t to range from 0.0 to 1.0
|
|
pcMesh->mTextureCoords[0][iCurrent].x = (s + 0.5f) / pcHeader->skinwidth;
|
|
pcMesh->mTextureCoords[0][iCurrent].y = 1.0f - (t + 0.5f) / pcHeader->skinheight;
|
|
}
|
|
pcMesh->mFaces[i].mIndices[0] = iTemp + 2;
|
|
pcMesh->mFaces[i].mIndices[1] = iTemp + 1;
|
|
pcMesh->mFaces[i].mIndices[2] = iTemp + 0;
|
|
pcTriangles++;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Setup material properties for Quake and older GameStudio files
|
|
void MDLImporter::SetupMaterialProperties_3DGS_MDL5_Quake1() {
|
|
const MDL::Header *const pcHeader = (const MDL::Header *)this->mBuffer;
|
|
|
|
// allocate ONE material
|
|
pScene->mMaterials = new aiMaterial *[1];
|
|
pScene->mMaterials[0] = new aiMaterial();
|
|
pScene->mNumMaterials = 1;
|
|
|
|
// setup the material's properties
|
|
const int iMode = (int)aiShadingMode_Gouraud;
|
|
aiMaterial *const pcHelper = (aiMaterial *)pScene->mMaterials[0];
|
|
pcHelper->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL);
|
|
|
|
aiColor4D clr;
|
|
if (0 != pcHeader->num_skins && pScene->mNumTextures) {
|
|
// can we replace the texture with a single color?
|
|
clr = this->ReplaceTextureWithColor(pScene->mTextures[0]);
|
|
if (is_not_qnan(clr.r)) {
|
|
delete pScene->mTextures[0];
|
|
delete[] pScene->mTextures;
|
|
|
|
pScene->mTextures = nullptr;
|
|
pScene->mNumTextures = 0;
|
|
} else {
|
|
clr.b = clr.a = clr.g = clr.r = 1.0f;
|
|
aiString szString;
|
|
::memcpy(szString.data, AI_MAKE_EMBEDDED_TEXNAME(0), 3);
|
|
szString.length = 2;
|
|
pcHelper->AddProperty(&szString, AI_MATKEY_TEXTURE_DIFFUSE(0));
|
|
}
|
|
}
|
|
|
|
pcHelper->AddProperty<aiColor4D>(&clr, 1, AI_MATKEY_COLOR_DIFFUSE);
|
|
pcHelper->AddProperty<aiColor4D>(&clr, 1, AI_MATKEY_COLOR_SPECULAR);
|
|
|
|
clr.r *= 0.05f;
|
|
clr.g *= 0.05f;
|
|
clr.b *= 0.05f;
|
|
clr.a = 1.0f;
|
|
pcHelper->AddProperty<aiColor4D>(&clr, 1, AI_MATKEY_COLOR_AMBIENT);
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Read a MDL 3,4,5 file
|
|
void MDLImporter::InternReadFile_3DGS_MDL345() {
|
|
ai_assert(nullptr != pScene);
|
|
|
|
// the header of MDL 3/4/5 is nearly identical to the original Quake1 header
|
|
BE_NCONST MDL::Header *pcHeader = (BE_NCONST MDL::Header *)this->mBuffer;
|
|
#ifdef AI_BUILD_BIG_ENDIAN
|
|
FlipQuakeHeader(pcHeader);
|
|
#endif
|
|
ValidateHeader_Quake1(pcHeader);
|
|
|
|
// current cursor position in the file
|
|
const unsigned char *szCurrent = (const unsigned char *)(pcHeader + 1);
|
|
const unsigned char *szEnd = mBuffer + iFileSize;
|
|
|
|
// need to read all textures
|
|
for (unsigned int i = 0; i < (unsigned int)pcHeader->num_skins; ++i) {
|
|
if (szCurrent + sizeof(uint32_t) > szEnd) {
|
|
throw DeadlyImportError("Texture data past end of file.");
|
|
}
|
|
BE_NCONST MDL::Skin *pcSkin;
|
|
pcSkin = (BE_NCONST MDL::Skin *)szCurrent;
|
|
AI_SWAP4(pcSkin->group);
|
|
// create one output image
|
|
unsigned int iSkip = i ? UINT_MAX : 0;
|
|
if (5 <= iGSFileVersion) {
|
|
// MDL5 format could contain MIPmaps
|
|
CreateTexture_3DGS_MDL5((unsigned char *)pcSkin + sizeof(uint32_t),
|
|
pcSkin->group, &iSkip);
|
|
} else {
|
|
CreateTexture_3DGS_MDL4((unsigned char *)pcSkin + sizeof(uint32_t),
|
|
pcSkin->group, &iSkip);
|
|
}
|
|
// need to skip one image
|
|
szCurrent += iSkip + sizeof(uint32_t);
|
|
}
|
|
// get a pointer to the texture coordinates
|
|
BE_NCONST MDL::TexCoord_MDL3 *pcTexCoords = (BE_NCONST MDL::TexCoord_MDL3 *)szCurrent;
|
|
szCurrent += sizeof(MDL::TexCoord_MDL3) * pcHeader->synctype;
|
|
|
|
// NOTE: for MDLn formats "synctype" corresponds to the number of UV coords
|
|
|
|
// get a pointer to the triangles
|
|
BE_NCONST MDL::Triangle_MDL3 *pcTriangles = (BE_NCONST MDL::Triangle_MDL3 *)szCurrent;
|
|
szCurrent += sizeof(MDL::Triangle_MDL3) * pcHeader->num_tris;
|
|
|
|
#ifdef AI_BUILD_BIG_ENDIAN
|
|
|
|
for (int i = 0; i < pcHeader->synctype; ++i) {
|
|
AI_SWAP2(pcTexCoords[i].u);
|
|
AI_SWAP2(pcTexCoords[i].v);
|
|
}
|
|
|
|
for (int i = 0; i < pcHeader->num_tris; ++i) {
|
|
AI_SWAP2(pcTriangles[i].index_xyz[0]);
|
|
AI_SWAP2(pcTriangles[i].index_xyz[1]);
|
|
AI_SWAP2(pcTriangles[i].index_xyz[2]);
|
|
AI_SWAP2(pcTriangles[i].index_uv[0]);
|
|
AI_SWAP2(pcTriangles[i].index_uv[1]);
|
|
AI_SWAP2(pcTriangles[i].index_uv[2]);
|
|
}
|
|
|
|
#endif
|
|
|
|
VALIDATE_FILE_SIZE(szCurrent);
|
|
|
|
// setup materials
|
|
SetupMaterialProperties_3DGS_MDL5_Quake1();
|
|
|
|
// allocate enough storage to hold all vertices and triangles
|
|
aiMesh *pcMesh = new aiMesh();
|
|
pcMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
|
|
|
|
pcMesh->mNumVertices = pcHeader->num_tris * 3;
|
|
pcMesh->mNumFaces = pcHeader->num_tris;
|
|
pcMesh->mFaces = new aiFace[pcMesh->mNumFaces];
|
|
|
|
// there won't be more than one mesh inside the file
|
|
pScene->mRootNode = new aiNode();
|
|
pScene->mRootNode->mNumMeshes = 1;
|
|
pScene->mRootNode->mMeshes = new unsigned int[1];
|
|
pScene->mRootNode->mMeshes[0] = 0;
|
|
pScene->mNumMeshes = 1;
|
|
pScene->mMeshes = new aiMesh *[1];
|
|
pScene->mMeshes[0] = pcMesh;
|
|
|
|
// allocate output storage
|
|
pcMesh->mNumVertices = (unsigned int)pcHeader->num_tris * 3;
|
|
pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices];
|
|
pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices];
|
|
|
|
if (pcHeader->synctype) {
|
|
pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices];
|
|
pcMesh->mNumUVComponents[0] = 2;
|
|
}
|
|
|
|
// now get a pointer to the first frame in the file
|
|
BE_NCONST MDL::Frame *pcFrames = (BE_NCONST MDL::Frame *)szCurrent;
|
|
AI_SWAP4(pcFrames->type);
|
|
|
|
// byte packed vertices
|
|
// FIXME: these two snippets below are almost identical ... join them?
|
|
/////////////////////////////////////////////////////////////////////////////////////
|
|
if (0 == pcFrames->type || 3 >= this->iGSFileVersion) {
|
|
|
|
const MDL::SimpleFrame *pcFirstFrame = (const MDL::SimpleFrame *)(szCurrent + sizeof(uint32_t));
|
|
const MDL::Vertex *pcVertices = (const MDL::Vertex *)((pcFirstFrame->name) + sizeof(pcFirstFrame->name));
|
|
|
|
VALIDATE_FILE_SIZE(pcVertices + pcHeader->num_verts);
|
|
|
|
// now iterate through all triangles
|
|
unsigned int iCurrent = 0;
|
|
for (unsigned int i = 0; i < (unsigned int)pcHeader->num_tris; ++i) {
|
|
pcMesh->mFaces[i].mIndices = new unsigned int[3];
|
|
pcMesh->mFaces[i].mNumIndices = 3;
|
|
|
|
unsigned int iTemp = iCurrent;
|
|
for (unsigned int c = 0; c < 3; ++c, ++iCurrent) {
|
|
// read vertices
|
|
unsigned int iIndex = pcTriangles->index_xyz[c];
|
|
if (iIndex >= (unsigned int)pcHeader->num_verts) {
|
|
iIndex = pcHeader->num_verts - 1;
|
|
ASSIMP_LOG_WARN("Index overflow in MDLn vertex list");
|
|
}
|
|
|
|
aiVector3D &vec = pcMesh->mVertices[iCurrent];
|
|
vec.x = (float)pcVertices[iIndex].v[0] * pcHeader->scale[0];
|
|
vec.x += pcHeader->translate[0];
|
|
|
|
vec.y = (float)pcVertices[iIndex].v[1] * pcHeader->scale[1];
|
|
vec.y += pcHeader->translate[1];
|
|
// vec.y *= -1.0f;
|
|
|
|
vec.z = (float)pcVertices[iIndex].v[2] * pcHeader->scale[2];
|
|
vec.z += pcHeader->translate[2];
|
|
|
|
// read the normal vector from the precalculated normal table
|
|
MD2::LookupNormalIndex(pcVertices[iIndex].normalIndex, pcMesh->mNormals[iCurrent]);
|
|
// pcMesh->mNormals[iCurrent].y *= -1.0f;
|
|
|
|
// read texture coordinates
|
|
if (pcHeader->synctype) {
|
|
ImportUVCoordinate_3DGS_MDL345(pcMesh->mTextureCoords[0][iCurrent],
|
|
pcTexCoords, pcTriangles->index_uv[c]);
|
|
}
|
|
}
|
|
pcMesh->mFaces[i].mIndices[0] = iTemp + 2;
|
|
pcMesh->mFaces[i].mIndices[1] = iTemp + 1;
|
|
pcMesh->mFaces[i].mIndices[2] = iTemp + 0;
|
|
pcTriangles++;
|
|
}
|
|
|
|
}
|
|
// short packed vertices
|
|
/////////////////////////////////////////////////////////////////////////////////////
|
|
else {
|
|
// now get a pointer to the first frame in the file
|
|
const MDL::SimpleFrame_MDLn_SP *pcFirstFrame = (const MDL::SimpleFrame_MDLn_SP *)(szCurrent + sizeof(uint32_t));
|
|
|
|
// get a pointer to the vertices
|
|
const MDL::Vertex_MDL4 *pcVertices = (const MDL::Vertex_MDL4 *)((pcFirstFrame->name) +
|
|
sizeof(pcFirstFrame->name));
|
|
|
|
VALIDATE_FILE_SIZE(pcVertices + pcHeader->num_verts);
|
|
|
|
// now iterate through all triangles
|
|
unsigned int iCurrent = 0;
|
|
for (unsigned int i = 0; i < (unsigned int)pcHeader->num_tris; ++i) {
|
|
pcMesh->mFaces[i].mIndices = new unsigned int[3];
|
|
pcMesh->mFaces[i].mNumIndices = 3;
|
|
|
|
unsigned int iTemp = iCurrent;
|
|
for (unsigned int c = 0; c < 3; ++c, ++iCurrent) {
|
|
// read vertices
|
|
unsigned int iIndex = pcTriangles->index_xyz[c];
|
|
if (iIndex >= (unsigned int)pcHeader->num_verts) {
|
|
iIndex = pcHeader->num_verts - 1;
|
|
ASSIMP_LOG_WARN("Index overflow in MDLn vertex list");
|
|
}
|
|
|
|
aiVector3D &vec = pcMesh->mVertices[iCurrent];
|
|
vec.x = (float)pcVertices[iIndex].v[0] * pcHeader->scale[0];
|
|
vec.x += pcHeader->translate[0];
|
|
|
|
vec.y = (float)pcVertices[iIndex].v[1] * pcHeader->scale[1];
|
|
vec.y += pcHeader->translate[1];
|
|
// vec.y *= -1.0f;
|
|
|
|
vec.z = (float)pcVertices[iIndex].v[2] * pcHeader->scale[2];
|
|
vec.z += pcHeader->translate[2];
|
|
|
|
// read the normal vector from the precalculated normal table
|
|
MD2::LookupNormalIndex(pcVertices[iIndex].normalIndex, pcMesh->mNormals[iCurrent]);
|
|
// pcMesh->mNormals[iCurrent].y *= -1.0f;
|
|
|
|
// read texture coordinates
|
|
if (pcHeader->synctype) {
|
|
ImportUVCoordinate_3DGS_MDL345(pcMesh->mTextureCoords[0][iCurrent],
|
|
pcTexCoords, pcTriangles->index_uv[c]);
|
|
}
|
|
}
|
|
pcMesh->mFaces[i].mIndices[0] = iTemp + 2;
|
|
pcMesh->mFaces[i].mIndices[1] = iTemp + 1;
|
|
pcMesh->mFaces[i].mIndices[2] = iTemp + 0;
|
|
pcTriangles++;
|
|
}
|
|
}
|
|
|
|
// For MDL5 we will need to build valid texture coordinates
|
|
// basing upon the file loaded (only support one file as skin)
|
|
if (0x5 == iGSFileVersion)
|
|
CalculateUVCoordinates_MDL5();
|
|
return;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Get a single UV coordinate for Quake and older GameStudio files
|
|
void MDLImporter::ImportUVCoordinate_3DGS_MDL345(
|
|
aiVector3D &vOut,
|
|
const MDL::TexCoord_MDL3 *pcSrc,
|
|
unsigned int iIndex) {
|
|
ai_assert(nullptr != pcSrc);
|
|
const MDL::Header *const pcHeader = (const MDL::Header *)this->mBuffer;
|
|
|
|
// validate UV indices
|
|
if (iIndex >= (unsigned int)pcHeader->synctype) {
|
|
iIndex = pcHeader->synctype - 1;
|
|
ASSIMP_LOG_WARN("Index overflow in MDLn UV coord list");
|
|
}
|
|
|
|
float s = (float)pcSrc[iIndex].u;
|
|
float t = (float)pcSrc[iIndex].v;
|
|
|
|
// Scale s and t to range from 0.0 to 1.0
|
|
if (0x5 != iGSFileVersion) {
|
|
s = (s + 0.5f) / pcHeader->skinwidth;
|
|
t = 1.0f - (t + 0.5f) / pcHeader->skinheight;
|
|
}
|
|
|
|
vOut.x = s;
|
|
vOut.y = t;
|
|
vOut.z = 0.0f;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Compute UV coordinates for a MDL5 file
|
|
void MDLImporter::CalculateUVCoordinates_MDL5() {
|
|
const MDL::Header *const pcHeader = (const MDL::Header *)this->mBuffer;
|
|
if (pcHeader->num_skins && this->pScene->mNumTextures) {
|
|
const aiTexture *pcTex = this->pScene->mTextures[0];
|
|
|
|
// if the file is loaded in DDS format: get the size of the
|
|
// texture from the header of the DDS file
|
|
// skip three DWORDs and read first height, then the width
|
|
unsigned int iWidth, iHeight;
|
|
if (!pcTex->mHeight) {
|
|
const uint32_t *piPtr = (uint32_t *)pcTex->pcData;
|
|
|
|
piPtr += 3;
|
|
iHeight = (unsigned int)*piPtr++;
|
|
iWidth = (unsigned int)*piPtr;
|
|
if (!iHeight || !iWidth) {
|
|
ASSIMP_LOG_WARN("Either the width or the height of the "
|
|
"embedded DDS texture is zero. Unable to compute final texture "
|
|
"coordinates. The texture coordinates remain in their original "
|
|
"0-x/0-y (x,y = texture size) range.");
|
|
iWidth = 1;
|
|
iHeight = 1;
|
|
}
|
|
} else {
|
|
iWidth = pcTex->mWidth;
|
|
iHeight = pcTex->mHeight;
|
|
}
|
|
|
|
if (1 != iWidth || 1 != iHeight) {
|
|
const float fWidth = (float)iWidth;
|
|
const float fHeight = (float)iHeight;
|
|
aiMesh *pcMesh = this->pScene->mMeshes[0];
|
|
for (unsigned int i = 0; i < pcMesh->mNumVertices; ++i) {
|
|
if (!pcMesh->HasTextureCoords(0)) {
|
|
continue;
|
|
}
|
|
pcMesh->mTextureCoords[0][i].x /= fWidth;
|
|
pcMesh->mTextureCoords[0][i].y /= fHeight;
|
|
pcMesh->mTextureCoords[0][i].y = 1.0f - pcMesh->mTextureCoords[0][i].y; // DX to OGL
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Validate the header of a MDL7 file
|
|
void MDLImporter::ValidateHeader_3DGS_MDL7(const MDL::Header_MDL7 *pcHeader) {
|
|
ai_assert(nullptr != pcHeader);
|
|
|
|
// There are some fixed sizes ...
|
|
if (sizeof(MDL::ColorValue_MDL7) != pcHeader->colorvalue_stc_size) {
|
|
throw DeadlyImportError(
|
|
"[3DGS MDL7] sizeof(MDL::ColorValue_MDL7) != pcHeader->colorvalue_stc_size");
|
|
}
|
|
if (sizeof(MDL::TexCoord_MDL7) != pcHeader->skinpoint_stc_size) {
|
|
throw DeadlyImportError(
|
|
"[3DGS MDL7] sizeof(MDL::TexCoord_MDL7) != pcHeader->skinpoint_stc_size");
|
|
}
|
|
if (sizeof(MDL::Skin_MDL7) != pcHeader->skin_stc_size) {
|
|
throw DeadlyImportError(
|
|
"sizeof(MDL::Skin_MDL7) != pcHeader->skin_stc_size");
|
|
}
|
|
|
|
// if there are no groups ... how should we load such a file?
|
|
if (!pcHeader->groups_num) {
|
|
throw DeadlyImportError("[3DGS MDL7] No frames found");
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// resolve bone animation matrices
|
|
void MDLImporter::CalcAbsBoneMatrices_3DGS_MDL7(MDL::IntBone_MDL7 **apcOutBones) {
|
|
const MDL::Header_MDL7 *pcHeader = (const MDL::Header_MDL7 *)this->mBuffer;
|
|
const MDL::Bone_MDL7 *pcBones = (const MDL::Bone_MDL7 *)(pcHeader + 1);
|
|
ai_assert(nullptr != apcOutBones);
|
|
|
|
// first find the bone that has NO parent, calculate the
|
|
// animation matrix for it, then go on and search for the next parent
|
|
// index (0) and so on until we can't find a new node.
|
|
uint16_t iParent = 0xffff;
|
|
uint32_t iIterations = 0;
|
|
while (iIterations++ < pcHeader->bones_num) {
|
|
for (uint32_t iBone = 0; iBone < pcHeader->bones_num; ++iBone) {
|
|
BE_NCONST MDL::Bone_MDL7 *pcBone = _AI_MDL7_ACCESS_PTR(pcBones, iBone,
|
|
pcHeader->bone_stc_size, MDL::Bone_MDL7);
|
|
|
|
AI_SWAP2(pcBone->parent_index);
|
|
AI_SWAP4(pcBone->x);
|
|
AI_SWAP4(pcBone->y);
|
|
AI_SWAP4(pcBone->z);
|
|
|
|
if (iParent == pcBone->parent_index) {
|
|
// MDL7 readme
|
|
////////////////////////////////////////////////////////////////
|
|
/*
|
|
The animation matrix is then calculated the following way:
|
|
|
|
vector3 bPos = <absolute bone position>
|
|
matrix44 laM; // local animation matrix
|
|
sphrvector key_rotate = <bone rotation>
|
|
|
|
matrix44 m1,m2;
|
|
create_trans_matrix(m1, -bPos.x, -bPos.y, -bPos.z);
|
|
create_trans_matrix(m2, -bPos.x, -bPos.y, -bPos.z);
|
|
|
|
create_rotation_matrix(laM,key_rotate);
|
|
|
|
laM = sm1 * laM;
|
|
laM = laM * sm2;
|
|
*/
|
|
/////////////////////////////////////////////////////////////////
|
|
|
|
MDL::IntBone_MDL7 *const pcOutBone = apcOutBones[iBone];
|
|
|
|
// store the parent index of the bone
|
|
pcOutBone->iParent = pcBone->parent_index;
|
|
if (0xffff != iParent) {
|
|
const MDL::IntBone_MDL7 *pcParentBone = apcOutBones[iParent];
|
|
pcOutBone->mOffsetMatrix.a4 = -pcParentBone->vPosition.x;
|
|
pcOutBone->mOffsetMatrix.b4 = -pcParentBone->vPosition.y;
|
|
pcOutBone->mOffsetMatrix.c4 = -pcParentBone->vPosition.z;
|
|
}
|
|
pcOutBone->vPosition.x = pcBone->x;
|
|
pcOutBone->vPosition.y = pcBone->y;
|
|
pcOutBone->vPosition.z = pcBone->z;
|
|
pcOutBone->mOffsetMatrix.a4 -= pcBone->x;
|
|
pcOutBone->mOffsetMatrix.b4 -= pcBone->y;
|
|
pcOutBone->mOffsetMatrix.c4 -= pcBone->z;
|
|
|
|
if (AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_NOT_THERE == pcHeader->bone_stc_size) {
|
|
// no real name for our poor bone is specified :-(
|
|
pcOutBone->mName.length = ai_snprintf(pcOutBone->mName.data, MAXLEN,
|
|
"UnnamedBone_%i", iBone);
|
|
} else {
|
|
// Make sure we won't run over the buffer's end if there is no
|
|
// terminal 0 character (however the documentation says there
|
|
// should be one)
|
|
uint32_t iMaxLen = pcHeader->bone_stc_size - 16;
|
|
for (uint32_t qq = 0; qq < iMaxLen; ++qq) {
|
|
if (!pcBone->name[qq]) {
|
|
iMaxLen = qq;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// store the name of the bone
|
|
pcOutBone->mName.length = (size_t)iMaxLen;
|
|
::memcpy(pcOutBone->mName.data, pcBone->name, pcOutBone->mName.length);
|
|
pcOutBone->mName.data[pcOutBone->mName.length] = '\0';
|
|
}
|
|
}
|
|
}
|
|
++iParent;
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// read bones from a MDL7 file
|
|
MDL::IntBone_MDL7 **MDLImporter::LoadBones_3DGS_MDL7() {
|
|
const MDL::Header_MDL7 *pcHeader = (const MDL::Header_MDL7 *)this->mBuffer;
|
|
if (pcHeader->bones_num) {
|
|
// validate the size of the bone data structure in the file
|
|
if (AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_20_CHARS != pcHeader->bone_stc_size &&
|
|
AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_32_CHARS != pcHeader->bone_stc_size &&
|
|
AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_NOT_THERE != pcHeader->bone_stc_size) {
|
|
ASSIMP_LOG_WARN("Unknown size of bone data structure");
|
|
return nullptr;
|
|
}
|
|
|
|
MDL::IntBone_MDL7 **apcBonesOut = new MDL::IntBone_MDL7 *[pcHeader->bones_num];
|
|
for (uint32_t crank = 0; crank < pcHeader->bones_num; ++crank)
|
|
apcBonesOut[crank] = new MDL::IntBone_MDL7();
|
|
|
|
// and calculate absolute bone offset matrices ...
|
|
CalcAbsBoneMatrices_3DGS_MDL7(apcBonesOut);
|
|
return apcBonesOut;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// read faces from a MDL7 file
|
|
void MDLImporter::ReadFaces_3DGS_MDL7(const MDL::IntGroupInfo_MDL7 &groupInfo,
|
|
MDL::IntGroupData_MDL7 &groupData) {
|
|
const MDL::Header_MDL7 *pcHeader = (const MDL::Header_MDL7 *)this->mBuffer;
|
|
MDL::Triangle_MDL7 *pcGroupTris = groupInfo.pcGroupTris;
|
|
|
|
// iterate through all triangles and build valid display lists
|
|
unsigned int iOutIndex = 0;
|
|
for (unsigned int iTriangle = 0; iTriangle < (unsigned int)groupInfo.pcGroup->numtris; ++iTriangle) {
|
|
AI_SWAP2(pcGroupTris->v_index[0]);
|
|
AI_SWAP2(pcGroupTris->v_index[1]);
|
|
AI_SWAP2(pcGroupTris->v_index[2]);
|
|
|
|
// iterate through all indices of the current triangle
|
|
for (unsigned int c = 0; c < 3; ++c, ++iOutIndex) {
|
|
|
|
// validate the vertex index
|
|
unsigned int iIndex = pcGroupTris->v_index[c];
|
|
if (iIndex > (unsigned int)groupInfo.pcGroup->numverts) {
|
|
// (we might need to read this section a second time - to process frame vertices correctly)
|
|
pcGroupTris->v_index[c] = (uint16_t)(iIndex = groupInfo.pcGroup->numverts - 1);
|
|
ASSIMP_LOG_WARN("Index overflow in MDL7 vertex list");
|
|
}
|
|
|
|
// write the output face index
|
|
groupData.pcFaces[iTriangle].mIndices[2 - c] = iOutIndex;
|
|
|
|
aiVector3D &vPosition = groupData.vPositions[iOutIndex];
|
|
vPosition.x = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts, iIndex, pcHeader->mainvertex_stc_size).x;
|
|
vPosition.y = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts, iIndex, pcHeader->mainvertex_stc_size).y;
|
|
vPosition.z = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts, iIndex, pcHeader->mainvertex_stc_size).z;
|
|
|
|
// if we have bones, save the index
|
|
if (!groupData.aiBones.empty()) {
|
|
groupData.aiBones[iOutIndex] = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts,
|
|
iIndex, pcHeader->mainvertex_stc_size)
|
|
.vertindex;
|
|
}
|
|
|
|
// now read the normal vector
|
|
if (AI_MDL7_FRAMEVERTEX030305_STCSIZE <= pcHeader->mainvertex_stc_size) {
|
|
// read the full normal vector
|
|
aiVector3D &vNormal = groupData.vNormals[iOutIndex];
|
|
vNormal.x = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts, iIndex, pcHeader->mainvertex_stc_size).norm[0];
|
|
AI_SWAP4(vNormal.x);
|
|
vNormal.y = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts, iIndex, pcHeader->mainvertex_stc_size).norm[1];
|
|
AI_SWAP4(vNormal.y);
|
|
vNormal.z = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts, iIndex, pcHeader->mainvertex_stc_size).norm[2];
|
|
AI_SWAP4(vNormal.z);
|
|
} else if (AI_MDL7_FRAMEVERTEX120503_STCSIZE <= pcHeader->mainvertex_stc_size) {
|
|
// read the normal vector from Quake2's smart table
|
|
aiVector3D &vNormal = groupData.vNormals[iOutIndex];
|
|
MD2::LookupNormalIndex(_AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts, iIndex,
|
|
pcHeader->mainvertex_stc_size)
|
|
.norm162index,
|
|
vNormal);
|
|
}
|
|
// validate and process the first uv coordinate set
|
|
if (pcHeader->triangle_stc_size >= AI_MDL7_TRIANGLE_STD_SIZE_ONE_UV) {
|
|
|
|
if (groupInfo.pcGroup->num_stpts) {
|
|
AI_SWAP2(pcGroupTris->skinsets[0].st_index[0]);
|
|
AI_SWAP2(pcGroupTris->skinsets[0].st_index[1]);
|
|
AI_SWAP2(pcGroupTris->skinsets[0].st_index[2]);
|
|
|
|
iIndex = pcGroupTris->skinsets[0].st_index[c];
|
|
if (iIndex > (unsigned int)groupInfo.pcGroup->num_stpts) {
|
|
iIndex = groupInfo.pcGroup->num_stpts - 1;
|
|
ASSIMP_LOG_WARN("Index overflow in MDL7 UV coordinate list (#1)");
|
|
}
|
|
|
|
float u = groupInfo.pcGroupUVs[iIndex].u;
|
|
float v = 1.0f - groupInfo.pcGroupUVs[iIndex].v; // DX to OGL
|
|
|
|
groupData.vTextureCoords1[iOutIndex].x = u;
|
|
groupData.vTextureCoords1[iOutIndex].y = v;
|
|
}
|
|
// assign the material index, but only if it is existing
|
|
if (pcHeader->triangle_stc_size >= AI_MDL7_TRIANGLE_STD_SIZE_ONE_UV_WITH_MATINDEX) {
|
|
AI_SWAP4(pcGroupTris->skinsets[0].material);
|
|
groupData.pcFaces[iTriangle].iMatIndex[0] = pcGroupTris->skinsets[0].material;
|
|
}
|
|
}
|
|
// validate and process the second uv coordinate set
|
|
if (pcHeader->triangle_stc_size >= AI_MDL7_TRIANGLE_STD_SIZE_TWO_UV) {
|
|
|
|
if (groupInfo.pcGroup->num_stpts) {
|
|
AI_SWAP2(pcGroupTris->skinsets[1].st_index[0]);
|
|
AI_SWAP2(pcGroupTris->skinsets[1].st_index[1]);
|
|
AI_SWAP2(pcGroupTris->skinsets[1].st_index[2]);
|
|
AI_SWAP4(pcGroupTris->skinsets[1].material);
|
|
|
|
iIndex = pcGroupTris->skinsets[1].st_index[c];
|
|
if (iIndex > (unsigned int)groupInfo.pcGroup->num_stpts) {
|
|
iIndex = groupInfo.pcGroup->num_stpts - 1;
|
|
ASSIMP_LOG_WARN("Index overflow in MDL7 UV coordinate list (#2)");
|
|
}
|
|
|
|
float u = groupInfo.pcGroupUVs[iIndex].u;
|
|
float v = 1.0f - groupInfo.pcGroupUVs[iIndex].v;
|
|
|
|
groupData.vTextureCoords2[iOutIndex].x = u;
|
|
groupData.vTextureCoords2[iOutIndex].y = v; // DX to OGL
|
|
|
|
// check whether we do really need the second texture
|
|
// coordinate set ... wastes memory and loading time
|
|
if (0 != iIndex && (u != groupData.vTextureCoords1[iOutIndex].x ||
|
|
v != groupData.vTextureCoords1[iOutIndex].y))
|
|
groupData.bNeed2UV = true;
|
|
|
|
// if the material differs, we need a second skin, too
|
|
if (pcGroupTris->skinsets[1].material != pcGroupTris->skinsets[0].material)
|
|
groupData.bNeed2UV = true;
|
|
}
|
|
// assign the material index
|
|
groupData.pcFaces[iTriangle].iMatIndex[1] = pcGroupTris->skinsets[1].material;
|
|
}
|
|
}
|
|
// get the next triangle in the list
|
|
pcGroupTris = (MDL::Triangle_MDL7 *)((const char *)pcGroupTris + pcHeader->triangle_stc_size);
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// handle frames in a MDL7 file
|
|
bool MDLImporter::ProcessFrames_3DGS_MDL7(const MDL::IntGroupInfo_MDL7 &groupInfo,
|
|
MDL::IntGroupData_MDL7 &groupData,
|
|
MDL::IntSharedData_MDL7 &shared,
|
|
const unsigned char *szCurrent,
|
|
const unsigned char **szCurrentOut) {
|
|
ai_assert(nullptr != szCurrent);
|
|
ai_assert(nullptr != szCurrentOut);
|
|
|
|
const MDL::Header_MDL7 *pcHeader = (const MDL::Header_MDL7 *)mBuffer;
|
|
|
|
// if we have no bones we can simply skip all frames,
|
|
// otherwise we'll need to process them.
|
|
// FIX: If we need another frame than the first we must apply frame vertex replacements ...
|
|
for (unsigned int iFrame = 0; iFrame < (unsigned int)groupInfo.pcGroup->numframes; ++iFrame) {
|
|
MDL::IntFrameInfo_MDL7 frame((BE_NCONST MDL::Frame_MDL7 *)szCurrent, iFrame);
|
|
|
|
AI_SWAP4(frame.pcFrame->vertices_count);
|
|
AI_SWAP4(frame.pcFrame->transmatrix_count);
|
|
|
|
const unsigned int iAdd = pcHeader->frame_stc_size +
|
|
frame.pcFrame->vertices_count * pcHeader->framevertex_stc_size +
|
|
frame.pcFrame->transmatrix_count * pcHeader->bonetrans_stc_size;
|
|
|
|
if (((const char *)szCurrent - (const char *)pcHeader) + iAdd > (unsigned int)pcHeader->data_size) {
|
|
ASSIMP_LOG_WARN("Index overflow in frame area. "
|
|
"Ignoring all frames and all further mesh groups, too.");
|
|
|
|
// don't parse more groups if we can't even read one
|
|
// FIXME: sometimes this seems to occur even for valid files ...
|
|
*szCurrentOut = szCurrent;
|
|
return false;
|
|
}
|
|
// our output frame?
|
|
if (configFrameID == iFrame) {
|
|
BE_NCONST MDL::Vertex_MDL7 *pcFrameVertices = (BE_NCONST MDL::Vertex_MDL7 *)(szCurrent + pcHeader->frame_stc_size);
|
|
|
|
for (unsigned int qq = 0; qq < frame.pcFrame->vertices_count; ++qq) {
|
|
// I assume this are simple replacements for normal vertices, the bone index serving
|
|
// as the index of the vertex to be replaced.
|
|
uint16_t iIndex = _AI_MDL7_ACCESS(pcFrameVertices, qq, pcHeader->framevertex_stc_size, MDL::Vertex_MDL7).vertindex;
|
|
AI_SWAP2(iIndex);
|
|
if (iIndex >= groupInfo.pcGroup->numverts) {
|
|
ASSIMP_LOG_WARN("Invalid vertex index in frame vertex section");
|
|
continue;
|
|
}
|
|
|
|
aiVector3D vPosition, vNormal;
|
|
|
|
vPosition.x = _AI_MDL7_ACCESS_VERT(pcFrameVertices, qq, pcHeader->framevertex_stc_size).x;
|
|
AI_SWAP4(vPosition.x);
|
|
vPosition.y = _AI_MDL7_ACCESS_VERT(pcFrameVertices, qq, pcHeader->framevertex_stc_size).y;
|
|
AI_SWAP4(vPosition.y);
|
|
vPosition.z = _AI_MDL7_ACCESS_VERT(pcFrameVertices, qq, pcHeader->framevertex_stc_size).z;
|
|
AI_SWAP4(vPosition.z);
|
|
|
|
// now read the normal vector
|
|
if (AI_MDL7_FRAMEVERTEX030305_STCSIZE <= pcHeader->mainvertex_stc_size) {
|
|
// read the full normal vector
|
|
vNormal.x = _AI_MDL7_ACCESS_VERT(pcFrameVertices, qq, pcHeader->framevertex_stc_size).norm[0];
|
|
AI_SWAP4(vNormal.x);
|
|
vNormal.y = _AI_MDL7_ACCESS_VERT(pcFrameVertices, qq, pcHeader->framevertex_stc_size).norm[1];
|
|
AI_SWAP4(vNormal.y);
|
|
vNormal.z = _AI_MDL7_ACCESS_VERT(pcFrameVertices, qq, pcHeader->framevertex_stc_size).norm[2];
|
|
AI_SWAP4(vNormal.z);
|
|
} else if (AI_MDL7_FRAMEVERTEX120503_STCSIZE <= pcHeader->mainvertex_stc_size) {
|
|
// read the normal vector from Quake2's smart table
|
|
MD2::LookupNormalIndex(_AI_MDL7_ACCESS_VERT(pcFrameVertices, qq,
|
|
pcHeader->framevertex_stc_size)
|
|
.norm162index,
|
|
vNormal);
|
|
}
|
|
|
|
// FIXME: O(n^2) at the moment ...
|
|
BE_NCONST MDL::Triangle_MDL7 *pcGroupTris = groupInfo.pcGroupTris;
|
|
unsigned int iOutIndex = 0;
|
|
for (unsigned int iTriangle = 0; iTriangle < (unsigned int)groupInfo.pcGroup->numtris; ++iTriangle) {
|
|
// iterate through all indices of the current triangle
|
|
for (unsigned int c = 0; c < 3; ++c, ++iOutIndex) {
|
|
// replace the vertex with the new data
|
|
const unsigned int iCurIndex = pcGroupTris->v_index[c];
|
|
if (iCurIndex == iIndex) {
|
|
groupData.vPositions[iOutIndex] = vPosition;
|
|
groupData.vNormals[iOutIndex] = vNormal;
|
|
}
|
|
}
|
|
// get the next triangle in the list
|
|
pcGroupTris = (BE_NCONST MDL::Triangle_MDL7 *)((const char *)
|
|
pcGroupTris +
|
|
pcHeader->triangle_stc_size);
|
|
}
|
|
}
|
|
}
|
|
// parse bone trafo matrix keys (only if there are bones ...)
|
|
if (shared.apcOutBones) {
|
|
ParseBoneTrafoKeys_3DGS_MDL7(groupInfo, frame, shared);
|
|
}
|
|
szCurrent += iAdd;
|
|
}
|
|
*szCurrentOut = szCurrent;
|
|
return true;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Sort faces by material, handle multiple UVs correctly
|
|
void MDLImporter::SortByMaterials_3DGS_MDL7(
|
|
const MDL::IntGroupInfo_MDL7 &groupInfo,
|
|
MDL::IntGroupData_MDL7 &groupData,
|
|
MDL::IntSplitGroupData_MDL7 &splitGroupData) {
|
|
const unsigned int iNumMaterials = (unsigned int)splitGroupData.shared.pcMats.size();
|
|
if (!groupData.bNeed2UV) {
|
|
// if we don't need a second set of texture coordinates there is no reason to keep it in memory ...
|
|
groupData.vTextureCoords2.clear();
|
|
|
|
// allocate the array
|
|
splitGroupData.aiSplit = new std::vector<unsigned int> *[iNumMaterials];
|
|
|
|
for (unsigned int m = 0; m < iNumMaterials; ++m)
|
|
splitGroupData.aiSplit[m] = new std::vector<unsigned int>();
|
|
|
|
// iterate through all faces and sort by material
|
|
for (unsigned int iFace = 0; iFace < (unsigned int)groupInfo.pcGroup->numtris; ++iFace) {
|
|
// check range
|
|
if (groupData.pcFaces[iFace].iMatIndex[0] >= iNumMaterials) {
|
|
// use the last material instead
|
|
splitGroupData.aiSplit[iNumMaterials - 1]->push_back(iFace);
|
|
|
|
// sometimes MED writes -1, but normally only if there is only
|
|
// one skin assigned. No warning in this case
|
|
if (0xFFFFFFFF != groupData.pcFaces[iFace].iMatIndex[0])
|
|
ASSIMP_LOG_WARN("Index overflow in MDL7 material list [#0]");
|
|
} else
|
|
splitGroupData.aiSplit[groupData.pcFaces[iFace].iMatIndex[0]]->push_back(iFace);
|
|
}
|
|
} else {
|
|
// we need to build combined materials for each combination of
|
|
std::vector<MDL::IntMaterial_MDL7> avMats;
|
|
avMats.reserve(iNumMaterials * 2);
|
|
|
|
// fixme: why on the heap?
|
|
std::vector<std::vector<unsigned int> *> aiTempSplit(iNumMaterials * 2);
|
|
for (unsigned int m = 0; m < iNumMaterials; ++m)
|
|
aiTempSplit[m] = new std::vector<unsigned int>();
|
|
|
|
// iterate through all faces and sort by material
|
|
for (unsigned int iFace = 0; iFace < (unsigned int)groupInfo.pcGroup->numtris; ++iFace) {
|
|
// check range
|
|
unsigned int iMatIndex = groupData.pcFaces[iFace].iMatIndex[0];
|
|
if (iMatIndex >= iNumMaterials) {
|
|
// sometimes MED writes -1, but normally only if there is only
|
|
// one skin assigned. No warning in this case
|
|
if (UINT_MAX != iMatIndex)
|
|
ASSIMP_LOG_WARN("Index overflow in MDL7 material list [#1]");
|
|
iMatIndex = iNumMaterials - 1;
|
|
}
|
|
unsigned int iMatIndex2 = groupData.pcFaces[iFace].iMatIndex[1];
|
|
|
|
unsigned int iNum = iMatIndex;
|
|
if (UINT_MAX != iMatIndex2 && iMatIndex != iMatIndex2) {
|
|
if (iMatIndex2 >= iNumMaterials) {
|
|
// sometimes MED writes -1, but normally only if there is only
|
|
// one skin assigned. No warning in this case
|
|
ASSIMP_LOG_WARN("Index overflow in MDL7 material list [#2]");
|
|
iMatIndex2 = iNumMaterials - 1;
|
|
}
|
|
|
|
// do a slow search in the list ...
|
|
iNum = 0;
|
|
bool bFound = false;
|
|
for (std::vector<MDL::IntMaterial_MDL7>::iterator i = avMats.begin(); i != avMats.end(); ++i, ++iNum) {
|
|
if ((*i).iOldMatIndices[0] == iMatIndex && (*i).iOldMatIndices[1] == iMatIndex2) {
|
|
// reuse this material
|
|
bFound = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!bFound) {
|
|
// build a new material ...
|
|
MDL::IntMaterial_MDL7 sHelper;
|
|
sHelper.pcMat = new aiMaterial();
|
|
sHelper.iOldMatIndices[0] = iMatIndex;
|
|
sHelper.iOldMatIndices[1] = iMatIndex2;
|
|
JoinSkins_3DGS_MDL7(splitGroupData.shared.pcMats[iMatIndex],
|
|
splitGroupData.shared.pcMats[iMatIndex2], sHelper.pcMat);
|
|
|
|
// and add it to the list
|
|
avMats.push_back(sHelper);
|
|
iNum = (unsigned int)avMats.size() - 1;
|
|
}
|
|
// adjust the size of the file array
|
|
if (iNum == aiTempSplit.size()) {
|
|
aiTempSplit.push_back(new std::vector<unsigned int>());
|
|
}
|
|
}
|
|
aiTempSplit[iNum]->push_back(iFace);
|
|
}
|
|
|
|
// now add the newly created materials to the old list
|
|
if (0 == groupInfo.iIndex) {
|
|
splitGroupData.shared.pcMats.resize(avMats.size());
|
|
for (unsigned int o = 0; o < avMats.size(); ++o)
|
|
splitGroupData.shared.pcMats[o] = avMats[o].pcMat;
|
|
} else {
|
|
// This might result in redundant materials ...
|
|
splitGroupData.shared.pcMats.resize(iNumMaterials + avMats.size());
|
|
for (unsigned int o = iNumMaterials; o < avMats.size(); ++o)
|
|
splitGroupData.shared.pcMats[o] = avMats[o].pcMat;
|
|
}
|
|
|
|
// and build the final face-to-material array
|
|
splitGroupData.aiSplit = new std::vector<unsigned int> *[aiTempSplit.size()];
|
|
for (unsigned int m = 0; m < iNumMaterials; ++m)
|
|
splitGroupData.aiSplit[m] = aiTempSplit[m];
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Read a MDL7 file
|
|
void MDLImporter::InternReadFile_3DGS_MDL7() {
|
|
ai_assert(nullptr != pScene);
|
|
|
|
MDL::IntSharedData_MDL7 sharedData;
|
|
|
|
// current cursor position in the file
|
|
BE_NCONST MDL::Header_MDL7 *pcHeader = (BE_NCONST MDL::Header_MDL7 *)this->mBuffer;
|
|
const unsigned char *szCurrent = (const unsigned char *)(pcHeader + 1);
|
|
|
|
AI_SWAP4(pcHeader->version);
|
|
AI_SWAP4(pcHeader->bones_num);
|
|
AI_SWAP4(pcHeader->groups_num);
|
|
AI_SWAP4(pcHeader->data_size);
|
|
AI_SWAP4(pcHeader->entlump_size);
|
|
AI_SWAP4(pcHeader->medlump_size);
|
|
AI_SWAP2(pcHeader->bone_stc_size);
|
|
AI_SWAP2(pcHeader->skin_stc_size);
|
|
AI_SWAP2(pcHeader->colorvalue_stc_size);
|
|
AI_SWAP2(pcHeader->material_stc_size);
|
|
AI_SWAP2(pcHeader->skinpoint_stc_size);
|
|
AI_SWAP2(pcHeader->triangle_stc_size);
|
|
AI_SWAP2(pcHeader->mainvertex_stc_size);
|
|
AI_SWAP2(pcHeader->framevertex_stc_size);
|
|
AI_SWAP2(pcHeader->bonetrans_stc_size);
|
|
AI_SWAP2(pcHeader->frame_stc_size);
|
|
|
|
// validate the header of the file. There are some structure
|
|
// sizes that are expected by the loader to be constant
|
|
this->ValidateHeader_3DGS_MDL7(pcHeader);
|
|
|
|
// load all bones (they are shared by all groups, so
|
|
// we'll need to add them to all groups/meshes later)
|
|
// apcBonesOut is a list of all bones or nullptr if they could not been loaded
|
|
szCurrent += pcHeader->bones_num * pcHeader->bone_stc_size;
|
|
sharedData.apcOutBones = this->LoadBones_3DGS_MDL7();
|
|
|
|
// vector to held all created meshes
|
|
std::vector<aiMesh *> *avOutList;
|
|
|
|
// 3 meshes per group - that should be OK for most models
|
|
avOutList = new std::vector<aiMesh *>[pcHeader->groups_num];
|
|
for (uint32_t i = 0; i < pcHeader->groups_num; ++i)
|
|
avOutList[i].reserve(3);
|
|
|
|
// buffer to held the names of all groups in the file
|
|
const size_t buffersize(AI_MDL7_MAX_GROUPNAMESIZE * pcHeader->groups_num);
|
|
char *aszGroupNameBuffer = new char[buffersize];
|
|
|
|
// read all groups
|
|
for (unsigned int iGroup = 0; iGroup < (unsigned int)pcHeader->groups_num; ++iGroup) {
|
|
MDL::IntGroupInfo_MDL7 groupInfo((BE_NCONST MDL::Group_MDL7 *)szCurrent, iGroup);
|
|
szCurrent = (const unsigned char *)(groupInfo.pcGroup + 1);
|
|
|
|
VALIDATE_FILE_SIZE(szCurrent);
|
|
|
|
AI_SWAP4(groupInfo.pcGroup->groupdata_size);
|
|
AI_SWAP4(groupInfo.pcGroup->numskins);
|
|
AI_SWAP4(groupInfo.pcGroup->num_stpts);
|
|
AI_SWAP4(groupInfo.pcGroup->numtris);
|
|
AI_SWAP4(groupInfo.pcGroup->numverts);
|
|
AI_SWAP4(groupInfo.pcGroup->numframes);
|
|
|
|
if (1 != groupInfo.pcGroup->typ) {
|
|
// Not a triangle-based mesh
|
|
ASSIMP_LOG_WARN("[3DGS MDL7] Not a triangle mesh group. Continuing happily");
|
|
}
|
|
|
|
// store the name of the group
|
|
const unsigned int ofs = iGroup * AI_MDL7_MAX_GROUPNAMESIZE;
|
|
::memcpy(&aszGroupNameBuffer[ofs],
|
|
groupInfo.pcGroup->name, AI_MDL7_MAX_GROUPNAMESIZE);
|
|
|
|
// make sure '\0' is at the end
|
|
aszGroupNameBuffer[ofs + AI_MDL7_MAX_GROUPNAMESIZE - 1] = '\0';
|
|
|
|
// read all skins
|
|
sharedData.pcMats.reserve(sharedData.pcMats.size() + groupInfo.pcGroup->numskins);
|
|
sharedData.abNeedMaterials.resize(sharedData.abNeedMaterials.size() +
|
|
groupInfo.pcGroup->numskins,
|
|
false);
|
|
|
|
for (unsigned int iSkin = 0; iSkin < (unsigned int)groupInfo.pcGroup->numskins; ++iSkin) {
|
|
ParseSkinLump_3DGS_MDL7(szCurrent, &szCurrent, sharedData.pcMats);
|
|
}
|
|
// if we have absolutely no skin loaded we need to generate a default material
|
|
if (sharedData.pcMats.empty()) {
|
|
const int iMode = (int)aiShadingMode_Gouraud;
|
|
sharedData.pcMats.push_back(new aiMaterial());
|
|
aiMaterial *pcHelper = (aiMaterial *)sharedData.pcMats[0];
|
|
pcHelper->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL);
|
|
|
|
aiColor3D clr;
|
|
clr.b = clr.g = clr.r = 0.6f;
|
|
pcHelper->AddProperty<aiColor3D>(&clr, 1, AI_MATKEY_COLOR_DIFFUSE);
|
|
pcHelper->AddProperty<aiColor3D>(&clr, 1, AI_MATKEY_COLOR_SPECULAR);
|
|
|
|
clr.b = clr.g = clr.r = 0.05f;
|
|
pcHelper->AddProperty<aiColor3D>(&clr, 1, AI_MATKEY_COLOR_AMBIENT);
|
|
|
|
aiString szName;
|
|
szName.Set(AI_DEFAULT_MATERIAL_NAME);
|
|
pcHelper->AddProperty(&szName, AI_MATKEY_NAME);
|
|
|
|
sharedData.abNeedMaterials.resize(1, false);
|
|
}
|
|
|
|
// now get a pointer to all texture coords in the group
|
|
groupInfo.pcGroupUVs = (BE_NCONST MDL::TexCoord_MDL7 *)szCurrent;
|
|
for (int i = 0; i < groupInfo.pcGroup->num_stpts; ++i) {
|
|
AI_SWAP4(groupInfo.pcGroupUVs[i].u);
|
|
AI_SWAP4(groupInfo.pcGroupUVs[i].v);
|
|
}
|
|
szCurrent += pcHeader->skinpoint_stc_size * groupInfo.pcGroup->num_stpts;
|
|
|
|
// now get a pointer to all triangle in the group
|
|
groupInfo.pcGroupTris = (Triangle_MDL7 *)szCurrent;
|
|
szCurrent += pcHeader->triangle_stc_size * groupInfo.pcGroup->numtris;
|
|
|
|
// now get a pointer to all vertices in the group
|
|
groupInfo.pcGroupVerts = (BE_NCONST MDL::Vertex_MDL7 *)szCurrent;
|
|
for (int i = 0; i < groupInfo.pcGroup->numverts; ++i) {
|
|
AI_SWAP4(groupInfo.pcGroupVerts[i].x);
|
|
AI_SWAP4(groupInfo.pcGroupVerts[i].y);
|
|
AI_SWAP4(groupInfo.pcGroupVerts[i].z);
|
|
|
|
AI_SWAP2(groupInfo.pcGroupVerts[i].vertindex);
|
|
//We can not swap the normal information now as we don't know which of the two kinds it is
|
|
}
|
|
szCurrent += pcHeader->mainvertex_stc_size * groupInfo.pcGroup->numverts;
|
|
VALIDATE_FILE_SIZE(szCurrent);
|
|
|
|
MDL::IntSplitGroupData_MDL7 splitGroupData(sharedData, avOutList[iGroup]);
|
|
MDL::IntGroupData_MDL7 groupData;
|
|
if (groupInfo.pcGroup->numtris && groupInfo.pcGroup->numverts) {
|
|
// build output vectors
|
|
const unsigned int iNumVertices = groupInfo.pcGroup->numtris * 3;
|
|
groupData.vPositions.resize(iNumVertices);
|
|
groupData.vNormals.resize(iNumVertices);
|
|
|
|
if (sharedData.apcOutBones) groupData.aiBones.resize(iNumVertices, UINT_MAX);
|
|
|
|
// it is also possible that there are 0 UV coordinate sets
|
|
if (groupInfo.pcGroup->num_stpts) {
|
|
groupData.vTextureCoords1.resize(iNumVertices, aiVector3D());
|
|
|
|
// check whether the triangle data structure is large enough
|
|
// to contain a second UV coordinate set
|
|
if (pcHeader->triangle_stc_size >= AI_MDL7_TRIANGLE_STD_SIZE_TWO_UV) {
|
|
groupData.vTextureCoords2.resize(iNumVertices, aiVector3D());
|
|
groupData.bNeed2UV = true;
|
|
}
|
|
}
|
|
groupData.pcFaces.resize(groupInfo.pcGroup->numtris);
|
|
|
|
// read all faces into the preallocated arrays
|
|
ReadFaces_3DGS_MDL7(groupInfo, groupData);
|
|
|
|
// sort by materials
|
|
SortByMaterials_3DGS_MDL7(groupInfo, groupData,
|
|
splitGroupData);
|
|
|
|
for (unsigned int qq = 0; qq < sharedData.pcMats.size(); ++qq) {
|
|
if (!splitGroupData.aiSplit[qq]->empty())
|
|
sharedData.abNeedMaterials[qq] = true;
|
|
}
|
|
} else
|
|
ASSIMP_LOG_WARN("[3DGS MDL7] Mesh group consists of 0 "
|
|
"vertices or faces. It will be skipped.");
|
|
|
|
// process all frames and generate output meshes
|
|
ProcessFrames_3DGS_MDL7(groupInfo, groupData, sharedData, szCurrent, &szCurrent);
|
|
GenerateOutputMeshes_3DGS_MDL7(groupData, splitGroupData);
|
|
}
|
|
|
|
// generate a nodegraph and subnodes for each group
|
|
pScene->mRootNode = new aiNode();
|
|
|
|
// now we need to build a final mesh list
|
|
for (uint32_t i = 0; i < pcHeader->groups_num; ++i)
|
|
pScene->mNumMeshes += (unsigned int)avOutList[i].size();
|
|
|
|
pScene->mMeshes = new aiMesh *[pScene->mNumMeshes];
|
|
{
|
|
unsigned int p = 0, q = 0;
|
|
for (uint32_t i = 0; i < pcHeader->groups_num; ++i) {
|
|
for (unsigned int a = 0; a < avOutList[i].size(); ++a) {
|
|
pScene->mMeshes[p++] = avOutList[i][a];
|
|
}
|
|
if (!avOutList[i].empty()) ++pScene->mRootNode->mNumChildren;
|
|
}
|
|
// we will later need an extra node to serve as parent for all bones
|
|
if (sharedData.apcOutBones) ++pScene->mRootNode->mNumChildren;
|
|
this->pScene->mRootNode->mChildren = new aiNode *[pScene->mRootNode->mNumChildren];
|
|
p = 0;
|
|
for (uint32_t i = 0; i < pcHeader->groups_num; ++i) {
|
|
if (avOutList[i].empty()) continue;
|
|
|
|
aiNode *const pcNode = pScene->mRootNode->mChildren[p] = new aiNode();
|
|
pcNode->mNumMeshes = (unsigned int)avOutList[i].size();
|
|
pcNode->mMeshes = new unsigned int[pcNode->mNumMeshes];
|
|
pcNode->mParent = this->pScene->mRootNode;
|
|
for (unsigned int a = 0; a < pcNode->mNumMeshes; ++a)
|
|
pcNode->mMeshes[a] = q + a;
|
|
q += (unsigned int)avOutList[i].size();
|
|
|
|
// setup the name of the node
|
|
char *const szBuffer = &aszGroupNameBuffer[i * AI_MDL7_MAX_GROUPNAMESIZE];
|
|
if ('\0' == *szBuffer) {
|
|
const size_t maxSize(buffersize - (i * AI_MDL7_MAX_GROUPNAMESIZE));
|
|
pcNode->mName.length = ai_snprintf(szBuffer, maxSize, "Group_%u", p);
|
|
} else {
|
|
pcNode->mName.length = (ai_uint32)::strlen(szBuffer);
|
|
}
|
|
::strncpy(pcNode->mName.data, szBuffer, MAXLEN - 1);
|
|
++p;
|
|
}
|
|
}
|
|
|
|
// if there is only one root node with a single child we can optimize it a bit ...
|
|
if (1 == pScene->mRootNode->mNumChildren && !sharedData.apcOutBones) {
|
|
aiNode *pcOldRoot = this->pScene->mRootNode;
|
|
pScene->mRootNode = pcOldRoot->mChildren[0];
|
|
pcOldRoot->mChildren[0] = nullptr;
|
|
delete pcOldRoot;
|
|
pScene->mRootNode->mParent = nullptr;
|
|
} else
|
|
pScene->mRootNode->mName.Set("<mesh_root>");
|
|
|
|
delete[] avOutList;
|
|
delete[] aszGroupNameBuffer;
|
|
AI_DEBUG_INVALIDATE_PTR(avOutList);
|
|
AI_DEBUG_INVALIDATE_PTR(aszGroupNameBuffer);
|
|
|
|
// build a final material list.
|
|
CopyMaterials_3DGS_MDL7(sharedData);
|
|
HandleMaterialReferences_3DGS_MDL7();
|
|
|
|
// generate output bone animations and add all bones to the scenegraph
|
|
if (sharedData.apcOutBones) {
|
|
// this step adds empty dummy bones to the nodegraph
|
|
// insert another dummy node to avoid name conflicts
|
|
aiNode *const pc = pScene->mRootNode->mChildren[pScene->mRootNode->mNumChildren - 1] = new aiNode();
|
|
|
|
pc->mName.Set("<skeleton_root>");
|
|
|
|
// add bones to the nodegraph
|
|
AddBonesToNodeGraph_3DGS_MDL7((const Assimp::MDL::IntBone_MDL7 **)
|
|
sharedData.apcOutBones,
|
|
pc, 0xffff);
|
|
|
|
// this steps build a valid output animation
|
|
BuildOutputAnims_3DGS_MDL7((const Assimp::MDL::IntBone_MDL7 **)
|
|
sharedData.apcOutBones);
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Copy materials
|
|
void MDLImporter::CopyMaterials_3DGS_MDL7(MDL::IntSharedData_MDL7 &shared) {
|
|
pScene->mNumMaterials = (unsigned int)shared.pcMats.size();
|
|
pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials];
|
|
for (unsigned int i = 0; i < pScene->mNumMaterials; ++i)
|
|
pScene->mMaterials[i] = shared.pcMats[i];
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Process material references
|
|
void MDLImporter::HandleMaterialReferences_3DGS_MDL7() {
|
|
// search for referrer materials
|
|
for (unsigned int i = 0; i < pScene->mNumMaterials; ++i) {
|
|
int iIndex = 0;
|
|
if (AI_SUCCESS == aiGetMaterialInteger(pScene->mMaterials[i], AI_MDL7_REFERRER_MATERIAL, &iIndex)) {
|
|
for (unsigned int a = 0; a < pScene->mNumMeshes; ++a) {
|
|
aiMesh *const pcMesh = pScene->mMeshes[a];
|
|
if (i == pcMesh->mMaterialIndex) {
|
|
pcMesh->mMaterialIndex = iIndex;
|
|
}
|
|
}
|
|
// collapse the rest of the array
|
|
delete pScene->mMaterials[i];
|
|
for (unsigned int pp = i; pp < pScene->mNumMaterials - 1; ++pp) {
|
|
|
|
pScene->mMaterials[pp] = pScene->mMaterials[pp + 1];
|
|
for (unsigned int a = 0; a < pScene->mNumMeshes; ++a) {
|
|
aiMesh *const pcMesh = pScene->mMeshes[a];
|
|
if (pcMesh->mMaterialIndex > i) --pcMesh->mMaterialIndex;
|
|
}
|
|
}
|
|
--pScene->mNumMaterials;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Read bone transformation keys
|
|
void MDLImporter::ParseBoneTrafoKeys_3DGS_MDL7(
|
|
const MDL::IntGroupInfo_MDL7 &groupInfo,
|
|
IntFrameInfo_MDL7 &frame,
|
|
MDL::IntSharedData_MDL7 &shared) {
|
|
const MDL::Header_MDL7 *const pcHeader = (const MDL::Header_MDL7 *)this->mBuffer;
|
|
|
|
// only the first group contains bone animation keys
|
|
if (frame.pcFrame->transmatrix_count) {
|
|
if (!groupInfo.iIndex) {
|
|
// skip all frames vertices. We can't support them
|
|
const MDL::BoneTransform_MDL7 *pcBoneTransforms = (const MDL::BoneTransform_MDL7 *)(((const char *)frame.pcFrame) + pcHeader->frame_stc_size +
|
|
frame.pcFrame->vertices_count * pcHeader->framevertex_stc_size);
|
|
|
|
// read all transformation matrices
|
|
for (unsigned int iTrafo = 0; iTrafo < frame.pcFrame->transmatrix_count; ++iTrafo) {
|
|
if (pcBoneTransforms->bone_index >= pcHeader->bones_num) {
|
|
ASSIMP_LOG_WARN("Index overflow in frame area. "
|
|
"Unable to parse this bone transformation");
|
|
} else {
|
|
AddAnimationBoneTrafoKey_3DGS_MDL7(frame.iIndex,
|
|
pcBoneTransforms, shared.apcOutBones);
|
|
}
|
|
pcBoneTransforms = (const MDL::BoneTransform_MDL7 *)((const char *)pcBoneTransforms + pcHeader->bonetrans_stc_size);
|
|
}
|
|
} else {
|
|
ASSIMP_LOG_WARN("Ignoring animation keyframes in groups != 0");
|
|
}
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Attach bones to the output nodegraph
|
|
void MDLImporter::AddBonesToNodeGraph_3DGS_MDL7(const MDL::IntBone_MDL7 **apcBones,
|
|
aiNode *pcParent, uint16_t iParentIndex) {
|
|
ai_assert(nullptr != apcBones);
|
|
ai_assert(nullptr != pcParent);
|
|
|
|
// get a pointer to the header ...
|
|
const MDL::Header_MDL7 *const pcHeader = (const MDL::Header_MDL7 *)this->mBuffer;
|
|
|
|
const MDL::IntBone_MDL7 **apcBones2 = apcBones;
|
|
for (uint32_t i = 0; i < pcHeader->bones_num; ++i) {
|
|
|
|
const MDL::IntBone_MDL7 *const pcBone = *apcBones2++;
|
|
if (pcBone->iParent == iParentIndex) {
|
|
++pcParent->mNumChildren;
|
|
}
|
|
}
|
|
pcParent->mChildren = new aiNode *[pcParent->mNumChildren];
|
|
unsigned int qq = 0;
|
|
for (uint32_t i = 0; i < pcHeader->bones_num; ++i) {
|
|
|
|
const MDL::IntBone_MDL7 *const pcBone = *apcBones++;
|
|
if (pcBone->iParent != iParentIndex) continue;
|
|
|
|
aiNode *pcNode = pcParent->mChildren[qq++] = new aiNode();
|
|
pcNode->mName = aiString(pcBone->mName);
|
|
|
|
AddBonesToNodeGraph_3DGS_MDL7(apcBones, pcNode, (uint16_t)i);
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Build output animations
|
|
void MDLImporter::BuildOutputAnims_3DGS_MDL7(
|
|
const MDL::IntBone_MDL7 **apcBonesOut) {
|
|
ai_assert(nullptr != apcBonesOut);
|
|
const MDL::Header_MDL7 *const pcHeader = (const MDL::Header_MDL7 *)mBuffer;
|
|
|
|
// one animation ...
|
|
aiAnimation *pcAnim = new aiAnimation();
|
|
for (uint32_t i = 0; i < pcHeader->bones_num; ++i) {
|
|
if (!apcBonesOut[i]->pkeyPositions.empty()) {
|
|
|
|
// get the last frame ... (needn't be equal to pcHeader->frames_num)
|
|
for (size_t qq = 0; qq < apcBonesOut[i]->pkeyPositions.size(); ++qq) {
|
|
pcAnim->mDuration = std::max(pcAnim->mDuration, (double)
|
|
apcBonesOut[i]
|
|
->pkeyPositions[qq]
|
|
.mTime);
|
|
}
|
|
++pcAnim->mNumChannels;
|
|
}
|
|
}
|
|
if (pcAnim->mDuration) {
|
|
pcAnim->mChannels = new aiNodeAnim *[pcAnim->mNumChannels];
|
|
|
|
unsigned int iCnt = 0;
|
|
for (uint32_t i = 0; i < pcHeader->bones_num; ++i) {
|
|
if (!apcBonesOut[i]->pkeyPositions.empty()) {
|
|
const MDL::IntBone_MDL7 *const intBone = apcBonesOut[i];
|
|
|
|
aiNodeAnim *const pcNodeAnim = pcAnim->mChannels[iCnt++] = new aiNodeAnim();
|
|
pcNodeAnim->mNodeName = aiString(intBone->mName);
|
|
|
|
// allocate enough storage for all keys
|
|
pcNodeAnim->mNumPositionKeys = (unsigned int)intBone->pkeyPositions.size();
|
|
pcNodeAnim->mNumScalingKeys = (unsigned int)intBone->pkeyPositions.size();
|
|
pcNodeAnim->mNumRotationKeys = (unsigned int)intBone->pkeyPositions.size();
|
|
|
|
pcNodeAnim->mPositionKeys = new aiVectorKey[pcNodeAnim->mNumPositionKeys];
|
|
pcNodeAnim->mScalingKeys = new aiVectorKey[pcNodeAnim->mNumPositionKeys];
|
|
pcNodeAnim->mRotationKeys = new aiQuatKey[pcNodeAnim->mNumPositionKeys];
|
|
|
|
// copy all keys
|
|
for (unsigned int qq = 0; qq < pcNodeAnim->mNumPositionKeys; ++qq) {
|
|
pcNodeAnim->mPositionKeys[qq] = intBone->pkeyPositions[qq];
|
|
pcNodeAnim->mScalingKeys[qq] = intBone->pkeyScalings[qq];
|
|
pcNodeAnim->mRotationKeys[qq] = intBone->pkeyRotations[qq];
|
|
}
|
|
}
|
|
}
|
|
|
|
// store the output animation
|
|
pScene->mNumAnimations = 1;
|
|
pScene->mAnimations = new aiAnimation *[1];
|
|
pScene->mAnimations[0] = pcAnim;
|
|
} else
|
|
delete pcAnim;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
void MDLImporter::AddAnimationBoneTrafoKey_3DGS_MDL7(unsigned int iTrafo,
|
|
const MDL::BoneTransform_MDL7 *pcBoneTransforms,
|
|
MDL::IntBone_MDL7 **apcBonesOut) {
|
|
ai_assert(nullptr != pcBoneTransforms);
|
|
ai_assert(nullptr != apcBonesOut);
|
|
|
|
// first .. get the transformation matrix
|
|
aiMatrix4x4 mTransform;
|
|
mTransform.a1 = pcBoneTransforms->m[0];
|
|
mTransform.b1 = pcBoneTransforms->m[1];
|
|
mTransform.c1 = pcBoneTransforms->m[2];
|
|
mTransform.d1 = pcBoneTransforms->m[3];
|
|
|
|
mTransform.a2 = pcBoneTransforms->m[4];
|
|
mTransform.b2 = pcBoneTransforms->m[5];
|
|
mTransform.c2 = pcBoneTransforms->m[6];
|
|
mTransform.d2 = pcBoneTransforms->m[7];
|
|
|
|
mTransform.a3 = pcBoneTransforms->m[8];
|
|
mTransform.b3 = pcBoneTransforms->m[9];
|
|
mTransform.c3 = pcBoneTransforms->m[10];
|
|
mTransform.d3 = pcBoneTransforms->m[11];
|
|
|
|
// now decompose the transformation matrix into separate
|
|
// scaling, rotation and translation
|
|
aiVectorKey vScaling, vPosition;
|
|
aiQuatKey qRotation;
|
|
|
|
// FIXME: Decompose will assert in debug builds if the matrix is invalid ...
|
|
mTransform.Decompose(vScaling.mValue, qRotation.mValue, vPosition.mValue);
|
|
|
|
// now generate keys
|
|
vScaling.mTime = qRotation.mTime = vPosition.mTime = (double)iTrafo;
|
|
|
|
// add the keys to the bone
|
|
MDL::IntBone_MDL7 *const pcBoneOut = apcBonesOut[pcBoneTransforms->bone_index];
|
|
pcBoneOut->pkeyPositions.push_back(vPosition);
|
|
pcBoneOut->pkeyScalings.push_back(vScaling);
|
|
pcBoneOut->pkeyRotations.push_back(qRotation);
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Construct output meshes
|
|
void MDLImporter::GenerateOutputMeshes_3DGS_MDL7(
|
|
MDL::IntGroupData_MDL7 &groupData,
|
|
MDL::IntSplitGroupData_MDL7 &splitGroupData) {
|
|
const MDL::IntSharedData_MDL7 &shared = splitGroupData.shared;
|
|
|
|
// get a pointer to the header ...
|
|
const MDL::Header_MDL7 *const pcHeader = (const MDL::Header_MDL7 *)this->mBuffer;
|
|
const unsigned int iNumOutBones = pcHeader->bones_num;
|
|
|
|
for (std::vector<aiMaterial *>::size_type i = 0; i < shared.pcMats.size(); ++i) {
|
|
if (!splitGroupData.aiSplit[i]->empty()) {
|
|
|
|
// allocate the output mesh
|
|
aiMesh *pcMesh = new aiMesh();
|
|
|
|
pcMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
|
|
pcMesh->mMaterialIndex = (unsigned int)i;
|
|
|
|
// allocate output storage
|
|
pcMesh->mNumFaces = (unsigned int)splitGroupData.aiSplit[i]->size();
|
|
pcMesh->mFaces = new aiFace[pcMesh->mNumFaces];
|
|
|
|
pcMesh->mNumVertices = pcMesh->mNumFaces * 3;
|
|
pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices];
|
|
pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices];
|
|
|
|
if (!groupData.vTextureCoords1.empty()) {
|
|
pcMesh->mNumUVComponents[0] = 2;
|
|
pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices];
|
|
if (!groupData.vTextureCoords2.empty()) {
|
|
pcMesh->mNumUVComponents[1] = 2;
|
|
pcMesh->mTextureCoords[1] = new aiVector3D[pcMesh->mNumVertices];
|
|
}
|
|
}
|
|
|
|
// iterate through all faces and build an unique set of vertices
|
|
unsigned int iCurrent = 0;
|
|
for (unsigned int iFace = 0; iFace < pcMesh->mNumFaces; ++iFace) {
|
|
pcMesh->mFaces[iFace].mNumIndices = 3;
|
|
pcMesh->mFaces[iFace].mIndices = new unsigned int[3];
|
|
|
|
unsigned int iSrcFace = splitGroupData.aiSplit[i]->operator[](iFace);
|
|
const MDL::IntFace_MDL7 &oldFace = groupData.pcFaces[iSrcFace];
|
|
|
|
// iterate through all face indices
|
|
for (unsigned int c = 0; c < 3; ++c) {
|
|
const uint32_t iIndex = oldFace.mIndices[c];
|
|
pcMesh->mVertices[iCurrent] = groupData.vPositions[iIndex];
|
|
pcMesh->mNormals[iCurrent] = groupData.vNormals[iIndex];
|
|
|
|
if (!groupData.vTextureCoords1.empty()) {
|
|
|
|
pcMesh->mTextureCoords[0][iCurrent] = groupData.vTextureCoords1[iIndex];
|
|
if (!groupData.vTextureCoords2.empty()) {
|
|
pcMesh->mTextureCoords[1][iCurrent] = groupData.vTextureCoords2[iIndex];
|
|
}
|
|
}
|
|
pcMesh->mFaces[iFace].mIndices[c] = iCurrent++;
|
|
}
|
|
}
|
|
|
|
// if we have bones in the mesh we'll need to generate
|
|
// proper vertex weights for them
|
|
if (!groupData.aiBones.empty()) {
|
|
std::vector<std::vector<unsigned int>> aaiVWeightList;
|
|
aaiVWeightList.resize(iNumOutBones);
|
|
|
|
int iCurrentWeight = 0;
|
|
for (unsigned int iFace = 0; iFace < pcMesh->mNumFaces; ++iFace) {
|
|
unsigned int iSrcFace = splitGroupData.aiSplit[i]->operator[](iFace);
|
|
const MDL::IntFace_MDL7 &oldFace = groupData.pcFaces[iSrcFace];
|
|
|
|
// iterate through all face indices
|
|
for (unsigned int c = 0; c < 3; ++c) {
|
|
unsigned int iBone = groupData.aiBones[oldFace.mIndices[c]];
|
|
if (UINT_MAX != iBone) {
|
|
if (iBone >= iNumOutBones) {
|
|
ASSIMP_LOG_ERROR("Bone index overflow. "
|
|
"The bone index of a vertex exceeds the allowed range. ");
|
|
iBone = iNumOutBones - 1;
|
|
}
|
|
aaiVWeightList[iBone].push_back(iCurrentWeight);
|
|
}
|
|
++iCurrentWeight;
|
|
}
|
|
}
|
|
// now check which bones are required ...
|
|
for (std::vector<std::vector<unsigned int>>::const_iterator k = aaiVWeightList.begin(); k != aaiVWeightList.end(); ++k) {
|
|
if (!(*k).empty()) {
|
|
++pcMesh->mNumBones;
|
|
}
|
|
}
|
|
pcMesh->mBones = new aiBone *[pcMesh->mNumBones];
|
|
iCurrent = 0;
|
|
for (std::vector<std::vector<unsigned int>>::const_iterator k = aaiVWeightList.begin(); k != aaiVWeightList.end(); ++k, ++iCurrent) {
|
|
if ((*k).empty())
|
|
continue;
|
|
|
|
// seems we'll need this node
|
|
aiBone *pcBone = pcMesh->mBones[iCurrent] = new aiBone();
|
|
pcBone->mName = aiString(shared.apcOutBones[iCurrent]->mName);
|
|
pcBone->mOffsetMatrix = shared.apcOutBones[iCurrent]->mOffsetMatrix;
|
|
|
|
// setup vertex weights
|
|
pcBone->mNumWeights = (unsigned int)(*k).size();
|
|
pcBone->mWeights = new aiVertexWeight[pcBone->mNumWeights];
|
|
|
|
for (unsigned int weight = 0; weight < pcBone->mNumWeights; ++weight) {
|
|
pcBone->mWeights[weight].mVertexId = (*k)[weight];
|
|
pcBone->mWeights[weight].mWeight = 1.0f;
|
|
}
|
|
}
|
|
}
|
|
// add the mesh to the list of output meshes
|
|
splitGroupData.avOutList.push_back(pcMesh);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Join to materials
|
|
void MDLImporter::JoinSkins_3DGS_MDL7(
|
|
aiMaterial *pcMat1,
|
|
aiMaterial *pcMat2,
|
|
aiMaterial *pcMatOut) {
|
|
ai_assert(nullptr != pcMat1);
|
|
ai_assert(nullptr != pcMat2);
|
|
ai_assert(nullptr != pcMatOut);
|
|
|
|
// first create a full copy of the first skin property set
|
|
// and assign it to the output material
|
|
aiMaterial::CopyPropertyList(pcMatOut, pcMat1);
|
|
|
|
int iVal = 0;
|
|
pcMatOut->AddProperty<int>(&iVal, 1, AI_MATKEY_UVWSRC_DIFFUSE(0));
|
|
|
|
// then extract the diffuse texture from the second skin,
|
|
// setup 1 as UV source and we have it
|
|
aiString sString;
|
|
if (AI_SUCCESS == aiGetMaterialString(pcMat2, AI_MATKEY_TEXTURE_DIFFUSE(0), &sString)) {
|
|
iVal = 1;
|
|
pcMatOut->AddProperty<int>(&iVal, 1, AI_MATKEY_UVWSRC_DIFFUSE(1));
|
|
pcMatOut->AddProperty(&sString, AI_MATKEY_TEXTURE_DIFFUSE(1));
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Read a Half-life 1 MDL
|
|
void MDLImporter::InternReadFile_HL1(const std::string &pFile, const uint32_t iMagicWord) {
|
|
// We can't correctly load an MDL from a MDL "sequence" file.
|
|
if (iMagicWord == AI_MDL_MAGIC_NUMBER_BE_HL2b || iMagicWord == AI_MDL_MAGIC_NUMBER_LE_HL2b)
|
|
throw DeadlyImportError("Impossible to properly load a model from an MDL sequence file.");
|
|
|
|
// Read the MDL file.
|
|
HalfLife::HL1MDLLoader loader(
|
|
pScene,
|
|
mIOHandler,
|
|
mBuffer,
|
|
pFile,
|
|
mHL1ImportSettings);
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Read a half-life 2 MDL
|
|
void MDLImporter::InternReadFile_HL2() {
|
|
//const MDL::Header_HL2* pcHeader = (const MDL::Header_HL2*)this->mBuffer;
|
|
throw DeadlyImportError("HL2 MDLs are not implemented");
|
|
}
|
|
|
|
#endif // !! ASSIMP_BUILD_NO_MDL_IMPORTER
|