浏览代码

Bufixes to the ASE loader, added "RemoveRedundantMaterials"-Step. Rewrite debug output.

git-svn-id: https://assimp.svn.sourceforge.net/svnroot/assimp/trunk@74 67173fc5-114c-0410-ac8e-9d2fd5bffc1f
aramis_acg 17 年之前
父节点
当前提交
8aa56a62c2

+ 7 - 9
code/ASELoader.cpp

@@ -105,7 +105,7 @@ bool ASEImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
 void ASEImporter::InternReadFile( 
 	const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler)
 {
-	boost::scoped_ptr<IOStream> file( pIOHandler->Open( pFile, "rt"));
+	boost::scoped_ptr<IOStream> file( pIOHandler->Open( pFile, "rb"));
 
 	// Check whether we can read from the file
 	if( file.get() == NULL)
@@ -846,9 +846,9 @@ void ASEImporter::ConvertMeshes(ASE::Mesh& mesh, std::vector<aiMesh*>& avOutMesh
 							}
 							++iBase;
 						}
-						p_pcOut->mFaces[q].mIndices[0] = iBase-2;
-						p_pcOut->mFaces[q].mIndices[1] = iBase-1;
-						p_pcOut->mFaces[q].mIndices[2] = iBase;
+						p_pcOut->mFaces[q].mIndices[0] = iBase-3;
+						p_pcOut->mFaces[q].mIndices[1] = iBase-2;
+						p_pcOut->mFaces[q].mIndices[2] = iBase-1;
 					}
 				}
 				// convert texture coordinates
@@ -1071,14 +1071,13 @@ void ASEImporter::BuildMaterialIndices()
 	ai_assert(NULL != pcScene);
 
 	// iterate through all materials and check whether we need them
-	unsigned int iNum = 0;
 	for (unsigned int iMat = 0; iMat < this->mParser->m_vMaterials.size();++iMat)
 	{
 		if (this->mParser->m_vMaterials[iMat].bNeed)
 		{
 			// convert it to the aiMaterial layout
 			this->ConvertMaterial(this->mParser->m_vMaterials[iMat]);
-			iNum++;
+			++pcScene->mNumMaterials;
 		}
 		for (unsigned int iSubMat = 0; iSubMat < this->mParser->m_vMaterials[
 			iMat].avSubMaterials.size();++iSubMat)
@@ -1087,17 +1086,16 @@ void ASEImporter::BuildMaterialIndices()
 			{
 				// convert it to the aiMaterial layout
 				this->ConvertMaterial(this->mParser->m_vMaterials[iMat].avSubMaterials[iSubMat]);
-				iNum++;
+				++pcScene->mNumMaterials;
 			}
 		}
 	}
 
 	// allocate the output material array
-	pcScene->mNumMaterials = iNum;
 	pcScene->mMaterials = new aiMaterial*[pcScene->mNumMaterials];
 	Dot3DS::Material** pcIntMaterials = new Dot3DS::Material*[pcScene->mNumMaterials];
 
-	iNum = 0;
+	unsigned int iNum = 0;
 	for (unsigned int iMat = 0; iMat < this->mParser->m_vMaterials.size();++iMat)
 	{
 		if (this->mParser->m_vMaterials[iMat].bNeed)

+ 17 - 3
code/ASEParser.cpp

@@ -83,7 +83,11 @@ using namespace Assimp::ASE;
 	{ \
 		return; \
 	} \
-	else if(IsLineEnd(*this->m_szFile))++this->iLineNumber; \
+	if(IsLineEnd(*this->m_szFile) && !bLastWasEndLine) \
+	{ \
+		++this->iLineNumber; \
+		bLastWasEndLine = true; \
+	} else bLastWasEndLine = false; \
 	++this->m_szFile; 
 
 #define AI_ASE_HANDLE_SECTION(iDepth, level, msg) \
@@ -102,7 +106,11 @@ using namespace Assimp::ASE;
 		this->LogError("Encountered unexpected EOL while parsing a " msg \
 		" chunk (Level " level ")"); \
 	} \
