assimp/code/MDLLoader.cpp

1848 lines
60 KiB
C++
Raw Blame History

/*
---------------------------------------------------------------------------
Open Asset Import Library (ASSIMP)
---------------------------------------------------------------------------
Copyright (c) 2006-2008, ASSIMP Development 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 Development Team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------
*/
/** @file Implementation of the MDL importer class */
#include "MaterialSystem.h"
#include "MDLLoader.h"
#include "MDLDefaultColorMap.h"
#include "DefaultLogger.h"
#include "../include/IOStream.h"
#include "../include/IOSystem.h"
#include "../include/aiMesh.h"
#include "../include/aiScene.h"
#include "../include/aiAssert.h"
#include <boost/scoped_ptr.hpp>
using namespace Assimp;
extern float g_avNormals[162][3];
// ------------------------------------------------------------------------------------------------
inline bool is_qnan(float p_fIn)
{
// NOTE: Comparison against qnan is generally problematic
// because qnan == qnan is false AFAIK
union FTOINT
{
float fFloat;
int32_t iInt;
} one, two;
one.fFloat = std::numeric_limits<float>::quiet_NaN();
two.fFloat = p_fIn;
return (one.iInt == two.iInt);
}
// ------------------------------------------------------------------------------------------------
inline bool is_not_qnan(float p_fIn)
{
return !is_qnan(p_fIn);
}
// ------------------------------------------------------------------------------------------------
// Constructor to be privately used by Importer
MDLImporter::MDLImporter()
{
// nothing to do here
}
// ------------------------------------------------------------------------------------------------
// Destructor, private as well
MDLImporter::~MDLImporter()
{
// nothing to do here
}
// ------------------------------------------------------------------------------------------------
// Returns whether the class can handle the format of the given file.
bool MDLImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
{
// simple check of file extension is enough for the moment
std::string::size_type pos = pFile.find_last_of('.');
// no file extension - can't read
if( pos == std::string::npos)
return false;
std::string extension = pFile.substr( pos);
if (extension.length() < 4)return false;
if (extension[0] != '.')return false;
if (extension[1] != 'm' && extension[1] != 'M')return false;
if (extension[2] != 'd' && extension[2] != 'D')return false;
if (extension[3] != 'l' && extension[3] != 'L')return false;
return true;
}
// ------------------------------------------------------------------------------------------------
// Imports the given file into the given scene structure.
void MDLImporter::InternReadFile(
const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler)
{
boost::scoped_ptr<IOStream> file( pIOHandler->Open( pFile));
// Check whether we can read from the file
if( file.get() == NULL)
{
throw new ImportErrorException( "Failed to open MDL file " + pFile + ".");
}
// check whether the ply file is large enough to contain
// at least the file header
size_t fileSize = file->FileSize();
if( fileSize < sizeof(MDL::Header))
{
throw new ImportErrorException( ".mdl File is too small.");
}
// allocate storage and copy the contents of the file to a memory buffer
this->pScene = pScene;
this->pIOHandler = pIOHandler;
this->mBuffer = new unsigned char[fileSize+1];
file->Read( (void*)mBuffer, 1, fileSize);
// determine the file subtype and call the appropriate member function
// Original Quake1 format
this->m_pcHeader = (const MDL::Header*)this->mBuffer;
if (AI_MDL_MAGIC_NUMBER_BE == this->m_pcHeader->ident ||
AI_MDL_MAGIC_NUMBER_LE == this->m_pcHeader->ident)
{
DefaultLogger::get()->debug("MDL subtype: Quake 1, magic word is IDPO");
this->InternReadFile_Quake1();
}
// GameStudio A4 MDL3 format
else if (AI_MDL_MAGIC_NUMBER_BE_GS4 == this->m_pcHeader->ident ||
AI_MDL_MAGIC_NUMBER_LE_GS4 == this->m_pcHeader->ident)
{
DefaultLogger::get()->debug("MDL subtype: 3D GameStudio A4, magic word is MDL3");
this->iGSFileVersion = 3;
this->InternReadFile_GameStudio();
}
// GameStudio A5+ MDL4 format
else if (AI_MDL_MAGIC_NUMBER_BE_GS5a == this->m_pcHeader->ident ||
AI_MDL_MAGIC_NUMBER_LE_GS5a == this->m_pcHeader->ident)
{
DefaultLogger::get()->debug("MDL subtype: 3D GameStudio A4, magic word is MDL4");
this->iGSFileVersion = 4;
this->InternReadFile_GameStudio();
}
// GameStudio A5+ MDL5 format
else if (AI_MDL_MAGIC_NUMBER_BE_GS5b == this->m_pcHeader->ident ||
AI_MDL_MAGIC_NUMBER_LE_GS5b == this->m_pcHeader->ident)
{
DefaultLogger::get()->debug("MDL subtype: 3D GameStudio A5, magic word is MDL5");
this->iGSFileVersion = 5;
this->InternReadFile_GameStudio();
}
// GameStudio A6+ MDL6 format
else if (AI_MDL_MAGIC_NUMBER_BE_GS6 == this->m_pcHeader->ident ||
AI_MDL_MAGIC_NUMBER_LE_GS6 == this->m_pcHeader->ident)
{
DefaultLogger::get()->debug("MDL subtype: 3D GameStudio A6, magic word is MDL6");
this->iGSFileVersion = 6;
this->InternReadFile_GameStudio();
}
// GameStudio A7 MDL7 format
else if (AI_MDL_MAGIC_NUMBER_BE_GS7 == this->m_pcHeader->ident ||
AI_MDL_MAGIC_NUMBER_LE_GS7 == this->m_pcHeader->ident)
{
DefaultLogger::get()->debug("MDL subtype: 3D GameStudio A7, magic word is MDL7");
this->iGSFileVersion = 7;
this->InternReadFile_GameStudioA7();
}
// IDST/IDSQ Format (CS:S/HL<48>, etc ...)
else if (AI_MDL_MAGIC_NUMBER_BE_HL2a == this->m_pcHeader->ident ||
AI_MDL_MAGIC_NUMBER_LE_HL2a == this->m_pcHeader->ident ||
AI_MDL_MAGIC_NUMBER_BE_HL2b == this->m_pcHeader->ident ||
AI_MDL_MAGIC_NUMBER_LE_HL2b == this->m_pcHeader->ident)
{
DefaultLogger::get()->debug("MDL subtype: CS:S\\HL<EFBFBD>, magic word is IDST/IDSQ");
this->InternReadFile_HL2();
}
else
{
// we're definitely unable to load this file
throw new ImportErrorException( "Unknown MDL subformat " + pFile +
". Magic word is not known");
}
// make sure that the normals are facing outwards
for (unsigned int i = 0; i < pScene->mNumMeshes;++i)
this->FlipNormals(pScene->mMeshes[i]);
// delete the file buffer
delete[] this->mBuffer;
return;
}
// ------------------------------------------------------------------------------------------------
void MDLImporter::SearchPalette(const unsigned char** pszColorMap)
{
// now try to find the color map in the current directory
IOStream* pcStream = this->pIOHandler->Open("colormap.lmp","rb");
const unsigned char* szColorMap = (const unsigned char*)::g_aclrDefaultColorMap;
if(pcStream)
{
if (pcStream->FileSize() >= 768)
{
szColorMap = new unsigned char[256*3];
pcStream->Read(const_cast<unsigned char*>(szColorMap),256*3,1);
DefaultLogger::get()->info("Found valid colormap.lmp in directory. "
"It will be used to decode embedded textures in palletized formats.");
}
delete pcStream;
pcStream = NULL;
}
*pszColorMap = szColorMap;
return;
}
// ------------------------------------------------------------------------------------------------
void MDLImporter::FreePalette(const unsigned char* szColorMap)
{
if (szColorMap != (const unsigned char*)::g_aclrDefaultColorMap)
{
delete[] szColorMap;
}
return;
}
// ------------------------------------------------------------------------------------------------
void MDLImporter::CreateTextureARGB8(const unsigned char* szData)
{
// allocate a new texture object
aiTexture* pcNew = new aiTexture();
pcNew->mWidth = this->m_pcHeader->skinwidth;
pcNew->mHeight = this->m_pcHeader->skinheight;
pcNew->pcData = new aiTexel[pcNew->mWidth * pcNew->mHeight];
const unsigned char* szColorMap;
this->SearchPalette(&szColorMap);
// copy texture data
for (unsigned int i = 0; i < pcNew->mWidth*pcNew->mHeight;++i)
{
const unsigned char val = szData[i];
const unsigned char* sz = &szColorMap[val*3];
pcNew->pcData[i].a = 0xFF;
pcNew->pcData[i].r = *sz++;
pcNew->pcData[i].g = *sz++;
pcNew->pcData[i].b = *sz;
}
this->FreePalette(szColorMap);
// store the texture
aiTexture** pc = this->pScene->mTextures;
this->pScene->mTextures = new aiTexture*[this->pScene->mNumTextures+1];
for (unsigned int i = 0; i < this->pScene->mNumTextures;++i)
this->pScene->mTextures[i] = pc[i];
this->pScene->mTextures[this->pScene->mNumTextures] = pcNew;
this->pScene->mNumTextures++;
delete[] pc;
return;
}
// ------------------------------------------------------------------------------------------------
void MDLImporter::CreateTextureARGB8_GS4(const unsigned char* szData,
unsigned int iType,
unsigned int* piSkip)
{
ai_assert(NULL != piSkip);
// allocate a new texture object
aiTexture* pcNew = new aiTexture();
pcNew->mWidth = this->m_pcHeader->skinwidth;
pcNew->mHeight = this->m_pcHeader->skinheight;
pcNew->pcData = new aiTexel[pcNew->mWidth * pcNew->mHeight];
// 8 Bit paletized. Use Q1 default palette.
if (0 == iType)
{
const unsigned char* szColorMap;
this->SearchPalette(&szColorMap);
// copy texture data
unsigned int i = 0;
for (; i < pcNew->mWidth*pcNew->mHeight;++i)
{
const unsigned char val = szData[i];
const unsigned char* sz = &szColorMap[val*3];
pcNew->pcData[i].a = 0xFF;
pcNew->pcData[i].r = *sz++;
pcNew->pcData[i].g = *sz++;
pcNew->pcData[i].b = *sz;
}
*piSkip = i;
this->FreePalette(szColorMap);
}
// R5G6B5 format
else if (2 == iType)
{
// copy texture data
unsigned int i = 0;
for (i = 0; i < pcNew->mWidth*pcNew->mHeight;++i)
{
MDL::RGB565 val = ((MDL::RGB565*)szData)[i];
pcNew->pcData[i].a = 0xFF;
pcNew->pcData[i].r = (unsigned char)val.b << 3;
pcNew->pcData[i].g = (unsigned char)val.g << 2;
pcNew->pcData[i].b = (unsigned char)val.r << 3;
}
*piSkip = i * 2;
}
// ARGB4 format
else if (3 == iType)
{
// copy texture data
unsigned int i = 0;
for (i = 0; i < pcNew->mWidth*pcNew->mHeight;++i)
{
MDL::ARGB4 val = ((MDL::ARGB4*)szData)[i];
pcNew->pcData[i].a = (unsigned char)val.a << 4;
pcNew->pcData[i].r = (unsigned char)val.r << 4;
pcNew->pcData[i].g = (unsigned char)val.g << 4;
pcNew->pcData[i].b = (unsigned char)val.b << 4;
}
*piSkip = i * 2;
}
// store the texture
aiTexture** pc = this->pScene->mTextures;
this->pScene->mTextures = new aiTexture*[this->pScene->mNumTextures+1];
for (unsigned int i = 0; i < this->pScene->mNumTextures;++i)
this->pScene->mTextures[i] = pc[i];
this->pScene->mTextures[this->pScene->mNumTextures] = pcNew;
this->pScene->mNumTextures++;
delete[] pc;
return;
}
// ------------------------------------------------------------------------------------------------
void MDLImporter::ParseTextureColorData(const unsigned char* szData,
unsigned int iType,
unsigned int* piSkip,
aiTexture* pcNew)
{
// allocate storage for the texture image
pcNew->pcData = new aiTexel[pcNew->mWidth * pcNew->mHeight];
// R5G6B5 format (with or without MIPs)
if (2 == iType || 10 == iType)
{
// copy texture data
unsigned int i = 0;
for (i = 0; i < pcNew->mWidth*pcNew->mHeight;++i)
{
MDL::RGB565 val = ((MDL::RGB565*)szData)[i];
pcNew->pcData[i].a = 0xFF;
pcNew->pcData[i].r = (unsigned char)val.b << 3;
pcNew->pcData[i].g = (unsigned char)val.g << 2;
pcNew->pcData[i].b = (unsigned char)val.r << 3;
}
*piSkip = i * 2;
// apply MIP maps
if (10 == iType)
{
*piSkip += ((i >> 2) + (i >> 4) + (i >> 6)) << 1;
}
}
// ARGB4 format (with or without MIPs)
else if (3 == iType || 11 == iType)
{
// copy texture data
unsigned int i = 0;
for (i = 0; i < pcNew->mWidth*pcNew->mHeight;++i)
{
MDL::ARGB4 val = ((MDL::ARGB4*)szData)[i];
pcNew->pcData[i].a = (unsigned char)val.a << 4;
pcNew->pcData[i].r = (unsigned char)val.r << 4;
pcNew->pcData[i].g = (unsigned char)val.g << 4;
pcNew->pcData[i].b = (unsigned char)val.b << 4;
}
*piSkip = i * 2;
// apply MIP maps
if (11 == iType)
{
*piSkip += ((i >> 2) + (i >> 4) + (i >> 6)) << 1;
}
}
// RGB8 format (with or without MIPs)
else if (4 == iType || 12 == iType)
{
// copy texture data
unsigned int i = 0;
for (i = 0; i < pcNew->mWidth*pcNew->mHeight;++i)
{
const unsigned char* _szData = &szData[i*3];
pcNew->pcData[i].a = 0xFF;
pcNew->pcData[i].b = *_szData++;
pcNew->pcData[i].g = *_szData++;
pcNew->pcData[i].r = *_szData;
}
// apply MIP maps
*piSkip = i * 3;
if (12 == iType)
{
*piSkip += ((i >> 2) + (i >> 4) + (i >> 6)) *3;
}
}
// ARGB8 format (with ir without MIPs)
else if (5 == iType || 13 == iType)
{
// copy texture data
unsigned int i = 0;
for (i = 0; i < pcNew->mWidth*pcNew->mHeight;++i)
{
const unsigned char* _szData = &szData[i*4];
pcNew->pcData[i].b = *_szData++;
pcNew->pcData[i].g = *_szData++;
pcNew->pcData[i].r = *_szData++;
pcNew->pcData[i].a = *_szData;
}
// apply MIP maps
*piSkip = i << 2;
if (13 == iType)
{
*piSkip += (i + (i >> 2) + (i >> 4) + (i >> 6)) << 2;
}
}
// palletized 8 bit texture. As for Quake 1
else if (0 == iType)
{
const unsigned char* szColorMap;
this->SearchPalette(&szColorMap);
// copy texture data
unsigned int i = 0;
for (; i < pcNew->mWidth*pcNew->mHeight;++i)
{
const unsigned char val = szData[i];
const unsigned char* sz = &szColorMap[val*3];
pcNew->pcData[i].a = 0xFF;
pcNew->pcData[i].r = *sz++;
pcNew->pcData[i].g = *sz++;
pcNew->pcData[i].b = *sz;
}
*piSkip = i;
this->FreePalette(szColorMap);
}
return;
}
// ------------------------------------------------------------------------------------------------
void MDLImporter::CreateTextureARGB8_GS5(const unsigned char* szData,
unsigned int iType,
unsigned int* piSkip)
{
ai_assert(NULL != piSkip);
// allocate a new texture object
aiTexture* pcNew = new aiTexture();
// first read the size of the texture
pcNew->mWidth = *((uint32_t*)szData);
szData += sizeof(uint32_t);
pcNew->mHeight = *((uint32_t*)szData);
szData += sizeof(uint32_t);
if (6 == iType)
{
// this is a compressed texture in DDS format
*piSkip = pcNew->mWidth;
pcNew->mHeight = 0;
pcNew->achFormatHint[0] = 'd';
pcNew->achFormatHint[1] = 'd';
pcNew->achFormatHint[2] = 's';
pcNew->achFormatHint[3] = '\0';
pcNew->pcData = (aiTexel*) new unsigned char[pcNew->mWidth];
memcpy(pcNew->pcData,szData,pcNew->mWidth);
}
else
{
// parse the color data of the texture
this->ParseTextureColorData(szData,iType,
piSkip,pcNew);
}
*piSkip += sizeof(uint32_t) * 2;
// store the texture
aiTexture** pc = this->pScene->mTextures;
this->pScene->mTextures = new aiTexture*[this->pScene->mNumTextures+1];
for (unsigned int i = 0; i < this->pScene->mNumTextures;++i)
this->pScene->mTextures[i] = pc[i];
this->pScene->mTextures[this->pScene->mNumTextures] = pcNew;
this->pScene->mNumTextures++;
delete[] pc;
return;
}
// ------------------------------------------------------------------------------------------------
void MDLImporter::InternReadFile_Quake1( )
{
ai_assert(NULL != pScene);
if(0 == this->m_pcHeader->num_frames)
{
throw new ImportErrorException( "[Quake 1 MDL] No frames found");
}
// allocate enough storage to hold all vertices and triangles
aiMesh* pcMesh = new aiMesh();
// current cursor position in the file
const unsigned char* szCurrent = (const unsigned char*)(this->m_pcHeader+1);
// need to read all textures
for (unsigned int i = 0; i < (unsigned int)this->m_pcHeader->num_skins;++i)
{
union{const MDL::Skin* pcSkin;const MDL::GroupSkin* pcGroupSkin;};
pcSkin = (const MDL::Skin*)szCurrent;
if (0 == pcSkin->group)
{
// create one output image
this->CreateTextureARGB8((unsigned char*)pcSkin + sizeof(uint32_t));
// need to skip one image
szCurrent += this->m_pcHeader->skinheight * this->m_pcHeader->skinwidth+ sizeof(uint32_t);
}
else
{
// need to skip multiple images
const unsigned int iNumImages = (unsigned int)pcGroupSkin->nb;
szCurrent += sizeof(uint32_t) * 2;
if (0 != iNumImages)
{
// however, create only one output image (the first)
this->CreateTextureARGB8(szCurrent + iNumImages * sizeof(float));
for (unsigned int a = 0; a < iNumImages;++a)
{
szCurrent += this->m_pcHeader->skinheight * this->m_pcHeader->skinwidth +
sizeof(float);
}
}
}
}
// get a pointer to the texture coordinates
const MDL::TexCoord* pcTexCoords = (const MDL::TexCoord*)szCurrent;
szCurrent += sizeof(MDL::TexCoord) * this->m_pcHeader->num_verts;
// get a pointer to the triangles
const MDL::Triangle* pcTriangles = (const MDL::Triangle*)szCurrent;
szCurrent += sizeof(MDL::Triangle) * this->m_pcHeader->num_tris;
// now get a pointer to the first frame in the file
const MDL::Frame* pcFrames = (const MDL::Frame*)szCurrent;
const MDL::SimpleFrame* pcFirstFrame;
if (0 == pcFrames->type)
{
// get address of single frame
pcFirstFrame = &pcFrames->frame;
}
else
{
// get the first frame in the group
const MDL::GroupFrame* pcFrames2 = (const MDL::GroupFrame*)pcFrames;
pcFirstFrame = (const MDL::SimpleFrame*)(&pcFrames2->time + pcFrames->type);
}
const MDL::Vertex* pcVertices = (const MDL::Vertex*) ((pcFirstFrame->name) +
sizeof(pcFirstFrame->name));
pcMesh->mNumVertices = this->m_pcHeader->num_tris * 3;
pcMesh->mNumFaces = this->m_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->mNumMaterials = 1;
pScene->mRootNode = new aiNode();
pScene->mRootNode->mNumMeshes = 1;
pScene->mRootNode->mMeshes = new unsigned int[1];
pScene->mRootNode->mMeshes[0] = 0;
pScene->mMaterials = new aiMaterial*[1];
pScene->mMaterials[0] = new MaterialHelper();
pScene->mNumMeshes = 1;
pScene->mMeshes = new aiMesh*[1];
pScene->mMeshes[0] = pcMesh;
// setup the material properties
const int iMode = (int)aiShadingMode_Gouraud;
MaterialHelper* pcHelper = (MaterialHelper*)pScene->mMaterials[0];
pcHelper->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL);
aiColor3D clr;
clr.b = clr.g = clr.r = 1.0f;
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);
if (0 != this->m_pcHeader->num_skins)
{
aiString szString;
memcpy(szString.data,AI_MAKE_EMBEDDED_TEXNAME(0),3);
szString.length = 2;
pcHelper->AddProperty(&szString,AI_MATKEY_TEXTURE_DIFFUSE(0));
}
// now iterate through all triangles
unsigned int iCurrent = 0;
for (unsigned int i = 0; i < (unsigned int) this->m_pcHeader->num_tris;++i)
{
pcMesh->mFaces[i].mIndices = new unsigned int[3];
pcMesh->mFaces[i].mNumIndices = 3;
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)this->m_pcHeader->num_verts)
{
iIndex = this->m_pcHeader->num_verts-1;
DefaultLogger::get()->warn("Index overflow in Q1-MDL vertex list.");
}
aiVector3D& vec = pcMesh->mVertices[iCurrent];
vec.x = (float)pcVertices[iIndex].v[0] * this->m_pcHeader->scale[0];
vec.x += this->m_pcHeader->translate[0];
// (flip z and y component)
vec.z = (float)pcVertices[iIndex].v[1] * this->m_pcHeader->scale[1];
vec.z += this->m_pcHeader->translate[1];
vec.y = (float)pcVertices[iIndex].v[2] * this->m_pcHeader->scale[2];
vec.y += this->m_pcHeader->translate[2];
// flip the Z-axis
//pcMesh->mVertices[iBase+c].z *= -1.0f;
// read the normal vector from the precalculated normal table
pcMesh->mNormals[iCurrent] = *((const aiVector3D*)(&g_avNormals[std::min(
int(pcVertices[iIndex].normalIndex),
int(sizeof(g_avNormals) / sizeof(g_avNormals[0]))-1)]));
//pcMesh->mNormals[iBase+c].z *= -1.0f;
std::swap ( pcMesh->mNormals[iCurrent].y,pcMesh->mNormals[iCurrent].z );
// 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 += this->m_pcHeader->skinwidth * 0.5f;
}
// Scale s and t to range from 0.0 to 1.0
pcMesh->mTextureCoords[0][iCurrent].x = (s + 0.5f) / this->m_pcHeader->skinwidth;
pcMesh->mTextureCoords[0][iCurrent].y = 1.0f-(t + 0.5f) / this->m_pcHeader->skinheight;
}
pcTriangles++;
}
return;
}
// ------------------------------------------------------------------------------------------------
void MDLImporter::InternReadFile_GameStudio( )
{
ai_assert(NULL != pScene);
if(0 == this->m_pcHeader->num_frames)
{
throw new ImportErrorException( "[3DGS MDL] No frames found");
}
// allocate enough storage to hold all vertices and triangles
aiMesh* pcMesh = new aiMesh();
// current cursor position in the file
const unsigned char* szCurrent = (const unsigned char*)(this->m_pcHeader+1);
// need to read all textures
for (unsigned int i = 0; i < (unsigned int)this->m_pcHeader->num_skins;++i)
{
union{const MDL::Skin* pcSkin;const MDL::GroupSkin* pcGroupSkin;};
pcSkin = (const MDL::Skin*)szCurrent;
// create one output image
unsigned int iSkip = 0;
if (5 <= this->iGSFileVersion)
{
// MDL5 format could contain MIPmaps
this->CreateTextureARGB8_GS5((unsigned char*)pcSkin + sizeof(uint32_t),
pcSkin->group,&iSkip);
}
else
{
this->CreateTextureARGB8_GS4((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
const MDL::TexCoord_MDL3* pcTexCoords = (const MDL::TexCoord_MDL3*)szCurrent;
szCurrent += sizeof(MDL::TexCoord_MDL3) * this->m_pcHeader->synctype;
// NOTE: for MDLn formats syntype corresponds to the number of UV coords
// get a pointer to the triangles
const MDL::Triangle_MDL3* pcTriangles = (const MDL::Triangle_MDL3*)szCurrent;
szCurrent += sizeof(MDL::Triangle_MDL3) * this->m_pcHeader->num_tris;
pcMesh->mNumVertices = this->m_pcHeader->num_tris * 3;
pcMesh->mNumFaces = this->m_pcHeader->num_tris;
pcMesh->mFaces = new aiFace[pcMesh->mNumFaces];
pcMesh->mNumUVComponents[0] = 2;
// there won't be more than one mesh inside the file
pScene->mNumMaterials = 1;
pScene->mRootNode = new aiNode();
pScene->mRootNode->mNumMeshes = 1;
pScene->mRootNode->mMeshes = new unsigned int[1];
pScene->mRootNode->mMeshes[0] = 0;
pScene->mMaterials = new aiMaterial*[1];
pScene->mMaterials[0] = new MaterialHelper();
pScene->mNumMeshes = 1;
pScene->mMeshes = new aiMesh*[1];
pScene->mMeshes[0] = pcMesh;
std::vector<aiVector3D> vPositions;
std::vector<aiVector3D> vTexCoords;
std::vector<aiVector3D> vNormals;
vPositions.resize(pScene->mMeshes[0]->mNumFaces*3,aiVector3D());
vTexCoords.resize(pScene->mMeshes[0]->mNumFaces*3,aiVector3D());
vNormals.resize(pScene->mMeshes[0]->mNumFaces*3,aiVector3D());
// setup the material properties
const int iMode = (int)aiShadingMode_Gouraud;
MaterialHelper* pcHelper = (MaterialHelper*)pScene->mMaterials[0];
pcHelper->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL);
aiColor3D clr;
clr.b = clr.g = clr.r = 1.0f;
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);
if (0 != this->m_pcHeader->num_skins)
{
aiString szString;
memcpy(szString.data,AI_MAKE_EMBEDDED_TEXNAME(0),3);
szString.length = 2;
pcHelper->AddProperty(&szString,AI_MATKEY_TEXTURE_DIFFUSE(0));
}
// now get a pointer to the first frame in the file
const MDL::Frame* pcFrames = (const MDL::Frame*)szCurrent;
// byte packed vertices
if (0 == pcFrames->type || 3 == this->iGSFileVersion)
{
const MDL::SimpleFrame* pcFirstFrame = (const MDL::SimpleFrame*)
(szCurrent + sizeof(uint32_t));
// get a pointer to the vertices
const MDL::Vertex* pcVertices = (const MDL::Vertex*) ((pcFirstFrame->name) +
sizeof(pcFirstFrame->name));
// now iterate through all triangles
unsigned int iCurrent = 0;
for (unsigned int i = 0; i < (unsigned int) this->m_pcHeader->num_tris;++i)
{
pcMesh->mFaces[i].mIndices = new unsigned int[3];
pcMesh->mFaces[i].mNumIndices = 3;
for (unsigned int c = 0; c < 3;++c,++iCurrent)
{
pcMesh->mFaces[i].mIndices[c] = iCurrent;
// read vertices
unsigned int iIndex = pcTriangles->index_xyz[c];
if (iIndex >= (unsigned int)this->m_pcHeader->num_verts)
{
iIndex = this->m_pcHeader->num_verts-1;
DefaultLogger::get()->warn("Index overflow in MDL3/4/5/6 vertex list");
}
aiVector3D& vec = vPositions[iCurrent];
vec.x = (float)pcVertices[iIndex].v[0] * this->m_pcHeader->scale[0];
vec.x += this->m_pcHeader->translate[0];
// (flip z and y component)
vec.z = (float)pcVertices[iIndex].v[1] * this->m_pcHeader->scale[1];
vec.z += this->m_pcHeader->translate[1];
vec.y = (float)pcVertices[iIndex].v[2] * this->m_pcHeader->scale[2];
vec.y += this->m_pcHeader->translate[2];
// read the normal vector from the precalculated normal table
vNormals[iCurrent] = *((const aiVector3D*)(&g_avNormals[std::min(
int(pcVertices[iIndex].normalIndex),
int(sizeof(g_avNormals) / sizeof(g_avNormals[0]))-1)]));
//vNormals[iBase+c].z *= -1.0f;
std::swap ( vNormals[iCurrent].y,vNormals[iCurrent].z );
// read texture coordinates
iIndex = pcTriangles->index_uv[c];
// validate UV indices
if (iIndex >= (unsigned int)this->m_pcHeader->synctype)
{
iIndex = this->m_pcHeader->synctype-1;
DefaultLogger::get()->warn("Index overflow in MDL3/4/5/6 UV coord list");
}
float s = (float)pcTexCoords[iIndex].u;
float t = (float)pcTexCoords[iIndex].v;
// Scale s and t to range from 0.0 to 1.0
if (5 != this->iGSFileVersion &&
this->m_pcHeader->skinwidth && this->m_pcHeader->skinheight)
{
s = (s + 0.5f) / this->m_pcHeader->skinwidth;
t = 1.0f-(t + 0.5f) / this->m_pcHeader->skinheight;
}
vTexCoords[iCurrent].x = s;
vTexCoords[iCurrent].y = t;
}
pcTriangles++;
}
}
// short packed vertices (duplicating the code is smaller than using templates ....)
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));
// now iterate through all triangles
unsigned int iCurrent = 0;
for (unsigned int i = 0; i < (unsigned int) this->m_pcHeader->num_tris;++i)
{
pcMesh->mFaces[i].mIndices = new unsigned int[3];
pcMesh->mFaces[i].mNumIndices = 3;
for (unsigned int c = 0; c < 3;++c,++iCurrent)
{
pcMesh->mFaces[i].mIndices[c] = iCurrent;
// read vertices
unsigned int iIndex = pcTriangles->index_xyz[c];
if (iIndex >= (unsigned int)this->m_pcHeader->num_verts)
{
iIndex = this->m_pcHeader->num_verts-1;
DefaultLogger::get()->warn("Index overflow in MDL3/4/5/6 vertex list");
}
aiVector3D& vec = vPositions[iCurrent];
vec.x = (float)pcVertices[iIndex].v[0] * this->m_pcHeader->scale[0];
vec.x += this->m_pcHeader->translate[0];
// (flip z and y component)
vec.z = (float)pcVertices[iIndex].v[1] * this->m_pcHeader->scale[1];
vec.z += this->m_pcHeader->translate[1];
vec.y = (float)pcVertices[iIndex].v[2] * this->m_pcHeader->scale[2];
vec.y += this->m_pcHeader->translate[2];
// read the normal vector from the precalculated normal table
vNormals[iCurrent] = *((const aiVector3D*)(&g_avNormals[std::min(
int(pcVertices[iIndex].normalIndex),
int(sizeof(g_avNormals) / sizeof(g_avNormals[0]))-1)]));
std::swap ( vNormals[iCurrent].y,vNormals[iCurrent].z );
// read texture coordinates
iIndex = pcTriangles->index_uv[c];
// validate UV indices
if (iIndex >= (unsigned int) this->m_pcHeader->synctype)
{
iIndex = this->m_pcHeader->synctype-1;
DefaultLogger::get()->warn("Index overflow in MDL3/4/5/6 UV coord list");
}
float s = (float)pcTexCoords[iIndex].u;
float t = (float)pcTexCoords[iIndex].v;
// Scale s and t to range from 0.0 to 1.0
if (5 != this->iGSFileVersion &&
this->m_pcHeader->skinwidth && this->m_pcHeader->skinheight)
{
s = (s + 0.5f) / this->m_pcHeader->skinwidth;
t = 1.0f-(t + 0.5f) / this->m_pcHeader->skinheight;
}
vTexCoords[iCurrent].x = s;
vTexCoords[iCurrent].y = t;
}
pcTriangles++;
}
}
// For MDL5 we will need to build valid texture coordinates
// basing upon the file loaded (only support one file as skin)
if (5 == this->iGSFileVersion)
{
if (0 != this->m_pcHeader->num_skins && 0 != this->pScene->mNumTextures)
{
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 (0 == pcTex->mHeight)
{
uint32_t* piPtr = (uint32_t*)pcTex->pcData;
piPtr += 3;
iHeight = (unsigned int)*piPtr++;
iWidth = (unsigned int)*piPtr;
}
else
{
iWidth = pcTex->mWidth;
iHeight = pcTex->mHeight;
}
for (std::vector<aiVector3D>::iterator
i = vTexCoords.begin();
i != vTexCoords.end();++i)
{
(*i).x /= iWidth;
(*i).y /= iHeight;
(*i).y = 1.0f- (*i).y; // DX to OGL
}
}
}
// allocate output storage
pScene->mMeshes[0]->mNumVertices = vPositions.size();
pScene->mMeshes[0]->mVertices = new aiVector3D[vPositions.size()];
pScene->mMeshes[0]->mNormals = new aiVector3D[vPositions.size()];
pScene->mMeshes[0]->mTextureCoords[0] = new aiVector3D[vPositions.size()];
// memcpy() the data to the c-syle arrays
memcpy(pScene->mMeshes[0]->mVertices, &vPositions[0],
vPositions.size() * sizeof(aiVector3D));
memcpy(pScene->mMeshes[0]->mNormals, &vNormals[0],
vPositions.size() * sizeof(aiVector3D));
memcpy(pScene->mMeshes[0]->mTextureCoords[0], &vTexCoords[0],
vPositions.size() * sizeof(aiVector3D));
return;
}
// ------------------------------------------------------------------------------------------------
void MDLImporter::ParseSkinLump_GameStudioA7(
const unsigned char* szCurrent,
const unsigned char** szCurrentOut,
std::vector<MaterialHelper*>& pcMats)
{
ai_assert(NULL != szCurrent);
ai_assert(NULL != szCurrentOut);
*szCurrentOut = szCurrent;
const MDL::Skin_MDL7* pcSkin = (const MDL::Skin_MDL7*)szCurrent;
szCurrent += 12;
// allocate an output material
MaterialHelper* pcMatOut = new MaterialHelper();
pcMats.push_back(pcMatOut);
aiTexture* pcNew = NULL;
// get the type of the skin
unsigned int iMasked = (unsigned int)(pcSkin->typ & 0xF);
// skip length of file name
szCurrent += AI_MDL7_MAX_TEXNAMESIZE;
if (0x1 == iMasked)
{
// ***** REFERENCE TO ANOTHER SKIN INDEX *****
// NOTE: Documentation - if you can call it a documentation, I prefer
// the expression "rubbish" - states it is currently unused. However,
// I don't know what ideas the terrible developers of Conitec will
// have tomorrow, so Im going to implement it.
int referrer = pcSkin->width;
pcMatOut->AddProperty<int>(&referrer,1,"quakquakquak");
}
else if (0x6 == iMasked)
{
// ***** EMBEDDED DDS FILE *****
if (1 != pcSkin->height)
{
DefaultLogger::get()->warn("Found a reference to an embedded DDS texture, "
"but texture height is not equal to 1, which is not supported by MED");
}
pcNew = new aiTexture();
pcNew->mHeight = 0;
pcNew->achFormatHint[0] = 'd';
pcNew->achFormatHint[1] = 'd';
pcNew->achFormatHint[2] = 's';
pcNew->achFormatHint[3] = '\0';
pcNew->pcData = (aiTexel*) new unsigned char[pcNew->mWidth];
memcpy(pcNew->pcData,szCurrent,pcNew->mWidth);
szCurrent += pcSkin->width;
}
if (0x7 == iMasked)
{
// ***** REFERENCE TO EXTERNAL FILE *****
if (1 != pcSkin->height)
{
DefaultLogger::get()->warn("Found a reference to an external texture, "
"but texture height is not equal to 1, which is not supported by MED");
}
aiString szFile;
const size_t iLen = strlen((const char*)szCurrent);
size_t iLen2 = iLen+1;
iLen2 = iLen2 > MAXLEN ? MAXLEN : iLen2;
memcpy(szFile.data,(const char*)szCurrent,iLen2);
szFile.length = iLen;
szCurrent += iLen2;
// place this as diffuse texture
pcMatOut->AddProperty(&szFile,AI_MATKEY_TEXTURE_DIFFUSE(0));
}
else if (0 != iMasked || 0 == pcSkin->typ)
{
// ***** STANDARD COLOR TEXTURE *****
pcNew = new aiTexture();
if (0 == pcSkin->height || 0 == pcSkin->width)
{
DefaultLogger::get()->warn("Found embedded texture, but its width "
"an height are both 0. Is this a joke?");
// generate an empty chess pattern
pcNew->mWidth = pcNew->mHeight = 8;
pcNew->pcData = new aiTexel[64];
for (unsigned int x = 0; x < 8;++x)
{
for (unsigned int y = 0; y < 8;++y)
{
bool bSet = false;
if (0 == x % 2 && 0 != y % 2 ||
0 != x % 2 && 0 == y % 2)bSet = true;
aiTexel* pc = &pcNew->pcData[y * 8 + x];
if (bSet)pc->r = pc->b = pc->g = 0xFF;
else pc->r = pc->b = pc->g = 0;
pc->a = 0xFF;
}
}
}
else
{
// it is a standard color texture. Fill in width and height
// and call the same function we used for loading MDL5 files
pcNew->mWidth = pcSkin->width;
pcNew->mHeight = pcSkin->height;
unsigned int iSkip = 0;
this->ParseTextureColorData(szCurrent,iMasked,&iSkip,pcNew);
// skip length of texture data
szCurrent += iSkip;
}
}
// check whether a material definition is contained in the skin
if (pcSkin->typ & AI_MDL7_SKINTYPE_MATERIAL)
{
const MDL::Material_MDL7* pcMatIn = (const MDL::Material_MDL7*)szCurrent;
szCurrent = (unsigned char*)(pcMatIn+1);
aiColor4D clrTemp;
// read diffuse color
clrTemp.a = 1.0f; //pcMatIn->Diffuse.a;
clrTemp.r = pcMatIn->Diffuse.r;
clrTemp.g = pcMatIn->Diffuse.g;
clrTemp.b = pcMatIn->Diffuse.b;
pcMatOut->AddProperty<aiColor4D>(&clrTemp,1,AI_MATKEY_COLOR_DIFFUSE);
// read specular color
clrTemp.a = 1.0f; //pcMatIn->Specular.a;
clrTemp.r = pcMatIn->Specular.r;
clrTemp.g = pcMatIn->Specular.g;
clrTemp.b = pcMatIn->Specular.b;
pcMatOut->AddProperty<aiColor4D>(&clrTemp,1,AI_MATKEY_COLOR_SPECULAR);
// read ambient color
clrTemp.a = 1.0f; //pcMatIn->Ambient.a;
clrTemp.r = pcMatIn->Ambient.r;
clrTemp.g = pcMatIn->Ambient.g;
clrTemp.b = pcMatIn->Ambient.b;
pcMatOut->AddProperty<aiColor4D>(&clrTemp,1,AI_MATKEY_COLOR_AMBIENT);
// read emissive color
clrTemp.a = 1.0f; //pcMatIn->Emissive.a;
clrTemp.r = pcMatIn->Emissive.r;
clrTemp.g = pcMatIn->Emissive.g;
clrTemp.b = pcMatIn->Emissive.b;
pcMatOut->AddProperty<aiColor4D>(&clrTemp,1,AI_MATKEY_COLOR_EMISSIVE);
// FIX: Take the opacity from the ambient color
// the doc says something else, but it is fact that MED exports the
// opacity like this .... ARRRGGHH!
clrTemp.a = pcMatIn->Ambient.a;
pcMatOut->AddProperty<float>(&clrTemp.a,1,AI_MATKEY_OPACITY);
// read phong power
int iShadingMode = (int)aiShadingMode_Gouraud;
if (0.0f != pcMatIn->Power)
{
iShadingMode = (int)aiShadingMode_Phong;
pcMatOut->AddProperty<float>(&pcMatIn->Power,1,AI_MATKEY_SHININESS);
}
pcMatOut->AddProperty<int>(&iShadingMode,1,AI_MATKEY_SHADING_MODEL);
}
// if an ASCII effect description (HLSL?) is contained in the file,
// we can simply ignore it ...
if (pcSkin->typ & AI_MDL7_SKINTYPE_MATERIAL_ASCDEF)
{
int32_t iMe = *((int32_t*)szCurrent);
szCurrent += sizeof(char) * iMe + sizeof(int32_t);
}
// if an embedded texture has been loaded setup the corresponding
// data structures in the aiScene instance
if (NULL != pcNew)
{
// place this as diffuse texture
char szCurrent[5];
sprintf(szCurrent,"*%i",this->pScene->mNumTextures);
aiString szFile;
const size_t iLen = strlen((const char*)szCurrent);
size_t iLen2 = iLen+1;
iLen2 = iLen2 > MAXLEN ? MAXLEN : iLen2;
memcpy(szFile.data,(const char*)szCurrent,iLen2);
szFile.length = iLen;
pcMatOut->AddProperty(&szFile,AI_MATKEY_TEXTURE_DIFFUSE(0));
// store the texture
aiTexture** pc = this->pScene->mTextures;
this->pScene->mTextures = new aiTexture*[this->pScene->mNumTextures+1];
for (unsigned int i = 0; i < this->pScene->mNumTextures;++i)
this->pScene->mTextures[i] = pc[i];
this->pScene->mTextures[this->pScene->mNumTextures] = pcNew;
this->pScene->mNumTextures++;
delete[] pc;
}
// place the name of the skin in the material
const size_t iLen = strlen(pcSkin->texture_name);
if (0 != iLen)
{
aiString szFile;
memcpy(szFile.data,pcSkin->texture_name,sizeof(pcSkin->texture_name));
szFile.length = iLen;
pcMatOut->AddProperty(&szFile,AI_MATKEY_NAME);
}
*szCurrentOut = szCurrent;
return;
}
#define _AI_MDL7_ACCESS(_data, _index, _limit, _type) \
(*((const _type*)(((const char*)_data) + _index * _limit)))
#define _AI_MDL7_ACCESS_VERT(_data, _index, _limit) \
_AI_MDL7_ACCESS(_data,_index,_limit,MDL::Vertex_MDL7)
// ------------------------------------------------------------------------------------------------
void MDLImporter::ValidateHeader_GameStudioA7(const MDL::Header_MDL7* pcHeader)
{
ai_assert(NULL != pcHeader);
if (sizeof(MDL::ColorValue_MDL7) != pcHeader->colorvalue_stc_size)
{
// LOG
throw new ImportErrorException(
"[3DGS MDL7] sizeof(MDL::ColorValue_MDL7) != pcHeader->colorvalue_stc_size");
}
if (sizeof(MDL::TexCoord_MDL7) != pcHeader->skinpoint_stc_size)
{
// LOG
throw new ImportErrorException(
"[3DGS MDL7] sizeof(MDL::TexCoord_MDL7) != pcHeader->skinpoint_stc_size");
}
if (sizeof(MDL::Skin_MDL7) != pcHeader->skin_stc_size)
{
// LOG
throw new ImportErrorException(
"sizeof(MDL::Skin_MDL7) != pcHeader->skin_stc_size");
}
// if there are no groups ... how should we load such a file?
if(0 == pcHeader->groups_num)
{
// LOG
throw new ImportErrorException( "[3DGS MDL7] No frames found");
}
return;
}
// ------------------------------------------------------------------------------------------------
void MDLImporter::InternReadFile_GameStudioA7( )
{
ai_assert(NULL != pScene);
// current cursor position in the file
const MDL::Header_MDL7* pcHeader = (const MDL::Header_MDL7*)this->m_pcHeader;
const unsigned char* szCurrent = (const unsigned char*)(pcHeader+1);
// validate the header of the file. There are some structure
// sizes that are expected by the loader to be constant
this->ValidateHeader_GameStudioA7(pcHeader);
// skip all bones
szCurrent += sizeof(MDL::Bone_MDL7) * pcHeader->bones_num;
// allocate a material list
std::vector<MaterialHelper*> pcMats;
// vector to hold all created meshes
std::vector<aiMesh*> avOutList;
avOutList.reserve(pcHeader->groups_num);
// read all groups
for (unsigned int iGroup = 0; iGroup < (unsigned int)pcHeader->groups_num;++iGroup)
{
const MDL::Group_MDL7* pcGroup = (const MDL::Group_MDL7*)szCurrent;
szCurrent = (const unsigned char*)(pcGroup+1);
if (1 != pcGroup->typ)
{
// Not a triangle-based mesh
DefaultLogger::get()->warn("[3DGS MDL7] Mesh group is not basing on"
"triangles. Continuing happily");
}
// read all skins
pcMats.reserve(pcMats.size() + pcGroup->numskins);
for (unsigned int iSkin = 0; iSkin < (unsigned int)pcGroup->numskins;++iSkin)
{
this->ParseSkinLump_GameStudioA7(szCurrent,&szCurrent,pcMats);
}
// if we have absolutely no skin loaded we need to generate a default material
if (pcMats.empty())
{
const int iMode = (int)aiShadingMode_Gouraud;
pcMats.push_back(new MaterialHelper());
MaterialHelper* pcHelper = (MaterialHelper*)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);
}
// now get a pointer to all texture coords in the group
const MDL::TexCoord_MDL7* pcGroupUVs = (const MDL::TexCoord_MDL7*)szCurrent;
szCurrent += pcHeader->skinpoint_stc_size * pcGroup->num_stpts;
// now get a pointer to all triangle in the group
const MDL::Triangle_MDL7* pcGroupTris = (const MDL::Triangle_MDL7*)szCurrent;
szCurrent += pcHeader->triangle_stc_size * pcGroup->numtris;
// now get a pointer to all vertices in the group
const MDL::Vertex_MDL7* pcGroupVerts = (const MDL::Vertex_MDL7*)szCurrent;
szCurrent += pcHeader->mainvertex_stc_size * pcGroup->numverts;
// build output vectors
std::vector<aiVector3D> vPositions;
vPositions.resize(pcGroup->numtris * 3);
std::vector<aiVector3D> vNormals;
vNormals.resize(pcGroup->numtris * 3);
std::vector<aiVector3D> vTextureCoords1;
vTextureCoords1.resize(pcGroup->numtris * 3,
aiVector3D(std::numeric_limits<float>::quiet_NaN(),0.0f,0.0f));
std::vector<aiVector3D> vTextureCoords2;
bool bNeed2UV = false;
if (pcHeader->triangle_stc_size >= sizeof(MDL::Triangle_MDL7))
{
vTextureCoords2.resize(pcGroup->numtris * 3,
aiVector3D(std::numeric_limits<float>::quiet_NaN(),0.0f,0.0f));
bNeed2UV = true;
}
MDL::IntFace_MDL7* pcFaces = new MDL::IntFace_MDL7[pcGroup->numtris];
// iterate through all triangles and build valid display lists
for (unsigned int iTriangle = 0; iTriangle < (unsigned int)pcGroup->numtris; ++iTriangle)
{
// iterate through all indices of the current triangle
for (unsigned int c = 0; c < 3;++c)
{
// validate the vertex index
unsigned int iIndex = pcGroupTris->v_index[c];
if(iIndex > (unsigned int)pcGroup->numverts)
{
// LOG
iIndex = pcGroup->numverts-1;
DefaultLogger::get()->warn("Index overflow in MDL7 vertex list");
}
unsigned int iOutIndex = iTriangle * 3 + c;
// write the output face index
pcFaces[iTriangle].mIndices[c] = iOutIndex;
// swap z and y axis
vPositions[iOutIndex].x = _AI_MDL7_ACCESS_VERT(pcGroupVerts,iIndex,pcHeader->mainvertex_stc_size) .x;
vPositions[iOutIndex].z = _AI_MDL7_ACCESS_VERT(pcGroupVerts,iIndex,pcHeader->mainvertex_stc_size) .y;
vPositions[iOutIndex].y = _AI_MDL7_ACCESS_VERT(pcGroupVerts,iIndex,pcHeader->mainvertex_stc_size) .z;
// now read the normal vector
if (AI_MDL7_FRAMEVERTEX030305_STCSIZE <= pcHeader->mainvertex_stc_size)
{
// read the full normal vector
vNormals[iOutIndex].x = _AI_MDL7_ACCESS_VERT(pcGroupVerts,iIndex,pcHeader->mainvertex_stc_size) .norm[0];
vNormals[iOutIndex].z = _AI_MDL7_ACCESS_VERT(pcGroupVerts,iIndex,pcHeader->mainvertex_stc_size) .norm[1];
vNormals[iOutIndex].y = _AI_MDL7_ACCESS_VERT(pcGroupVerts,iIndex,pcHeader->mainvertex_stc_size) .norm[2];
// FIX: It seems to be necessary to invert all normals
vNormals[iOutIndex].x *= -1.0f;
vNormals[iOutIndex].y *= -1.0f;
vNormals[iOutIndex].z *= -1.0f;
}
else if (AI_MDL7_FRAMEVERTEX120503_STCSIZE <= pcHeader->mainvertex_stc_size)
{
// read the normal vector from Quake2's smart table
vNormals[iOutIndex] = *((const aiVector3D*)(&g_avNormals[std::min(
int(_AI_MDL7_ACCESS_VERT(pcGroupVerts,iIndex,pcHeader->mainvertex_stc_size) .norm162index),
int(sizeof(g_avNormals) / sizeof(g_avNormals[0]))-1)]));
std::swap(vNormals[iOutIndex].z,vNormals[iOutIndex].y);
}
// validate and process the first uv coordinate set
// *************************************************************
const unsigned int iMin = sizeof(MDL::Triangle_MDL7)-
sizeof(MDL::SkinSet_MDL7)-sizeof(uint32_t);
const unsigned int iMin2 = sizeof(MDL::Triangle_MDL7)-
sizeof(MDL::SkinSet_MDL7);
if (pcHeader->triangle_stc_size >= iMin)
{
iIndex = pcGroupTris->skinsets[0].st_index[c];
if(iIndex > (unsigned int)pcGroup->num_stpts)
{
iIndex = pcGroup->num_stpts-1;
}
float u = pcGroupUVs[iIndex].u;
float v = 1.0f-pcGroupUVs[iIndex].v;
vTextureCoords1[iOutIndex].x = u;
vTextureCoords1[iOutIndex].y = v;
// assign the material index, but only if it is existing
if (pcHeader->triangle_stc_size >= iMin2)
{
pcFaces[iTriangle].iMatIndex[0] = pcGroupTris->skinsets[0].material;
}
}
// validate and process the second uv coordinate set
// *************************************************************
if (pcHeader->triangle_stc_size >= sizeof(MDL::Triangle_MDL7))
{
iIndex = pcGroupTris->skinsets[1].st_index[c];
if(iIndex > (unsigned int)pcGroup->num_stpts)
{
iIndex = pcGroup->num_stpts-1;
}
float u = pcGroupUVs[iIndex].u;
float v = 1.0f-pcGroupUVs[iIndex].v;
vTextureCoords2[iOutIndex].x = u;
vTextureCoords2[iOutIndex].y = v;
// check whether we do really need the second texture
// coordinate set ... wastes memory and loading time
if (0 != iIndex && (u != vTextureCoords1[iOutIndex].x ||
v != vTextureCoords1[iOutIndex].y))
{
bNeed2UV = true;
}
// if the material differs, we need a second skin, too
if (pcGroupTris->skinsets[1].material != pcGroupTris->skinsets[0].material)
{
bNeed2UV = true;
}
// assign the material index
pcFaces[iTriangle].iMatIndex[1] = pcGroupTris->skinsets[1].material;
}
}
// get the next triangle in the list
pcGroupTris = (const MDL::Triangle_MDL7*)((const char*)pcGroupTris + pcHeader->triangle_stc_size);
}
// if we don't need a second set of texture coordinates there is no reason to keep it in memory ...
std::vector<unsigned int>** aiSplit;
unsigned int iNumMaterials = 0;
if (!bNeed2UV)
{
vTextureCoords2.clear();
// allocate the array
aiSplit = new std::vector<unsigned int>*[pcMats.size()];
iNumMaterials = pcMats.size();
for (unsigned int m = 0; m < pcMats.size();++m)
aiSplit[m] = new std::vector<unsigned int>();
// iterate through all faces and sort by material
for (unsigned int iFace = 0; iFace < (unsigned int)pcGroup->numtris;++iFace)
{
// check range
if (pcFaces[iFace].iMatIndex[0] >= iNumMaterials)
{
// use the last material instead
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 != pcFaces[iFace].iMatIndex[0])
DefaultLogger::get()->warn("Index overflow in MDL7 material list [#0]");
}
else aiSplit[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(pcMats.size()*2);
std::vector<std::vector<unsigned int>* > aiTempSplit;
aiTempSplit.reserve(pcMats.size()*2);
for (unsigned int m = 0; m < pcMats.size();++m)
aiTempSplit[m] = new std::vector<unsigned int>();
// iterate through all faces and sort by material
for (unsigned int iFace = 0; iFace < (unsigned int)pcGroup->numtris;++iFace)
{
// check range
unsigned int iMatIndex = pcFaces[iFace].iMatIndex[0];
if (iMatIndex >= iNumMaterials)
{
iMatIndex = iNumMaterials-1;
// sometimes MED writes -1, but normally only if there is only
// one skin assigned. No warning in this case
if(0xFFFFFFFF != iMatIndex)
DefaultLogger::get()->warn("Index overflow in MDL7 material list [#1]");
}
unsigned int iMatIndex2 = pcFaces[iFace].iMatIndex[1];
if (iMatIndex2 >= iNumMaterials)
{
// sometimes MED writes -1, but normally only if there is only
// one skin assigned. No warning in this case
if(0xFFFFFFFF != iMatIndex2)
DefaultLogger::get()->warn("Index overflow in MDL7 material list [#2]");
}
// do a slow O(log(n)*n) seach in the list ...
unsigned int 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 MaterialHelper();
sHelper.iOldMatIndices[0] = iMatIndex;
sHelper.iOldMatIndices[1] = iMatIndex2;
this->JoinSkins_GameStudioA7(pcMats[iMatIndex],pcMats[iMatIndex2],sHelper.pcMat);
// and add it to the list
avMats.push_back(sHelper);
iNum = 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 == iGroup)
{
pcMats.resize(avMats.size());
for (unsigned int o = 0; o < avMats.size();++o)
pcMats[o] = avMats[o].pcMat;
}
else
{
// TODO: This might result in redundant materials ...
unsigned int iOld = pcMats.size();
pcMats.resize(pcMats.size() + avMats.size());
for (unsigned int o = iOld; o < avMats.size();++o)
pcMats[o] = avMats[o].pcMat;
}
iNumMaterials = pcMats.size();
// and build the final face-to-material array
aiSplit = new std::vector<unsigned int>*[aiTempSplit.size()];
for (unsigned int m = 0; m < iNumMaterials;++m)
aiSplit[m] = aiTempSplit[m];
// no need to delete the member of aiTempSplit
}
// now generate output meshes
unsigned int iOldSize = avOutList.size();
this->GenerateOutputMeshes_GameStudioA7(
(const std::vector<unsigned int>**)aiSplit,pcMats,
avOutList,pcFaces,vPositions,vNormals, vTextureCoords1,vTextureCoords2);
// store the group index temporarily
ai_assert(AI_MAX_NUMBER_OF_TEXTURECOORDS >= 3);
for (unsigned int l = iOldSize;l < avOutList.size();++l)
{
avOutList[l]->mNumUVComponents[2] = iGroup;
}
// delete the face-to-material helper array
for (unsigned int m = 0; m < iNumMaterials;++m)
delete aiSplit[m];
delete[] aiSplit;
// now we need to skip all faces
for(unsigned int iFrame = 0; iFrame < (unsigned int)pcGroup->numframes;++iFrame)
{
const MDL::Frame_MDL7* pcFrame = (const MDL::Frame_MDL7*)szCurrent;
unsigned int iAdd = pcHeader->frame_stc_size +
pcFrame->vertices_count * pcHeader->framevertex_stc_size +
pcFrame->transmatrix_count * pcHeader->bonetrans_stc_size;
if (((unsigned int)szCurrent - (unsigned int)pcHeader) + iAdd > (unsigned int)pcHeader->data_size)
{
DefaultLogger::get()->warn("Index overflow in frame area. Ignoring frames");
// don't parse more groups if we can't even read one
goto __BREAK_OUT;
}
szCurrent += iAdd;
}
}
__BREAK_OUT: // EVIL ;-)
// now we need to build a final mesh list
this->pScene->mNumMeshes = avOutList.size();
this->pScene->mMeshes = new aiMesh*[avOutList.size()];
for (unsigned int i = 0; i < avOutList.size();++i)
{
this->pScene->mMeshes[i] = avOutList[i];
}
// build a final material list. Offset all mesh material indices
this->pScene->mNumMaterials = pcMats.size();
this->pScene->mMaterials = new aiMaterial*[this->pScene->mNumMaterials];
for (unsigned int i = 0; i < this->pScene->mNumMaterials;++i)
this->pScene->mMaterials[i] = pcMats[i];
// search for referrer materials
for (unsigned int i = 0; i < this->pScene->mNumMaterials;++i)
{
int iIndex = 0;
if (AI_SUCCESS == aiGetMaterialInteger(this->pScene->mMaterials[i],
"quakquakquak", &iIndex) )
{
for (unsigned int a = 0; a < avOutList.size();++a)
{
if (i == avOutList[a]->mMaterialIndex)
{
avOutList[a]->mMaterialIndex = iIndex;
}
}
// TODO: Remove the material from the list
}
}
// now generate a nodegraph whose rootnode references all meshes
this->pScene->mRootNode = new aiNode();
this->pScene->mRootNode->mNumMeshes = this->pScene->mNumMeshes;
this->pScene->mRootNode->mMeshes = new unsigned int[this->pScene->mRootNode->mNumMeshes];
for (unsigned int i = 0; i < this->pScene->mRootNode->mNumMeshes;++i)
this->pScene->mRootNode->mMeshes[i] = i;
// seems we're finished now
return;
}
// ------------------------------------------------------------------------------------------------
void MDLImporter::GenerateOutputMeshes_GameStudioA7(
const std::vector<unsigned int>** aiSplit,
const std::vector<MaterialHelper*>& pcMats,
std::vector<aiMesh*>& avOutList,
const MDL::IntFace_MDL7* pcFaces,
const std::vector<aiVector3D>& vPositions,
const std::vector<aiVector3D>& vNormals,
const std::vector<aiVector3D>& vTextureCoords1,
const std::vector<aiVector3D>& vTextureCoords2)
{
ai_assert(NULL != aiSplit);
ai_assert(NULL != pcFaces);
for (unsigned int i = 0; i < pcMats.size();++i)
{
if (!aiSplit[i]->empty())
{
// allocate the output mesh
aiMesh* pcMesh = new aiMesh();
pcMesh->mNumUVComponents[0] = 2;
pcMesh->mMaterialIndex = i;
// allocate output storage
pcMesh->mNumFaces = 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];
pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices];
if (!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 = aiSplit[i]->operator[](iFace);
const MDL::IntFace_MDL7& oldFace = pcFaces[iSrcFace];
// iterate through all face indices
for (unsigned int c = 0; c < 3;++c)
{
pcMesh->mVertices[iCurrent] = vPositions[oldFace.mIndices[c]];
pcMesh->mNormals[iCurrent] = vNormals[oldFace.mIndices[c]];
pcMesh->mTextureCoords[0][iCurrent] = vTextureCoords1[oldFace.mIndices[c]];
if (!vTextureCoords2.empty())
{
pcMesh->mTextureCoords[1][iCurrent] = vTextureCoords2[oldFace.mIndices[c]];
}
pcMesh->mFaces[iFace].mIndices[c] = iCurrent;
++iCurrent;
}
}
// add the mesh to the list of output meshes
avOutList.push_back(pcMesh);
}
}
return;
}
// ------------------------------------------------------------------------------------------------
void MDLImporter::JoinSkins_GameStudioA7(
MaterialHelper* pcMat1,
MaterialHelper* pcMat2,
MaterialHelper* pcMatOut)
{
ai_assert(NULL != pcMat1);
ai_assert(NULL != pcMat2);
ai_assert(NULL != pcMatOut);
// first create a full copy of the first skin property set
// and assign it to the output material
MaterialHelper::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));
}
return;
}
// ------------------------------------------------------------------------------------------------
void MDLImporter::FlipNormals(aiMesh* pcMesh)
{
ai_assert(NULL != pcMesh);
// compute the bounding box of both the model vertices + normals and
// the umodified model vertices. Then check whether the first BB
// is smaller than the second. In this case we can assume that the
// normals need to be flipped, although there are a few special cases ..
// convex, concave, planar models ...
aiVector3D vMin0(1e10f,1e10f,1e10f);
aiVector3D vMin1(1e10f,1e10f,1e10f);
aiVector3D vMax0(-1e10f,-1e10f,-1e10f);
aiVector3D vMax1(-1e10f,-1e10f,-1e10f);
for (unsigned int i = 0; i < pcMesh->mNumVertices;++i)
{
vMin1.x = std::min(vMin1.x,pcMesh->mVertices[i].x);
vMin1.y = std::min(vMin1.y,pcMesh->mVertices[i].y);
vMin1.z = std::min(vMin1.z,pcMesh->mVertices[i].z);
vMax1.x = std::max(vMax1.x,pcMesh->mVertices[i].x);
vMax1.y = std::max(vMax1.y,pcMesh->mVertices[i].y);
vMax1.z = std::max(vMax1.z,pcMesh->mVertices[i].z);
aiVector3D vWithNormal = pcMesh->mVertices[i] + pcMesh->mNormals[i];
vMin0.x = std::min(vMin0.x,vWithNormal.x);
vMin0.y = std::min(vMin0.y,vWithNormal.y);
vMin0.z = std::min(vMin0.z,vWithNormal.z);
vMax0.x = std::max(vMax0.x,vWithNormal.x);
vMax0.y = std::max(vMax0.y,vWithNormal.y);
vMax0.z = std::max(vMax0.z,vWithNormal.z);
}
if (fabsf((vMax0.x - vMin0.x) * (vMax0.y - vMin0.y) * (vMax0.z - vMin0.z)) <=
fabsf((vMax1.x - vMin1.x) * (vMax1.y - vMin1.y) * (vMax1.z - vMin1.z)))
{
DefaultLogger::get()->info("The models normals are facing inwards "
"(or the model is too planar or concave). Flipping the normal set ...");
for (unsigned int i = 0; i < pcMesh->mNumVertices;++i)
{
pcMesh->mNormals[i] *= -1.0f;
}
}
return;
}
// ------------------------------------------------------------------------------------------------
void MDLImporter::InternReadFile_HL2( )
{
const MDL::Header_HL2* pcHeader = (const MDL::Header_HL2*)this->mBuffer;
return;
}