#ifndef ASSIMP_BUILD_NO_MD3_IMPORTER
#include "MD3Loader.h"
#include <assimp/SceneCombiner.h>
#include "GenericProperty.h"
#include "RemoveComments.h"
#include "ParsingUtils.h"
#include "Importer.h"
#include <assimp/DefaultLogger.hpp>
#include <memory>
#include <assimp/IOSystem.hpp>
#include <assimp/material.h>
#include <assimp/scene.h>
#include <assimp/importerdesc.h>
#include <cctype>
using namespace Assimp;
static const aiImporterDesc desc = {
"Quake III Mesh Importer",
"",
"",
"",
aiImporterFlags_SupportBinaryFlavour,
0,
0,
0,
0,
"md3"
};
Q3Shader::BlendFunc StringToBlendFunc(const std::string& m)
{
if (m == "GL_ONE") {
return Q3Shader::BLEND_GL_ONE;
}
if (m == "GL_ZERO") {
return Q3Shader::BLEND_GL_ZERO;
}
if (m == "GL_SRC_ALPHA") {
return Q3Shader::BLEND_GL_SRC_ALPHA;
}
if (m == "GL_ONE_MINUS_SRC_ALPHA") {
return Q3Shader::BLEND_GL_ONE_MINUS_SRC_ALPHA;
}
if (m == "GL_ONE_MINUS_DST_COLOR") {
return Q3Shader::BLEND_GL_ONE_MINUS_DST_COLOR;
}
DefaultLogger::get()->error("Q3Shader: Unknown blend function: " + m);
return Q3Shader::BLEND_NONE;
}
bool Q3Shader::LoadShader(ShaderData& fill, const std::string& pFile,IOSystem* io)
{
std::unique_ptr<IOStream> file( io->Open( pFile, "rt"));
if (!file.get())
return false;
DefaultLogger::get()->info("Loading Quake3 shader file " + pFile);
const size_t s = file->FileSize();
std::vector<char> _buff(s+1);
file->Read(&_buff[0],s,1);
_buff[s] = 0;
CommentRemover::RemoveLineComments("//",&_buff[0]);
const char* buff = &_buff[0];
Q3Shader::ShaderDataBlock* curData = NULL;
Q3Shader::ShaderMapBlock* curMap = NULL;
for (;SkipSpacesAndLineEnd(&buff);SkipLine(&buff)) {
if (*buff == '{') {
++buff;
if (!curData) {
DefaultLogger::get()->error("Q3Shader: Unexpected shader section token \'{\'");
return true; }
for (;SkipSpacesAndLineEnd(&buff);SkipLine(&buff)) {
if (*buff == '{') {
++buff;
curData->maps.push_back(Q3Shader::ShaderMapBlock());
curMap = &curData->maps.back();
for (;SkipSpacesAndLineEnd(&buff);SkipLine(&buff)) {
if (TokenMatchI(buff,"map",3) || TokenMatchI(buff,"clampmap",8)) {
curMap->name = GetNextToken(buff);
}
else if (TokenMatchI(buff,"blendfunc",9)) {
const std::string blend_src = GetNextToken(buff);
if (blend_src == "add") {
curMap->blend_src = Q3Shader::BLEND_GL_ONE;
curMap->blend_dest = Q3Shader::BLEND_GL_ONE;
}
else if (blend_src == "filter") {
curMap->blend_src = Q3Shader::BLEND_GL_DST_COLOR;
curMap->blend_dest = Q3Shader::BLEND_GL_ZERO;
}
else if (blend_src == "blend") {
curMap->blend_src = Q3Shader::BLEND_GL_SRC_ALPHA;
curMap->blend_dest = Q3Shader::BLEND_GL_ONE_MINUS_SRC_ALPHA;
}
else {
curMap->blend_src = StringToBlendFunc(blend_src);
curMap->blend_dest = StringToBlendFunc(GetNextToken(buff));
}
}
else if (TokenMatchI(buff,"alphafunc",9)) {
const std::string at = GetNextToken(buff);
if (at == "GT0") {
curMap->alpha_test = Q3Shader::AT_GT0;
}
else if (at == "LT128") {
curMap->alpha_test = Q3Shader::AT_LT128;
}
else if (at == "GE128") {
curMap->alpha_test = Q3Shader::AT_GE128;
}
}
else if (*buff == '}') {
++buff;
curMap = NULL;
break;
}
}
}
else if (*buff == '}') {
++buff;
curData = NULL;
break;
}
else if (TokenMatchI(buff,"cull",4)) {
SkipSpaces(&buff);
if (!ASSIMP_strincmp(buff,"back",4)) {
curData->cull = Q3Shader::CULL_CCW;
}
else if (!ASSIMP_strincmp(buff,"front",5)) {
curData->cull = Q3Shader::CULL_CW;
}
else if (!ASSIMP_strincmp(buff,"none",4) || !ASSIMP_strincmp(buff,"disable",7)) {
curData->cull = Q3Shader::CULL_NONE;
}
else DefaultLogger::get()->error("Q3Shader: Unrecognized cull mode");
}
}
}
else {
fill.blocks.push_back(Q3Shader::ShaderDataBlock());
curData = &fill.blocks.back();
curData->name = GetNextToken(buff);
}
}
return true;
}
bool Q3Shader::LoadSkin(SkinData& fill, const std::string& pFile,IOSystem* io)
{
std::unique_ptr<IOStream> file( io->Open( pFile, "rt"));
if (!file.get())
return false;
DefaultLogger::get()->info("Loading Quake3 skin file " + pFile);
const size_t s = file->FileSize();
std::vector<char> _buff(s+1);const char* buff = &_buff[0];
file->Read(&_buff[0],s,1);
_buff[s] = 0;
std::replace(_buff.begin(),_buff.end(),',',' ');
for (;*buff;) {
SkipSpacesAndLineEnd(&buff);
std::string ss = GetNextToken(buff);
if (!::strncmp(&ss[0],"tag_",std::min((size_t)4, ss.length())))
continue;
fill.textures.push_back(SkinData::TextureEntry());
SkinData::TextureEntry& s = fill.textures.back();
s.first = ss;
s.second = GetNextToken(buff);
}
return true;
}
void Q3Shader::ConvertShaderToMaterial(aiMaterial* out, const ShaderDataBlock& shader)
{
ai_assert(NULL != out);
if (shader.cull == Q3Shader::CULL_NONE) {
const int twosided = 1;
out->AddProperty(&twosided,1,AI_MATKEY_TWOSIDED);
}
unsigned int cur_emissive = 0, cur_diffuse = 0, cur_lm =0;
for (std::list< Q3Shader::ShaderMapBlock >::const_iterator it = shader.maps.begin(); it != shader.maps.end();++it) {
aiString s((*it).name);
aiTextureType type; unsigned int index;
if ((*it).blend_src == Q3Shader::BLEND_GL_ONE && (*it).blend_dest == Q3Shader::BLEND_GL_ONE) {
if (it == shader.maps.begin()) {
const int additive = aiBlendMode_Additive;
out->AddProperty(&additive,1,AI_MATKEY_BLEND_FUNC);
index = cur_diffuse++;
type = aiTextureType_DIFFUSE;
}
else {
index = cur_emissive++;
type = aiTextureType_EMISSIVE;
}
}
else if ((*it).blend_src == Q3Shader::BLEND_GL_DST_COLOR && (*it).blend_dest == Q3Shader::BLEND_GL_ZERO) {
index = cur_lm++;
type = aiTextureType_LIGHTMAP;
}
else {
const int blend = aiBlendMode_Default;
out->AddProperty(&blend,1,AI_MATKEY_BLEND_FUNC);
index = cur_diffuse++;
type = aiTextureType_DIFFUSE;
}
out->AddProperty(&s,AI_MATKEY_TEXTURE(type,index));
const int use_alpha = ((*it).alpha_test != Q3Shader::AT_NONE ? aiTextureFlags_UseAlpha : aiTextureFlags_IgnoreAlpha);
out->AddProperty(&use_alpha,1,AI_MATKEY_TEXFLAGS(type,index));
}
if (0 != cur_emissive) {
aiColor3D one(1.f,1.f,1.f);
out->AddProperty(&one,1,AI_MATKEY_COLOR_EMISSIVE);
}
}
MD3Importer::MD3Importer()
: configFrameID (0)
, configHandleMP (true)
, configSpeedFlag()
, pcHeader()
, mBuffer()
, fileSize()
, mScene()
, mIOHandler()
{}
MD3Importer::~MD3Importer()
{}
bool MD3Importer::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
{
const std::string extension = GetExtension(pFile);
if (extension == "md3")
return true;
if (!extension.length() || checkSig) {
uint32_t tokens[1];
tokens[0] = AI_MD3_MAGIC_NUMBER_LE;
return CheckMagicToken(pIOHandler,pFile,tokens,1);
}
return false;
}
void MD3Importer::ValidateHeaderOffsets()
{
if (pcHeader->IDENT != AI_MD3_MAGIC_NUMBER_BE &&
pcHeader->IDENT != AI_MD3_MAGIC_NUMBER_LE)
throw DeadlyImportError( "Invalid MD3 file: Magic bytes not found");
if (pcHeader->VERSION > 15)
DefaultLogger::get()->warn( "Unsupported MD3 file version. Continuing happily ...");
if (!pcHeader->NUM_SURFACES)
throw DeadlyImportError( "Invalid md3 file: NUM_SURFACES is 0");
if (pcHeader->OFS_FRAMES >= fileSize || pcHeader->OFS_SURFACES >= fileSize ||
pcHeader->OFS_EOF > fileSize) {
throw DeadlyImportError("Invalid MD3 header: some offsets are outside the file");
}
if (pcHeader->NUM_SURFACES > AI_MAX_ALLOC(MD3::Surface)) {
throw DeadlyImportError("Invalid MD3 header: too many surfaces, would overflow");
}
if (pcHeader->OFS_SURFACES + pcHeader->NUM_SURFACES * sizeof(MD3::Surface) >= fileSize) {
throw DeadlyImportError("Invalid MD3 header: some surfaces are outside the file");
}
if (pcHeader->NUM_FRAMES <= configFrameID )
throw DeadlyImportError("The requested frame is not existing the file");
}
void MD3Importer::ValidateSurfaceHeaderOffsets(const MD3::Surface* pcSurf)
{
const int32_t ofs = int32_t((const unsigned char*)pcSurf-this->mBuffer);
if (pcSurf->OFS_TRIANGLES + ofs + pcSurf->NUM_TRIANGLES * sizeof(MD3::Triangle) > fileSize ||
pcSurf->OFS_SHADERS + ofs + pcSurf->NUM_SHADER * sizeof(MD3::Shader) > fileSize ||
pcSurf->OFS_ST + ofs + pcSurf->NUM_VERTICES * sizeof(MD3::TexCoord) > fileSize ||
pcSurf->OFS_XYZNORMAL + ofs + pcSurf->NUM_VERTICES * sizeof(MD3::Vertex) > fileSize) {
throw DeadlyImportError("Invalid MD3 surface header: some offsets are outside the file");
}
if (pcSurf->NUM_TRIANGLES > AI_MD3_MAX_TRIANGLES) {
DefaultLogger::get()->warn("MD3: Quake III triangle limit exceeded");
}
if (pcSurf->NUM_SHADER > AI_MD3_MAX_SHADERS) {
DefaultLogger::get()->warn("MD3: Quake III shader limit exceeded");
}
if (pcSurf->NUM_VERTICES > AI_MD3_MAX_VERTS) {
DefaultLogger::get()->warn("MD3: Quake III vertex limit exceeded");
}
if (pcSurf->NUM_FRAMES > AI_MD3_MAX_FRAMES) {
DefaultLogger::get()->warn("MD3: Quake III frame limit exceeded");
}
}
const aiImporterDesc* MD3Importer::GetInfo () const
{
return &desc;
}
void MD3Importer::SetupProperties(const Importer* pImp)
{
configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MD3_KEYFRAME,-1);
if(static_cast<unsigned int>(-1) == configFrameID) {
configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_GLOBAL_KEYFRAME,0);
}
configHandleMP = (0 != pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MD3_HANDLE_MULTIPART,1));
configSkinFile = (pImp->GetPropertyString(AI_CONFIG_IMPORT_MD3_SKIN_NAME,"default"));
configShaderFile = (pImp->GetPropertyString(AI_CONFIG_IMPORT_MD3_SHADER_SRC,""));
configSpeedFlag = (0 != pImp->GetPropertyInteger(AI_CONFIG_FAVOUR_SPEED,0));
}
void MD3Importer::ReadSkin(Q3Shader::SkinData& fill) const
{
std::string::size_type s = filename.find_last_of('_');
if (s == std::string::npos) {
s = filename.find_last_of('.');
if (s == std::string::npos) {
s = filename.size();
}
}
ai_assert(s != std::string::npos);
const std::string skin_file = path + filename.substr(0,s) + "_" + configSkinFile + ".skin";
Q3Shader::LoadSkin(fill,skin_file,mIOHandler);
}
void MD3Importer::ReadShader(Q3Shader::ShaderData& fill) const
{
const std::string::size_type s = path.find_last_of("\\/",path.length()-2);
const std::string model_file = path.substr(s+1,path.length()-(s+2));
if (!configShaderFile.length()) {
if(!Q3Shader::LoadShader(fill,path + "..\\..\\..\\scripts\\" + model_file + ".shader",mIOHandler)) {
Q3Shader::LoadShader(fill,path + "..\\..\\..\\scripts\\" + filename + ".shader",mIOHandler);
}
}
else {
const std::string::size_type st = configShaderFile.find_last_of('.');
if (st == std::string::npos) {
if(!Q3Shader::LoadShader(fill,configShaderFile + model_file + ".shader",mIOHandler)) {
Q3Shader::LoadShader(fill,configShaderFile + filename + ".shader",mIOHandler);
}
}
else {
Q3Shader::LoadShader(fill,configShaderFile,mIOHandler);
}
}
}
void RemoveSingleNodeFromList(aiNode* nd)
{
if (!nd || nd->mNumChildren || !nd->mParent)return;
aiNode* par = nd->mParent;
for (unsigned int i = 0; i < par->mNumChildren;++i) {
if (par->mChildren[i] == nd) {
--par->mNumChildren;
for (;i < par->mNumChildren;++i) {
par->mChildren[i] = par->mChildren[i+1];
}
delete nd;
break;
}
}
}
bool MD3Importer::ReadMultipartFile()
{
std::string::size_type s = filename.find_last_of('_'), t = filename.find_last_of('.');
if (t == std::string::npos)
t = filename.size();
if (s == std::string::npos)
s = t;
const std::string mod_filename = filename.substr(0,s);
const std::string suffix = filename.substr(s,t-s);
if (mod_filename == "lower" || mod_filename == "upper" || mod_filename == "head"){
const std::string lower = path + "lower" + suffix + ".md3";
const std::string upper = path + "upper" + suffix + ".md3";
const std::string head = path + "head" + suffix + ".md3";
aiScene* scene_upper = NULL;
aiScene* scene_lower = NULL;
aiScene* scene_head = NULL;
std::string failure;
aiNode* tag_torso, *tag_head;
std::vector<AttachmentInfo> attach;
DefaultLogger::get()->info("Multi part MD3 player model: lower, upper and head parts are joined");
BatchLoader::PropertyMap props;
SetGenericProperty( props.ints, AI_CONFIG_IMPORT_MD3_HANDLE_MULTIPART, 0);
BatchLoader batch(mIOHandler);
const unsigned int _lower = batch.AddLoadRequest(lower,0,&props);
const unsigned int _upper = batch.AddLoadRequest(upper,0,&props);
const unsigned int _head = batch.AddLoadRequest(head,0,&props);
batch.LoadAll();
aiScene* master = new aiScene();
aiNode* nd = master->mRootNode = new aiNode();
nd->mName.Set("<MD3_Player>");
scene_lower = batch.GetImport(_lower);
if (!scene_lower) {
DefaultLogger::get()->error("M3D: Failed to read multi part model, lower.md3 fails to load");
failure = "lower";
goto error_cleanup;
}
scene_upper = batch.GetImport(_upper);
if (!scene_upper) {
DefaultLogger::get()->error("M3D: Failed to read multi part model, upper.md3 fails to load");
failure = "upper";
goto error_cleanup;
}
scene_head = batch.GetImport(_head);
if (!scene_head) {
DefaultLogger::get()->error("M3D: Failed to read multi part model, head.md3 fails to load");
failure = "head";
goto error_cleanup;
}
scene_lower->mRootNode->mName.Set("lower");
attach.push_back(AttachmentInfo(scene_lower, nd));
tag_torso = scene_lower->mRootNode->FindNode("tag_torso");
if (!tag_torso) {
DefaultLogger::get()->error("M3D: Failed to find attachment tag for multi part model: tag_torso expected");
goto error_cleanup;
}
scene_upper->mRootNode->mName.Set("upper");
attach.push_back(AttachmentInfo(scene_upper,tag_torso));
tag_head = scene_upper->mRootNode->FindNode("tag_head");
if (!tag_head) {
DefaultLogger::get()->error("M3D: Failed to find attachment tag for multi part model: tag_head expected");
goto error_cleanup;
}
scene_head->mRootNode->mName.Set("head");
attach.push_back(AttachmentInfo(scene_head,tag_head));
RemoveSingleNodeFromList (scene_upper->mRootNode->FindNode("tag_torso"));
RemoveSingleNodeFromList (scene_head-> mRootNode->FindNode("tag_head" ));
scene_head->mRootNode->mTransformation = aiMatrix4x4();
scene_lower->mRootNode->mTransformation = aiMatrix4x4();
scene_upper->mRootNode->mTransformation = aiMatrix4x4();
SceneCombiner::MergeScenes(&mScene,master, attach,
AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES |
AI_INT_MERGE_SCENE_GEN_UNIQUE_MATNAMES |
AI_INT_MERGE_SCENE_RESOLVE_CROSS_ATTACHMENTS |
(!configSpeedFlag ? AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY : 0));
mScene->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);
return true;
error_cleanup:
delete scene_upper;
delete scene_lower;
delete scene_head;
delete master;
if (failure == mod_filename) {
throw DeadlyImportError("MD3: failure to read multipart host file");
}
}
return false;
}
void MD3Importer::ConvertPath(const char* texture_name, const char* header_name, std::string& out) const
{
const char* end1 = ::strrchr(header_name,'\\');
if (!end1)end1 = ::strrchr(header_name,'/');
const char* end2 = ::strrchr(texture_name,'\\');
if (!end2)end2 = ::strrchr(texture_name,'/');
if (end2) {
size_t len2;
const size_t len1 = (size_t)(end1 - header_name);
if (!ASSIMP_strincmp(texture_name,"models",6) && (texture_name[6] == '/' || texture_name[6] == '\\')) {
len2 = 6;
if (!header_name[0]) {
out = end2+1;
return;
}
}
else len2 = std::min (len1, (size_t)(end2 - texture_name ));
if (!ASSIMP_strincmp(texture_name,header_name,static_cast<unsigned int>(len2))) {
out = end2+1;
return;
}
}
out = texture_name;
}
void MD3Importer::InternReadFile( const std::string& pFile,
aiScene* pScene, IOSystem* pIOHandler)
{
mFile = pFile;
mScene = pScene;
mIOHandler = pIOHandler;
std::string::size_type s = mFile.find_last_of("/\\");
if (s == std::string::npos) {
s = 0;
}
else ++s;
filename = mFile.substr(s), path = mFile.substr(0,s);
for( std::string::iterator it = filename .begin(); it != filename.end(); ++it)
*it = tolower( *it);
if (configHandleMP) {
if (ReadMultipartFile())
return;
}
std::unique_ptr<IOStream> file( pIOHandler->Open( pFile));
if( file.get() == NULL)
throw DeadlyImportError( "Failed to open MD3 file " + pFile + ".");
fileSize = (unsigned int)file->FileSize();
if( fileSize < sizeof(MD3::Header))
throw DeadlyImportError( "MD3 File is too small.");
std::vector<unsigned char> mBuffer2 (fileSize);
file->Read( &mBuffer2[0], 1, fileSize);
mBuffer = &mBuffer2[0];
pcHeader = (BE_NCONST MD3::Header*)mBuffer;
#ifdef AI_BUILD_BIG_ENDIAN
AI_SWAP4(pcHeader->VERSION);
AI_SWAP4(pcHeader->FLAGS);
AI_SWAP4(pcHeader->IDENT);
AI_SWAP4(pcHeader->NUM_FRAMES);
AI_SWAP4(pcHeader->NUM_SKINS);
AI_SWAP4(pcHeader->NUM_SURFACES);
AI_SWAP4(pcHeader->NUM_TAGS);
AI_SWAP4(pcHeader->OFS_EOF);
AI_SWAP4(pcHeader->OFS_FRAMES);
AI_SWAP4(pcHeader->OFS_SURFACES);
AI_SWAP4(pcHeader->OFS_TAGS);
#endif
ValidateHeaderOffsets();
BE_NCONST MD3::Surface* pcSurfaces = (BE_NCONST MD3::Surface*)(mBuffer + pcHeader->OFS_SURFACES);
BE_NCONST MD3::Tag* pcTags = (BE_NCONST MD3::Tag*)(mBuffer + pcHeader->OFS_TAGS);
pScene->mNumMeshes = pcHeader->NUM_SURFACES;
if (pcHeader->NUM_SURFACES == 0) {
throw DeadlyImportError("MD3: No surfaces");
} else if (pcHeader->NUM_SURFACES > AI_MAX_ALLOC(aiMesh)) {
throw DeadlyImportError("MD3: Too many surfaces, would run out of memory");
}
pScene->mMeshes = new aiMesh*[pScene->mNumMeshes];
pScene->mNumMaterials = pcHeader->NUM_SURFACES;
pScene->mMaterials = new aiMaterial*[pScene->mNumMeshes];
::memset(pScene->mMeshes,0,pScene->mNumMeshes*sizeof(aiMesh*));
::memset(pScene->mMaterials,0,pScene->mNumMaterials*sizeof(aiMaterial*));
Q3Shader::SkinData skins;
ReadSkin(skins);
Q3Shader::ShaderData shaders;
ReadShader(shaders);
const char* header_name = pcHeader->NAME;
if (!shaders.blocks.empty()) {
for (std::list< Q3Shader::ShaderDataBlock >::iterator dit = shaders.blocks.begin(); dit != shaders.blocks.end(); ++dit) {
ConvertPath((*dit).name.c_str(),header_name,(*dit).name);
for (std::list< Q3Shader::ShaderMapBlock >::iterator mit = (*dit).maps.begin(); mit != (*dit).maps.end(); ++mit) {
ConvertPath((*mit).name.c_str(),header_name,(*mit).name);
}
}
}
unsigned int iNum = pcHeader->NUM_SURFACES;
unsigned int iNumMaterials = 0;
while (iNum-- > 0) {
#ifdef AI_BUILD_BIG_ENDIAN
AI_SWAP4(pcSurfaces->FLAGS);
AI_SWAP4(pcSurfaces->IDENT);
AI_SWAP4(pcSurfaces->NUM_FRAMES);
AI_SWAP4(pcSurfaces->NUM_SHADER);
AI_SWAP4(pcSurfaces->NUM_TRIANGLES);
AI_SWAP4(pcSurfaces->NUM_VERTICES);
AI_SWAP4(pcSurfaces->OFS_END);
AI_SWAP4(pcSurfaces->OFS_SHADERS);
AI_SWAP4(pcSurfaces->OFS_ST);
AI_SWAP4(pcSurfaces->OFS_TRIANGLES);
AI_SWAP4(pcSurfaces->OFS_XYZNORMAL);
#endif
ValidateSurfaceHeaderOffsets(pcSurfaces);
BE_NCONST MD3::Vertex* pcVertices = (BE_NCONST MD3::Vertex*)
(((uint8_t*)pcSurfaces) + pcSurfaces->OFS_XYZNORMAL);
BE_NCONST MD3::Triangle* pcTriangles = (BE_NCONST MD3::Triangle*)
(((uint8_t*)pcSurfaces) + pcSurfaces->OFS_TRIANGLES);
BE_NCONST MD3::TexCoord* pcUVs = (BE_NCONST MD3::TexCoord*)
(((uint8_t*)pcSurfaces) + pcSurfaces->OFS_ST);
BE_NCONST MD3::Shader* pcShaders = (BE_NCONST MD3::Shader*)
(((uint8_t*)pcSurfaces) + pcSurfaces->OFS_SHADERS);
if (0 == pcSurfaces->NUM_VERTICES || 0 == pcSurfaces->NUM_TRIANGLES)
{
pcSurfaces = (BE_NCONST MD3::Surface*)(((uint8_t*)pcSurfaces) + pcSurfaces->OFS_END);
pScene->mNumMeshes--;
continue;
}
pScene->mMeshes[iNum] = new aiMesh();
aiMesh* pcMesh = pScene->mMeshes[iNum];
std::string _texture_name;
const char* texture_name = NULL;
std::list< Q3Shader::SkinData::TextureEntry >::iterator it = std::find(
skins.textures.begin(), skins.textures.end(), pcSurfaces->NAME );
if (it != skins.textures.end()) {
texture_name = &*( _texture_name = (*it).second).begin();
DefaultLogger::get()->debug("MD3: Assigning skin texture " + (*it).second + " to surface " + pcSurfaces->NAME);
(*it).resolved = true; }
if (!texture_name && pcSurfaces->NUM_SHADER) {
texture_name = pcShaders->NAME;
}
std::string convertedPath;
if (texture_name) {
ConvertPath(texture_name,header_name,convertedPath);
}
const Q3Shader::ShaderDataBlock* shader = NULL;
if (!shaders.blocks.empty()) {
std::string::size_type s = convertedPath.find_last_of('.');
if (s == std::string::npos)
s = convertedPath.length();
const std::string without_ext = convertedPath.substr(0,s);
std::list< Q3Shader::ShaderDataBlock >::const_iterator dit = std::find(shaders.blocks.begin(),shaders.blocks.end(),without_ext);
if (dit != shaders.blocks.end()) {
shader = &*dit;
DefaultLogger::get()->info("Found shader record for " +without_ext );
}
else DefaultLogger::get()->warn("Unable to find shader record for " +without_ext );
}
aiMaterial* pcHelper = new aiMaterial();
const int iMode = (int)aiShadingMode_Gouraud;
pcHelper->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL);
aiColor3D clr;
clr.b = clr.g = clr.r = 0.05f;
pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_AMBIENT);
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);
aiString name;
name.Set("MD3_[" + configSkinFile + "][" + pcSurfaces->NAME + "]");
pcHelper->AddProperty(&name,AI_MATKEY_NAME);
if (!shader) {
aiString szString;
if (convertedPath.length()) {
szString.Set(convertedPath);
}
else {
DefaultLogger::get()->warn("Texture file name has zero length. Using default name");
szString.Set("dummy_texture.bmp");
}
pcHelper->AddProperty(&szString,AI_MATKEY_TEXTURE_DIFFUSE(0));
int no_alpha = aiTextureFlags_IgnoreAlpha;
pcHelper->AddProperty(&no_alpha,1,AI_MATKEY_TEXFLAGS_DIFFUSE(0));
}
else {
Q3Shader::ConvertShaderToMaterial(pcHelper,*shader);
}
pScene->mMaterials[iNumMaterials] = (aiMaterial*)pcHelper;
pcMesh->mMaterialIndex = iNumMaterials++;
#ifdef AI_BUILD_BIG_ENDIAN
for (uint32_t i = 0; i < pcSurfaces->NUM_VERTICES;++i) {
AI_SWAP2( pcVertices[i].NORMAL );
AI_SWAP2( pcVertices[i].X );
AI_SWAP2( pcVertices[i].Y );
AI_SWAP2( pcVertices[i].Z );
AI_SWAP4( pcUVs[i].U );
AI_SWAP4( pcUVs[i].U );
}
for (uint32_t i = 0; i < pcSurfaces->NUM_TRIANGLES;++i) {
AI_SWAP4(pcTriangles[i].INDEXES[0]);
AI_SWAP4(pcTriangles[i].INDEXES[1]);
AI_SWAP4(pcTriangles[i].INDEXES[2]);
}
#endif
pcMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
pcMesh->mNumVertices = pcSurfaces->NUM_TRIANGLES*3;
pcMesh->mNumFaces = pcSurfaces->NUM_TRIANGLES;
pcMesh->mFaces = new aiFace[pcSurfaces->NUM_TRIANGLES];
pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices];
pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices];
pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices];
pcMesh->mNumUVComponents[0] = 2;
unsigned int iCurrent = 0;
for (unsigned int i = 0; i < (unsigned int)pcSurfaces->NUM_TRIANGLES;++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;
aiVector3D& vec = pcMesh->mVertices[iCurrent];
uint32_t index = pcTriangles->INDEXES[c];
if (index >= pcSurfaces->NUM_VERTICES) {
throw DeadlyImportError( "MD3: Invalid vertex index");
}
vec.x = pcVertices[index].X*AI_MD3_XYZ_SCALE;
vec.y = pcVertices[index].Y*AI_MD3_XYZ_SCALE;
vec.z = pcVertices[index].Z*AI_MD3_XYZ_SCALE;
aiVector3D& nor = pcMesh->mNormals[iCurrent];
LatLngNormalToVec3(pcVertices[pcTriangles->INDEXES[c]].NORMAL,(ai_real*)&nor);
pcMesh->mTextureCoords[0][iCurrent].x = pcUVs[ pcTriangles->INDEXES[c]].U;
pcMesh->mTextureCoords[0][iCurrent].y = 1.0f-pcUVs[ pcTriangles->INDEXES[c]].V;
}
if (!shader || shader->cull == Q3Shader::CULL_CW) {
std::swap(pcMesh->mFaces[i].mIndices[2],pcMesh->mFaces[i].mIndices[1]);
}
pcTriangles++;
}
pcSurfaces = (BE_NCONST MD3::Surface*)(((unsigned char*)pcSurfaces) + pcSurfaces->OFS_END);
}
if (!DefaultLogger::isNullLogger()) {
for (std::list< Q3Shader::SkinData::TextureEntry>::const_iterator it = skins.textures.begin();it != skins.textures.end(); ++it) {
if (!(*it).resolved) {
DefaultLogger::get()->error("MD3: Failed to match skin " + (*it).first + " to surface " + (*it).second);
}
}
}
if (!pScene->mNumMeshes)
throw DeadlyImportError( "MD3: File contains no valid mesh");
pScene->mNumMaterials = iNumMaterials;
pScene->mRootNode = new aiNode("<MD3Root>");
pScene->mRootNode->mNumMeshes = pScene->mNumMeshes;
pScene->mRootNode->mMeshes = new unsigned int[pScene->mNumMeshes];
if (pcHeader->NUM_TAGS) {
pScene->mRootNode->mNumChildren = pcHeader->NUM_TAGS;
pScene->mRootNode->mChildren = new aiNode*[pcHeader->NUM_TAGS];
for (unsigned int i = 0; i < pcHeader->NUM_TAGS; ++i, ++pcTags) {
aiNode* nd = pScene->mRootNode->mChildren[i] = new aiNode();
nd->mName.Set((const char*)pcTags->NAME);
nd->mParent = pScene->mRootNode;
AI_SWAP4(pcTags->origin.x);
AI_SWAP4(pcTags->origin.y);
AI_SWAP4(pcTags->origin.z);
nd->mTransformation.a4 = pcTags->origin.x;
nd->mTransformation.b4 = pcTags->origin.y;
nd->mTransformation.c4 = pcTags->origin.z;
for (unsigned int a = 0; a < 3;++a) {
for (unsigned int m = 0; m < 3;++m) {
nd->mTransformation[m][a] = pcTags->orientation[a][m];
AI_SWAP4(nd->mTransformation[m][a]);
}
}
}
}
for (unsigned int i = 0; i < pScene->mNumMeshes;++i)
pScene->mRootNode->mMeshes[i] = i;
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);
}
#endif