-	else if(IsLineEnd(*this->m_szFile))++this->iLineNumber; \
+	if(IsLineEnd(*this->m_szFile) && !bLastWasEndLine) \
+		{ \
+		++this->iLineNumber; \
+		bLastWasEndLine = true; \
+	} else bLastWasEndLine = false; \
 	++this->m_szFile; 
 
 // ------------------------------------------------------------------------------------------------
@@ -120,6 +128,7 @@ Parser::Parser (const char* szFile)
 	this->iLastFrame = 0;
 	this->iFrameSpeed = 30;    // use 30 as default value for this property
 	this->iTicksPerFrame = 1;  // use 1 as default value for this property
+	this->bLastWasEndLine = false; // need to handle \r\n seqs due to binary file mapping
 }
 // ------------------------------------------------------------------------------------------------
 void Parser::LogWarning(const char* szWarn)
@@ -177,7 +186,12 @@ bool Parser::SkipToNextToken()
 		char me = *this->m_szFile;
 
 		// increase the line number counter if necessary
-		if (IsLineEnd(me))++this->iLineNumber;
+		if (IsLineEnd(me) && !bLastWasEndLine)
+		{
+			++this->iLineNumber;
+			bLastWasEndLine = true;
+		}
+		else bLastWasEndLine = false;
 		if ('*' == me || '}' == me || '{' == me)return true;
 		else if ('\0' == me)return false;
 

+ 3 - 0
code/ASEParser.h

@@ -550,6 +550,9 @@ public:
 
 	//! Ticks per frame
 	unsigned int iTicksPerFrame;
+
+	//! true if the last character read was an end-line character
+	bool bLastWasEndLine;
 };
 
 

+ 32 - 2
code/Importer.cpp

@@ -130,6 +130,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #if (!defined AI_BUILD_NO_IMPROVECACHELOCALITY_PROCESS)
 #	include "ImproveCacheLocality.h"
 #endif
+#if (!defined AI_BUILD_NO_REMOVE_REDUNDANTMATERIALS_PROCESS)
+#	include "RemoveRedundantMaterials.h"
+#endif
+
 
 using namespace Assimp;
 
@@ -142,7 +146,8 @@ Importer::Importer() :
 {
 	// allocate a default IO handler
 	mIOHandler = new DefaultIOSystem;
-	mIsDefaultHandler = true;
+	mIsDefaultHandler = true; 
+	bExtraVerbose = false; // disable extra verbose mode by default
 
 	// add an instance of each worker class here
 #if (!defined AI_BUILD_NO_X_IMPORTER)
@@ -182,7 +187,10 @@ Importer::Importer() :
 	// add an instance of each post processing step here in the order 
 	// of sequence it is executed
 #if (!defined AI_BUILD_NO_VALIDATEDS_PROCESS)
-	mPostProcessingSteps.push_back( new ValidateDSProcess());
+	mPostProcessingSteps.push_back( new ValidateDSProcess()); // must be first
+#endif
+#if (!defined AI_BUILD_NO_REMOVE_REDUNDANTMATERIALS_PROCESS)
+	mPostProcessingSteps.push_back( new RemoveRedundantMatsProcess());
 #endif
 #if (!defined AI_BUILD_NO_TRIANGULATE_PROCESS)
 	mPostProcessingSteps.push_back( new TriangulateProcess());
@@ -328,6 +336,14 @@ const aiScene* Importer::ReadFile( const std::string& pFile, unsigned int pFlags
 	// if successful, apply all active post processing steps to the imported data
 	if( mScene)
 	{
+		if (bExtraVerbose)
+		{
+			pFlags |= aiProcess_ValidateDataStructure;
+
+			// use the MSB to tell the ValidateDS-Step that e're in extra verbose mode
+			// TODO: temporary solution, clean up later
+			mScene->mFlags |= 0x80000000; 
+		}
 		for( unsigned int a = 0; a < mPostProcessingSteps.size(); a++)
 		{
 			BaseProcess* process = mPostProcessingSteps[a];
@@ -336,7 +352,21 @@ const aiScene* Importer::ReadFile( const std::string& pFile, unsigned int pFlags
 				process->ExecuteOnScene( this );
 			}
 			if( !mScene)break; // error string has already been set ...
+
+			// if the extra verbose mode is active execute the
+			// VaidateDataStructureStep again after each step
+			if (bExtraVerbose && a)
+			{
+				DefaultLogger::get()->debug("Extra verbose: revalidating data structures");
+				((ValidateDSProcess*)mPostProcessingSteps[0])->ExecuteOnScene (this);
+				if( !mScene)
+				{
+					DefaultLogger::get()->error("Extra verbose: failed to revalidate data structures");
+					break; // error string has already been set ...
+				}
+			}
 		}
+		if (bExtraVerbose)mScene->mFlags &= ~0x80000000; 
 	}
 
 	// if failed, extract the error string

+ 14 - 11
code/ImproveCacheLocality.cpp

@@ -63,6 +63,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 using namespace Assimp;
 
+#if _MSC_VER >= 1400
+#	define sprintf sprintf_s
+#endif
+
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
 ImproveCacheLocalityProcess::ImproveCacheLocalityProcess()
@@ -93,14 +97,14 @@ void ImproveCacheLocalityProcess::Execute( aiScene* pScene)
 
 	for( unsigned int a = 0; a < pScene->mNumMeshes; a++)
 	{
-		this->ProcessMesh( pScene->mMeshes[a]);
+		this->ProcessMesh( pScene->mMeshes[a],a);
 	}
 	DefaultLogger::get()->debug("ImproveCacheLocalityProcess finished. ");
 }
 
 // ------------------------------------------------------------------------------------------------
 // Improves the cache coherency of a specific mesh
-void ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh)
+void ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshNum)
 {
 	ai_assert(NULL != pMesh);
 
@@ -162,12 +166,7 @@ void ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh)
 		}
 	}
 	delete[] piFIFOStack;
