MD3
- finished support for multi-part player models - skin files are now read - shaders are parsed, but not yet processed yet DefaultIOSystem - file size is now cached over multiple calls to FileSize() MaterialSystem - added AI_MATKEY_BLEND_FUNC property and the aiBlendMode enum to allow MD3 and Collada to pass transparency information correctly. git-svn-id: https://assimp.svn.sourceforge.net/svnroot/assimp/trunk@346 67173fc5-114c-0410-ac8e-9d2fd5bffc1fpull/1/head
parent
d70c092b71
commit
2b9461fbf7
|
@ -38,7 +38,9 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
---------------------------------------------------------------------------
|
---------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
/** @file Default File I/O implementation for #Importer */
|
/** @file DefaultIOStream.cpp
|
||||||
|
* @brief Default File I/O implementation for #Importer
|
||||||
|
*/
|
||||||
|
|
||||||
#include "AssimpPCH.h"
|
#include "AssimpPCH.h"
|
||||||
|
|
||||||
|
@ -80,11 +82,12 @@ size_t DefaultIOStream::Write(const void* pvBuffer,
|
||||||
aiReturn DefaultIOStream::Seek(size_t pOffset,
|
aiReturn DefaultIOStream::Seek(size_t pOffset,
|
||||||
aiOrigin pOrigin)
|
aiOrigin pOrigin)
|
||||||
{
|
{
|
||||||
if (!mFile)return AI_FAILURE;
|
if (!mFile)
|
||||||
|
return AI_FAILURE;
|
||||||
|
|
||||||
// Just to check whether our enum maps one to one with the CRT constants
|
// Just to check whether our enum maps one to one with the CRT constants
|
||||||
ai_assert(aiOrigin_CUR == SEEK_CUR && aiOrigin_END == SEEK_END
|
BOOST_STATIC_ASSERT(aiOrigin_CUR == SEEK_CUR &&
|
||||||
&& aiOrigin_SET == SEEK_SET);
|
aiOrigin_END == SEEK_END && aiOrigin_SET == SEEK_SET);
|
||||||
|
|
||||||
// do the seek
|
// do the seek
|
||||||
return (0 == ::fseek(mFile, (long)pOffset,(int)pOrigin) ? AI_SUCCESS : AI_FAILURE);
|
return (0 == ::fseek(mFile, (long)pOffset,(int)pOrigin) ? AI_SUCCESS : AI_FAILURE);
|
||||||
|
@ -102,25 +105,27 @@ size_t DefaultIOStream::Tell() const
|
||||||
// ----------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------
|
||||||
size_t DefaultIOStream::FileSize() const
|
size_t DefaultIOStream::FileSize() const
|
||||||
{
|
{
|
||||||
ai_assert (!mFilename.empty());
|
if (! mFile || mFilename.empty())
|
||||||
|
|
||||||
if (! mFile)
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
if (0xffffffff == cachedSize) {
|
||||||
|
|
||||||
// TODO: Is that really faster if we have already opened the file?
|
// TODO: Is that really faster if we're already owning a handle to the file?
|
||||||
#if defined _WIN32 && !defined __GNUC__
|
#if defined _WIN32 && !defined __GNUC__
|
||||||
struct __stat64 fileStat;
|
struct __stat64 fileStat;
|
||||||
int err = _stat64( mFilename.c_str(), &fileStat );
|
int err = _stat64( mFilename.c_str(), &fileStat );
|
||||||
if (0 != err)
|
if (0 != err)
|
||||||
return 0;
|
return 0;
|
||||||
return (size_t) (fileStat.st_size);
|
cachedSize = (size_t) (fileStat.st_size);
|
||||||
#else
|
#else
|
||||||
struct stat fileStat;
|
struct stat fileStat;
|
||||||
int err = stat(mFilename.c_str(), &fileStat );
|
int err = stat(mFilename.c_str(), &fileStat );
|
||||||
if (0 != err)
|
if (0 != err)
|
||||||
return 0;
|
return 0;
|
||||||
return (size_t) (fileStat.st_size);
|
cachedSize = (size_t) (fileStat.st_size);
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
|
return cachedSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------
|
||||||
|
|
|
@ -50,6 +50,9 @@ namespace Assimp {
|
||||||
// ----------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------
|
||||||
//! @class DefaultIOStream
|
//! @class DefaultIOStream
|
||||||
//! @brief Default IO implementation, use standard IO operations
|
//! @brief Default IO implementation, use standard IO operations
|
||||||
|
//! @note An instance of this class can exist without a valid file handle
|
||||||
|
//! attached to it. All calls fail, but the instance can nevertheless be
|
||||||
|
//! used with no restrictions.
|
||||||
class DefaultIOStream : public IOStream
|
class DefaultIOStream : public IOStream
|
||||||
{
|
{
|
||||||
friend class DefaultIOSystem;
|
friend class DefaultIOSystem;
|
||||||
|
@ -63,28 +66,33 @@ public:
|
||||||
~DefaultIOStream ();
|
~DefaultIOStream ();
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
// Read from stream
|
||||||
size_t Read(void* pvBuffer,
|
size_t Read(void* pvBuffer,
|
||||||
size_t pSize,
|
size_t pSize,
|
||||||
size_t pCount);
|
size_t pCount);
|
||||||
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
// Write to stream
|
||||||
size_t Write(const void* pvBuffer,
|
size_t Write(const void* pvBuffer,
|
||||||
size_t pSize,
|
size_t pSize,
|
||||||
size_t pCount);
|
size_t pCount);
|
||||||
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
// Seek specific position
|
||||||
aiReturn Seek(size_t pOffset,
|
aiReturn Seek(size_t pOffset,
|
||||||
aiOrigin pOrigin);
|
aiOrigin pOrigin);
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
// Get current seek position
|
||||||
size_t Tell() const;
|
size_t Tell() const;
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
// Get size of file
|
||||||
size_t FileSize() const;
|
size_t FileSize() const;
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
// Flush file contents
|
||||||
void Flush();
|
void Flush();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -92,13 +100,17 @@ private:
|
||||||
FILE* mFile;
|
FILE* mFile;
|
||||||
//! Filename
|
//! Filename
|
||||||
std::string mFilename;
|
std::string mFilename;
|
||||||
|
|
||||||
|
//! Cached file size
|
||||||
|
mutable size_t cachedSize;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------
|
||||||
inline DefaultIOStream::DefaultIOStream () :
|
inline DefaultIOStream::DefaultIOStream () :
|
||||||
mFile(NULL),
|
mFile (NULL),
|
||||||
mFilename("")
|
mFilename (""),
|
||||||
|
cachedSize (0xffffffff)
|
||||||
{
|
{
|
||||||
// empty
|
// empty
|
||||||
}
|
}
|
||||||
|
@ -108,7 +120,8 @@ inline DefaultIOStream::DefaultIOStream () :
|
||||||
inline DefaultIOStream::DefaultIOStream (FILE* pFile,
|
inline DefaultIOStream::DefaultIOStream (FILE* pFile,
|
||||||
const std::string &strFilename) :
|
const std::string &strFilename) :
|
||||||
mFile(pFile),
|
mFile(pFile),
|
||||||
mFilename(strFilename)
|
mFilename(strFilename),
|
||||||
|
cachedSize (0xffffffff)
|
||||||
{
|
{
|
||||||
// empty
|
// empty
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,9 +38,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @file Defines the helper data structures for importing MD3 files.
|
/** @file Md3FileData.h
|
||||||
http://linux.ucla.edu/~phaethon/q3/formats/md3format.html
|
*
|
||||||
*/
|
* @brief Defines helper data structures for importing MD3 files.
|
||||||
|
* http://linux.ucla.edu/~phaethon/q3/formats/md3format.html
|
||||||
|
*/
|
||||||
#ifndef AI_MD3FILEHELPER_H_INC
|
#ifndef AI_MD3FILEHELPER_H_INC
|
||||||
#define AI_MD3FILEHELPER_H_INC
|
#define AI_MD3FILEHELPER_H_INC
|
||||||
|
|
||||||
|
@ -77,10 +79,9 @@ namespace MD3 {
|
||||||
// master scale factor for all vertices in a MD3 model
|
// master scale factor for all vertices in a MD3 model
|
||||||
#define AI_MD3_XYZ_SCALE (1.0f/64.0f)
|
#define AI_MD3_XYZ_SCALE (1.0f/64.0f)
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------
|
||||||
/** \brief Data structure for the MD3 main header
|
/** @brief Data structure for the MD3 main header
|
||||||
*/
|
*/
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
struct Header
|
struct Header
|
||||||
{
|
{
|
||||||
//! magic number
|
//! magic number
|
||||||
|
@ -90,7 +91,7 @@ struct Header
|
||||||
uint32_t VERSION;
|
uint32_t VERSION;
|
||||||
|
|
||||||
//! original name in .pak archive
|
//! original name in .pak archive
|
||||||
unsigned char NAME[ AI_MD3_MAXQPATH ];
|
char NAME[ AI_MD3_MAXQPATH ];
|
||||||
|
|
||||||
//! unknown
|
//! unknown
|
||||||
int32_t FLAGS;
|
int32_t FLAGS;
|
||||||
|
@ -121,10 +122,9 @@ struct Header
|
||||||
} PACK_STRUCT;
|
} PACK_STRUCT;
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------
|
||||||
/** \brief Data structure for the frame header
|
/** @brief Data structure for the frame header
|
||||||
*/
|
*/
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
struct Frame
|
struct Frame
|
||||||
{
|
{
|
||||||
//! minimum bounds
|
//! minimum bounds
|
||||||
|
@ -145,14 +145,13 @@ struct Frame
|
||||||
} PACK_STRUCT;
|
} PACK_STRUCT;
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------
|
||||||
/** \brief Data structure for the tag header
|
/** @brief Data structure for the tag header
|
||||||
*/
|
*/
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
struct Tag
|
struct Tag
|
||||||
{
|
{
|
||||||
//! name of the tag
|
//! name of the tag
|
||||||
unsigned char NAME[ AI_MD3_MAXQPATH ];
|
char NAME[ AI_MD3_MAXQPATH ];
|
||||||
|
|
||||||
//! Local tag origin and orientation
|
//! Local tag origin and orientation
|
||||||
aiVector3D origin;
|
aiVector3D origin;
|
||||||
|
@ -161,17 +160,16 @@ struct Tag
|
||||||
} PACK_STRUCT;
|
} PACK_STRUCT;
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------
|
||||||
/** \brief Data structure for the surface header
|
/** @brief Data structure for the surface header
|
||||||
*/
|
*/
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
struct Surface
|
struct Surface
|
||||||
{
|
{
|
||||||
//! magic number
|
//! magic number
|
||||||
int32_t IDENT;
|
int32_t IDENT;
|
||||||
|
|
||||||
//! original name of the surface
|
//! original name of the surface
|
||||||
unsigned char NAME[ AI_MD3_MAXQPATH ];
|
char NAME[ AI_MD3_MAXQPATH ];
|
||||||
|
|
||||||
//! unknown
|
//! unknown
|
||||||
int32_t FLAGS;
|
int32_t FLAGS;
|
||||||
|
@ -205,24 +203,22 @@ struct Surface
|
||||||
int32_t OFS_END;
|
int32_t OFS_END;
|
||||||
} PACK_STRUCT;
|
} PACK_STRUCT;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------
|
||||||
/** \brief Data structure for a shader
|
/** @brief Data structure for a shader defined in there
|
||||||
*/
|
*/
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
struct Shader
|
struct Shader
|
||||||
{
|
{
|
||||||
//! filename of the shader
|
//! filename of the shader
|
||||||
unsigned char NAME[ AI_MD3_MAXQPATH ];
|
char NAME[ AI_MD3_MAXQPATH ];
|
||||||
|
|
||||||
//! index of the shader
|
//! index of the shader
|
||||||
uint32_t SHADER_INDEX;
|
uint32_t SHADER_INDEX;
|
||||||
} PACK_STRUCT;
|
} PACK_STRUCT;
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------
|
||||||
/** \brief Data structure for a triangle
|
/** @brief Data structure for a triangle
|
||||||
*/
|
*/
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
struct Triangle
|
struct Triangle
|
||||||
{
|
{
|
||||||
//! triangle indices
|
//! triangle indices
|
||||||
|
@ -230,10 +226,9 @@ struct Triangle
|
||||||
} PACK_STRUCT;
|
} PACK_STRUCT;
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------
|
||||||
/** \brief Data structure for an UV coord
|
/** @brief Data structure for an UV coord
|
||||||
*/
|
*/
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
struct TexCoord
|
struct TexCoord
|
||||||
{
|
{
|
||||||
//! UV coordinates
|
//! UV coordinates
|
||||||
|
@ -241,10 +236,9 @@ struct TexCoord
|
||||||
} PACK_STRUCT;
|
} PACK_STRUCT;
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------
|
||||||
/** \brief Data structure for a vertex
|
/** @brief Data structure for a vertex
|
||||||
*/
|
*/
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
struct Vertex
|
struct Vertex
|
||||||
{
|
{
|
||||||
//! X/Y/Z coordinates
|
//! X/Y/Z coordinates
|
||||||
|
@ -256,15 +250,14 @@ struct Vertex
|
||||||
|
|
||||||
#include "./../include/Compiler/poppack1.h"
|
#include "./../include/Compiler/poppack1.h"
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------
|
||||||
/** \brief Unpack a Q3 16 bit vector to its full float3 representation
|
/** @brief Unpack a Q3 16 bit vector to its full float3 representation
|
||||||
*
|
*
|
||||||
* \param p_iNormal Input normal vector in latitude/longitude form
|
* @param p_iNormal Input normal vector in latitude/longitude form
|
||||||
* \param p_afOut Pointer to an array of three floats to receive the result
|
* @param p_afOut Pointer to an array of three floats to receive the result
|
||||||
*
|
*
|
||||||
* \note This has been taken from q3 source (misc_model.c)
|
* @note This has been taken from q3 source (misc_model.c)
|
||||||
*/
|
*/
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
inline void LatLngNormalToVec3(uint16_t p_iNormal, float* p_afOut)
|
inline void LatLngNormalToVec3(uint16_t p_iNormal, float* p_afOut)
|
||||||
{
|
{
|
||||||
float lat = (float)(( p_iNormal >> 8u ) & 0xff);
|
float lat = (float)(( p_iNormal >> 8u ) & 0xff);
|
||||||
|
@ -279,14 +272,13 @@ inline void LatLngNormalToVec3(uint16_t p_iNormal, float* p_afOut)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------
|
||||||
/** \brief Pack a Q3 normal into 16bit latitute/longitude representation
|
/** @brief Pack a Q3 normal into 16bit latitute/longitude representation
|
||||||
* \param p_vIn Input vector
|
* @param p_vIn Input vector
|
||||||
* \param p_iOut Output normal
|
* @param p_iOut Output normal
|
||||||
*
|
*
|
||||||
* \note This has been taken from q3 source (mathlib.c)
|
* @note This has been taken from q3 source (mathlib.c)
|
||||||
*/
|
*/
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
inline void Vec3NormalToLatLng( const aiVector3D& p_vIn, uint16_t& p_iOut )
|
inline void Vec3NormalToLatLng( const aiVector3D& p_vIn, uint16_t& p_iOut )
|
||||||
{
|
{
|
||||||
// check for singularities
|
// check for singularities
|
||||||
|
|
|
@ -39,20 +39,155 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
---------------------------------------------------------------------------
|
---------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @file Implementation of the MD3 importer class */
|
/** @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"
|
#include "AssimpPCH.h"
|
||||||
#ifndef ASSIMP_BUILD_NO_MD3_IMPORTER
|
#ifndef ASSIMP_BUILD_NO_MD3_IMPORTER
|
||||||
|
|
||||||
#include "MD3Loader.h"
|
#include "MD3Loader.h"
|
||||||
#include "MaterialSystem.h"
|
|
||||||
#include "StringComparison.h"
|
|
||||||
#include "ByteSwap.h"
|
#include "ByteSwap.h"
|
||||||
#include "SceneCombiner.h"
|
#include "SceneCombiner.h"
|
||||||
#include "GenericProperty.h"
|
#include "GenericProperty.h"
|
||||||
|
#include "RemoveComments.h"
|
||||||
|
#include "ParsingUtils.h"
|
||||||
|
|
||||||
using namespace Assimp;
|
using namespace Assimp;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
// Load a Quake 3 shader
|
||||||
|
void Q3Shader::LoadShader(ShaderData& fill, const std::string& pFile,IOSystem* io)
|
||||||
|
{
|
||||||
|
boost::scoped_ptr<IOStream> file( io->Open( pFile, "rt"));
|
||||||
|
if (!file.get())
|
||||||
|
return; // 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 (;;SkipLine(&buff)) {
|
||||||
|
|
||||||
|
if(!SkipSpacesAndLineEnd(&buff))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (*buff == '{') {
|
||||||
|
// append to last section, if any
|
||||||
|
if (!curData) {
|
||||||
|
DefaultLogger::get()->error("Q3Shader: Unexpected shader section token \'{\'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read this map section
|
||||||
|
for (;;SkipLine(&buff)) {
|
||||||
|
if(!SkipSpacesAndLineEnd(&buff))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (*buff == '{') {
|
||||||
|
// add new map section
|
||||||
|
curData->maps.push_back(Q3Shader::ShaderMapBlock());
|
||||||
|
curMap = &curData->maps.back();
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (*buff == '}') {
|
||||||
|
// close this map section
|
||||||
|
if (curMap)
|
||||||
|
curMap = NULL;
|
||||||
|
else {
|
||||||
|
curData = NULL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 'map' - Specifies texture file name
|
||||||
|
else if (TokenMatchI(buff,"map",3) || TokenMatchI(buff,"clampmap",8)) {
|
||||||
|
curMap->name = GetNextToken(buff);
|
||||||
|
}
|
||||||
|
// 'blendfunc' - Alpha blending mode
|
||||||
|
else if (TokenMatchI(buff,"blendfunc",9)) {
|
||||||
|
// fixme
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 'cull' specifies culling behaviour for the model
|
||||||
|
else if (TokenMatch(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 curData->cull = Q3Shader::CULL_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
// add new section
|
||||||
|
fill.blocks.push_back(Q3Shader::ShaderDataBlock());
|
||||||
|
curData = &fill.blocks.back();
|
||||||
|
|
||||||
|
// get the name of this section
|
||||||
|
curData->name = GetNextToken(buff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
// Load a Quake 3 skin
|
||||||
|
void Q3Shader::LoadSkin(SkinData& fill, const std::string& pFile,IOSystem* io)
|
||||||
|
{
|
||||||
|
boost::scoped_ptr<IOStream> file( io->Open( pFile, "rt"));
|
||||||
|
if (!file.get())
|
||||||
|
return; // 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
// Constructor to be privately used by Importer
|
// Constructor to be privately used by Importer
|
||||||
MD3Importer::MD3Importer()
|
MD3Importer::MD3Importer()
|
||||||
|
@ -84,16 +219,16 @@ bool MD3Importer::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void MD3Importer::ValidateHeaderOffsets()
|
void MD3Importer::ValidateHeaderOffsets()
|
||||||
{
|
{
|
||||||
// check magic number
|
// Check magic number
|
||||||
if (pcHeader->IDENT != AI_MD3_MAGIC_NUMBER_BE &&
|
if (pcHeader->IDENT != AI_MD3_MAGIC_NUMBER_BE &&
|
||||||
pcHeader->IDENT != AI_MD3_MAGIC_NUMBER_LE)
|
pcHeader->IDENT != AI_MD3_MAGIC_NUMBER_LE)
|
||||||
throw new ImportErrorException( "Invalid MD3 file: Magic bytes not found");
|
throw new ImportErrorException( "Invalid MD3 file: Magic bytes not found");
|
||||||
|
|
||||||
// check file format version
|
// Check file format version
|
||||||
if (pcHeader->VERSION > 15)
|
if (pcHeader->VERSION > 15)
|
||||||
DefaultLogger::get()->warn( "Unsupported MD3 file version. Continuing happily ...");
|
DefaultLogger::get()->warn( "Unsupported MD3 file version. Continuing happily ...");
|
||||||
|
|
||||||
// check some values whether they are valid
|
// Check some offset values whether they are valid
|
||||||
if (!pcHeader->NUM_SURFACES)
|
if (!pcHeader->NUM_SURFACES)
|
||||||
throw new ImportErrorException( "Invalid md3 file: NUM_SURFACES is 0");
|
throw new ImportErrorException( "Invalid md3 file: NUM_SURFACES is 0");
|
||||||
|
|
||||||
|
@ -109,24 +244,28 @@ void MD3Importer::ValidateHeaderOffsets()
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void MD3Importer::ValidateSurfaceHeaderOffsets(const MD3::Surface* pcSurf)
|
void MD3Importer::ValidateSurfaceHeaderOffsets(const MD3::Surface* pcSurf)
|
||||||
{
|
{
|
||||||
// calculate the relative offset of the surface
|
// Calculate the relative offset of the surface
|
||||||
const int32_t ofs = int32_t((const unsigned char*)pcSurf-this->mBuffer);
|
const int32_t ofs = int32_t((const unsigned char*)pcSurf-this->mBuffer);
|
||||||
|
|
||||||
if (pcSurf->OFS_TRIANGLES + ofs + pcSurf->NUM_TRIANGLES * sizeof(MD3::Triangle) > fileSize ||
|
// Check whether all data chunks are inside the valid range
|
||||||
pcSurf->OFS_SHADERS + ofs + pcSurf->NUM_SHADER * sizeof(MD3::Shader) > fileSize ||
|
if (pcSurf->OFS_TRIANGLES + ofs + pcSurf->NUM_TRIANGLES * sizeof(MD3::Triangle) > fileSize ||
|
||||||
pcSurf->OFS_ST + ofs + pcSurf->NUM_VERTICES * sizeof(MD3::TexCoord) > fileSize ||
|
pcSurf->OFS_SHADERS + ofs + pcSurf->NUM_SHADER * sizeof(MD3::Shader) > fileSize ||
|
||||||
pcSurf->OFS_XYZNORMAL + ofs + pcSurf->NUM_VERTICES * sizeof(MD3::Vertex) > 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");
|
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)
|
if (pcSurf->NUM_TRIANGLES > AI_MD3_MAX_TRIANGLES)
|
||||||
DefaultLogger::get()->warn("The model contains more triangles than Quake 3 supports");
|
DefaultLogger::get()->warn("MD3: Quake III triangle limit exceeded");
|
||||||
if (pcSurf->NUM_SHADER > AI_MD3_MAX_SHADERS)
|
if (pcSurf->NUM_SHADER > AI_MD3_MAX_SHADERS)
|
||||||
DefaultLogger::get()->warn("The model contains more shaders than Quake 3 supports");
|
DefaultLogger::get()->warn("MD3: Quake III shader limit exceeded");
|
||||||
if (pcSurf->NUM_VERTICES > AI_MD3_MAX_VERTS)
|
if (pcSurf->NUM_VERTICES > AI_MD3_MAX_VERTS)
|
||||||
DefaultLogger::get()->warn("The model contains more vertices than Quake 3 supports");
|
DefaultLogger::get()->warn("MD3: Quake III vertex limit exceeded");
|
||||||
if (pcSurf->NUM_FRAMES > AI_MD3_MAX_FRAMES)
|
if (pcSurf->NUM_FRAMES > AI_MD3_MAX_FRAMES)
|
||||||
DefaultLogger::get()->warn("The model contains more frames than Quake 3 supports");
|
DefaultLogger::get()->warn("MD3: Quake III frame limit exceeded");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
@ -149,28 +288,53 @@ void MD3Importer::SetupProperties(const Importer* pImp)
|
||||||
|
|
||||||
// AI_CONFIG_IMPORT_MD3_HANDLE_MULTIPART
|
// AI_CONFIG_IMPORT_MD3_HANDLE_MULTIPART
|
||||||
configHandleMP = (0 != pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MD3_HANDLE_MULTIPART,1));
|
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"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
// Try to read the skin for a MD3 file
|
||||||
|
void MD3Importer::ReadSkin(Q3Shader::SkinData& fill)
|
||||||
|
{
|
||||||
|
// 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
// Read a multi-part Q3 player model
|
// Read a multi-part Q3 player model
|
||||||
bool MD3Importer::ReadMultipartFile()
|
bool MD3Importer::ReadMultipartFile()
|
||||||
{
|
{
|
||||||
std::string::size_type s = mFile.find_last_of('/');
|
// check whether the file name contains a common postfix, e.g lower_2.md3
|
||||||
if (s == std::string::npos) {
|
std::string::size_type s = filename.find_last_of('_'), t = filename.find_last_of('.');
|
||||||
s = mFile.find_last_of('\\');
|
ai_assert(t != std::string::npos);
|
||||||
}
|
if (s == std::string::npos)
|
||||||
if (s == std::string::npos) {
|
s = t;
|
||||||
s = 0;
|
|
||||||
}
|
|
||||||
else ++s;
|
|
||||||
std::string filename = mFile.substr(s), path = mFile.substr(0,s);
|
|
||||||
for( std::string::iterator it = filename .begin(); it != filename.end(); ++it)
|
|
||||||
*it = tolower( *it);
|
|
||||||
|
|
||||||
if (filename == "lower.md3" || filename == "upper.md3" || filename == "head.md3"){
|
const std::string mod_filename = filename.substr(0,s);
|
||||||
std::string lower = path + "lower.md3";
|
const std::string suffix = filename.substr(s,t-s);
|
||||||
std::string upper = path + "upper.md3";
|
|
||||||
std::string head = path + "head.md3";
|
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
|
// ensure we won't try to load ourselves recursively
|
||||||
BatchLoader::PropertyMap props;
|
BatchLoader::PropertyMap props;
|
||||||
|
@ -189,35 +353,45 @@ bool MD3Importer::ReadMultipartFile()
|
||||||
nd->mName.Set("<M3D_Player>");
|
nd->mName.Set("<M3D_Player>");
|
||||||
|
|
||||||
// ... and get them. We need all of them.
|
// ... and get them. We need all of them.
|
||||||
aiScene* scene_lower = batch.GetImport(lower);
|
scene_lower = batch.GetImport(lower);
|
||||||
if (!scene_lower)
|
if (!scene_lower) {
|
||||||
throw new ImportErrorException("M3D: Failed to read multipart model, lower.md3 fails to load");
|
DefaultLogger::get()->error("M3D: Failed to read multipart model, lower.md3 fails to load");
|
||||||
|
failure = "lower";
|
||||||
|
goto error_cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
aiScene* scene_upper = batch.GetImport(upper);
|
scene_upper = batch.GetImport(upper);
|
||||||
if (!scene_upper)
|
if (!scene_upper) {
|
||||||
throw new ImportErrorException("M3D: Failed to read multipart model, upper.md3 fails to load");
|
DefaultLogger::get()->error("M3D: Failed to read multipart model, upper.md3 fails to load");
|
||||||
|
failure = "upper";
|
||||||
|
goto error_cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
aiScene* scene_head = batch.GetImport(head);
|
scene_head = batch.GetImport(head);
|
||||||
if (!scene_head)
|
if (!scene_head) {
|
||||||
throw new ImportErrorException("M3D: Failed to read multipart model, head.md3 fails to load");
|
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
|
// build attachment infos. search for typical Q3 tags
|
||||||
std::vector<AttachmentInfo> attach;
|
|
||||||
|
|
||||||
// original root
|
// original root
|
||||||
attach.push_back(AttachmentInfo(scene_lower, nd));
|
attach.push_back(AttachmentInfo(scene_lower, nd));
|
||||||
|
|
||||||
// tag_torso
|
// tag_torso
|
||||||
aiNode* tag_torso = scene_lower->mRootNode->FindNode("tag_torso");
|
tag_torso = scene_lower->mRootNode->FindNode("tag_torso");
|
||||||
if (!tag_torso) {
|
if (!tag_torso) {
|
||||||
throw new ImportErrorException("M3D: Unable to find attachment tag: tag_torso expected");
|
DefaultLogger::get()->error("M3D: Failed to find attachment tag for multipart model: tag_torso expected");
|
||||||
|
goto error_cleanup;
|
||||||
}
|
}
|
||||||
attach.push_back(AttachmentInfo(scene_upper,tag_torso));
|
attach.push_back(AttachmentInfo(scene_upper,tag_torso));
|
||||||
|
|
||||||
// tag_head
|
// tag_head
|
||||||
aiNode* tag_head = scene_upper->mRootNode->FindNode("tag_head");
|
tag_head = scene_upper->mRootNode->FindNode("tag_head");
|
||||||
if (!tag_head) {
|
if (!tag_head) {
|
||||||
throw new ImportErrorException("M3D: Unable to find attachment tag: tag_head expected");
|
DefaultLogger::get()->error("M3D: Failed to find attachment tag for multipart model: tag_head expected");
|
||||||
|
goto error_cleanup;
|
||||||
}
|
}
|
||||||
attach.push_back(AttachmentInfo(scene_head,tag_head));
|
attach.push_back(AttachmentInfo(scene_head,tag_head));
|
||||||
|
|
||||||
|
@ -228,6 +402,16 @@ bool MD3Importer::ReadMultipartFile()
|
||||||
AI_INT_MERGE_SCENE_RESOLVE_CROSS_ATTACHMENTS);
|
AI_INT_MERGE_SCENE_RESOLVE_CROSS_ATTACHMENTS);
|
||||||
|
|
||||||
return true;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -241,6 +425,20 @@ void MD3Importer::InternReadFile( const std::string& pFile,
|
||||||
mScene = pScene;
|
mScene = pScene;
|
||||||
mIOHandler = pIOHandler;
|
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
|
// Load multi-part model file, if necessary
|
||||||
if (configHandleMP) {
|
if (configHandleMP) {
|
||||||
if (ReadMultipartFile())
|
if (ReadMultipartFile())
|
||||||
|
@ -253,19 +451,19 @@ void MD3Importer::InternReadFile( const std::string& pFile,
|
||||||
if( file.get() == NULL)
|
if( file.get() == NULL)
|
||||||
throw new ImportErrorException( "Failed to open MD3 file " + pFile + ".");
|
throw new ImportErrorException( "Failed to open MD3 file " + pFile + ".");
|
||||||
|
|
||||||
// check whether the md3 file is large enough to contain
|
// Check whether the md3 file is large enough to contain the header
|
||||||
// at least the file header
|
|
||||||
fileSize = (unsigned int)file->FileSize();
|
fileSize = (unsigned int)file->FileSize();
|
||||||
if( fileSize < sizeof(MD3::Header))
|
if( fileSize < sizeof(MD3::Header))
|
||||||
throw new ImportErrorException( "MD3 File is too small.");
|
throw new ImportErrorException( "MD3 File is too small.");
|
||||||
|
|
||||||
// allocate storage and copy the contents of the file to a memory buffer
|
// Allocate storage and copy the contents of the file to a memory buffer
|
||||||
std::vector<unsigned char> mBuffer2 (fileSize);
|
std::vector<unsigned char> mBuffer2 (fileSize);
|
||||||
file->Read( &mBuffer2[0], 1, fileSize);
|
file->Read( &mBuffer2[0], 1, fileSize);
|
||||||
mBuffer = &mBuffer2[0];
|
mBuffer = &mBuffer2[0];
|
||||||
|
|
||||||
pcHeader = (BE_NCONST MD3::Header*)mBuffer;
|
pcHeader = (BE_NCONST MD3::Header*)mBuffer;
|
||||||
|
|
||||||
|
// Ensure correct endianess
|
||||||
#ifdef AI_BUILD_BIG_ENDIAN
|
#ifdef AI_BUILD_BIG_ENDIAN
|
||||||
|
|
||||||
AI_SWAP4(pcHeader->VERSION);
|
AI_SWAP4(pcHeader->VERSION);
|
||||||
|
@ -282,33 +480,38 @@ void MD3Importer::InternReadFile( const std::string& pFile,
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// validate the header
|
// Validate the file header
|
||||||
ValidateHeaderOffsets();
|
ValidateHeaderOffsets();
|
||||||
|
|
||||||
// navigate to the list of surfaces
|
// Navigate to the list of surfaces
|
||||||
BE_NCONST MD3::Surface* pcSurfaces = (BE_NCONST MD3::Surface*)(mBuffer + pcHeader->OFS_SURFACES);
|
BE_NCONST MD3::Surface* pcSurfaces = (BE_NCONST MD3::Surface*)(mBuffer + pcHeader->OFS_SURFACES);
|
||||||
|
|
||||||
// navigate to the list of tags
|
// Navigate to the list of tags
|
||||||
BE_NCONST MD3::Tag* pcTags = (BE_NCONST MD3::Tag*)(mBuffer + pcHeader->OFS_TAGS);
|
BE_NCONST MD3::Tag* pcTags = (BE_NCONST MD3::Tag*)(mBuffer + pcHeader->OFS_TAGS);
|
||||||
|
|
||||||
// allocate output storage
|
// Allocate output storage
|
||||||
pScene->mNumMeshes = pcHeader->NUM_SURFACES;
|
pScene->mNumMeshes = pcHeader->NUM_SURFACES;
|
||||||
pScene->mMeshes = new aiMesh*[pScene->mNumMeshes];
|
pScene->mMeshes = new aiMesh*[pScene->mNumMeshes];
|
||||||
|
|
||||||
pScene->mNumMaterials = pcHeader->NUM_SURFACES;
|
pScene->mNumMaterials = pcHeader->NUM_SURFACES;
|
||||||
pScene->mMaterials = new aiMaterial*[pScene->mNumMeshes];
|
pScene->mMaterials = new aiMaterial*[pScene->mNumMeshes];
|
||||||
|
|
||||||
// if an exception is thrown before the meshes are allocated ->
|
// Set arrays to zero to ensue proper destruction if an exception is raised
|
||||||
// otherwise the pointer value would be invalid and delete would crash
|
|
||||||
::memset(pScene->mMeshes,0,pScene->mNumMeshes*sizeof(aiMesh*));
|
::memset(pScene->mMeshes,0,pScene->mNumMeshes*sizeof(aiMesh*));
|
||||||
::memset(pScene->mMaterials,0,pScene->mNumMaterials*sizeof(aiMaterial*));
|
::memset(pScene->mMaterials,0,pScene->mNumMaterials*sizeof(aiMaterial*));
|
||||||
|
|
||||||
|
// Now read possible skins from .skin file
|
||||||
|
Q3Shader::SkinData skins;
|
||||||
|
ReadSkin(skins);
|
||||||
|
|
||||||
|
// Read all surfaces from the file
|
||||||
unsigned int iNum = pcHeader->NUM_SURFACES;
|
unsigned int iNum = pcHeader->NUM_SURFACES;
|
||||||
unsigned int iNumMaterials = 0;
|
unsigned int iNumMaterials = 0;
|
||||||
unsigned int iDefaultMatIndex = 0xFFFFFFFF;
|
unsigned int iDefaultMatIndex = 0xFFFFFFFF;
|
||||||
while (iNum-- > 0)
|
while (iNum-- > 0)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// Ensure correct endianess
|
||||||
#ifdef AI_BUILD_BIG_ENDIAN
|
#ifdef AI_BUILD_BIG_ENDIAN
|
||||||
|
|
||||||
AI_SWAP4(pcSurfaces->FLAGS);
|
AI_SWAP4(pcSurfaces->FLAGS);
|
||||||
|
@ -325,26 +528,26 @@ void MD3Importer::InternReadFile( const std::string& pFile,
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// validate the surface
|
// Validate the surface header
|
||||||
ValidateSurfaceHeaderOffsets(pcSurfaces);
|
ValidateSurfaceHeaderOffsets(pcSurfaces);
|
||||||
|
|
||||||
// navigate to the vertex list of the surface
|
// Navigate to the vertex list of the surface
|
||||||
BE_NCONST MD3::Vertex* pcVertices = (BE_NCONST MD3::Vertex*)
|
BE_NCONST MD3::Vertex* pcVertices = (BE_NCONST MD3::Vertex*)
|
||||||
(((uint8_t*)pcSurfaces) + pcSurfaces->OFS_XYZNORMAL);
|
(((uint8_t*)pcSurfaces) + pcSurfaces->OFS_XYZNORMAL);
|
||||||
|
|
||||||
// navigate to the triangle list of the surface
|
// Navigate to the triangle list of the surface
|
||||||
BE_NCONST MD3::Triangle* pcTriangles = (BE_NCONST MD3::Triangle*)
|
BE_NCONST MD3::Triangle* pcTriangles = (BE_NCONST MD3::Triangle*)
|
||||||
(((uint8_t*)pcSurfaces) + pcSurfaces->OFS_TRIANGLES);
|
(((uint8_t*)pcSurfaces) + pcSurfaces->OFS_TRIANGLES);
|
||||||
|
|
||||||
// navigate to the texture coordinate list of the surface
|
// Navigate to the texture coordinate list of the surface
|
||||||
BE_NCONST MD3::TexCoord* pcUVs = (BE_NCONST MD3::TexCoord*)
|
BE_NCONST MD3::TexCoord* pcUVs = (BE_NCONST MD3::TexCoord*)
|
||||||
(((uint8_t*)pcSurfaces) + pcSurfaces->OFS_ST);
|
(((uint8_t*)pcSurfaces) + pcSurfaces->OFS_ST);
|
||||||
|
|
||||||
// navigate to the shader list of the surface
|
// Navigate to the shader list of the surface
|
||||||
BE_NCONST MD3::Shader* pcShaders = (BE_NCONST MD3::Shader*)
|
BE_NCONST MD3::Shader* pcShaders = (BE_NCONST MD3::Shader*)
|
||||||
(((uint8_t*)pcSurfaces) + pcSurfaces->OFS_SHADERS);
|
(((uint8_t*)pcSurfaces) + pcSurfaces->OFS_SHADERS);
|
||||||
|
|
||||||
// if the submesh is empty ignore it
|
// If the submesh is empty ignore it
|
||||||
if (0 == pcSurfaces->NUM_VERTICES || 0 == pcSurfaces->NUM_TRIANGLES)
|
if (0 == pcSurfaces->NUM_VERTICES || 0 == pcSurfaces->NUM_TRIANGLES)
|
||||||
{
|
{
|
||||||
pcSurfaces = (BE_NCONST MD3::Surface*)(((uint8_t*)pcSurfaces) + pcSurfaces->OFS_END);
|
pcSurfaces = (BE_NCONST MD3::Surface*)(((uint8_t*)pcSurfaces) + pcSurfaces->OFS_END);
|
||||||
|
@ -352,6 +555,7 @@ void MD3Importer::InternReadFile( const std::string& pFile,
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure correct endianess
|
||||||
#ifdef AI_BUILD_BIG_ENDIAN
|
#ifdef AI_BUILD_BIG_ENDIAN
|
||||||
|
|
||||||
for (uint32_t i = 0; i < pcSurfaces->NUM_VERTICES;++i)
|
for (uint32_t i = 0; i < pcSurfaces->NUM_VERTICES;++i)
|
||||||
|
@ -373,7 +577,7 @@ void MD3Importer::InternReadFile( const std::string& pFile,
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// allocate the output mesh
|
// Allocate the output mesh
|
||||||
pScene->mMeshes[iNum] = new aiMesh();
|
pScene->mMeshes[iNum] = new aiMesh();
|
||||||
aiMesh* pcMesh = pScene->mMeshes[iNum];
|
aiMesh* pcMesh = pScene->mMeshes[iNum];
|
||||||
pcMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
|
pcMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
|
||||||
|
@ -386,7 +590,7 @@ void MD3Importer::InternReadFile( const std::string& pFile,
|
||||||
pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices];
|
pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices];
|
||||||
pcMesh->mNumUVComponents[0] = 2;
|
pcMesh->mNumUVComponents[0] = 2;
|
||||||
|
|
||||||
// fill in all triangles
|
// Fill in all triangles
|
||||||
unsigned int iCurrent = 0;
|
unsigned int iCurrent = 0;
|
||||||
for (unsigned int i = 0; i < (unsigned int)pcSurfaces->NUM_TRIANGLES;++i)
|
for (unsigned int i = 0; i < (unsigned int)pcSurfaces->NUM_TRIANGLES;++i)
|
||||||
{
|
{
|
||||||
|
@ -396,16 +600,16 @@ void MD3Importer::InternReadFile( const std::string& pFile,
|
||||||
unsigned int iTemp = iCurrent;
|
unsigned int iTemp = iCurrent;
|
||||||
for (unsigned int c = 0; c < 3;++c,++iCurrent)
|
for (unsigned int c = 0; c < 3;++c,++iCurrent)
|
||||||
{
|
{
|
||||||
// read vertices
|
// Read vertices
|
||||||
pcMesh->mVertices[iCurrent].x = pcVertices[ pcTriangles->INDEXES[c]].X*AI_MD3_XYZ_SCALE;
|
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].y = pcVertices[ pcTriangles->INDEXES[c]].Y*AI_MD3_XYZ_SCALE;
|
||||||
pcMesh->mVertices[iCurrent].z = pcVertices[ pcTriangles->INDEXES[c]].Z*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
|
// Convert the normal vector to uncompressed float3 format
|
||||||
LatLngNormalToVec3(pcVertices[pcTriangles->INDEXES[c]].NORMAL,
|
LatLngNormalToVec3(pcVertices[pcTriangles->INDEXES[c]].NORMAL,
|
||||||
(float*)&pcMesh->mNormals[iCurrent]);
|
(float*)&pcMesh->mNormals[iCurrent]);
|
||||||
|
|
||||||
// read texture coordinates
|
// Read texture coordinates
|
||||||
pcMesh->mTextureCoords[0][iCurrent].x = pcUVs[ pcTriangles->INDEXES[c]].U;
|
pcMesh->mTextureCoords[0][iCurrent].x = pcUVs[ pcTriangles->INDEXES[c]].U;
|
||||||
pcMesh->mTextureCoords[0][iCurrent].y = 1.0f-pcUVs[ pcTriangles->INDEXES[c]].V;
|
pcMesh->mTextureCoords[0][iCurrent].y = 1.0f-pcUVs[ pcTriangles->INDEXES[c]].V;
|
||||||
}
|
}
|
||||||
|
@ -416,111 +620,109 @@ void MD3Importer::InternReadFile( const std::string& pFile,
|
||||||
pcTriangles++;
|
pcTriangles++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the first shader (= texture?) assigned to the surface
|
std::string _texture_name;
|
||||||
if (pcSurfaces->NUM_SHADER)
|
const char* texture_name = NULL, *header_name = pcHeader->NAME;
|
||||||
{
|
|
||||||
// make a relative path.
|
|
||||||
// if the MD3's internal path itself and the given path are using
|
|
||||||
// the same directory remove it
|
|
||||||
const char* szEndDir1 = ::strrchr((const char*)pcHeader->NAME,'\\');
|
|
||||||
if (!szEndDir1)szEndDir1 = ::strrchr((const char*)pcHeader->NAME,'/');
|
|
||||||
|
|
||||||
const char* szEndDir2 = ::strrchr((const char*)pcShaders->NAME,'\\');
|
// Check whether we have a texture record for this surface in the .skin file
|
||||||
if (!szEndDir2)szEndDir2 = ::strrchr((const char*)pcShaders->NAME,'/');
|
std::list< Q3Shader::SkinData::TextureEntry >::iterator it = std::find(
|
||||||
|
skins.textures.begin(), skins.textures.end(), pcSurfaces->NAME );
|
||||||
|
|
||||||
if (szEndDir1 && szEndDir2)
|
if (it != skins.textures.end()) {
|
||||||
{
|
texture_name = &*( _texture_name = (*it).second).begin();
|
||||||
// both of them are valid
|
DefaultLogger::get()->debug("MD3: Assigning skin texture " + (*it).second + " to surface " + pcSurfaces->NAME);
|
||||||
const unsigned int iLen1 = (unsigned int)(szEndDir1 - (const char*)pcHeader->NAME);
|
(*it).resolved = true; // mark entry as resolved
|
||||||
const unsigned int iLen2 = std::min (iLen1, (unsigned int)(szEndDir2 - (const char*)pcShaders->NAME) );
|
|
||||||
|
|
||||||
bool bSuccess = true;
|
|
||||||
for (unsigned int a = 0; a < iLen2;++a)
|
|
||||||
{
|
|
||||||
char sz = ::tolower ( pcShaders->NAME[a] );
|
|
||||||
char sz2 = ::tolower ( pcHeader->NAME[a] );
|
|
||||||
if (sz != sz2)
|
|
||||||
{
|
|
||||||
bSuccess = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (bSuccess) {
|
|
||||||
// use the file name only
|
|
||||||
szEndDir2++;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// use the full path
|
|
||||||
szEndDir2 = (const char*)pcShaders->NAME;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MaterialHelper* pcHelper = new MaterialHelper();
|
|
||||||
|
|
||||||
if (szEndDir2) {
|
|
||||||
aiString szString;
|
|
||||||
if (szEndDir2[0]) {
|
|
||||||
const size_t iLen = ::strlen(szEndDir2);
|
|
||||||
::memcpy(szString.data,szEndDir2,iLen);
|
|
||||||
szString.data[iLen] = '\0';
|
|
||||||
szString.length = iLen;
|
|
||||||
}
|
|
||||||
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 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);
|
|
||||||
|
|
||||||
aiString szName;
|
|
||||||
szName.Set(AI_DEFAULT_MATERIAL_NAME);
|
|
||||||
pcHelper->AddProperty(&szName,AI_MATKEY_NAME);
|
|
||||||
|
|
||||||
pScene->mMaterials[iNumMaterials] = (aiMaterial*)pcHelper;
|
|
||||||
pcMesh->mMaterialIndex = iNumMaterials++;
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
if (0xFFFFFFFF != iDefaultMatIndex) {
|
|
||||||
pcMesh->mMaterialIndex = iDefaultMatIndex;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MaterialHelper* pcHelper = new MaterialHelper();
|
|
||||||
|
|
||||||
// fill in a default material
|
// Get the first shader (= texture?) assigned to the surface
|
||||||
int iMode = (int)aiShadingMode_Gouraud;
|
if (!texture_name && pcSurfaces->NUM_SHADER) {
|
||||||
pcHelper->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL);
|
texture_name = pcShaders->NAME;
|
||||||
|
}
|
||||||
|
|
||||||
aiColor3D clr;
|
const char* end2 = NULL;
|
||||||
clr.b = clr.g = clr.r = 0.6f;
|
if (texture_name) {
|
||||||
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;
|
// If the MD3's internal path itself and the given path are using
|
||||||
pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_AMBIENT);
|
// the same directory, remove it completely to get right output paths.
|
||||||
|
const char* end1 = ::strrchr(header_name,'\\');
|
||||||
|
if (!end1)end1 = ::strrchr(header_name,'/');
|
||||||
|
|
||||||
pScene->mMaterials[iNumMaterials] = (aiMaterial*)pcHelper;
|
end2 = ::strrchr(texture_name,'\\');
|
||||||
iDefaultMatIndex = pcMesh->mMaterialIndex = iNumMaterials++;
|
if (!end2)end2 = ::strrchr(texture_name,'/');
|
||||||
|
|
||||||
|
// HACK: If the paths starts with "models/players", ignore the
|
||||||
|
// next hierarchy level, it specifies just the model name.
|
||||||
|
// Ignored by Q3, it might be not equal to the real model location.
|
||||||
|
if (end1 && end2) {
|
||||||
|
|
||||||
|
size_t len2;
|
||||||
|
const size_t len1 = (size_t)(end1 - header_name);
|
||||||
|
if (!ASSIMP_strincmp(header_name,"models/players/",15)) {
|
||||||
|
len2 = 15;
|
||||||
|
}
|
||||||
|
else len2 = std::min (len1, (size_t)(end2 - texture_name ));
|
||||||
|
|
||||||
|
if (!ASSIMP_strincmp(texture_name,header_name,len2)) {
|
||||||
|
// Use the file name only
|
||||||
|
end2++;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Use the full path
|
||||||
|
end2 = (const char*)texture_name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// go to the next surface
|
|
||||||
|
MaterialHelper* pcHelper = new MaterialHelper();
|
||||||
|
|
||||||
|
// Setup dummy texture file name to ensure UV coordinates are kept during postprocessing
|
||||||
|
aiString szString;
|
||||||
|
if (end2 && end2[0]) {
|
||||||
|
const size_t iLen = ::strlen(end2);
|
||||||
|
::memcpy(szString.data,end2,iLen);
|
||||||
|
szString.data[iLen] = '\0';
|
||||||
|
szString.length = iLen;
|
||||||
|
}
|
||||||
|
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));
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
pScene->mMaterials[iNumMaterials] = (aiMaterial*)pcHelper;
|
||||||
|
pcMesh->mMaterialIndex = iNumMaterials++;
|
||||||
|
|
||||||
|
// Go to the next surface
|
||||||
pcSurfaces = (BE_NCONST MD3::Surface*)(((unsigned char*)pcSurfaces) + pcSurfaces->OFS_END);
|
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)
|
if (!pScene->mNumMeshes)
|
||||||
throw new ImportErrorException( "Invalid MD3 file: File contains no valid mesh");
|
throw new ImportErrorException( "MD3: File contains no valid mesh");
|
||||||
pScene->mNumMaterials = iNumMaterials;
|
pScene->mNumMaterials = iNumMaterials;
|
||||||
|
|
||||||
// Now we need to generate an empty node graph
|
// Now we need to generate an empty node graph
|
||||||
|
@ -543,12 +745,12 @@ void MD3Importer::InternReadFile( const std::string& pFile,
|
||||||
AI_SWAP4(pcTags->origin.y);
|
AI_SWAP4(pcTags->origin.y);
|
||||||
AI_SWAP4(pcTags->origin.z);
|
AI_SWAP4(pcTags->origin.z);
|
||||||
|
|
||||||
// copy local origin
|
// Copy local origin
|
||||||
nd->mTransformation.a4 = pcTags->origin.x;
|
nd->mTransformation.a4 = pcTags->origin.x;
|
||||||
nd->mTransformation.b4 = pcTags->origin.y;
|
nd->mTransformation.b4 = pcTags->origin.y;
|
||||||
nd->mTransformation.c4 = pcTags->origin.z;
|
nd->mTransformation.c4 = pcTags->origin.z;
|
||||||
|
|
||||||
// copy rest of transformation
|
// Copy rest of transformation (need to transpose to match row-order matrix)
|
||||||
for (unsigned int a = 0; a < 3;++a) {
|
for (unsigned int a = 0; a < 3;++a) {
|
||||||
for (unsigned int m = 0; m < 3;++m) {
|
for (unsigned int m = 0; m < 3;++m) {
|
||||||
nd->mTransformation[m][a] = pcTags->orientation[a][m];
|
nd->mTransformation[m][a] = pcTags->orientation[a][m];
|
||||||
|
|
151
code/MD3Loader.h
151
code/MD3Loader.h
|
@ -38,7 +38,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @file Definition of the .MD3 importer class. */
|
/** @file Md3Loader.h
|
||||||
|
* @brief Declaration of the .MD3 importer class.
|
||||||
|
*/
|
||||||
#ifndef AI_MD3LOADER_H_INCLUDED
|
#ifndef AI_MD3LOADER_H_INCLUDED
|
||||||
#define AI_MD3LOADER_H_INCLUDED
|
#define AI_MD3LOADER_H_INCLUDED
|
||||||
|
|
||||||
|
@ -47,16 +49,142 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
#include "../include/aiTypes.h"
|
#include "../include/aiTypes.h"
|
||||||
|
|
||||||
struct aiNode;
|
|
||||||
|
|
||||||
#include "MD3FileData.h"
|
#include "MD3FileData.h"
|
||||||
namespace Assimp {
|
namespace Assimp {
|
||||||
class MaterialHelper;
|
class MaterialHelper;
|
||||||
|
|
||||||
using namespace MD3;
|
using namespace MD3;
|
||||||
|
namespace Q3Shader {
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
/** Used to load MD3 files
|
/** @brief Tiny utility data structure to hold the data of a .skin file
|
||||||
|
*/
|
||||||
|
struct SkinData
|
||||||
|
{
|
||||||
|
//! A single entryin texture list
|
||||||
|
struct TextureEntry : public std::pair<std::string,std::string>
|
||||||
|
{
|
||||||
|
// did we resolve this texture entry?
|
||||||
|
bool resolved;
|
||||||
|
|
||||||
|
// for std::find()
|
||||||
|
bool operator == (const std::string& f) const {
|
||||||
|
return f == first;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//! List of textures
|
||||||
|
std::list<TextureEntry> textures;
|
||||||
|
|
||||||
|
// rest is ignored for the moment
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
/** @brief Specifies cull modi for Quake shader files.
|
||||||
|
*/
|
||||||
|
enum ShaderCullMode
|
||||||
|
{
|
||||||
|
CULL_NONE,
|
||||||
|
CULL_CW,
|
||||||
|
CULL_CCW,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
/** @brief Specifies alpha blend modi (src + dest) for Quake shader files
|
||||||
|
*/
|
||||||
|
enum BlendFunc
|
||||||
|
{
|
||||||
|
BLEND_NONE,
|
||||||
|
BLEND_GL_ONE,
|
||||||
|
BLEND_GL_ZERO,
|
||||||
|
BLEND_GL_DST_COLOR,
|
||||||
|
BLEND_GL_ONE_MINUS_DST_COLOR,
|
||||||
|
BLEND_GL_SRC_ALPHA,
|
||||||
|
BLEND_GL_ONE_MINUS_SRC_ALPHA
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
/** @brief Specifies alpha test modi for Quake texture maps
|
||||||
|
*/
|
||||||
|
enum AlphaTestFunc
|
||||||
|
{
|
||||||
|
AT_NONE,
|
||||||
|
AT_GT0,
|
||||||
|
AT_LT128,
|
||||||
|
AT_GE128
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
/** @brief Tiny utility data structure to hold a .shader map data block
|
||||||
|
*/
|
||||||
|
struct ShaderMapBlock
|
||||||
|
{
|
||||||
|
ShaderMapBlock()
|
||||||
|
: blend_src (BLEND_NONE)
|
||||||
|
, blend_dest (BLEND_NONE)
|
||||||
|
, alpha_test (AT_NONE)
|
||||||
|
{}
|
||||||
|
|
||||||
|
//! Name of referenced map
|
||||||
|
std::string name;
|
||||||
|
|
||||||
|
//! Blend and alpha test settings for texture
|
||||||
|
BlendFunc blend_src,blend_dest;
|
||||||
|
AlphaTestFunc alpha_test;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
/** @brief Tiny utility data structure to hold a .shader data block
|
||||||
|
*/
|
||||||
|
struct ShaderDataBlock
|
||||||
|
{
|
||||||
|
ShaderDataBlock()
|
||||||
|
: cull (CULL_CCW)
|
||||||
|
{}
|
||||||
|
|
||||||
|
//! Name of referenced data element
|
||||||
|
std::string name;
|
||||||
|
|
||||||
|
//! Cull mode for the element
|
||||||
|
ShaderCullMode cull;
|
||||||
|
|
||||||
|
//! Maps defined in the shader
|
||||||
|
std::list<ShaderMapBlock> maps;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
/** @brief Tiny utility data structure to hold the data of a .shader file
|
||||||
|
*/
|
||||||
|
struct ShaderData
|
||||||
|
{
|
||||||
|
//! Shader data blocks
|
||||||
|
std::list<ShaderDataBlock> blocks;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
/** @brief Load a shader file
|
||||||
|
*
|
||||||
|
* Generally, parsing is error tolerant. There's no failure.
|
||||||
|
* @param fill Receives output data
|
||||||
|
* @param file File to be read.
|
||||||
|
* @param io IOSystem to be used for reading
|
||||||
|
*/
|
||||||
|
void LoadShader(ShaderData& fill, const std::string& file,IOSystem* io);
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
/** @brief Load a skin file
|
||||||
|
*
|
||||||
|
* Generally, parsing is error tolerant. There's no failure.
|
||||||
|
* @param fill Receives output data
|
||||||
|
* @param file File to be read.
|
||||||
|
* @param io IOSystem to be used for reading
|
||||||
|
*/
|
||||||
|
void LoadSkin(SkinData& fill, const std::string& file,IOSystem* io);
|
||||||
|
|
||||||
|
} // ! namespace Q3SHader
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
/** @brief Importer class to load MD3 files
|
||||||
*/
|
*/
|
||||||
class MD3Importer : public BaseImporter
|
class MD3Importer : public BaseImporter
|
||||||
{
|
{
|
||||||
|
@ -111,6 +239,12 @@ protected:
|
||||||
*/
|
*/
|
||||||
bool ReadMultipartFile();
|
bool ReadMultipartFile();
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
/** Try to read the skin for a MD3 file
|
||||||
|
* @param fill Receives output information
|
||||||
|
*/
|
||||||
|
void ReadSkin(Q3Shader::SkinData& fill);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
/** Configuration option: frame to be loaded */
|
/** Configuration option: frame to be loaded */
|
||||||
|
@ -119,6 +253,9 @@ protected:
|
||||||
/** Configuration option: process multi-part files */
|
/** Configuration option: process multi-part files */
|
||||||
bool configHandleMP;
|
bool configHandleMP;
|
||||||
|
|
||||||
|
/** Configuration option: name of skin file to be read */
|
||||||
|
std::string configSkinFile;
|
||||||
|
|
||||||
/** Header of the MD3 file */
|
/** Header of the MD3 file */
|
||||||
BE_NCONST MD3::Header* pcHeader;
|
BE_NCONST MD3::Header* pcHeader;
|
||||||
|
|
||||||
|
@ -131,6 +268,12 @@ protected:
|
||||||
/** Current file name */
|
/** Current file name */
|
||||||
std::string mFile;
|
std::string mFile;
|
||||||
|
|
||||||
|
/** Current base directory */
|
||||||
|
std::string path;
|
||||||
|
|
||||||
|
/** Pure file we're currently reading */
|
||||||
|
std::string filename;
|
||||||
|
|
||||||
/** Output scene to be filled */
|
/** Output scene to be filled */
|
||||||
aiScene* mScene;
|
aiScene* mScene;
|
||||||
|
|
||||||
|
|
|
@ -370,7 +370,7 @@ private:
|
||||||
inline bool SkipLine( const char* in, const char** out)
|
inline bool SkipLine( const char* in, const char** out)
|
||||||
{
|
{
|
||||||
++lineNumber;
|
++lineNumber;
|
||||||
return ::SkipLine(in,out);
|
return Assimp::SkipLine(in,out);
|
||||||
}
|
}
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
inline bool SkipLine( )
|
inline bool SkipLine( )
|
||||||
|
@ -407,7 +407,7 @@ private:
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
inline bool SkipSpaces( )
|
inline bool SkipSpaces( )
|
||||||
{
|
{
|
||||||
return ::SkipSpaces((const char**)&buffer);
|
return Assimp::SkipSpaces((const char**)&buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
char* buffer;
|
char* buffer;
|
||||||
|
|
|
@ -39,10 +39,15 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
/** @file Defines helper functions for text parsing */
|
/** @file ParsingUtils.h
|
||||||
|
* @brief Defines helper functions for text parsing
|
||||||
|
*/
|
||||||
#ifndef AI_PARSING_UTILS_H_INC
|
#ifndef AI_PARSING_UTILS_H_INC
|
||||||
#define AI_PARSING_UTILS_H_INC
|
#define AI_PARSING_UTILS_H_INC
|
||||||
|
|
||||||
|
#include "StringComparison.h"
|
||||||
|
namespace Assimp {
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------
|
||||||
template <class char_t>
|
template <class char_t>
|
||||||
AI_FORCE_INLINE bool IsSpace( const char_t in)
|
AI_FORCE_INLINE bool IsSpace( const char_t in)
|
||||||
|
@ -129,16 +134,6 @@ AI_FORCE_INLINE bool IsNumeric( char_t in)
|
||||||
return in >= '0' && in <= '9' || '-' == in || '+' == in;
|
return in >= '0' && in <= '9' || '-' == in || '+' == in;
|
||||||
}
|
}
|
||||||
// ---------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------
|
||||||
AI_FORCE_INLINE bool TokenMatch(const char*& in, const char* token, unsigned int len)
|
|
||||||
{
|
|
||||||
if (!::strncmp(token,in,len) && IsSpaceOrNewLine(in[len]))
|
|
||||||
{
|
|
||||||
in += len+1;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// ---------------------------------------------------------------------------------
|
|
||||||
AI_FORCE_INLINE bool TokenMatch(char*& in, const char* token, unsigned int len)
|
AI_FORCE_INLINE bool TokenMatch(char*& in, const char* token, unsigned int len)
|
||||||
{
|
{
|
||||||
if (!::strncmp(token,in,len) && IsSpaceOrNewLine(in[len]))
|
if (!::strncmp(token,in,len) && IsSpaceOrNewLine(in[len]))
|
||||||
|
@ -149,9 +144,38 @@ AI_FORCE_INLINE bool TokenMatch(char*& in, const char* token, unsigned int len)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// ---------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------
|
||||||
|
/** @brief Case-ignoring version of TokenMatch
|
||||||
|
* @param in Input
|
||||||
|
* @param token Token to check for
|
||||||
|
* @param len Number of characters to check
|
||||||
|
*/
|
||||||
|
AI_FORCE_INLINE bool TokenMatchI(const char*& in, const char* token, unsigned int len)
|
||||||
|
{
|
||||||
|
if (!ASSIMP_strincmp(token,in,len) && IsSpaceOrNewLine(in[len]))
|
||||||
|
{
|
||||||
|
in += len+1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// ---------------------------------------------------------------------------------
|
||||||
|
AI_FORCE_INLINE bool TokenMatch(const char*& in, const char* token, unsigned int len)
|
||||||
|
{
|
||||||
|
return TokenMatch(const_cast<char*&>(in), token, len);
|
||||||
|
}
|
||||||
|
// ---------------------------------------------------------------------------------
|
||||||
AI_FORCE_INLINE void SkipToken(const char*& in)
|
AI_FORCE_INLINE void SkipToken(const char*& in)
|
||||||
{
|
{
|
||||||
SkipSpaces(&in);
|
SkipSpaces(&in);
|
||||||
while (!IsSpaceOrNewLine(*in))++in;
|
while (!IsSpaceOrNewLine(*in))++in;
|
||||||
}
|
}
|
||||||
|
// ---------------------------------------------------------------------------------
|
||||||
|
AI_FORCE_INLINE std::string GetNextToken(const char*& in)
|
||||||
|
{
|
||||||
|
SkipSpacesAndLineEnd(&in);
|
||||||
|
const char* cur = in;
|
||||||
|
while (!IsSpaceOrNewLine(*in))++in;
|
||||||
|
return std::string(cur,(size_t)(in-cur));
|
||||||
|
}
|
||||||
|
} // ! namespace Assimp
|
||||||
#endif // ! AI_PARSING_UTILS_H_INC
|
#endif // ! AI_PARSING_UTILS_H_INC
|
||||||
|
|
|
@ -360,7 +360,7 @@ protected:
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
inline bool SkipLine( const char* in, const char** out)
|
inline bool SkipLine( const char* in, const char** out)
|
||||||
{
|
{
|
||||||
::SkipLine(in,out);
|
Assimp::SkipLine(in,out);
|
||||||
++iLineNumber;
|
++iLineNumber;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -368,7 +368,7 @@ protected:
|
||||||
inline bool SkipSpacesAndLineEnd( const char* in, const char** out)
|
inline bool SkipSpacesAndLineEnd( const char* in, const char** out)
|
||||||
{
|
{
|
||||||
++iLineNumber;
|
++iLineNumber;
|
||||||
return ::SkipSpacesAndLineEnd(in,out);
|
return Assimp::SkipSpacesAndLineEnd(in,out);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -144,7 +144,20 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
* combine all three files if one of them is loaded.
|
* combine all three files if one of them is loaded.
|
||||||
* Property type: integer (0: false; !0: true). Default value: true.
|
* Property type: integer (0: false; !0: true). Default value: true.
|
||||||
*/
|
*/
|
||||||
#define AI_CONFIG_IMPORT_MD3_HANDLE_MULTIPART "IMPORT_MD3_HANDLE_MULTIPART"
|
#define AI_CONFIG_IMPORT_MD3_HANDLE_MULTIPART \
|
||||||
|
"IMPORT_MD3_HANDLE_MULTIPART"
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
/** @brief Tells the MD3 loader which skin files to load.
|
||||||
|
*
|
||||||
|
* When loading MD3 files, Assimp checks whether a file
|
||||||
|
* <md3_file_name>_<skin_name>.skin is existing. These files are used by
|
||||||
|
* Quake III to be able to assign different skins (e.g. red and blue team)
|
||||||
|
* to models. 'default', 'red', 'blue' are typical skin names.
|
||||||
|
* Property type: String. Default value: "default".
|
||||||
|
*/
|
||||||
|
#define AI_CONFIG_IMPORT_MD3_SKIN_NAME \
|
||||||
|
"IMPORT_MD3_SKIN_NAME"
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
|
@ -441,6 +441,54 @@ enum aiTextureFlags
|
||||||
//! @endcond
|
//! @endcond
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
/** @brief Defines alpha-blend flags.
|
||||||
|
*
|
||||||
|
* If you're familiar with OpenGL or D3D, these flags aren't new to you.
|
||||||
|
* The define *how* the final color value of a pixel is computed, basing
|
||||||
|
* on the previous color at that pixel and the new color value from the
|
||||||
|
* material.
|
||||||
|
* The blend formula is:
|
||||||
|
* @code
|
||||||
|
* SourceColor * SourceBlend + DestColor * DestBlend
|
||||||
|
* @endcode
|
||||||
|
* where <DestColor> is the previous color in the framebuffer at this
|
||||||
|
* position and <SourceColor> is the material colro before the transparency
|
||||||
|
* calculation.<br>
|
||||||
|
* This corresponds to the #AI_MATKEY_BLEND_FUNC property.
|
||||||
|
*/
|
||||||
|
enum aiBlendMode
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Formula:
|
||||||
|
* @code
|
||||||
|
* SourceColor*SourceAlpha + DestColor*(1-SourceAlpha)
|
||||||
|
* @endcode
|
||||||
|
*/
|
||||||
|
aiBlendMode_Default = 0x0,
|
||||||
|
|
||||||
|
/** Additive blending
|
||||||
|
*
|
||||||
|
* Formula:
|
||||||
|
* @code
|
||||||
|
* SourceColor*1 + DestColor*1
|
||||||
|
* @endcode
|
||||||
|
*/
|
||||||
|
aiBlendMode_Additive = 0x1,
|
||||||
|
|
||||||
|
// we don't need more for the moment, but we might need them
|
||||||
|
// in future versions ...
|
||||||
|
|
||||||
|
/** @cond never
|
||||||
|
* This value is not used. It forces the compiler to use at least
|
||||||
|
* 32 Bit integers to represent this enum.
|
||||||
|
*/
|
||||||
|
_aiBlendMode_Force32Bit = 0x9fffffff
|
||||||
|
//! @endcond
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
#include "./Compiler/pushpack1.h"
|
#include "./Compiler/pushpack1.h"
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
@ -688,18 +736,33 @@ extern "C" {
|
||||||
* Integer property. 1 to enable wireframe mode for rendering.
|
* Integer property. 1 to enable wireframe mode for rendering.
|
||||||
* A material with this property set to 1 should appear as wireframe, even
|
* A material with this property set to 1 should appear as wireframe, even
|
||||||
* if the scene is rendered solid.
|
* if the scene is rendered solid.
|
||||||
* <br>
|
* <br>
|
||||||
* <b>Type:</b> int <br>
|
* <b>Type:</b> int <br>
|
||||||
* <b>Default value:</b> <tt>0</tt>
|
* <b>Default value:</b> <tt>0</tt>
|
||||||
*/
|
*/
|
||||||
#define AI_MATKEY_ENABLE_WIREFRAME "$mat.wireframe",0,0
|
#define AI_MATKEY_ENABLE_WIREFRAME "$mat.wireframe",0,0
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
/** @def AI_MATKEY_BLEND_FUNC
|
||||||
|
* Integer property (one of the #aiBlendMode enumerated values). Defines
|
||||||
|
* the blend function to be used to combine the material color for a specific
|
||||||
|
* pixel with the previous framebuffer color at this position. This
|
||||||
|
* corresponds to the #AI_MATKEY_OPACITY and #AI_MATKEY_COLOR_TRANSPARENT
|
||||||
|
* property. No alpha-blending needs to be turned on if the opacity for all
|
||||||
|
* color channels is 1.0 and the blend mode is set to #aiBlendMode_Default.
|
||||||
|
* <br>
|
||||||
|
* <b>Type:</b> int (#aiBlendMode)<br>
|
||||||
|
* <b>Default value:</b> <tt>#aiBlendMode_Default</tt>
|
||||||
|
*/
|
||||||
|
#define AI_MATKEY_BLEND_FUNC "$mat.blend",0,0
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
/** @def AI_MATKEY_OPACITY
|
/** @def AI_MATKEY_OPACITY
|
||||||
* Defines the base opacity of the material. This value defines how
|
* Defines the base opacity of the material. To get the opacity value for
|
||||||
* transparent the material actually is. However, in order to get absolutely
|
* a particular channel, this value is multiplied with the corresponding
|
||||||
* correct results you'll also need to evaluate the
|
* channel of the #AI_MATKEY_COLOR_TRANSPARENT property. The final value
|
||||||
* #AI_MATKEY_COLOR_TRANSPARENT property.
|
* is fed in the blend function defined by the #AI_MATKEY_BLEND_FUNC
|
||||||
|
* property.
|
||||||
* <br>
|
* <br>
|
||||||
* <b>Type:</b> float<br>
|
* <b>Type:</b> float<br>
|
||||||
* <b>Default value:</b> <tt>1.0f</tt><br>
|
* <b>Default value:</b> <tt>1.0f</tt><br>
|
||||||
|
@ -755,7 +818,8 @@ extern "C" {
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
/** @def AI_MATKEY_COLOR_DIFFUSE
|
/** @def AI_MATKEY_COLOR_DIFFUSE
|
||||||
* Defines the diffuse base color of the material. <br>
|
* Defines the diffuse base color of the material.
|
||||||
|
* If stored as 4-component color, the alpha channel is ignored.<br>
|
||||||
* <b>Type:</b> color (#aiColor4D or #aiColor3D) <br>
|
* <b>Type:</b> color (#aiColor4D or #aiColor3D) <br>
|
||||||
* <b>Default value:</b> <tt>0.0f|0.0f|0.0f|1.0f </tt>
|
* <b>Default value:</b> <tt>0.0f|0.0f|0.0f|1.0f </tt>
|
||||||
*/
|
*/
|
||||||
|
@ -763,39 +827,46 @@ extern "C" {
|
||||||
|
|
||||||
/** @def AI_MATKEY_COLOR_AMBIENT
|
/** @def AI_MATKEY_COLOR_AMBIENT
|
||||||
* Declares the amount of ambient light emitted from
|
* Declares the amount of ambient light emitted from
|
||||||
* the surface of this object. <br>
|
* the surface of this object. If stored as 4-component color,
|
||||||
* <b>Type:</b> color (#aiColor4D or #aiColor3D) <br>
|
* the alpha channel is ignored.<br><br>
|
||||||
* <b>Default value:</b> <tt>0.0f|0.0f|0.0f|1.0f </tt>
|
* <b>Type:</b> color (#aiColor4D or #aiColor3D) <br>
|
||||||
|
* <b>Default value:</b> <tt>0.0f|0.0f|0.0f|1.0f </tt>
|
||||||
*/
|
*/
|
||||||
#define AI_MATKEY_COLOR_AMBIENT "$clr.ambient",0,0
|
#define AI_MATKEY_COLOR_AMBIENT "$clr.ambient",0,0
|
||||||
|
|
||||||
/** @def AI_MATKEY_COLOR_SPECULAR
|
/** @def AI_MATKEY_COLOR_SPECULAR
|
||||||
* Declares the color of light specularly reflected from
|
* Declares the color of light specularly reflected from
|
||||||
* the surface of this object. <br>
|
* the surface of this object. If stored as 4-component color, the
|
||||||
* <b>Type:</b> color (#aiColor4D or #aiColor3D) <br>
|
* alpha channel is ignored.<br><br>
|
||||||
* <b>Default value:</b> <tt>0.0f|0.0f|0.0f|1.0f </tt>
|
* <b>Type:</b> color (#aiColor4D or #aiColor3D) <br>
|
||||||
|
* <b>Default value:</b> <tt>0.0f|0.0f|0.0f|1.0f </tt>
|
||||||
*/
|
*/
|
||||||
#define AI_MATKEY_COLOR_SPECULAR "$clr.specular",0,0
|
#define AI_MATKEY_COLOR_SPECULAR "$clr.specular",0,0
|
||||||
|
|
||||||
/** @def AI_MATKEY_COLOR_EMISSIVE
|
/** @def AI_MATKEY_COLOR_EMISSIVE
|
||||||
* Declares the amount of light emitted from the
|
* Declares the amount of light emitted from the
|
||||||
* surface of this object. <br>
|
* surface of this object. If stored as 4-component color, the alpha
|
||||||
* <b>Type:</b> color (#aiColor4D or #aiColor3D) <br>
|
* channel is ignored.<br><br>
|
||||||
* <b>Default value:</b> <tt>0.0f|0.0f|0.0f|1.0f </tt>
|
* <b>Type:</b> color (#aiColor4D or #aiColor3D) <br>
|
||||||
|
* <b>Default value:</b> <tt>0.0f|0.0f|0.0f|1.0f </tt>
|
||||||
*/
|
*/
|
||||||
#define AI_MATKEY_COLOR_EMISSIVE "$clr.emissive",0,0
|
#define AI_MATKEY_COLOR_EMISSIVE "$clr.emissive",0,0
|
||||||
|
|
||||||
/** @def AI_MATKEY_COLOR_TRANSPARENT
|
/** @def AI_MATKEY_COLOR_TRANSPARENT
|
||||||
* Defines the transparent base color of the material. <br>
|
* Defines the transparent base color of the material. If stored as
|
||||||
* <b>Type:</b> color (#aiColor4D or #aiColor3D) <br>
|
* 4-component color, the alpha channel is ignored. This
|
||||||
* <b>Default value:</b> <tt>0.0f|0.0f|0.0f|1.0f </tt>
|
* corresponds to the #AI_MATKEY_OPACITY and #AI_MATKEY_BLEND_FUNC
|
||||||
|
* material properties.<br> <br>
|
||||||
|
* <b>Type:</b> color (#aiColor4D or #aiColor3D) <br>
|
||||||
|
* <b>Default value:</b> <tt>0.0f|0.0f|0.0f|1.0f </tt>
|
||||||
*/
|
*/
|
||||||
#define AI_MATKEY_COLOR_TRANSPARENT "$clr.transparent",0,0
|
#define AI_MATKEY_COLOR_TRANSPARENT "$clr.transparent",0,0
|
||||||
|
|
||||||
/** @def AI_MATKEY_COLOR_REFLECTIVE
|
/** @def AI_MATKEY_COLOR_REFLECTIVE
|
||||||
* Declares the color of a perfect mirror reflection. <br>
|
* Declares the color of a perfect mirror reflection. If stored as
|
||||||
* <b>Type:</b> color (#aiColor4D or #aiColor3D) <br>
|
* 4-component color, the alpha channel is ignored.<br> <br>
|
||||||
* <b>Default value:</b> <tt>0.0f|0.0f|0.0f|1.0f </tt>
|
* <b>Type:</b> color (#aiColor4D or #aiColor3D) <br>
|
||||||
|
* <b>Default value:</b> <tt>0.0f|0.0f|0.0f|1.0f </tt>
|
||||||
*/
|
*/
|
||||||
#define AI_MATKEY_COLOR_REFLECTIVE "$clr.reflective",0,0
|
#define AI_MATKEY_COLOR_REFLECTIVE "$clr.reflective",0,0
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue