1031 lines
36 KiB
C++
1031 lines
36 KiB
C++
/*
|
|
---------------------------------------------------------------------------
|
|
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 MD3Loader.cpp
|
|
* @brief Implementation of the MD3 importer class
|
|
*
|
|
* Sources:
|
|
* http://www.gamers.org/dEngine/quake3/UQ3S
|
|
* http://linux.ucla.edu/~phaethon/q3/formats/md3format.html
|
|
* http://www.heppler.com/shader/shader/
|
|
*/
|
|
|
|
#include "AssimpPCH.h"
|
|
#ifndef ASSIMP_BUILD_NO_MD3_IMPORTER
|
|
|
|
#include "MD3Loader.h"
|
|
#include "ByteSwap.h"
|
|
#include "SceneCombiner.h"
|
|
#include "GenericProperty.h"
|
|
#include "RemoveComments.h"
|
|
#include "ParsingUtils.h"
|
|
|
|
using namespace Assimp;
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Convert a Q3 shader blend function to the appropriate enum value
|
|
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;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Load a Quake 3 shader
|
|
bool Q3Shader::LoadShader(ShaderData& fill, const std::string& pFile,IOSystem* io)
|
|
{
|
|
boost::scoped_ptr<IOStream> file( io->Open( pFile, "rt"));
|
|
if (!file.get())
|
|
return false; // if we can't access the file, don't worry and return
|
|
|
|
DefaultLogger::get()->info("Loading Quake3 shader file " + pFile);
|
|
|
|
// read file in memory
|
|
const size_t s = file->FileSize();
|
|
std::vector<char> _buff(s+1);
|
|
file->Read(&_buff[0],s,1);
|
|
_buff[s] = 0;
|
|
|
|
// remove comments from it (C++ style)
|
|
CommentRemover::RemoveLineComments("//",&_buff[0]);
|
|
const char* buff = &_buff[0];
|
|
|
|
Q3Shader::ShaderDataBlock* curData = NULL;
|
|
Q3Shader::ShaderMapBlock* curMap = NULL;
|
|
|
|
// read line per line
|
|
for (;SkipSpacesAndLineEnd(&buff);SkipLine(&buff)) {
|
|
|
|
if (*buff == '{') {
|
|
++buff;
|
|
|
|
// append to last section, if any
|
|
if (!curData) {
|
|
DefaultLogger::get()->error("Q3Shader: Unexpected shader section token \'{\'");
|
|
return true; // still no failure, the file is there
|
|
}
|
|
|
|
// read this data section
|
|
for (;SkipSpacesAndLineEnd(&buff);SkipLine(&buff)) {
|
|
if (*buff == '{') {
|
|
++buff;
|
|
// add new map section
|
|
curData->maps.push_back(Q3Shader::ShaderMapBlock());
|
|
curMap = &curData->maps.back();
|
|
|
|
for (;SkipSpacesAndLineEnd(&buff);SkipLine(&buff)) {
|
|
// 'map' - Specifies texture file name
|
|
if (TokenMatchI(buff,"map",3) || TokenMatchI(buff,"clampmap",8)) {
|
|
curMap->name = GetNextToken(buff);
|
|
}
|
|
// 'blendfunc' - Alpha blending mode
|
|
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));
|
|
}
|
|
}
|
|
// 'alphafunc' - Alpha testing mode
|
|
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;
|
|
// close this map section
|
|
curMap = NULL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
else if (*buff == '}') {
|
|
++buff;
|
|
curData = NULL;
|
|
break;
|
|
}
|
|
|
|
// 'cull' specifies culling behaviour for the model
|
|
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 {
|
|
// add new section
|
|
fill.blocks.push_back(Q3Shader::ShaderDataBlock());
|
|
curData = &fill.blocks.back();
|
|
|
|
// get the name of this section
|
|
curData->name = GetNextToken(buff);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Load a Quake 3 skin
|
|
bool Q3Shader::LoadSkin(SkinData& fill, const std::string& pFile,IOSystem* io)
|
|
{
|
|
boost::scoped_ptr<IOStream> file( io->Open( pFile, "rt"));
|
|
if (!file.get())
|
|
return false; // if we can't access the file, don't worry and return
|
|
|
|
DefaultLogger::get()->info("Loading Quake3 skin file " + pFile);
|
|
|
|
// read file in memory
|
|
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;
|
|
|
|
// remove commas
|
|
std::replace(_buff.begin(),_buff.end(),',',' ');
|
|
|
|
// read token by token and fill output table
|
|
for (;*buff;) {
|
|
SkipSpacesAndLineEnd(&buff);
|
|
|
|
// get first identifier
|
|
std::string ss = GetNextToken(buff);
|
|
|
|
// ignore tokens starting with tag_
|
|
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;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Convert Q3Shader to material
|
|
void Q3Shader::ConvertShaderToMaterial(MaterialHelper* out, const ShaderDataBlock& shader)
|
|
{
|
|
ai_assert(NULL != out);
|
|
|
|
/* IMPORTANT: This is not a real conversion. Actually we're just guessing and
|
|
* hacking around to build an aiMaterial that looks nearly equal to the
|
|
* original Quake 3 shader. We're missing some important features like
|
|
* animatable material properties in our material system, but at least
|
|
* multiple textures should be handled correctly.
|
|
*/
|
|
|
|
// Two-sided material?
|
|
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;
|
|
|
|
// Iterate through all textures
|
|
for (std::list< Q3Shader::ShaderMapBlock >::const_iterator it = shader.maps.begin(); it != shader.maps.end();++it) {
|
|
|
|
// CONVERSION BEHAVIOUR:
|
|
//
|
|
//
|
|
// If the texture is additive
|
|
// - if it is the first texture, assume additive blending for the whole material
|
|
// - otherwise register it as emissive texture.
|
|
//
|
|
// If the texture is using standard blend (or if the blend mode is unknown)
|
|
// - if first texture: assume default blending for material
|
|
// - in any case: set it as diffuse texture
|
|
//
|
|
// If the texture is using 'filter' blending
|
|
// - take as lightmap
|
|
//
|
|
// Textures with alpha funcs
|
|
// - aiTextureFlags_UseAlpha is set (otherwise aiTextureFlags_NoAlpha is explicitly set)
|
|
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 && 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;
|
|
}
|
|
|
|
// setup texture
|
|
out->AddProperty(&s,AI_MATKEY_TEXTURE(type,index));
|
|
|
|
// setup texture flags
|
|
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 at least one emissive texture was set, set the emissive base color to 1 to ensure
|
|
// the texture is actually displayed.
|
|
if (0 != cur_emissive) {
|
|
aiColor3D one(1.f,1.f,1.f);
|
|
out->AddProperty(&one,1,AI_MATKEY_COLOR_EMISSIVE);
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Constructor to be privately used by Importer
|
|
MD3Importer::MD3Importer()
|
|
: configFrameID (0)
|
|
, configHandleMP (true)
|
|
{}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Destructor, private as well
|
|
MD3Importer::~MD3Importer()
|
|
{}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Returns whether the class can handle the format of the given file.
|
|
bool MD3Importer::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
|
|
{
|
|
const std::string extension = GetExtension(pFile);
|
|
if (extension == "md3")
|
|
return true;
|
|
|
|
// if check for extension is not enough, check for the magic tokens
|
|
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()
|
|
{
|
|
// Check magic number
|
|
if (pcHeader->IDENT != AI_MD3_MAGIC_NUMBER_BE &&
|
|
pcHeader->IDENT != AI_MD3_MAGIC_NUMBER_LE)
|
|
throw new ImportErrorException( "Invalid MD3 file: Magic bytes not found");
|
|
|
|
// Check file format version
|
|
if (pcHeader->VERSION > 15)
|
|
DefaultLogger::get()->warn( "Unsupported MD3 file version. Continuing happily ...");
|
|
|
|
// Check some offset values whether they are valid
|
|
if (!pcHeader->NUM_SURFACES)
|
|
throw new ImportErrorException( "Invalid md3 file: NUM_SURFACES is 0");
|
|
|
|
if (pcHeader->OFS_FRAMES >= fileSize || pcHeader->OFS_SURFACES >= fileSize ||
|
|
pcHeader->OFS_EOF > fileSize) {
|
|
throw new ImportErrorException("Invalid MD3 header: some offsets are outside the file");
|
|
}
|
|
|
|
if (pcHeader->NUM_FRAMES <= configFrameID )
|
|
throw new ImportErrorException("The requested frame is not existing the file");
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
void MD3Importer::ValidateSurfaceHeaderOffsets(const MD3::Surface* pcSurf)
|
|
{
|
|
// Calculate the relative offset of the surface
|
|
const int32_t ofs = int32_t((const unsigned char*)pcSurf-this->mBuffer);
|
|
|
|
// Check whether all data chunks are inside the valid range
|
|
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 new ImportErrorException("Invalid MD3 surface header: some offsets are outside the file");
|
|
}
|
|
|
|
// Check whether all requirements for Q3 files are met. We don't
|
|
// care, but probably someone does.
|
|
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");
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
void MD3Importer::GetExtensionList(std::string& append)
|
|
{
|
|
append.append("*.md3");
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Setup configuration properties
|
|
void MD3Importer::SetupProperties(const Importer* pImp)
|
|
{
|
|
// The
|
|
// AI_CONFIG_IMPORT_MD3_KEYFRAME option overrides the
|
|
// AI_CONFIG_IMPORT_GLOBAL_KEYFRAME option.
|
|
configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MD3_KEYFRAME,0xffffffff);
|
|
if(0xffffffff == configFrameID) {
|
|
configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_GLOBAL_KEYFRAME,0);
|
|
}
|
|
|
|
// AI_CONFIG_IMPORT_MD3_HANDLE_MULTIPART
|
|
configHandleMP = (0 != pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MD3_HANDLE_MULTIPART,1));
|
|
|
|
// AI_CONFIG_IMPORT_MD3_SKIN_NAME
|
|
configSkinFile = (pImp->GetPropertyString(AI_CONFIG_IMPORT_MD3_SKIN_NAME,"default"));
|
|
|
|
// AI_CONFIG_IMPORT_MD3_SHADER_SRC
|
|
configShaderFile = (pImp->GetPropertyString(AI_CONFIG_IMPORT_MD3_SHADER_SRC,""));
|
|
|
|
// AI_CONFIG_FAVOUR_SPEED
|
|
configSpeedFlag = (0 != pImp->GetPropertyInteger(AI_CONFIG_FAVOUR_SPEED,0));
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Try to read the skin for a MD3 file
|
|
void MD3Importer::ReadSkin(Q3Shader::SkinData& fill) const
|
|
{
|
|
// skip any postfixes (e.g. lower_1.md3)
|
|
std::string::size_type s = filename.find_last_of('_');
|
|
if (s == std::string::npos) {
|
|
s = filename.find_last_of('.');
|
|
}
|
|
ai_assert(s != std::string::npos);
|
|
|
|
const std::string skin_file = path + filename.substr(0,s) + "_" + configSkinFile + ".skin";
|
|
Q3Shader::LoadSkin(fill,skin_file,mIOHandler);
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Try to read the shader for a MD3 file
|
|
void MD3Importer::ReadShader(Q3Shader::ShaderData& fill) const
|
|
{
|
|
// Determine Q3 model name from given path
|
|
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 no specific dir or file is given, use our default search behaviour
|
|
if (!configShaderFile.length()) {
|
|
if(!Q3Shader::LoadShader(fill,path + "..\\..\\..\\scripts\\" + model_file + ".shader",mIOHandler)) {
|
|
Q3Shader::LoadShader(fill,path + "..\\..\\..\\scripts\\" + filename + ".shader",mIOHandler);
|
|
}
|
|
}
|
|
else {
|
|
// If the given string specifies a file, load this file.
|
|
// Otherwise it's a directory.
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Tiny helper to remove a single node from its parent' list
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Read a multi-part Q3 player model
|
|
bool MD3Importer::ReadMultipartFile()
|
|
{
|
|
// check whether the file name contains a common postfix, e.g lower_2.md3
|
|
std::string::size_type s = filename.find_last_of('_'), t = filename.find_last_of('.');
|
|
ai_assert(t != std::string::npos);
|
|
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");
|
|
|
|
// ensure we won't try to load ourselves recursively
|
|
BatchLoader::PropertyMap props;
|
|
SetGenericProperty( props.ints, AI_CONFIG_IMPORT_MD3_HANDLE_MULTIPART, 0, NULL);
|
|
|
|
// now read these three files
|
|
BatchLoader batch(mIOHandler);
|
|
unsigned int _lower = batch.AddLoadRequest(lower,0,&props);
|
|
unsigned int _upper = batch.AddLoadRequest(upper,0,&props);
|
|
unsigned int _head = batch.AddLoadRequest(head,0,&props);
|
|
batch.LoadAll();
|
|
|
|
// now construct a dummy scene to place these three parts in
|
|
aiScene* master = new aiScene();
|
|
aiNode* nd = master->mRootNode = new aiNode();
|
|
nd->mName.Set("<MD3_Player>");
|
|
|
|
// ... and get them. We need all of them.
|
|
scene_lower = batch.GetImport(_lower);
|
|
if (!scene_lower) {
|
|
DefaultLogger::get()->error("M3D: Failed to read multipart 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 multipart 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 multipart model, head.md3 fails to load");
|
|
failure = "head";
|
|
goto error_cleanup;
|
|
}
|
|
|
|
// build attachment infos. search for typical Q3 tags
|
|
|
|
// original root
|
|
scene_lower->mRootNode->mName.Set("lower");
|
|
attach.push_back(AttachmentInfo(scene_lower, nd));
|
|
|
|
// tag_torso
|
|
tag_torso = scene_lower->mRootNode->FindNode("tag_torso");
|
|
if (!tag_torso) {
|
|
DefaultLogger::get()->error("M3D: Failed to find attachment tag for multipart model: tag_torso expected");
|
|
goto error_cleanup;
|
|
}
|
|
scene_upper->mRootNode->mName.Set("upper");
|
|
attach.push_back(AttachmentInfo(scene_upper,tag_torso));
|
|
|
|
// tag_head
|
|
tag_head = scene_upper->mRootNode->FindNode("tag_head");
|
|
if (!tag_head) {
|
|
DefaultLogger::get()->error("M3D: Failed to find attachment tag for multipart model: tag_head expected");
|
|
goto error_cleanup;
|
|
}
|
|
scene_head->mRootNode->mName.Set("head");
|
|
attach.push_back(AttachmentInfo(scene_head,tag_head));
|
|
|
|
// Remove tag_head and tag_torso from all other model parts ...
|
|
// this ensures (together with AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY)
|
|
// that tag_torso/tag_head is also the name of the (unique) output node
|
|
RemoveSingleNodeFromList (scene_upper->mRootNode->FindNode("tag_torso"));
|
|
RemoveSingleNodeFromList (scene_head-> mRootNode->FindNode("tag_head" ));
|
|
|
|
// and merge the scenes
|
|
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));
|
|
|
|
return true;
|
|
|
|
error_cleanup:
|
|
delete scene_upper;
|
|
delete scene_lower;
|
|
delete scene_head;
|
|
delete master;
|
|
|
|
if (failure == mod_filename) {
|
|
throw new ImportErrorException("MD3: failure to read multipart host file");
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Convert a MD3 path to a proper value
|
|
void MD3Importer::ConvertPath(const char* texture_name, const char* header_name, std::string& out) const
|
|
{
|
|
// If the MD3's internal path itself and the given path are using
|
|
// the same directory, remove it completely to get right output paths.
|
|
const char* end1 = ::strrchr(header_name,'\\');
|
|
if (!end1)end1 = ::strrchr(header_name,'/');
|
|
|
|
const char* end2 = ::strrchr(texture_name,'\\');
|
|
if (!end2)end2 = ::strrchr(texture_name,'/');
|
|
|
|
// HACK: If the paths starts with "models", ignore the
|
|
// next two hierarchy levels, it specifies just the model name.
|
|
// Ignored by Q3, it might be not equal to the real model location.
|
|
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; // ignore the seventh - could be slash or backslash
|
|
|
|
if (!header_name[0]) {
|
|
// Use the file name only
|
|
out = end2+1;
|
|
return;
|
|
}
|
|
}
|
|
else len2 = std::min (len1, (size_t)(end2 - texture_name ));
|
|
if (!ASSIMP_strincmp(texture_name,header_name,len2)) {
|
|
// Use the file name only
|
|
out = end2+1;
|
|
return;
|
|
}
|
|
}
|
|
// Use the full path
|
|
out = texture_name;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// Imports the given file into the given scene structure.
|
|
void MD3Importer::InternReadFile( const std::string& pFile,
|
|
aiScene* pScene, IOSystem* pIOHandler)
|
|
{
|
|
mFile = pFile;
|
|
mScene = pScene;
|
|
mIOHandler = pIOHandler;
|
|
|
|
// get base path and file name
|
|
// todo ... move to PathConverter
|
|
std::string::size_type s = mFile.find_last_of('/');
|
|
if (s == std::string::npos) {
|
|
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);
|
|
|
|
// Load multi-part model file, if necessary
|
|
if (configHandleMP) {
|
|
if (ReadMultipartFile())
|
|
return;
|
|
}
|
|
|
|
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 MD3 file " + pFile + ".");
|
|
|
|
// Check whether the md3 file is large enough to contain the header
|
|
fileSize = (unsigned int)file->FileSize();
|
|
if( fileSize < sizeof(MD3::Header))
|
|
throw new ImportErrorException( "MD3 File is too small.");
|
|
|
|
// Allocate storage and copy the contents of the file to a memory buffer
|
|
std::vector<unsigned char> mBuffer2 (fileSize);
|
|
file->Read( &mBuffer2[0], 1, fileSize);
|
|
mBuffer = &mBuffer2[0];
|
|
|
|
pcHeader = (BE_NCONST MD3::Header*)mBuffer;
|
|
|
|
// Ensure correct endianess
|
|
#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
|
|
|
|
// Validate the file header
|
|
ValidateHeaderOffsets();
|
|
|
|
// Navigate to the list of surfaces
|
|
BE_NCONST MD3::Surface* pcSurfaces = (BE_NCONST MD3::Surface*)(mBuffer + pcHeader->OFS_SURFACES);
|
|
|
|
// Navigate to the list of tags
|
|
BE_NCONST MD3::Tag* pcTags = (BE_NCONST MD3::Tag*)(mBuffer + pcHeader->OFS_TAGS);
|
|
|
|
// Allocate output storage
|
|
pScene->mNumMeshes = pcHeader->NUM_SURFACES;
|
|
pScene->mMeshes = new aiMesh*[pScene->mNumMeshes];
|
|
|
|
pScene->mNumMaterials = pcHeader->NUM_SURFACES;
|
|
pScene->mMaterials = new aiMaterial*[pScene->mNumMeshes];
|
|
|
|
// Set arrays to zero to ensue proper destruction if an exception is raised
|
|
::memset(pScene->mMeshes,0,pScene->mNumMeshes*sizeof(aiMesh*));
|
|
::memset(pScene->mMaterials,0,pScene->mNumMaterials*sizeof(aiMaterial*));
|
|
|
|
// Now read possible skins from .skin file
|
|
Q3Shader::SkinData skins;
|
|
ReadSkin(skins);
|
|
|
|
// And check whether we can locate a shader file for this model
|
|
Q3Shader::ShaderData shaders;
|
|
ReadShader(shaders);
|
|
|
|
// Adjust all texture paths in the shader
|
|
const char* header_name = pcHeader->NAME;
|
|
if (shaders.blocks.size()) {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Read all surfaces from the file
|
|
unsigned int iNum = pcHeader->NUM_SURFACES;
|
|
unsigned int iNumMaterials = 0;
|
|
while (iNum-- > 0) {
|
|
|
|
// Ensure correct endianess
|
|
#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
|
|
|
|
// Validate the surface header
|
|
ValidateSurfaceHeaderOffsets(pcSurfaces);
|
|
|
|
// Navigate to the vertex list of the surface
|
|
BE_NCONST MD3::Vertex* pcVertices = (BE_NCONST MD3::Vertex*)
|
|
(((uint8_t*)pcSurfaces) + pcSurfaces->OFS_XYZNORMAL);
|
|
|
|
// Navigate to the triangle list of the surface
|
|
BE_NCONST MD3::Triangle* pcTriangles = (BE_NCONST MD3::Triangle*)
|
|
(((uint8_t*)pcSurfaces) + pcSurfaces->OFS_TRIANGLES);
|
|
|
|
// Navigate to the texture coordinate list of the surface
|
|
BE_NCONST MD3::TexCoord* pcUVs = (BE_NCONST MD3::TexCoord*)
|
|
(((uint8_t*)pcSurfaces) + pcSurfaces->OFS_ST);
|
|
|
|
// Navigate to the shader list of the surface
|
|
BE_NCONST MD3::Shader* pcShaders = (BE_NCONST MD3::Shader*)
|
|
(((uint8_t*)pcSurfaces) + pcSurfaces->OFS_SHADERS);
|
|
|
|
// If the submesh is empty ignore it
|
|
if (0 == pcSurfaces->NUM_VERTICES || 0 == pcSurfaces->NUM_TRIANGLES)
|
|
{
|
|
pcSurfaces = (BE_NCONST MD3::Surface*)(((uint8_t*)pcSurfaces) + pcSurfaces->OFS_END);
|
|
pScene->mNumMeshes--;
|
|
continue;
|
|
}
|
|
|
|
// Allocate output mesh
|
|
pScene->mMeshes[iNum] = new aiMesh();
|
|
aiMesh* pcMesh = pScene->mMeshes[iNum];
|
|
|
|
std::string _texture_name;
|
|
const char* texture_name = NULL;
|
|
|
|
// Check whether we have a texture record for this surface in the .skin file
|
|
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; // mark entry as resolved
|
|
}
|
|
|
|
// Get the first shader (= texture?) assigned to the surface
|
|
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;
|
|
|
|
// Now search the current shader for a record with this name (
|
|
// excluding texture file extension)
|
|
if (shaders.blocks.size()) {
|
|
|
|
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()) {
|
|
// Hurra, wir haben einen. Tolle Sache.
|
|
shader = &*dit;
|
|
DefaultLogger::get()->info("Found shader record for " +without_ext );
|
|
}
|
|
else DefaultLogger::get()->warn("Unable to find shader record for " +without_ext );
|
|
}
|
|
|
|
MaterialHelper* pcHelper = new MaterialHelper();
|
|
|
|
const int iMode = (int)aiShadingMode_Gouraud;
|
|
pcHelper->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL);
|
|
|
|
// Add a small ambient color value - Quake 3 seems to have one
|
|
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);
|
|
|
|
// use surface name + skin_name as material name
|
|
aiString name;
|
|
name.Set("MD3_[" + configSkinFile + "][" + pcSurfaces->NAME + "]");
|
|
pcHelper->AddProperty(&name,AI_MATKEY_NAME);
|
|
|
|
if (!shader) {
|
|
// Setup dummy texture file name to ensure UV coordinates are kept during postprocessing
|
|
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));
|
|
|
|
// prevent transparency by default
|
|
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++;
|
|
|
|
// Ensure correct endianess
|
|
#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
|
|
|
|
// Fill mesh information
|
|
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;
|
|
|
|
// Fill in all triangles
|
|
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;
|
|
|
|
unsigned int iTemp = iCurrent;
|
|
for (unsigned int c = 0; c < 3;++c,++iCurrent)
|
|
{
|
|
pcMesh->mFaces[i].mIndices[c] = iCurrent;
|
|
|
|
// Read vertices
|
|
pcMesh->mVertices[iCurrent].x = pcVertices[ pcTriangles->INDEXES[c]].X*AI_MD3_XYZ_SCALE;
|
|
pcMesh->mVertices[iCurrent].y = pcVertices[ pcTriangles->INDEXES[c]].Y*AI_MD3_XYZ_SCALE;
|
|
pcMesh->mVertices[iCurrent].z = pcVertices[ pcTriangles->INDEXES[c]].Z*AI_MD3_XYZ_SCALE;
|
|
|
|
// Convert the normal vector to uncompressed float3 format
|
|
LatLngNormalToVec3(pcVertices[pcTriangles->INDEXES[c]].NORMAL,
|
|
(float*)&pcMesh->mNormals[iCurrent]);
|
|
|
|
// Read texture coordinates
|
|
pcMesh->mTextureCoords[0][iCurrent].x = pcUVs[ pcTriangles->INDEXES[c]].U;
|
|
pcMesh->mTextureCoords[0][iCurrent].y = 1.0f-pcUVs[ pcTriangles->INDEXES[c]].V;
|
|
}
|
|
// Flip face order if necessary
|
|
if (!shader || shader->cull == Q3Shader::CULL_CCW) {
|
|
pcMesh->mFaces[i].mIndices[0] = iTemp+2;
|
|
pcMesh->mFaces[i].mIndices[1] = iTemp+1;
|
|
pcMesh->mFaces[i].mIndices[2] = iTemp+0;
|
|
}
|
|
pcTriangles++;
|
|
}
|
|
|
|
// Go to the next surface
|
|
pcSurfaces = (BE_NCONST MD3::Surface*)(((unsigned char*)pcSurfaces) + pcSurfaces->OFS_END);
|
|
}
|
|
|
|
// For debugging purposes: check whether we found matches for all entries in the skins file
|
|
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 new ImportErrorException( "MD3: File contains no valid mesh");
|
|
pScene->mNumMaterials = iNumMaterials;
|
|
|
|
// Now we need to generate an empty node graph
|
|
pScene->mRootNode = new aiNode("<MD3Root>");
|
|
pScene->mRootNode->mNumMeshes = pScene->mNumMeshes;
|
|
pScene->mRootNode->mMeshes = new unsigned int[pScene->mNumMeshes];
|
|
|
|
// Attach tiny children for all tags
|
|
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);
|
|
|
|
// Copy local origin
|
|
nd->mTransformation.a4 = pcTags->origin.x;
|
|
nd->mTransformation.b4 = pcTags->origin.y;
|
|
nd->mTransformation.c4 = pcTags->origin.z;
|
|
|
|
// Copy rest of transformation (need to transpose to match row-order matrix)
|
|
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;
|
|
}
|
|
|
|
#endif // !! ASSIMP_BUILD_NO_MD3_IMPORTER
|