-
 	float fACMR = (float)iCacheMisses / pMesh->mNumFaces;
-	char szBuff[16];
-	sprintf(szBuff,"%f",fACMR);
-	DefaultLogger::get()->debug("Input ACMR: " + std::string(szBuff));
-
 
 	// first we need to build a vertex-triangle adjacency list
 	VertexTriangleAdjacency adj(pMesh->mFaces,pMesh->mNumFaces, pMesh->mNumVertices,true);
@@ -347,11 +346,15 @@ void ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh)
 			}
 		}
 	}
+	if (!DefaultLogger::isNullLogger())
+	{
+		char szBuff[128]; // should be sufficiently large in every case
+		float fACMR2 = (float)iCacheMisses / pMesh->mNumFaces;
 
-	fACMR = (float)iCacheMisses / pMesh->mNumFaces;
-	sprintf(szBuff,"%f",fACMR);
-	DefaultLogger::get()->debug("Output ACMR: " + std::string(szBuff));
-
+		sprintf(szBuff,"Mesh %i | ACMR in: %f out: %f | ~%.1f%%",meshNum,fACMR,fACMR2,
+			((fACMR - fACMR2) / fACMR) * 100.f);
+		DefaultLogger::get()->info(szBuff);
+	}
 	// sort the output index buffer back to the input array
 	piCSIter = piIBOutput;
 

+ 2 - 1
code/ImproveCacheLocality.h

@@ -89,8 +89,9 @@ protected:
 	// -------------------------------------------------------------------
 	/** Executes the postprocessing step on the given mesh
 	 * @param pMesh The mesh to process.
+	 * @param meshNum Index of the mesh to process
 	 */
-	void ProcessMesh( aiMesh* pMesh);
+	void ProcessMesh( aiMesh* pMesh, unsigned int meshNum);
 
 
 	//! Configuration parameter: specifies the size of the cache to

+ 44 - 7
code/JoinVerticesProcess.cpp

@@ -53,6 +53,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 using namespace Assimp;
 
+#if _MSC_VER >= 1400
+#	define sprintf sprintf_s
+#endif
+
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
 JoinVerticesProcess::JoinVerticesProcess()
