Bläddra i källkod

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-9d2fd5bffc1f

aramis_acg 16 år sedan
förälder
incheckning
2b9461fbf7
10 ändrade filer med 716 tillägg och 253 borttagningar
  1. 23 18
      code/DefaultIOStream.cpp
  2. 17 4
      code/DefaultIOStream.h
  3. 35 43
      code/MD3FileData.h
  4. 354 152
      code/MD3Loader.cpp
  5. 147 4
      code/MD3Loader.h
  6. 2 2
      code/MD5Parser.h
  7. 28 4
      code/ParsingUtils.h
  8. 2 2
      code/SMDLoader.h
  9. 14 1
      include/aiConfig.h
  10. 94 23
      include/aiMaterial.h

+ 23 - 18
code/DefaultIOStream.cpp

@@ -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.
 ---------------------------------------------------------------------------
 */
-/** @file Default File I/O implementation for #Importer */
+/** @file  DefaultIOStream.cpp
+ *  @brief Default File I/O implementation for #Importer 
+ */
 
 #include "AssimpPCH.h"
 
@@ -80,11 +82,12 @@ size_t DefaultIOStream::Write(const void* pvBuffer,
 aiReturn DefaultIOStream::Seek(size_t pOffset,
 	 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
-	ai_assert(aiOrigin_CUR == SEEK_CUR && aiOrigin_END == SEEK_END 
-		&& aiOrigin_SET == SEEK_SET);
+	BOOST_STATIC_ASSERT(aiOrigin_CUR == SEEK_CUR && 
+		aiOrigin_END == SEEK_END && aiOrigin_SET == SEEK_SET);
 
 	// do the seek
 	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
 {
-	ai_assert (!mFilename.empty());
-
-	if (! mFile)
+	if (! mFile || mFilename.empty())
 		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__
-	struct __stat64 fileStat; 
-	int err = _stat64(  mFilename.c_str(), &fileStat ); 
-	if (0 != err) 
-		return 0; 
-	return (size_t) (fileStat.st_size); 
+		struct __stat64 fileStat; 
+		int err = _stat64(  mFilename.c_str(), &fileStat ); 
+		if (0 != err) 
+			return 0; 
+		cachedSize = (size_t) (fileStat.st_size); 
 #else
-	struct stat fileStat; 
-	int err = stat(mFilename.c_str(), &fileStat ); 
-	if (0 != err) 
-		return 0; 
-	return (size_t) (fileStat.st_size); 
+		struct stat fileStat; 
+		int err = stat(mFilename.c_str(), &fileStat ); 
+		if (0 != err) 
+			return 0; 
+		cachedSize = (size_t) (fileStat.st_size); 
 #endif
+	}
+	return cachedSize;
 }
 
 // ----------------------------------------------------------------------------------

+ 17 - 4
code/DefaultIOStream.h

@@ -50,6 +50,9 @@ namespace Assimp	{
 // ----------------------------------------------------------------------------------
 //!	@class	DefaultIOStream
 //!	@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
 {
 	friend class DefaultIOSystem;
@@ -63,28 +66,33 @@ public:
 	~DefaultIOStream ();
 
 	// -------------------------------------------------------------------
+	// Read from stream
     size_t Read(void* pvBuffer, 
 		size_t pSize, 
 		size_t pCount);
 
 
 	// -------------------------------------------------------------------
+	// Write to stream
     size_t Write(const void* pvBuffer, 
 		size_t pSize,
 		size_t pCount);
 
-
 	// -------------------------------------------------------------------
+	// Seek specific position
 	aiReturn Seek(size_t pOffset,
 		aiOrigin pOrigin);
 
 	// -------------------------------------------------------------------
+	// Get current seek position
     size_t Tell() const;
 
 	// -------------------------------------------------------------------
+	// Get size of file
 	size_t FileSize() const;
 
 	// -------------------------------------------------------------------
+	// Flush file contents
 	void Flush();
 
 private:
@@ -92,13 +100,17 @@ private:
 	FILE* mFile;
 	//!	Filename
 	std::string	mFilename;
+
+	//! Cached file size
+	mutable size_t cachedSize;
 };
 
 
 // ----------------------------------------------------------------------------------
 inline DefaultIOStream::DefaultIOStream () : 
-	mFile(NULL), 
-	mFilename("") 
+	mFile		(NULL), 
+	mFilename	(""),
+	cachedSize	(0xffffffff)
 {
 	// empty
 }
@@ -108,7 +120,8 @@ inline DefaultIOStream::DefaultIOStream () :
 inline DefaultIOStream::DefaultIOStream (FILE* pFile, 
 		const std::string &strFilename) :
 	mFile(pFile), 
-	mFilename(strFilename)
+	mFilename(strFilename),
+	cachedSize	(0xffffffff)
 {
 	// empty
 }

+ 35 - 43
code/MD3FileData.h

@@ -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.
-      http://linux.ucla.edu/~phaethon/q3/formats/md3format.html
-*/
+/** @file Md3FileData.h
+ *
+ *  @brief Defines helper data structures for importing MD3 files.
+ *  http://linux.ucla.edu/~phaethon/q3/formats/md3format.html
+ */
 #ifndef 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
 #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
 {
 	//! magic number
@@ -90,7 +91,7 @@ struct Header
 	uint32_t VERSION;
 
 	//! original name in .pak archive
-	unsigned char NAME[ AI_MD3_MAXQPATH ];
+	char NAME[ AI_MD3_MAXQPATH ];
 
 	//! unknown
 	int32_t FLAGS;
@@ -121,10 +122,9 @@ struct Header
 } PACK_STRUCT;
 
 
-// ---------------------------------------------------------------------------
-/** \brief Data structure for the frame header
+// -------------------------------------------------------------------------------
+/** @brief Data structure for the frame header
  */
-// ---------------------------------------------------------------------------
 struct Frame
 {
 	//! minimum bounds
@@ -145,14 +145,13 @@ struct Frame
 } PACK_STRUCT;
 
 
-// ---------------------------------------------------------------------------
-/** \brief Data structure for the tag header
+// -------------------------------------------------------------------------------
+/** @brief Data structure for the tag header
  */
-// ---------------------------------------------------------------------------
 struct Tag
 {
 	//! name of the tag
-	unsigned char NAME[ AI_MD3_MAXQPATH ];
+	char NAME[ AI_MD3_MAXQPATH ];
 
 	//! Local tag origin and orientation
 	aiVector3D  origin;
@@ -161,17 +160,16 @@ struct Tag
 } PACK_STRUCT;
 
 
-// ---------------------------------------------------------------------------
-/** \brief Data structure for the surface header
+// -------------------------------------------------------------------------------
+/** @brief Data structure for the surface header
  */
-// ---------------------------------------------------------------------------
 struct Surface
 {
 	//! magic number
 	int32_t IDENT;
 
 	//! original name of the surface
-	unsigned char NAME[ AI_MD3_MAXQPATH ];
+	char NAME[ AI_MD3_MAXQPATH ];
 
 	//! unknown
 	int32_t FLAGS;
@@ -205,24 +203,22 @@ struct Surface
 	int32_t OFS_END;
 } PACK_STRUCT;
 
-// ---------------------------------------------------------------------------
-/** \brief Data structure for a shader
+// -------------------------------------------------------------------------------
+/** @brief Data structure for a shader defined in there
  */
-// ---------------------------------------------------------------------------
 struct Shader
 {
 	//! filename of the shader
-	unsigned char NAME[ AI_MD3_MAXQPATH ];
+	char NAME[ AI_MD3_MAXQPATH ];
 
 	//! index of the shader
 	uint32_t SHADER_INDEX;
 } PACK_STRUCT;
 
 
-// ---------------------------------------------------------------------------
-/** \brief Data structure for a triangle
+// -------------------------------------------------------------------------------
+/** @brief Data structure for a triangle
  */
-// ---------------------------------------------------------------------------
 struct Triangle
 {
 	//! triangle indices
@@ -230,10 +226,9 @@ struct Triangle
 } PACK_STRUCT;
 
 
-// ---------------------------------------------------------------------------
-/** \brief Data structure for an UV coord
+// -------------------------------------------------------------------------------
+/** @brief Data structure for an UV coord
  */
-// ---------------------------------------------------------------------------
 struct TexCoord
 {
 	//! UV coordinates
@@ -241,10 +236,9 @@ struct TexCoord
 } PACK_STRUCT;
 
 
-// ---------------------------------------------------------------------------
-/** \brief Data structure for a vertex
+// -------------------------------------------------------------------------------
+/** @brief Data structure for a vertex
  */
-// ---------------------------------------------------------------------------
 struct Vertex
 {
 	//! X/Y/Z coordinates
@@ -256,15 +250,14 @@ struct Vertex
 
 #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_afOut Pointer to an array of three floats to receive the result
+ *	@param p_iNormal Input normal vector in latitude/longitude form
+ *	@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)
 {
 	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
- *	\param p_vIn Input vector
- *	\param p_iOut Output normal
+// -------------------------------------------------------------------------------
+/**	@brief Pack a Q3 normal into 16bit latitute/longitude representation
+ *	@param p_vIn Input vector
+ *	@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 ) 
 {
 	// check for singularities

+ 354 - 152
code/MD3Loader.cpp

@@ -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"
 #ifndef ASSIMP_BUILD_NO_MD3_IMPORTER
 
 #include "MD3Loader.h"
-#include "MaterialSystem.h"
-#include "StringComparison.h"
 #include "ByteSwap.h"
 #include "SceneCombiner.h"
 #include "GenericProperty.h"
+#include "RemoveComments.h"
+#include "ParsingUtils.h"
 
 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
 MD3Importer::MD3Importer()
@@ -84,16 +219,16 @@ bool MD3Importer::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
 // ------------------------------------------------------------------------------------------------
 void MD3Importer::ValidateHeaderOffsets()
 {
-	// check magic number
+	// 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
+	// Check file format version
 	if (pcHeader->VERSION > 15)
 		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)
 		throw new ImportErrorException( "Invalid md3 file: NUM_SURFACES is 0");
 
@@ -109,24 +244,28 @@ void MD3Importer::ValidateHeaderOffsets()
 // ------------------------------------------------------------------------------------------------
 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);
 
-	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)	{
+	// 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("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)
-		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)
-		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)
-		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
 	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"));
 }
 
 // ------------------------------------------------------------------------------------------------
-// Read a multi-part Q3 player model
-bool MD3Importer::ReadMultipartFile()
+// Try to read the skin for a MD3 file
+void MD3Importer::ReadSkin(Q3Shader::SkinData& fill)
 {
-	std::string::size_type s = mFile.find_last_of('/');
-	if (s == std::string::npos) {
-		s = mFile.find_last_of('\\');
-	}
+	// skip any postfixes (e.g. lower_1.md3)
+	std::string::size_type s = filename.find_last_of('_');
 	if (s == std::string::npos) {
-		s = 0;
+		s = filename.find_last_of('.');
 	}
-	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);
+	ai_assert(s != std::string::npos);
 
-	if (filename == "lower.md3" || filename == "upper.md3" || filename == "head.md3"){
-		std::string lower = path + "lower.md3";
-		std::string upper = path + "upper.md3";
-		std::string head  = path + "head.md3";
+	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
+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;
@@ -189,35 +353,45 @@ bool MD3Importer::ReadMultipartFile()
 		nd->mName.Set("<M3D_Player>");
 
 		// ... and get them. We need all of them.
-		aiScene* scene_lower = batch.GetImport(lower);
-		if (!scene_lower)
-			throw new ImportErrorException("M3D: Failed to read multipart model, lower.md3 fails to load");
+		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;
+		}
 
-		aiScene* scene_upper = batch.GetImport(upper);
-		if (!scene_upper)
-			throw new ImportErrorException("M3D: Failed to read multipart model, upper.md3 fails to load");
+		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;
+		}
 
-		aiScene* scene_head  = batch.GetImport(head);
-		if (!scene_head)
-			throw new ImportErrorException("M3D: Failed to read multipart model, head.md3 fails to load");
+		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
-		std::vector<AttachmentInfo> attach;
 
 		// original root
 		attach.push_back(AttachmentInfo(scene_lower, nd));
 
 		// tag_torso
-		aiNode* tag_torso = scene_lower->mRootNode->FindNode("tag_torso");
+		tag_torso = scene_lower->mRootNode->FindNode("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));
 
 		// tag_head
-		aiNode* tag_head = scene_upper->mRootNode->FindNode("tag_head");
+		tag_head = scene_upper->mRootNode->FindNode("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));
 
@@ -228,6 +402,16 @@ bool MD3Importer::ReadMultipartFile()
 			AI_INT_MERGE_SCENE_RESOLVE_CROSS_ATTACHMENTS);
 
 		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;
 }
@@ -241,6 +425,20 @@ void MD3Importer::InternReadFile( const std::string& 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())
@@ -253,19 +451,19 @@ void MD3Importer::InternReadFile( const std::string& pFile,
 	if( file.get() == NULL)
 		throw new ImportErrorException( "Failed to open MD3 file " + pFile + ".");
 
-	// check whether the md3 file is large enough to contain
-	// at least the file header
+	// 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
+	// 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);
@@ -282,33 +480,38 @@ void MD3Importer::InternReadFile( const std::string& pFile,
 
 #endif
 
-	// validate the header
+	// Validate the file header
 	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);
 
-	// 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);
 
-	// allocate output storage
+	// 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];
 
-	// if an exception is thrown before the meshes are allocated ->
-	// otherwise the pointer value would be invalid and delete would crash
+	// 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);
+
+	// Read all surfaces from the file
 	unsigned int iNum = pcHeader->NUM_SURFACES;
 	unsigned int iNumMaterials = 0;
 	unsigned int iDefaultMatIndex = 0xFFFFFFFF;
 	while (iNum-- > 0)
 	{
 
+		// Ensure correct endianess
 #ifdef AI_BUILD_BIG_ENDIAN
 
 		AI_SWAP4(pcSurfaces->FLAGS);
@@ -325,26 +528,26 @@ void MD3Importer::InternReadFile( const std::string& pFile,
 
 #endif
 
-		// validate the surface
+		// Validate the surface header
 		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*)
 			(((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*)
 			(((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*)
 			(((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*)
 			(((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)
 		{
 			pcSurfaces = (BE_NCONST MD3::Surface*)(((uint8_t*)pcSurfaces) + pcSurfaces->OFS_END);
@@ -352,6 +555,7 @@ void MD3Importer::InternReadFile( const std::string& pFile,
 			continue;
 		}
 
+		// Ensure correct endianess
 #ifdef AI_BUILD_BIG_ENDIAN
 
 		for (uint32_t i = 0; i < pcSurfaces->NUM_VERTICES;++i)
@@ -373,7 +577,7 @@ void MD3Importer::InternReadFile( const std::string& pFile,
 
 #endif
 
-		// allocate the output mesh
+		// Allocate the output mesh
 		pScene->mMeshes[iNum] = new aiMesh();
 		aiMesh* pcMesh = pScene->mMeshes[iNum];
 		pcMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
@@ -386,7 +590,7 @@ void MD3Importer::InternReadFile( const std::string& pFile,
 		pcMesh->mTextureCoords[0]	= new aiVector3D[pcMesh->mNumVertices];
 		pcMesh->mNumUVComponents[0] = 2;
 
-		// fill in all triangles
+		// Fill in all triangles
 		unsigned int iCurrent = 0;
 		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;
 			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].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
+				// Convert the normal vector to uncompressed float3 format
 				LatLngNormalToVec3(pcVertices[pcTriangles->INDEXES[c]].NORMAL,
 					(float*)&pcMesh->mNormals[iCurrent]);
 
-				// read texture coordinates
+				// 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;
 			}
@@ -416,111 +620,109 @@ void MD3Importer::InternReadFile( const std::string& pFile,
 			pcTriangles++;
 		}
 
-		// get the first shader (= texture?) assigned to the surface
-		if (pcSurfaces->NUM_SHADER)
-		{
-			// 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,'/');
+		std::string _texture_name;
+		const char* texture_name = NULL, *header_name = pcHeader->NAME;
 
-			const char* szEndDir2 = ::strrchr((const char*)pcShaders->NAME,'\\');
-			if (!szEndDir2)szEndDir2 = ::strrchr((const char*)pcShaders->NAME,'/');
+		// 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 (szEndDir1 && szEndDir2)
-			{
-				// both of them are valid
-				const unsigned int iLen1 = (unsigned int)(szEndDir1 - (const char*)pcHeader->NAME);
-				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;
+		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;
+		}
+
+		const char* end2 = NULL;
+		if (texture_name) {
+
+			// 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,'/');
+
+			end2 = ::strrchr(texture_name,'\\');
+			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;
 				}
-			}
-			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 len2 = std::min (len1, (size_t)(end2 - texture_name ));
+
+				if (!ASSIMP_strincmp(texture_name,header_name,len2)) {
+					// Use the file name only
+					end2++;
 				}
-				else	{
-					DefaultLogger::get()->warn("Texture file name has zero length. Using default name");
-					szString.Set("dummy_texture.bmp");
+				else {
+					// Use the full path
+					end2 = (const char*)texture_name;
 				}
-				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);
+		MaterialHelper* pcHelper = new MaterialHelper();
 
-			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);
+		// 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));
 
-			aiString szName;
-			szName.Set(AI_DEFAULT_MATERIAL_NAME);
-			pcHelper->AddProperty(&szName,AI_MATKEY_NAME);
+		const int iMode = (int)aiShadingMode_Gouraud;
+		pcHelper->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL);
 
-			pScene->mMaterials[iNumMaterials] = (aiMaterial*)pcHelper;
-			pcMesh->mMaterialIndex = iNumMaterials++;
-		}
-		else
-		{
-			if (0xFFFFFFFF != iDefaultMatIndex)	{
-				pcMesh->mMaterialIndex = iDefaultMatIndex;
-			}
-			else
-			{
-				MaterialHelper* pcHelper = new MaterialHelper();
+		// 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);
 
-				// fill in a default material
-				int iMode = (int)aiShadingMode_Gouraud;
-				pcHelper->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL);
+		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);
 
-				aiColor3D clr;
-				clr.b = clr.g = clr.r = 0.6f;
-				pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_DIFFUSE);
-				pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_SPECULAR);
+		// use surface name + skin_name as material name
+		aiString name;
+		name.Set("MD3_[" + configSkinFile + "][" + pcSurfaces->NAME + "]");
+		pcHelper->AddProperty(&name,AI_MATKEY_NAME);
 
-				clr.b = clr.g = clr.r = 0.05f;
-				pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_AMBIENT);
+		pScene->mMaterials[iNumMaterials] = (aiMaterial*)pcHelper;
+		pcMesh->mMaterialIndex = iNumMaterials++;
+	
+		// Go to the next surface
+		pcSurfaces = (BE_NCONST MD3::Surface*)(((unsigned char*)pcSurfaces) + pcSurfaces->OFS_END);
+	}
 
-				pScene->mMaterials[iNumMaterials] = (aiMaterial*)pcHelper;
-				iDefaultMatIndex = pcMesh->mMaterialIndex = iNumMaterials++;
+	// 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);
 			}
 		}
-		// go to the next surface
-		pcSurfaces = (BE_NCONST MD3::Surface*)(((unsigned char*)pcSurfaces) + pcSurfaces->OFS_END);
 	}
 
 	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;
 
 	// 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.z);
 
-			// copy local origin
+			// 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
+			// 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];

+ 147 - 4
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
 #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"
 
-struct aiNode;
-
 #include "MD3FileData.h"
 namespace Assimp	{
 class MaterialHelper;
 
 using namespace MD3;
+namespace Q3Shader {
+
+// ---------------------------------------------------------------------------
+/** @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;
+};
 
 // ---------------------------------------------------------------------------
-/** Used to load MD3 files
+/** @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
 {
@@ -111,6 +239,12 @@ protected:
 	 */
 	bool ReadMultipartFile();
 
+	// -------------------------------------------------------------------
+	/** Try to read the skin for a MD3 file
+	 *  @param fill Receives output information
+	 */
+	void ReadSkin(Q3Shader::SkinData& fill);
+
 protected:
 
 	/** Configuration option: frame to be loaded */
@@ -119,6 +253,9 @@ protected:
 	/** Configuration option: process multi-part files */
 	bool configHandleMP;
 
+	/** Configuration option: name of skin file to be read */
+	std::string configSkinFile;
+
 	/** Header of the MD3 file */
 	BE_NCONST MD3::Header* pcHeader;
 
@@ -131,6 +268,12 @@ protected:
 	/** Current file name */
 	std::string mFile;
 
+	/** Current base directory  */
+	std::string path;
+
+	/** Pure file we're currently reading */
+	std::string filename;
+
 	/** Output scene to be filled */
 	aiScene* mScene;
 

+ 2 - 2
code/MD5Parser.h

@@ -370,7 +370,7 @@ private:
 	inline bool SkipLine( const char* in, const char** out)
 	{
 		++lineNumber;
-		return ::SkipLine(in,out);
+		return Assimp::SkipLine(in,out);
 	}
 	// -------------------------------------------------------------------
 	inline bool SkipLine( )
@@ -407,7 +407,7 @@ private:
 	// -------------------------------------------------------------------
 	inline bool SkipSpaces( )
 	{
-		return ::SkipSpaces((const char**)&buffer);
+		return Assimp::SkipSpaces((const char**)&buffer);
 	}
 
 	char* buffer;

+ 28 - 4
code/ParsingUtils.h

@@ -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
 #define AI_PARSING_UTILS_H_INC
 
+#include "StringComparison.h"
+namespace Assimp {
+
 // ---------------------------------------------------------------------------------
 template <class char_t>
 AI_FORCE_INLINE bool IsSpace( const char_t in)
@@ -129,7 +134,7 @@ AI_FORCE_INLINE bool IsNumeric( char_t in)
 	return in >= '0' && in <= '9' || '-' == in || '+' == in;
 }
 // ---------------------------------------------------------------------------------
-AI_FORCE_INLINE bool TokenMatch(const 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]))
 	{
@@ -139,9 +144,14 @@ AI_FORCE_INLINE bool TokenMatch(const char*& in, const char* token, unsigned int
 	return false;
 }
 // ---------------------------------------------------------------------------------
-AI_FORCE_INLINE bool TokenMatch(char*& in, const char* token, unsigned int len)
+/** @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 (!::strncmp(token,in,len) && IsSpaceOrNewLine(in[len]))
+	if (!ASSIMP_strincmp(token,in,len) && IsSpaceOrNewLine(in[len]))
 	{
 		in += len+1;
 		return true;
@@ -149,9 +159,23 @@ AI_FORCE_INLINE bool TokenMatch(char*& in, const char* token, unsigned int len)
 	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)
 {
 	SkipSpaces(&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

+ 2 - 2
code/SMDLoader.h

@@ -360,7 +360,7 @@ protected:
 	// -------------------------------------------------------------------
 	inline bool SkipLine( const char* in, const char** out)
 	{
-		::SkipLine(in,out);
+		Assimp::SkipLine(in,out);
 		++iLineNumber;
 		return true;
 	}
@@ -368,7 +368,7 @@ protected:
 	inline bool SkipSpacesAndLineEnd( const char* in, const char** out)
 	{
 		++iLineNumber;
-		return ::SkipSpacesAndLineEnd(in,out);
+		return Assimp::SkipSpacesAndLineEnd(in,out);
 	}
 
 private:

+ 14 - 1
include/aiConfig.h

@@ -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. 
  * 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"
 
 
 // ---------------------------------------------------------------------------

+ 94 - 23
include/aiMaterial.h

@@ -441,6 +441,54 @@ enum aiTextureFlags
 	//! @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"
 
 // ---------------------------------------------------------------------------
@@ -688,18 +736,33 @@ extern "C" {
  *  Integer property. 1 to enable wireframe mode for rendering.
  *  A material with this property set to 1 should appear as wireframe, even
  *  if the scene is rendered solid.
- * <br>
- * <b>Type:</b> int <br>
- * <b>Default value:</b> <tt>0</tt>
+ *  <br>
+ *  <b>Type:</b> int <br>
+ *  <b>Default value:</b> <tt>0</tt>
 */
 #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
- *  Defines the base opacity of the material. This value defines how
- *  transparent the material actually is. However, in order to get absolutely
- *  correct results you'll also need to evaluate the
- *  #AI_MATKEY_COLOR_TRANSPARENT property.
+ *  Defines the base opacity of the material. To get the opacity value for
+ *  a particular channel, this value is multiplied with the corresponding
+ *  channel of the #AI_MATKEY_COLOR_TRANSPARENT property. The final value
+ *  is fed in the blend function defined by the #AI_MATKEY_BLEND_FUNC
+ *  property.
  * <br>
  * <b>Type:</b> float<br>
  * <b>Default value:</b> <tt>1.0f</tt><br>
@@ -755,7 +818,8 @@ extern "C" {
 
 // ---------------------------------------------------------------------------
 /** @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>Default value:</b> <tt>0.0f|0.0f|0.0f|1.0f     </tt>    
 */
@@ -763,39 +827,46 @@ extern "C" {
 
 /** @def AI_MATKEY_COLOR_AMBIENT
  *  Declares the amount of ambient light emitted from
- *  the surface of this object.  <br>
- * <b>Type:</b> color (#aiColor4D or #aiColor3D)     <br>
- * <b>Default value:</b> <tt>0.0f|0.0f|0.0f|1.0f     </tt>       
+ *  the surface of this object. If stored as 4-component color, 
+ *  the alpha channel is ignored.<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_AMBIENT "$clr.ambient",0,0
 
 /** @def AI_MATKEY_COLOR_SPECULAR
  *  Declares the color of light specularly reflected from
- *  the surface of this object. <br>
- * <b>Type:</b> color (#aiColor4D or #aiColor3D)     <br>
- * <b>Default value:</b> <tt>0.0f|0.0f|0.0f|1.0f     </tt>
+ *  the surface of this object. If stored as 4-component color, the 
+ *  alpha channel is ignored.<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_SPECULAR "$clr.specular",0,0
 
 /** @def AI_MATKEY_COLOR_EMISSIVE
  *  Declares the amount of light emitted from the
- *  surface of this object. <br>
- * <b>Type:</b> color (#aiColor4D or #aiColor3D)     <br>
- * <b>Default value:</b> <tt>0.0f|0.0f|0.0f|1.0f     </tt>
+ *  surface of this object. If stored as 4-component color, the alpha
+ *  channel is ignored.<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_EMISSIVE "$clr.emissive",0,0
 
 /** @def AI_MATKEY_COLOR_TRANSPARENT
- *  Defines the transparent base color of the material. <br>
- * <b>Type:</b> color (#aiColor4D or #aiColor3D)        <br>
- * <b>Default value:</b> <tt>0.0f|0.0f|0.0f|1.0f        </tt>
+ *  Defines the transparent base color of the material. If stored as 
+ *  4-component color, the alpha channel is ignored. This 
+ *  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
 
 /** @def AI_MATKEY_COLOR_REFLECTIVE
- *  Declares the color of a perfect mirror reflection. <br>
- * <b>Type:</b> color (#aiColor4D or #aiColor3D)       <br>
- * <b>Default value:</b> <tt>0.0f|0.0f|0.0f|1.0f       </tt>
+ *  Declares the color of a perfect mirror reflection. If stored as
+ *  4-component color, the alpha channel is ignored.<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_REFLECTIVE "$clr.reflective",0,0