@@ -80,22 +84,44 @@ void JoinVerticesProcess::Execute( aiScene* pScene)
 {
 	DefaultLogger::get()->debug("JoinVerticesProcess begin");
 
-	bool bHas = false;
+	// get the total number of vertices BEFORE the step is executed
+	int iNumOldVertices = 0;
 	for( unsigned int a = 0; a < pScene->mNumMeshes; a++)
 	{
-		if(	this->ProcessMesh( pScene->mMeshes[a]))
-			bHas = true;
+		iNumOldVertices +=	pScene->mMeshes[a]->mNumVertices;
+	}
+
+	// execute the step
+	int iNumVertices = 0;
+	for( unsigned int a = 0; a < pScene->mNumMeshes; a++)
+	{
+		iNumVertices +=	this->ProcessMesh( pScene->mMeshes[a],a);
+	}
+	// if logging is active, print detailled statistics
+	if (!DefaultLogger::isNullLogger())
+	{
+		if (iNumOldVertices == iNumVertices)DefaultLogger::get()->debug("JoinVerticesProcess finished ");
+		else
+		{
+			char szBuff[128]; // should be sufficiently large in every case
+			sprintf(szBuff,"JoinVerticesProcess finished | Verts in: %i out: %i | ~%.1f%%",
+				iNumOldVertices,
+				iNumVertices,
+				((iNumOldVertices - iNumVertices) / (float)iNumOldVertices) * 100.f);
+			DefaultLogger::get()->info(szBuff);
+		}
 	}
-	if (bHas)DefaultLogger::get()->info("JoinVerticesProcess finished. Found vertices to join");
-	else DefaultLogger::get()->debug("JoinVerticesProcess finished. There was nothing to do.");
 }
 
 // ------------------------------------------------------------------------------------------------
 // Unites identical vertices in the given mesh
-bool JoinVerticesProcess::ProcessMesh( aiMesh* pMesh)
+int JoinVerticesProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex)
 {
 	// helper structure to hold all the data a single vertex can possibly have
 	typedef struct Vertex vertex;
+
+	if (!pMesh->HasPositions() || !pMesh->HasFaces())
+		return 0;
 	
 	struct Vertex
 	{
@@ -216,6 +242,17 @@ bool JoinVerticesProcess::ProcessMesh( aiMesh* pMesh)
 		}
 	}
 
+	if (!DefaultLogger::isNullLogger())
+	{
+		char szBuff[128]; // should be sufficiently large in every case
+		sprintf(szBuff,"Mesh %i | Verts in: %i out: %i | ~%.1f%%",
+			meshIndex,
+			pMesh->mNumVertices,
+			uniqueVertices.size(),
+			((pMesh->mNumVertices - uniqueVertices.size()) / (float)pMesh->mNumVertices) * 100.f);
+		DefaultLogger::get()->info(szBuff);
+	}
+
 	// replace vertex data with the unique data sets
 	pMesh->mNumVertices = (unsigned int)uniqueVertices.size();
 	// Position
@@ -310,5 +347,5 @@ bool JoinVerticesProcess::ProcessMesh( aiMesh* pMesh)
 		bone->mWeights = new aiVertexWeight[bone->mNumWeights];
 		memcpy( bone->mWeights, &newWeights[0], bone->mNumWeights * sizeof( aiVertexWeight));
 	}
-	return (iOldVerts != pMesh->mNumVertices);
+	return pMesh->mNumVertices;
 }

+ 2 - 1
code/JoinVerticesProcess.h

@@ -90,8 +90,9 @@ protected:
 	// -------------------------------------------------------------------
 	/** Unites identical vertices in the given mesh.
 	 * @param pMesh The mesh to process.
+	 * @param meshIndex Index of the mesh to process
 	 */
-	bool ProcessMesh( aiMesh* pMesh);
+	int ProcessMesh( aiMesh* pMesh, unsigned int meshIndex);
 
 	// -------------------------------------------------------------------
 	/** Little helper function to calculate the quadratic difference 

+ 81 - 4
code/MaterialSystem.cpp

@@ -47,6 +47,67 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 using namespace Assimp;
 
 
+// ------------------------------------------------------------------------------------------------
+// hashing function taken from 
+// http://www.azillionmonkeys.com/qed/hash.html
+// (incremental version of the hashing function)
+// (stdint.h should have been been included here)
+#undef get16bits
+#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \
+  || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__)
+#define get16bits(d) (*((const uint16_t *) (d)))
+#endif
+
+#if !defined (get16bits)
+#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)\
+                       +(uint32_t)(((const uint8_t *)(d))[0]) )
+#endif
+
+// ------------------------------------------------------------------------------------------------
+uint32_t SuperFastHash (const char * data, int len, uint32_t hash = 0) {
+uint32_t tmp;
+int rem;
+
+    if (len <= 0 || data == NULL) return 0;
+
+    rem = len & 3;
+    len >>= 2;
+
+    /* Main loop */
+    for (;len > 0; len--) {
+        hash  += get16bits (data);
+        tmp    = (get16bits (data+2) << 11) ^ hash;
+        hash   = (hash << 16) ^ tmp;
+        data  += 2*sizeof (uint16_t);
+        hash  += hash >> 11;
+    }
+
+    /* Handle end cases */
+    switch (rem) {
+        case 3: hash += get16bits (data);
+                hash ^= hash << 16;
+                hash ^= data[sizeof (uint16_t)] << 18;
+                hash += hash >> 11;
+                break;
+        case 2: hash += get16bits (data);
+                hash ^= hash << 11;
+                hash += hash >> 17;
+                break;
+        case 1: hash += *data;
+                hash ^= hash << 10;
+                hash += hash >> 1;
+    }
+
+    /* Force "avalanching" of final 127 bits */
+    hash ^= hash << 3;
+    hash += hash >> 5;
+    hash ^= hash << 4;
+    hash += hash >> 17;
+    hash ^= hash << 25;
+    hash += hash >> 6;
+
+    return hash;
+}
 // ------------------------------------------------------------------------------------------------
 aiReturn aiGetMaterialProperty(const aiMaterial* pMat, 
 	const char* pKey,
@@ -230,13 +291,30 @@ aiReturn aiGetMaterialString(const aiMaterial* pMat,
 	return AI_FAILURE;
 }
 // ------------------------------------------------------------------------------------------------
+uint32_t MaterialHelper::ComputeHash()
+{
+	uint32_t hash = 1503; // magic start value, choosen to be my birthday :-)
+	for (unsigned int i = 0; i < this->mNumProperties;++i)
+	{
+		aiMaterialProperty* prop;
+
+		// NOTE: We need to exclude the material name from the hash
+		if ((prop = this->mProperties[i]) && 0 != ::strcmp(prop->mKey->data,AI_MATKEY_NAME)) 
+		{
+			hash = SuperFastHash(prop->mKey->data,prop->mKey->length,hash);
+			hash = SuperFastHash(prop->mData,prop->mDataLength,hash);
+		}
+	}
+	return hash;
+}
+// ------------------------------------------------------------------------------------------------
 aiReturn MaterialHelper::RemoveProperty (const char* pKey)
 {
 	ai_assert(NULL != pKey);
 
 	for (unsigned int i = 0; i < this->mNumProperties;++i)
 	{
-		if (NULL != this->mProperties[i])
+		if (this->mProperties[i]) // just for safety
 		{
 			if (0 == ASSIMP_stricmp( this->mProperties[i]->mKey->data, pKey ))
 			{
@@ -272,7 +350,7 @@ aiReturn MaterialHelper::AddBinaryProperty (const void* pInput,
 	unsigned int iOutIndex = 0xFFFFFFFF;
 	for (unsigned int i = 0; i < this->mNumProperties;++i)
 	{
-		if (NULL != this->mProperties[i])
+		if (this->mProperties[i])
 		{
 			if (0 == ASSIMP_stricmp( this->mProperties[i]->mKey->data, pKey ))
 			{
@@ -304,8 +382,7 @@ aiReturn MaterialHelper::AddBinaryProperty (const void* pInput,
 		return AI_SUCCESS;
 	}
 
-	// resize the array ... allocate
-	// storage for 5 other properties
+	// resize the array ... allocate storage for 5 other properties
 	if (this->mNumProperties == this->mNumAllocated)
 	{
 		unsigned int iOld = this->mNumAllocated;

+ 11 - 0
code/MaterialSystem.h

@@ -104,6 +104,17 @@ public:
 	 */
 	aiReturn RemoveProperty (const char* pKey);
 
+
+	// -------------------------------------------------------------------
+	/** Computes a hash (hopefully unique) from all material properties
+	 *  The hash value must be updated after material properties have
+	 *  been changed.
+	 *
+	 *  \return Unique hash
+	 */
+	uint32_t ComputeHash();
+
+
 	// -------------------------------------------------------------------
 	/** Copy the property list of a material
 	 *  \param pcDest Destination material

+ 155 - 0
code/RemoveRedundantMaterials.cpp

@@ -0,0 +1,155 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (ASSIMP)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2008, ASSIMP Development Team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms, 
+with or without modification, are permitted provided that the following 
+conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the ASSIMP team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the ASSIMP Development Team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+/** @file Implementation of the "RemoveRedundantMaterials" post processing step 
+*/
+
+// internal headers
+#include "RemoveRedundantMaterials.h"
+#include "MaterialSystem.h"
+
+// public ASSIMP headers
+#include "../include/DefaultLogger.h"
+#include "../include/aiPostProcess.h"
+#include "../include/aiMesh.h"
+#include "../include/aiScene.h"
+
+using namespace Assimp;
+
+#if _MSC_VER >= 1400
+#	define sprintf sprintf_s
+#endif
+
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by Importer
+RemoveRedundantMatsProcess::RemoveRedundantMatsProcess()
+{
+}
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+RemoveRedundantMatsProcess::~RemoveRedundantMatsProcess()
+{
+	// nothing to do here
+}
+// -------------------------------------------------------------------
+// Returns whether the processing step is present in the given flag field.
+bool RemoveRedundantMatsProcess::IsActive( unsigned int pFlags) const
+{
+	return (pFlags & aiProcess_RemoveRedundantMaterials) != 0;
+}
+// -------------------------------------------------------------------
+// Executes the post processing step on the given imported data.
+void RemoveRedundantMatsProcess::Execute( aiScene* pScene)
+{
+	DefaultLogger::get()->debug("RemoveRedundantMatsProcess begin");
+
+	unsigned int iCnt = 0;
+	if (pScene->mNumMaterials)
+	{
+		// TODO: reimplement this algorithm to work in-place
+
+		unsigned int* aiMappingTable = new unsigned int[pScene->mNumMaterials];
+		unsigned int iNewNum = 0;
+
+		// iterate through all materials and calculate a hash for them
+		// store all hashes in a list and so a quick search whether
+		// we do already have a specific hash. This allows us to
+		// determine which materials are identical.
+		uint32_t* aiHashes;
+		aiHashes = new uint32_t[pScene->mNumMaterials];
+		for (unsigned int i = 0; i < pScene->mNumMaterials;++i)
+		{
+			uint32_t me = aiHashes[i] = ((MaterialHelper*)pScene->mMaterials[i])->ComputeHash();
+			for (unsigned int a = 0; a < i;++a)
+			{
+				if (me == aiHashes[a])
+				{				
+					++iCnt;
+					me = 0;
+					aiMappingTable[i] = aiMappingTable[a];
+					delete pScene->mMaterials[i];
+					break;
+				}
+			}
+			if (me)
+			{
+				aiMappingTable[i] = iNewNum++;
+			}
+		}
+		if (iCnt)
+		{
+			// build an output material list
+			aiMaterial** ppcMaterials = new aiMaterial*[iNewNum];
+			::memset(ppcMaterials,0,sizeof(void*)*iNewNum); 
+			for (unsigned int p = 0; p < pScene->mNumMaterials;++p)
+			{
+				// generate new names for all modified materials
+				const unsigned int idx = aiMappingTable[p]; 
+				if (!ppcMaterials[idx]) 
+				{
+					aiString sz;
+					sz.length = ::sprintf(sz.data,"aiMaterial #%i",p);
+					ppcMaterials[idx] = pScene->mMaterials[p];
+					((MaterialHelper*)pScene->mMaterials[p])->AddProperty(&sz,AI_MATKEY_NAME);
+				}
+			}
+			// update all material indices
+			for (unsigned int p = 0; p < pScene->mNumMeshes;++p)
+			{
+				aiMesh* mesh = pScene->mMeshes[p];
+				mesh->mMaterialIndex = aiMappingTable[mesh->mMaterialIndex];
+			}
+			// delete the old material list
+			delete[] pScene->mMaterials;
+			pScene->mMaterials = ppcMaterials;
+			pScene->mNumMaterials = iNewNum;
+		}
+		// delete temporary storage
+		delete[] aiHashes;
+		delete[] aiMappingTable;
+	}
+	if (!iCnt)DefaultLogger::get()->debug("RemoveRedundantMatsProcess finished ");
+	else 
+	{
+		char szBuffer[128]; // should be sufficiently large
+		::sprintf(szBuffer,"RemoveRedundantMatsProcess finished. Found %i redundant materials",iCnt);
+		DefaultLogger::get()->info(szBuffer);
+	}
+}

+ 85 - 0
code/RemoveRedundantMaterials.h

@@ -0,0 +1,85 @@
+/*
+Open Asset Import Library (ASSIMP)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2008, ASSIMP Development Team
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms, 
+with or without modification, are permitted provided that the 
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the ASSIMP team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the ASSIMP Development Team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file Defines a post processing step to remove redundant materials */
+#ifndef AI_REMOVEREDUNDANTMATERIALS_H_INC
+#define AI_REMOVEREDUNDANTMATERIALS_H_INC
+
+#include "BaseProcess.h"
+#include "../include/aiMesh.h"
+
+class RemoveRedundantMatsTest;
+namespace Assimp
+	{
+
+// ---------------------------------------------------------------------------
+/** RemoveRedundantMatsProcess: Class to remove redundant materials
+*/
+class ASSIMP_API RemoveRedundantMatsProcess : public BaseProcess
+{
+	friend class Importer;
+	friend class ::RemoveRedundantMatsTest; // grant the unit test full access to us
+
+protected:
+	/** Constructor to be privately used by Importer */
+	RemoveRedundantMatsProcess();
+
+	/** Destructor, private as well */
+	~RemoveRedundantMatsProcess();
+
+public:
+	// -------------------------------------------------------------------
+	/** Returns whether the processing step is present in the given flag field.
+	* @param pFlags The processing flags the importer was called with. A bitwise
+	*   combination of #aiPostProcessSteps.
+	* @return true if the process is present in this flag fields, false if not.
+	*/
+	bool IsActive( unsigned int pFlags) const;
+
+	// -------------------------------------------------------------------
+	/** Executes the post processing step on the given imported data.
+	* At the moment a process is not supposed to fail.
+	* @param pScene The imported data to work at.
+	*/
+	void Execute( aiScene* pScene);
+};
+}; // end of namespace Assimp
+
+#endif // !!AI_REMOVEREDUNDANTMATERIALS_H_INC

+ 4 - 1
code/ValidateDataStructure.cpp

@@ -293,7 +293,10 @@ void ValidateDSProcess::Validate( const aiMesh* pMesh)
 				{
 					this->ReportError("aiMesh::mFaces[%i]::mIndices[%i] is out of range",i,a);
 				}
-				if (abRefList[face.mIndices[a]])
+				// the MSB flag is temporarily used by the extra verbose
+				// mode to tell us that the JoinVerticesProcess might have 
+				// been executed already.
+				if ( !(this->mScene->mFlags & 0x80000000 ) && abRefList[face.mIndices[a]])
 				{
 					this->ReportError("aiMesh::mVertices[%i] is referenced twice - second "
 						"time by aiMesh::mFaces[%i]::mIndices[%i]",face.mIndices[a],i,a);

+ 9 - 1
include/aiPostProcess.h

@@ -154,7 +154,15 @@ enum aiPostProcessSteps
 	 * basing on the algorithm described in this paper:
 	 * http://www.cs.princeton.edu/gfx/pubs/Sander_2007_%3ETR/tipsy.pdf
 	 */
-	aiProcess_ImproveCacheLocality = 0x1600
+	aiProcess_ImproveCacheLocality = 0x800,
+
+	/** Searches for redundant materials and removes them.
+	 *
+	 * This is especially useful in combination with the PretransformVertices 
+	 * and OptimizeGraph steps. Both steps join small meshes, but they
+	 * can't do that if two meshes have different materials.
+	 */
+	aiProcess_RemoveRedundantMaterials = 0x1000,
 };
 
 // ---------------------------------------------------------------------------

+ 13 - 0
include/assimp.hpp

@@ -198,6 +198,16 @@ public:
 	inline const aiScene* GetScene()
 		{return this->mScene;}
 
+
+	// -------------------------------------------------------------------
+	/** Enables the "extra verbose" mode. In this mode the data 
+	* structure is validated after each post-process step to make sure
+	* all steps behave consequently in the same manner when modifying
+	* data structures.
+	*/
+	inline void SetExtraVerbose(bool bDo)
+		{this->bExtraVerbose = bDo;}
+
 private:
 
 	/** Empty copy constructor. */
@@ -222,6 +232,9 @@ protected:
 
 	/** The error description, if there was one. */
 	std::string mErrorString;
+
+	/** Used for testing */
+	bool bExtraVerbose;
 };
 
 } // End of namespace Assimp

+ 0 - 0
test/unit/ut3DSLoader.cpp


+ 0 - 0
test/unit/utASELoader.cpp


+ 0 - 0
test/unit/utMDLLoader.cpp


+ 0 - 0
test/unit/utOBJLoader.cpp


+ 1 - 1
tools/assimp_view/assimp_view.cpp

@@ -133,7 +133,7 @@ DWORD WINAPI LoadThreadProc(LPVOID lpParameter)
 		aiProcess_GenSmoothNormals		| // generate smooth normal vectors if not existing
 		aiProcess_ConvertToLeftHanded	| // convert everything to D3D left handed space
 		aiProcess_SplitLargeMeshes      | // split large, unrenderable meshes into submeshes
-		aiProcess_ValidateDataStructure | aiProcess_ImproveCacheLocality); // validate the output data structure
+		aiProcess_ValidateDataStructure | aiProcess_ImproveCacheLocality | aiProcess_RemoveRedundantMaterials); // validate the output data structure
 
 	// get the end time of zje operation, calculate delta t
 	double fEnd = (double)timeGetTime();

+ 0 - 16
workspaces/vc8/UnitTest.vcproj

@@ -675,14 +675,6 @@
 				RelativePath="..\..\test\unit\Main.cpp"
 				>
 			</File>
-			<File
-				RelativePath="..\..\test\unit\ut3DSLoader.cpp"
-				>
-			</File>
-			<File
-				RelativePath="..\..\test\unit\utASELoader.cpp"
-				>
-			</File>
 			<File
 				RelativePath="..\..\test\unit\utFixInfacingNormals.cpp"
 				>
@@ -711,14 +703,6 @@
 				RelativePath="..\..\test\unit\utMaterialSystem.cpp"
 				>
 			</File>
-			<File
-				RelativePath="..\..\test\unit\utMDLLoader.cpp"
-				>
-			</File>
-			<File
-				RelativePath="..\..\test\unit\utOBJLoader.cpp"
-				>
-			</File>
 			<File
 				RelativePath="..\..\test\unit\utPretransformVertices.cpp"
 				>

+ 12 - 0
workspaces/vc8/assimp.vcproj

@@ -654,6 +654,10 @@
 				RelativePath="..\..\include\assimp.hpp"
 				>
 			</File>
+			<File
+				RelativePath="..\..\include\DefaultLogger.h"
+				>
+			</File>
 			<File
 				RelativePath="..\..\include\IOStream.h"
 				>
@@ -762,6 +766,10 @@
 				RelativePath="..\..\code\RemoveComments.h"
 				>
 			</File>
+			<File
+				RelativePath="..\..\code\RemoveRedundantMaterials.h"
+				>
+			</File>
 			<File
 				RelativePath="..\..\code\SpatialSort.h"
 				>
@@ -1090,6 +1098,10 @@
 				RelativePath="..\..\code\RemoveComments.cpp"
 				>
 			</File>
+			<File
+				RelativePath="..\..\code\RemoveRedundantMaterials.cpp"
+				>
+			</File>
 			<File
 				RelativePath="..\..\code\SpatialSort.cpp"
 				>