Browse Source

MD3
- shaders are now processed
- multi-part player models are handled correctly

Material system
- added flags for 'usealpha' or 'ignorealpha' setting of textures

LWO
- fixed texture assignment bug due to invalid tag list

3DS
- improved handling of dummy nodes

Viewer:
- lines&points are now displayed
- improved animation control via slider (still some stuff missing)
- skeleton is now displayed
- some other minor fixes

Validator:
- some material issues are warnings now (no errors anymore)

git-svn-id: https://assimp.svn.sourceforge.net/svnroot/assimp/trunk@349 67173fc5-114c-0410-ac8e-9d2fd5bffc1f

aramis_acg 16 years ago
parent
commit
abe2d4834e

+ 2 - 2
code/3DSConverter.cpp

@@ -438,11 +438,11 @@ void Discreet3DSImporter::AddNodeToGraph(aiScene* pcSOut,aiNode* pcOut,
 	iArray.reserve(3);
 
 	aiMatrix4x4 abs;
-	if (pcIn->mName == "$$$DUMMY")	{
+	/*if (pcIn->mName == "$$$DUMMY")	{
 		// FIX: Append the "real" name of the dummy to the string
 		pcIn->mName = "Dummy." + pcIn->mDummyName;
 	}
-	else // if (pcIn->mName != "$$$DUMMY")
+	else*/ // if (pcIn->mName != "$$$DUMMY")
 	{		
 		// Find all meshes with the same name as the node
 		for (unsigned int a = 0; a < pcSOut->mNumMeshes;++a)

+ 8 - 6
code/3DSLoader.cpp

@@ -669,14 +669,16 @@ void Discreet3DSImporter::ParseHierarchyChunk(uint16_t parent)
 
 		// This is the "real" name of a $$$DUMMY object
 		{
-			if (mCurrentNode->mName != "$$$DUMMY")	{
-				DefaultLogger::get()->warn("3DS: Skipping dummy object name for non-dummy object");
+			const char* sz = (const char*) stream->GetPtr();
+			while (stream->GetI1());
+			// mCurrentNode->mDummyName = std::string(sz);
+
+			// FIX: if object name is DUMMY, take this one instead
+			if (mCurrentNode->mName == "$$$DUMMY")	{
+				//DefaultLogger::get()->warn("3DS: Skipping dummy object name for non-dummy object");
+				mCurrentNode->mName = std::string(sz);
 				break;
 			}
-
-			const char* sz = (const char*)stream->GetPtr();
-			while (stream->GetI1());
-			mCurrentNode->mDummyName = std::string(sz);
 		}
 		break;
 

+ 14 - 22
code/GenVertexNormalsProcess.cpp

@@ -76,10 +76,9 @@ bool GenVertexNormalsProcess::IsActive( unsigned int pFlags) const
 // Executes the post processing step on the given imported data.
 void GenVertexNormalsProcess::SetupProperties(const Importer* pImp)
 {
-	// get the current value of the property
-	this->configMaxAngle = pImp->GetPropertyFloat(AI_CONFIG_PP_GSN_MAX_SMOOTHING_ANGLE,175.f);
-	this->configMaxAngle = std::max(std::min(this->configMaxAngle,175.0f),0.0f);
-	this->configMaxAngle = AI_DEG_TO_RAD(this->configMaxAngle);
+	// Get the current value of the AI_CONFIG_PP_GSN_MAX_SMOOTHING_ANGLE property
+	configMaxAngle = pImp->GetPropertyFloat(AI_CONFIG_PP_GSN_MAX_SMOOTHING_ANGLE,175.f);
+	configMaxAngle = AI_DEG_TO_RAD(std::max(std::min(configMaxAngle,175.0f),0.0f));
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -98,8 +97,7 @@ void GenVertexNormalsProcess::Execute( aiScene* pScene)
 			bHas = true;
 	}
 
-	if (bHas)
-	{
+	if (bHas)	{
 		DefaultLogger::get()->info("GenVertexNormalsProcess finished. "
 			"Vertex normals have been calculated");
 	}
@@ -154,8 +152,7 @@ bool GenVertexNormalsProcess::GenMeshVertexNormals (aiMesh* pMesh, unsigned int
 	SpatialSort  _vertexFinder;
 	float posEpsilon;
 	const float epsilon = 1e-5f;
-	if (shared)
-	{
+	if (shared)	{
 		std::vector<std::pair<SpatialSort,float> >* avf;
 		shared->GetProperty(AI_SPP_SPATIAL_SORT,avf);
 		if (avf)
@@ -165,8 +162,7 @@ bool GenVertexNormalsProcess::GenMeshVertexNormals (aiMesh* pMesh, unsigned int
 			posEpsilon = blubb.second;
 		}
 	}
-	if (!vertexFinder)
-	{
+	if (!vertexFinder)	{
 		_vertexFinder.Fill(pMesh->mVertices, pMesh->mNumVertices, sizeof( aiVector3D));
 		vertexFinder = &_vertexFinder;
 		posEpsilon = ComputePositionEpsilon(pMesh);
@@ -174,22 +170,19 @@ bool GenVertexNormalsProcess::GenMeshVertexNormals (aiMesh* pMesh, unsigned int
 	std::vector<unsigned int> verticesFound;
 	aiVector3D* pcNew = new aiVector3D[pMesh->mNumVertices];
 
-	if (configMaxAngle >= AI_DEG_TO_RAD( 175.f ))
-	{
+	if (configMaxAngle >= AI_DEG_TO_RAD( 175.f ))	{
 		// There is no angle limit. Thus all vertices with positions close
 		// to each other will receive the same vertex normal. This allows us
 		// to optimize the whole algorithm a little bit ...
 		std::vector<bool> abHad(pMesh->mNumVertices,false);
-		for (unsigned int i = 0; i < pMesh->mNumVertices;++i)
-		{
+		for (unsigned int i = 0; i < pMesh->mNumVertices;++i)	{
 			if (abHad[i])continue;
 
 			// Get all vertices that share this one ...
 			vertexFinder->FindPositions( pMesh->mVertices[i], posEpsilon, verticesFound);
 
 			aiVector3D pcNor; 
-			for (unsigned int a = 0; a < verticesFound.size(); ++a)
-			{
+			for (unsigned int a = 0; a < verticesFound.size(); ++a)	{
 				const aiVector3D& v = pMesh->mNormals[verticesFound[a]];
 				if (is_not_qnan(v.x))pcNor += v;
 			}
@@ -204,17 +197,16 @@ bool GenVertexNormalsProcess::GenMeshVertexNormals (aiMesh* pMesh, unsigned int
 			}
 		}
 	}
-	else
-	{
+	// Slower code path if a smooth angle is set. There are many ways to achieve
+	// the effect, this one is the most straightforward one.
+	else	{
 		const float fLimit = ::cos(configMaxAngle); 
-		for (unsigned int i = 0; i < pMesh->mNumVertices;++i)
-		{
+		for (unsigned int i = 0; i < pMesh->mNumVertices;++i)	{
 			// Get all vertices that share this one ...
 			vertexFinder->FindPositions( pMesh->mVertices[i] , posEpsilon, verticesFound);
 
 			aiVector3D pcNor; 
-			for (unsigned int a = 0; a < verticesFound.size(); ++a)
-			{
+			for (unsigned int a = 0; a < verticesFound.size(); ++a)	{
 				const aiVector3D& v = pMesh->mNormals[verticesFound[a]];
 
 				// check whether the angle between the two normals is not too large

+ 38 - 48
code/LWOLoader.cpp

@@ -39,7 +39,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ---------------------------------------------------------------------------
 */
 
-/** @file Implementation of the LWO importer class */
+/** @file  LWOLoader.cpp
+ *  @brief Implementation of the LWO importer class
+ */
 
 #include "AssimpPCH.h"
 #ifndef ASSIMP_BUILD_NO_LWO_IMPORTER
@@ -230,6 +232,7 @@ void LWOImporter::InternReadFile( const std::string& pFile,
 						mSurfaces->push_back(LWO::Surface());
 						LWO::Surface& surf = mSurfaces->back();
 						surf.mColor.r = surf.mColor.g = surf.mColor.b = 0.6f; 
+						surf.mName = "LWODefaultSurface";
 					}
 					idx = iDefaultSurface;
 				}
@@ -329,8 +332,7 @@ void LWOImporter::InternReadFile( const std::string& pFile,
 						}
 
 						// process normals (MODO extension)
-						if (nrm)
-						{
+						if (nrm)	{
 							*nrm++ = ((aiVector3D*)&layer.mNormals.rawData[0])[idx];
 						}
 
@@ -401,7 +403,7 @@ void LWOImporter::InternReadFile( const std::string& pFile,
 	}
 
 	// copy the meshes to the output structure
-	if (apcMeshes.size()) // shouldn't occur, just to be sure we don't crash
+	if (apcMeshes.size()) // shouldn't happen, just to be sure we don't crash
 	{
 		pScene->mMeshes = new aiMesh*[ pScene->mNumMeshes = (unsigned int)apcMeshes.size() ];
 		::memcpy(pScene->mMeshes,&apcMeshes[0],pScene->mNumMeshes*sizeof(void*));
@@ -415,24 +417,23 @@ void LWOImporter::InternReadFile( const std::string& pFile,
 void LWOImporter::ComputeNormals(aiMesh* mesh, const std::vector<unsigned int>& smoothingGroups,
 	const LWO::Surface& surface)
 {
-	// allocate output storage
+	// Allocate output storage
 	mesh->mNormals = new aiVector3D[mesh->mNumVertices];
 
 	// First generate per-face normals
 	aiVector3D* out;
 	std::vector<aiVector3D> faceNormals;
 
+	// ... in some cases that's already enough
 	if (!surface.mMaximumSmoothAngle)
 		out = mesh->mNormals;
-	else
-	{
+	else	{
 		faceNormals.resize(mesh->mNumVertices);
 		out = &faceNormals[0];
 	}
 
 	aiFace* begin = mesh->mFaces, *const end = mesh->mFaces+mesh->mNumFaces;
-	for (; begin != end; ++begin)
-	{
+	for (; begin != end; ++begin)	{
 		aiFace& face = *begin;
 
 		// LWO doc: "the normal is defined as the cross product of the first and last edges"
@@ -447,7 +448,7 @@ void LWOImporter::ComputeNormals(aiMesh* mesh, const std::vector<unsigned int>&
 	if (!surface.mMaximumSmoothAngle)return;
 	const float posEpsilon = ComputePositionEpsilon(mesh);
 	
-	// now generate the spatial sort tree
+	// Now generate the spatial sort tree
 	SGSpatialSort sSort;
 	std::vector<unsigned int>::const_iterator it = smoothingGroups.begin();
 	for( begin =  mesh->mFaces; begin != end; ++begin, ++it)
@@ -459,70 +460,57 @@ void LWOImporter::ComputeNormals(aiMesh* mesh, const std::vector<unsigned int>&
 			sSort.Add(mesh->mVertices[tt],tt,*it);
 		}
 	}
-	// sort everything - this takes O(nlogn) time
+	// Sort everything - this takes O(nlogn) time
 	sSort.Prepare();
 	std::vector<unsigned int> poResult;
 	poResult.reserve(20);
 
-	// generate vertex normals. We have O(logn) for the binary lookup, which we need
+	// Generate vertex normals. We have O(logn) for the binary lookup, which we need
 	// for n elements, thus the EXPECTED complexity is O(nlogn)
-	if (surface.mMaximumSmoothAngle < 3.f && !configSpeedFlag)
-	{
+	if (surface.mMaximumSmoothAngle < 3.f && !configSpeedFlag)	{
 		const float fLimit = cos(surface.mMaximumSmoothAngle);
 
-		for( begin =  mesh->mFaces, it = smoothingGroups.begin(); begin != end; ++begin, ++it)
-		{
-			register unsigned int sg = *it;
-
-			aiFace& face = *begin;
+		for( begin =  mesh->mFaces, it = smoothingGroups.begin(); begin != end; ++begin, ++it)	{
+			const aiFace& face = *begin;
 			unsigned int* beginIdx = face.mIndices, *const endIdx = face.mIndices+face.mNumIndices;
 			for (; beginIdx != endIdx; ++beginIdx)
 			{
 				register unsigned int idx = *beginIdx;
-				sSort.FindPositions(mesh->mVertices[idx],sg,posEpsilon,poResult,true);
-
+				sSort.FindPositions(mesh->mVertices[idx],*it,posEpsilon,poResult,true);
 				std::vector<unsigned int>::const_iterator a, end = poResult.end();
 
 				aiVector3D vNormals;
-				for (a =  poResult.begin();a != end;++a)
-				{
+				for (a =  poResult.begin();a != end;++a)	{
 					const aiVector3D& v = faceNormals[*a];
-					if (v * faceNormals[idx] < fLimit)continue;
+					if (v * faceNormals[idx] < fLimit)
+						continue;
 					vNormals += v;
 				}
-				vNormals.Normalize();
-				mesh->mNormals[idx] = vNormals;
+				mesh->mNormals[idx] = vNormals.Normalize();
 			}
 		}
 	}
-	else // faster code path in case there is no smooth angle
-	{
+	 // faster code path in case there is no smooth angle
+	else	{
 		std::vector<bool> vertexDone(mesh->mNumVertices,false);
-
-		for( begin =  mesh->mFaces, it = smoothingGroups.begin(); begin != end; ++begin, ++it)
-		{
-			register unsigned int sg = *it;
-
-			aiFace& face = *begin;
+		for( begin =  mesh->mFaces, it = smoothingGroups.begin(); begin != end; ++begin, ++it)	{
+			const aiFace& face = *begin;
 			unsigned int* beginIdx = face.mIndices, *const endIdx = face.mIndices+face.mNumIndices;
 			for (; beginIdx != endIdx; ++beginIdx)
 			{
 				register unsigned int idx = *beginIdx;
-
-				if (vertexDone[idx])continue;
-				sSort.FindPositions(mesh->mVertices[idx],sg,posEpsilon,poResult,true);
-
+				if (vertexDone[idx])
+					continue;
+				sSort.FindPositions(mesh->mVertices[idx],*it,posEpsilon,poResult,true);
 				std::vector<unsigned int>::const_iterator a, end = poResult.end();
 
 				aiVector3D vNormals;
-				for (a =  poResult.begin();a != end;++a)
-				{
+				for (a =  poResult.begin();a != end;++a)	{
 					const aiVector3D& v = faceNormals[*a];
 					vNormals += v;
 				}
 				vNormals.Normalize();
-				for (a =  poResult.begin();a != end;++a)
-				{
+				for (a =  poResult.begin();a != end;++a)	{
 					mesh->mNormals[*a] = vNormals;
 					vertexDone[*a] = true;
 				}
@@ -617,9 +605,9 @@ void LWOImporter::ResolveTags()
 	mMapping->resize(mTags->size(),0xffffffff);
 	for (unsigned int a = 0; a  < mTags->size();++a)
 	{
+		const std::string& c = (*mTags)[a];
 		for (unsigned int i = 0; i < mSurfaces->size();++i)
 		{
-			const std::string& c = (*mTags)[a];
 			const std::string& d = (*mSurfaces)[i].mName;
 			if (!ASSIMP_stricmp(c,d))
 			{
@@ -688,9 +676,11 @@ void LWOImporter::LoadLWOTags(unsigned int size)
 	{
 		if (!(*szCur))
 		{
-			const unsigned int len = (unsigned int)(szCur-szLast);
-			mTags->push_back(std::string(szLast,len));
-			szCur += len & 1;
+			const size_t len = (size_t)(szCur-szLast);
+			// FIX: skip empty-sized tags
+			if (len)
+				mTags->push_back(std::string(szLast,len));
+			szCur += (len&0x1 ? 1 : 2);
 			szLast = szCur;
 		}
 		szCur++;
@@ -881,7 +871,7 @@ inline void CreateNewEntry(std::vector< T >& list, unsigned int srcIdx)
 }
 
 // ------------------------------------------------------------------------------------------------
-void LWOImporter::DoRecursiveVMAPAssignment(VMapEntry* base, unsigned int numRead, 
+inline void LWOImporter::DoRecursiveVMAPAssignment(VMapEntry* base, unsigned int numRead, 
 	unsigned int idx, float* data)
 {
 	ai_assert(NULL != data);
@@ -897,7 +887,7 @@ void LWOImporter::DoRecursiveVMAPAssignment(VMapEntry* base, unsigned int numRea
 }
 
 // ------------------------------------------------------------------------------------------------
-void AddToSingleLinkedList(ReferrerList& refList, unsigned int srcIdx, unsigned int destIdx)
+inline void AddToSingleLinkedList(ReferrerList& refList, unsigned int srcIdx, unsigned int destIdx)
 {
 	if(0xffffffff == refList[srcIdx])
 	{

+ 1 - 1
code/LWOLoader.h

@@ -455,7 +455,7 @@ inline void LWOImporter::GetS0(std::string& out,unsigned int max)
 		}
 		++mFileBuffer;
 	}
-	unsigned int len = (unsigned int) ((const char*)mFileBuffer-sz);
+	size_t len = (size_t) ((const char*)mFileBuffer-sz);
 	out = std::string(sz,len);
 	mFileBuffer += (len&0x1 ? 1 : 2);
 }

+ 1 - 2
code/LWOMaterial.cpp

@@ -663,8 +663,7 @@ void LWOImporter::LoadLWO2Surface(unsigned int size)
 		for (SurfaceList::iterator it = mSurfaces->begin(), end = mSurfaces->end()-1;
 			 it != end; ++it)
 		{
-			if ((*it).mName == derived)
-			{
+			if ((*it).mName == derived)	{
 				// we have it ...
 				surf = *it;
 				derived.clear();

+ 375 - 140
code/MD3Loader.cpp

@@ -60,13 +60,36 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 using namespace Assimp;
 
+// ------------------------------------------------------------------------------------------------
+// Convert a Q3 shader blend function to the appropriate enum value
+Q3Shader::BlendFunc StringToBlendFunc(const std::string& m)
+{
+	if (m == "GL_ONE") {
+		return Q3Shader::BLEND_GL_ONE;
+	}
+	if (m == "GL_ZERO") {
+		return Q3Shader::BLEND_GL_ZERO;
+	}
+	if (m == "GL_SRC_ALPHA") {
+		return Q3Shader::BLEND_GL_SRC_ALPHA;
+	}
+	if (m == "GL_ONE_MINUS_SRC_ALPHA") {
+		return Q3Shader::BLEND_GL_ONE_MINUS_SRC_ALPHA;
+	}
+	if (m == "GL_ONE_MINUS_DST_COLOR") {
+		return Q3Shader::BLEND_GL_ONE_MINUS_DST_COLOR;
+	}
+	DefaultLogger::get()->error("Q3Shader: Unknown blend function: " + m);
+	return Q3Shader::BLEND_NONE;
+}
+
 // ------------------------------------------------------------------------------------------------
 // Load a Quake 3 shader
-void Q3Shader::LoadShader(ShaderData& fill, const std::string& pFile,IOSystem* io)
+bool 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
+		return false; // if we can't access the file, don't worry and return
 
 	DefaultLogger::get()->info("Loading Quake3 shader file " + pFile);
 
@@ -84,59 +107,93 @@ void Q3Shader::LoadShader(ShaderData& fill, const std::string& pFile,IOSystem* i
 	Q3Shader::ShaderMapBlock*  curMap  = NULL;
 
 	// read line per line
-	for (;;SkipLine(&buff)) {
+	for (;SkipSpacesAndLineEnd(&buff);SkipLine(&buff)) {
 	
-		if(!SkipSpacesAndLineEnd(&buff))
-			break;
-
 		if (*buff == '{') {
+			++buff;
+
 			// append to last section, if any
 			if (!curData) {
 				DefaultLogger::get()->error("Q3Shader: Unexpected shader section token \'{\'");
-				return;
+				return true; // still no failure, the file is there
 			}
 
-			// read this map section
-			for (;;SkipLine(&buff)) {
-				if(!SkipSpacesAndLineEnd(&buff))
-					break;
-
+			// read this data section
+			for (;SkipSpacesAndLineEnd(&buff);SkipLine(&buff)) {
 				if (*buff == '{') {
+					++buff;
 					// add new map section
 					curData->maps.push_back(Q3Shader::ShaderMapBlock());
 					curMap = &curData->maps.back();
 
-				}
-				else if (*buff == '}') {
-					// close this map section
-					if (curMap)
-						curMap = NULL;
-					else {
-						curData = NULL;					
-						break;
+					for (;SkipSpacesAndLineEnd(&buff);SkipLine(&buff)) {
+						// 'map' - Specifies texture file name
+						if (TokenMatchI(buff,"map",3) || TokenMatchI(buff,"clampmap",8)) {
+							curMap->name = GetNextToken(buff);
+						}	
+						// 'blendfunc' - Alpha blending mode
+						else if (TokenMatchI(buff,"blendfunc",9)) {	
+							const std::string blend_src = GetNextToken(buff);
+							if (blend_src == "add") {
+								curMap->blend_src  = Q3Shader::BLEND_GL_ONE;
+								curMap->blend_dest = Q3Shader::BLEND_GL_ONE;
+							}
+							else if (blend_src == "filter") {
+								curMap->blend_src  = Q3Shader::BLEND_GL_DST_COLOR;
+								curMap->blend_dest = Q3Shader::BLEND_GL_ZERO;
+							}
+							else if (blend_src == "blend") {
+								curMap->blend_src  = Q3Shader::BLEND_GL_SRC_ALPHA;
+								curMap->blend_dest = Q3Shader::BLEND_GL_ONE_MINUS_SRC_ALPHA;
+							}
+							else {
+								curMap->blend_src  = StringToBlendFunc(blend_src);
+								curMap->blend_dest = StringToBlendFunc(GetNextToken(buff));
+							}
+						}
+						// 'alphafunc' - Alpha testing mode
+						else if (TokenMatchI(buff,"alphafunc",9)) {	
+							const std::string at = GetNextToken(buff);
+							if (at == "GT0") {
+								curMap->alpha_test = Q3Shader::AT_GT0;
+							}
+							else if (at == "LT128") {
+								curMap->alpha_test = Q3Shader::AT_LT128;
+							}
+							else if (at == "GE128") {
+								curMap->alpha_test = Q3Shader::AT_GE128;
+							}
+						}
+						else if (*buff == '}') {
+							++buff;
+							// close this map section
+							curMap = NULL;
+							break;
+						}
 					}
+
 				}
-				// '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
+				else if (*buff == '}') {
+					++buff;
+					curData = NULL;					
+					break;
 				}
-			}
-		}
 
-		// '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;
+				// 'cull' specifies culling behaviour for the model
+				else if (TokenMatchI(buff,"cull",4)) {
+					SkipSpaces(&buff);
+					if (!ASSIMP_strincmp(buff,"back",4)) {
+						curData->cull = Q3Shader::CULL_CCW;
+					}
+					else if (!ASSIMP_strincmp(buff,"front",5)) {
+						curData->cull = Q3Shader::CULL_CW;
+					}
+					else if (!ASSIMP_strincmp(buff,"none",4) || !ASSIMP_strincmp(buff,"disable",7)) {
+						curData->cull = Q3Shader::CULL_NONE;
+					}
+					else DefaultLogger::get()->error("Q3Shader: Unrecognized cull mode");
+				}
 			}
-			//else curData->cull = Q3Shader::CULL_NONE;
 		}
 
 		else {
@@ -148,15 +205,16 @@ void Q3Shader::LoadShader(ShaderData& fill, const std::string& pFile,IOSystem* i
 			curData->name = GetNextToken(buff);
 		}
 	}
+	return true;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Load a Quake 3 skin
-void Q3Shader::LoadSkin(SkinData& fill, const std::string& pFile,IOSystem* io)
+bool 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
+		return false; // if we can't access the file, don't worry and return
 
 	DefaultLogger::get()->info("Loading Quake3 skin file " + pFile);
 
@@ -177,7 +235,7 @@ void Q3Shader::LoadSkin(SkinData& fill, const std::string& pFile,IOSystem* io)
 		std::string ss = GetNextToken(buff);
 		
 		// ignore tokens starting with tag_
-		if (!::strncmp(&ss[0],"_tag",std::min((size_t)4, ss.length())))
+		if (!::strncmp(&ss[0],"tag_",std::min((size_t)4, ss.length())))
 			continue;
 
 		fill.textures.push_back(SkinData::TextureEntry());
@@ -186,6 +244,90 @@ void Q3Shader::LoadSkin(SkinData& fill, const std::string& pFile,IOSystem* io)
 		s.first  = ss;
 		s.second = GetNextToken(buff);
 	}
+	return true;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Convert Q3Shader to material
+void Q3Shader::ConvertShaderToMaterial(MaterialHelper* out, const ShaderDataBlock& shader)
+{
+	ai_assert(NULL != out);
+
+	/*  IMPORTANT: This is not a real conversion. Actually we're just guessing and
+	 *  hacking around to build an aiMaterial that looks nearly equal to the
+	 *  original Quake 3 shader. We're missing some important features like
+	 *  animatable material properties in our material system, but at least 
+	 *  multiple textures should be handled correctly.
+	 */ 
+
+	// Two-sided material?
+	if (shader.cull == Q3Shader::CULL_NONE) {
+		const int twosided = 1;
+		out->AddProperty(&twosided,1,AI_MATKEY_TWOSIDED);
+	}
+
+	unsigned int cur_emissive = 0, cur_diffuse = 0, cur_lm =0;
+
+	// Iterate through all textures
+	for (std::list< Q3Shader::ShaderMapBlock >::const_iterator it = shader.maps.begin(); it != shader.maps.end();++it) {
+		
+		// CONVERSION BEHAVIOUR:
+		//
+		//
+		// If the texture is additive
+		//  - if it is the first texture, assume additive blending for the whole material
+		//  - otherwise register it as emissive texture.
+		//
+		// If the texture is using standard blend (or if the blend mode is unknown)
+		//  - if first texture: assume default blending for material
+		//  - in any case: set it as diffuse texture
+		//
+		// If the texture is using 'filter' blending
+		//  - take as lightmap
+		//
+		// Textures with alpha funcs
+		//  - aiTextureFlags_UseAlpha is set (otherwise aiTextureFlags_NoAlpha is explicitly set)
+		aiString s((*it).name);
+		aiTextureType type; unsigned int index;
+
+		if ((*it).blend_src == Q3Shader::BLEND_GL_ONE && (*it).blend_dest == Q3Shader::BLEND_GL_ONE) {
+			if (it == shader.maps.begin()) {
+				const int additive = aiBlendMode_Additive;
+				out->AddProperty(&additive,1,AI_MATKEY_BLEND_FUNC);
+				
+				index = cur_diffuse++;
+				type  = aiTextureType_DIFFUSE;
+			}
+			else {
+				index = cur_emissive++;
+				type  = aiTextureType_EMISSIVE;
+			}
+		}
+		else if ((*it).blend_src == Q3Shader::BLEND_GL_DST_COLOR && Q3Shader::BLEND_GL_ZERO) {
+			index = cur_lm++;
+			type  = aiTextureType_LIGHTMAP;
+		}
+		else {
+			const int blend = aiBlendMode_Default;
+			out->AddProperty(&blend,1,AI_MATKEY_BLEND_FUNC);
+			
+			index = cur_diffuse++;
+			type  = aiTextureType_DIFFUSE;
+		}
+
+		// setup texture
+		out->AddProperty(&s,AI_MATKEY_TEXTURE(type,index));
+
+		// setup texture flags
+		const int use_alpha = ((*it).alpha_test != Q3Shader::AT_NONE ? aiTextureFlags_UseAlpha : aiTextureFlags_IgnoreAlpha);
+		out->AddProperty(&use_alpha,1,AI_MATKEY_TEXFLAGS(type,index));
+	}
+	// If at least one emissive texture was set, set the emissive base color to 1 to ensure
+	// the texture is actually displayed.
+	if (0 != cur_emissive) {
+		aiColor3D one(1.f,1.f,1.f);
+		out->AddProperty(&one,1,AI_MATKEY_COLOR_EMISSIVE);
+	}
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -291,11 +433,14 @@ void MD3Importer::SetupProperties(const Importer* pImp)
 
 	// AI_CONFIG_IMPORT_MD3_SKIN_NAME
 	configSkinFile = (pImp->GetPropertyString(AI_CONFIG_IMPORT_MD3_SKIN_NAME,"default"));
+
+	// AI_CONFIG_IMPORT_MD3_SHADER_SRC
+	configShaderFile = (pImp->GetPropertyString(AI_CONFIG_IMPORT_MD3_SHADER_SRC,""));
 }
 
 // ------------------------------------------------------------------------------------------------
 // Try to read the skin for a MD3 file
-void MD3Importer::ReadSkin(Q3Shader::SkinData& fill)
+void MD3Importer::ReadSkin(Q3Shader::SkinData& fill) const
 {
 	// skip any postfixes (e.g. lower_1.md3)
 	std::string::size_type s = filename.find_last_of('_');
@@ -308,6 +453,39 @@ void MD3Importer::ReadSkin(Q3Shader::SkinData& fill)
 	Q3Shader::LoadSkin(fill,skin_file,mIOHandler);
 }
 
+// ------------------------------------------------------------------------------------------------
+// Try to read the shader for a MD3 file
+void MD3Importer::ReadShader(Q3Shader::ShaderData& fill) const
+{
+	// Determine Q3 model name from given path
+	std::string::size_type s = path.find_last_of('\\',path.length()-2);
+	if (s == std::string::npos)
+		s = path.find_last_of('/',path.length()-2);
+
+	const std::string model_file = path.substr(s+1,path.length()-(s+2));
+
+	// If no specific dir or file is given, use our default search behaviour
+	if (!configShaderFile.length()) {
+		if(!Q3Shader::LoadShader(fill,path + "..\\..\\..\\scripts\\" + model_file + ".shader",mIOHandler)) {
+			Q3Shader::LoadShader(fill,path + "..\\..\\..\\scripts\\" + filename + ".shader",mIOHandler);
+		}
+	}
+	else {
+		// If the given string specifies a file, load this file.
+		// Otherwise it's a directory.
+		std::string::size_type st = configShaderFile.find_last_of('.');
+		if (st == std::string::npos) {
+			
+			if(!Q3Shader::LoadShader(fill,configShaderFile + model_file + ".shader",mIOHandler)) {
+				Q3Shader::LoadShader(fill,configShaderFile + filename + ".shader",mIOHandler);
+			}
+		}
+		else {
+			Q3Shader::LoadShader(fill,configShaderFile,mIOHandler);
+		}
+	}
+}
+
 // ------------------------------------------------------------------------------------------------
 // Read a multi-part Q3 player model
 bool MD3Importer::ReadMultipartFile()
@@ -416,6 +594,45 @@ error_cleanup:
 	return false;
 }
 
+// ------------------------------------------------------------------------------------------------
+// Convert a MD3 path to a proper value
+void MD3Importer::ConvertPath(const char* texture_name, const char* header_name, std::string& out) const
+{
+	// If the MD3's internal path itself and the given path are using
+	// the same directory, remove it completely to get right output paths.
+	const char* end1 = ::strrchr(header_name,'\\');
+	if (!end1)end1   = ::strrchr(header_name,'/');
+
+	const char* end2 = ::strrchr(texture_name,'\\');
+	if (!end2)end2   = ::strrchr(texture_name,'/');
+
+	// HACK: If the paths starts with "models", ignore the
+	// next two hierarchy levels, it specifies just the model name.
+	// Ignored by Q3, it might be not equal to the real model location.
+	if (end2)	{
+
+		size_t len2;
+		const size_t len1 = (size_t)(end1 - header_name);
+		if (!ASSIMP_strincmp(texture_name,"models",6) && (texture_name[6] == '/' || texture_name[6] == '\\')) {
+			len2 = 6; // ignore the seventh - could be slash or backslash
+
+			if (!header_name[0]) {
+				// Use the file name only
+				out = end2+1;
+				return;
+			}
+		}
+		else len2 = std::min (len1, (size_t)(end2 - texture_name ));
+		if (!ASSIMP_strincmp(texture_name,header_name,len2)) {
+			// Use the file name only
+			out = end2+1;
+			return;
+		}
+	}
+	// Use the full path
+	out = texture_name;
+}
+
 // ------------------------------------------------------------------------------------------------
 // Imports the given file into the given scene structure. 
 void MD3Importer::InternReadFile( const std::string& pFile, 
@@ -504,12 +721,27 @@ void MD3Importer::InternReadFile( const std::string& pFile,
 	Q3Shader::SkinData skins;
 	ReadSkin(skins);
 
+	// And check whether we can locate a shader file for this model
+	Q3Shader::ShaderData shaders;
+	ReadShader(shaders);
+
+	// Adjust all texture paths in the shader
+	const char* header_name = pcHeader->NAME;
+	if (shaders.blocks.size()) {
+		for (std::list< Q3Shader::ShaderDataBlock >::iterator dit = shaders.blocks.begin(); dit != shaders.blocks.end(); ++dit) {
+			ConvertPath((*dit).name.c_str(),header_name,(*dit).name);
+
+			for (std::list< Q3Shader::ShaderMapBlock >::iterator mit = (*dit).maps.begin(); mit != (*dit).maps.end(); ++mit) {
+				ConvertPath((*mit).name.c_str(),header_name,(*mit).name);
+			}
+		}
+	}
+
 	// Read all surfaces from the file
 	unsigned int iNum = pcHeader->NUM_SURFACES;
 	unsigned int iNumMaterials = 0;
 	unsigned int iDefaultMatIndex = 0xFFFFFFFF;
-	while (iNum-- > 0)
-	{
+	while (iNum-- > 0)	{
 
 		// Ensure correct endianess
 #ifdef AI_BUILD_BIG_ENDIAN
@@ -555,7 +787,96 @@ void MD3Importer::InternReadFile( const std::string& pFile,
 			continue;
 		}
 
-		// Ensure correct endianess
+		// Allocate output mesh
+		pScene->mMeshes[iNum] = new aiMesh();
+		aiMesh* pcMesh = pScene->mMeshes[iNum];
+
+		std::string _texture_name;
+		const char* texture_name = NULL;
+
+		// Check whether we have a texture record for this surface in the .skin file
+		std::list< Q3Shader::SkinData::TextureEntry >::iterator it = std::find( 
+			skins.textures.begin(), skins.textures.end(), pcSurfaces->NAME );
+
+		if (it != skins.textures.end()) {
+			texture_name = &*( _texture_name = (*it).second).begin();
+			DefaultLogger::get()->debug("MD3: Assigning skin texture " + (*it).second + " to surface " + pcSurfaces->NAME);
+			(*it).resolved = true; // mark entry as resolved
+		}
+
+		// Get the first shader (= texture?) assigned to the surface
+		if (!texture_name && pcSurfaces->NUM_SHADER)	{
+			texture_name = pcShaders->NAME;
+		}
+
+		std::string convertedPath;
+		if (texture_name) {
+			ConvertPath(texture_name,header_name,convertedPath);
+		}
+
+		const Q3Shader::ShaderDataBlock* shader = NULL;
+
+		// Now search the current shader for a record with this name (
+		// excluding texture file extension)
+		if (shaders.blocks.size()) {
+
+			std::string::size_type s = convertedPath.find_last_of('.');
+			if (s == std::string::npos)
+				s = convertedPath.length();
+
+			const std::string without_ext = convertedPath.substr(0,s);
+			std::list< Q3Shader::ShaderDataBlock >::const_iterator dit = std::find(shaders.blocks.begin(),shaders.blocks.end(),without_ext);
+			if (dit != shaders.blocks.end()) {
+				// Hurra, wir haben einen. Tolle Sache.
+				shader = &*dit;
+				DefaultLogger::get()->info("Found shader record for " +without_ext );
+			}
+			else DefaultLogger::get()->warn("Unable to find shader record for " +without_ext );
+		}
+
+		MaterialHelper* pcHelper = new MaterialHelper();
+
+		const int iMode = (int)aiShadingMode_Gouraud;
+		pcHelper->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL);
+
+		// Add a small ambient color value - Quake 3 seems to have one
+		aiColor3D clr;
+		clr.b = clr.g = clr.r = 0.05f;
+		pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_AMBIENT);
+
+		clr.b = clr.g = clr.r = 1.0f;
+		pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_DIFFUSE);
+		pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_SPECULAR);
+
+		// use surface name + skin_name as material name
+		aiString name;
+		name.Set("MD3_[" + configSkinFile + "][" + pcSurfaces->NAME + "]");
+		pcHelper->AddProperty(&name,AI_MATKEY_NAME);
+
+		if (!shader) {
+			// Setup dummy texture file name to ensure UV coordinates are kept during postprocessing
+			aiString szString;
+			if (convertedPath.length())	{
+				szString.Set(convertedPath);
+			}
+			else	{
+				DefaultLogger::get()->warn("Texture file name has zero length. Using default name");
+				szString.Set("dummy_texture.bmp");
+			}
+			pcHelper->AddProperty(&szString,AI_MATKEY_TEXTURE_DIFFUSE(0));
+
+			// prevent transparency by default
+			int no_alpha = aiTextureFlags_IgnoreAlpha;
+			pcHelper->AddProperty(&no_alpha,1,AI_MATKEY_TEXFLAGS_DIFFUSE(0));
+		}
+		else {
+			Q3Shader::ConvertShaderToMaterial(pcHelper,*shader);
+		}
+
+		pScene->mMaterials[iNumMaterials] = (aiMaterial*)pcHelper;
+		pcMesh->mMaterialIndex = iNumMaterials++;
+
+			// Ensure correct endianess
 #ifdef AI_BUILD_BIG_ENDIAN
 
 		for (uint32_t i = 0; i < pcSurfaces->NUM_VERTICES;++i)
@@ -577,9 +898,7 @@ void MD3Importer::InternReadFile( const std::string& pFile,
 
 #endif
 
-		// Allocate the output mesh
-		pScene->mMeshes[iNum] = new aiMesh();
-		aiMesh* pcMesh = pScene->mMeshes[iNum];
+		// Fill mesh information
 		pcMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
 
 		pcMesh->mNumVertices		= pcSurfaces->NUM_TRIANGLES*3;
@@ -600,6 +919,8 @@ void MD3Importer::InternReadFile( const std::string& pFile,
 			unsigned int iTemp = iCurrent;
 			for (unsigned int c = 0; c < 3;++c,++iCurrent)
 			{
+				pcMesh->mFaces[i].mIndices[c] = iCurrent;
+
 				// Read vertices
 				pcMesh->mVertices[iCurrent].x = pcVertices[ pcTriangles->INDEXES[c]].X*AI_MD3_XYZ_SCALE;
 				pcMesh->mVertices[iCurrent].y = pcVertices[ pcTriangles->INDEXES[c]].Y*AI_MD3_XYZ_SCALE;
@@ -613,100 +934,14 @@ void MD3Importer::InternReadFile( const std::string& pFile,
 				pcMesh->mTextureCoords[0][iCurrent].x = pcUVs[ pcTriangles->INDEXES[c]].U;
 				pcMesh->mTextureCoords[0][iCurrent].y = 1.0f-pcUVs[ pcTriangles->INDEXES[c]].V;
 			}
-			// FIX: flip the face ordering for use with OpenGL
-			pcMesh->mFaces[i].mIndices[0] = iTemp+2;
-			pcMesh->mFaces[i].mIndices[1] = iTemp+1;
-			pcMesh->mFaces[i].mIndices[2] = iTemp+0;
-			pcTriangles++;
-		}
-
-		std::string _texture_name;
-		const char* texture_name = NULL, *header_name = pcHeader->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 (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;
-				}
-				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;
-				}
+			// Flip face order if necessary
+			if (!shader || shader->cull == Q3Shader::CULL_CCW) {
+				pcMesh->mFaces[i].mIndices[0] = iTemp+2;
+				pcMesh->mFaces[i].mIndices[1] = iTemp+1;
+				pcMesh->mFaces[i].mIndices[2] = iTemp+0;
 			}
+			pcTriangles++;
 		}
-
-		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);

+ 44 - 3
code/MD3Loader.h

@@ -131,6 +131,12 @@ struct ShaderMapBlock
 	//! Blend and alpha test settings for texture
 	BlendFunc blend_src,blend_dest;
 	AlphaTestFunc alpha_test;
+
+
+	//! For std::find()
+	bool operator== (const std::string& o) const {
+		return !ASSIMP_stricmp(o,name);
+	}
 };
 
 // ---------------------------------------------------------------------------
@@ -150,6 +156,12 @@ struct ShaderDataBlock
 
 	//! Maps defined in the shader
 	std::list<ShaderMapBlock> maps;
+
+
+	//! For std::find()
+	bool operator== (const std::string& o) const {
+		return !ASSIMP_stricmp(o,name);
+	}
 };
 
 // ---------------------------------------------------------------------------
@@ -168,8 +180,18 @@ struct ShaderData
  *  @param fill Receives output data
  *  @param file File to be read.
  *  @param io IOSystem to be used for reading
+ *  @return false if file is not accessible
+ */
+bool LoadShader(ShaderData& fill, const std::string& file,IOSystem* io);
+
+
+// ---------------------------------------------------------------------------
+/** @brief Convert a Q3Shader to an aiMaterial
+ *
+ *  @param[out] out Material structure to be filled.
+ *  @param[in] shader Input shader
  */
-void LoadShader(ShaderData& fill, const std::string& file,IOSystem* io);
+void ConvertShaderToMaterial(MaterialHelper* out, const ShaderDataBlock& shader);
 
 // ---------------------------------------------------------------------------
 /** @brief Load a skin file
@@ -178,8 +200,9 @@ void LoadShader(ShaderData& fill, const std::string& file,IOSystem* io);
  *  @param fill Receives output data
  *  @param file File to be read.
  *  @param io IOSystem to be used for reading
+ *  @return false if file is not accessible
  */
-void LoadSkin(SkinData& fill, const std::string& file,IOSystem* io);
+bool LoadSkin(SkinData& fill, const std::string& file,IOSystem* io);
 
 } // ! namespace Q3SHader
 
@@ -243,7 +266,22 @@ protected:
 	/** Try to read the skin for a MD3 file
 	 *  @param fill Receives output information
 	 */
-	void ReadSkin(Q3Shader::SkinData& fill);
+	void ReadSkin(Q3Shader::SkinData& fill) const;
+
+	// -------------------------------------------------------------------
+	/** Try to read the shader for a MD3 file
+	 *  @param fill Receives output information
+	 */
+	void ReadShader(Q3Shader::ShaderData& fill) const;
+
+	// -------------------------------------------------------------------
+	/** Convert a texture path in a MD3 file to a proper value
+	 *  @param[in] texture_name Path to be converted
+	 *  @param[in] header_path Base path specified in MD3 header
+	 *  @param[out] out Receives the converted output string
+	 */
+	void ConvertPath(const char* texture_name, const char* header_path, 
+		std::string& out) const;
 
 protected:
 
@@ -256,6 +294,9 @@ protected:
 	/** Configuration option: name of skin file to be read */
 	std::string configSkinFile;
 
+	/** Configuration option: name or path of shader */
+	std::string configShaderFile;
+
 	/** Header of the MD3 file */
 	BE_NCONST MD3::Header* pcHeader;
 

+ 5 - 13
code/MaterialSystem.cpp

@@ -288,24 +288,16 @@ uint32_t MaterialHelper::ComputeHash(bool includeMatName /*= false*/)
 	{
 		aiMaterialProperty* prop;
 
-		// If specified, exclude the material name from the hash
-		if ((prop = mProperties[i]) && (includeMatName || ::strcmp(prop->mKey.data,"$mat.name")))
+		// Exclude all properties whose first character is '?' from the hash
+		// See doc for aiMaterialProperty.
+		if ((prop = mProperties[i]) && (includeMatName || prop->mKey.data[0] != '?'))
 		{
 			hash = SuperFastHash(prop->mKey.data,(unsigned int)prop->mKey.length,hash);
 			hash = SuperFastHash(prop->mData,prop->mDataLength,hash);
 
 			// Combine the semantic and the index with the hash
-			// We print them to a string to make sure the quality
-			// of the hashing state isn't affected (our hashing
-			// procedure was originally intended for plaintest).
-			char buff[32];
-			unsigned int len;
-			
-			len = ASSIMP_itoa10(buff,prop->mSemantic);
-			hash = SuperFastHash(buff,len-1,hash);
-
-			len = ASSIMP_itoa10(buff,prop->mIndex);
-			hash = SuperFastHash(buff,len-1,hash);
+			hash = SuperFastHash((const char*)&prop->mSemantic,sizeof(unsigned int),hash);
+			hash = SuperFastHash((const char*)&prop->mIndex,sizeof(unsigned int),hash);
 		}
 	}
 	return hash;

+ 3 - 2
code/MaterialSystem.h

@@ -131,8 +131,9 @@ public:
 	 *  proprty and call this method again, the resulting hash value will be 
 	 *  different.
 	 *
-	 *  @param  includeMatName Set to 'true' to take the #AI_MATKEY_NAME property
-	 *    into account. The default value is false.
+	 *  @param  includeMatName Set to 'true' to take all properties with
+	 *    '?' as initial character in their name into account. 
+	 *    Currently #AI_MATKEY_NAME is the only example.
 	 *  @return Unique hash
 	 */
 	uint32_t ComputeHash(bool includeMatName = false);

+ 5 - 11
code/PretransformVertices.cpp

@@ -201,7 +201,7 @@ void CollectData( aiScene* pcScene, aiNode* pcNode, unsigned int iMat,
 				// aiFace destructor ...
 				pcMesh->mFaces[planck].mIndices = NULL;
 
-				// FIX: update the mPrimitiveTypes member of the mesh
+				// Update the mPrimitiveTypes member of the mesh
 				switch (pcMesh->mFaces[planck].mNumIndices)
 				{
 				case 0x1:
@@ -249,13 +249,11 @@ void GetVFormatList( aiScene* pcScene, unsigned int iMat,
 // Compute the absolute transformation matrices of each node
 void ComputeAbsoluteTransform( aiNode* pcNode )
 {
-	if (pcNode->mParent)
-	{
+	if (pcNode->mParent)	{
 		pcNode->mTransformation = pcNode->mParent->mTransformation*pcNode->mTransformation;
 	}
 
-	for (unsigned int i = 0;i < pcNode->mNumChildren;++i)
-	{
+	for (unsigned int i = 0;i < pcNode->mNumChildren;++i)	{
 		ComputeAbsoluteTransform(pcNode->mChildren[i]);
 	}
 }
@@ -291,17 +289,13 @@ void PretransformVertices::Execute( aiScene* pScene)
 	std::vector<aiMesh*> apcOutMeshes;
 	apcOutMeshes.reserve(pScene->mNumMaterials<<1u);
 	std::list<unsigned int> aiVFormats;
-	for (unsigned int i = 0; i < pScene->mNumMaterials;++i)
-	{
+	for (unsigned int i = 0; i < pScene->mNumMaterials;++i)	{
 		// get the list of all vertex formats for this material
 		aiVFormats.clear();
 		GetVFormatList(pScene,i,aiVFormats);
 		aiVFormats.sort();
 		aiVFormats.unique();
-		for (std::list<unsigned int>::const_iterator
-			j =  aiVFormats.begin();
-			j != aiVFormats.end();++j)
-		{
+		for (std::list<unsigned int>::const_iterator j =  aiVFormats.begin();j != aiVFormats.end();++j)	{
 			unsigned int iVertices = 0;
 			unsigned int iFaces = 0; 
 			CountVerticesAndFaces(pScene,pScene->mRootNode,i,*j,&iFaces,&iVertices);

+ 7 - 10
code/RemoveRedundantMaterials.cpp

@@ -95,8 +95,7 @@ void RemoveRedundantMatsProcess::Execute( aiScene* pScene)
 		for (unsigned int i = 0; i < pScene->mNumMaterials;++i)
 		{
 			// if the material is not referenced ... remove it
-			if (!abReferenced[i])
-			{
+			if (!abReferenced[i])	{
 				++unreferenced;
 				continue;
 			}
@@ -104,8 +103,7 @@ void RemoveRedundantMatsProcess::Execute( aiScene* pScene)
 			uint32_t me = aiHashes[i] = ((MaterialHelper*)pScene->mMaterials[i])->ComputeHash();
 			for (unsigned int a = 0; a < i;++a)
 			{
-				if (me == aiHashes[a])
-				{				
+				if (me == aiHashes[a])	{				
 					++iCnt;
 					me = 0;
 					aiMappingTable[i] = aiMappingTable[a];
@@ -113,27 +111,26 @@ void RemoveRedundantMatsProcess::Execute( aiScene* pScene)
 					break;
 				}
 			}
-			if (me)
-			{
+			if (me)	{
 				aiMappingTable[i] = iNewNum++;
 			}
 		}
-		if (iCnt)
-		{
+		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)
 			{
 				// if the material is not referenced ... remove it
-				if (!abReferenced[p])continue;
+				if (!abReferenced[p])
+					continue;
 
 				// 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);
+					sz.length = ::sprintf(sz.data,"JoinedMaterial_#%i",p);
 					((MaterialHelper*)ppcMaterials[idx])->AddProperty(&sz,AI_MATKEY_NAME);
 				}
 				else ppcMaterials[idx] = pScene->mMaterials[p];

+ 31 - 41
code/ValidateDataStructure.cpp

@@ -635,17 +635,15 @@ void ValidateDSProcess::SearchForInvalidTextures(const aiMaterial* pMaterial,
 	for (unsigned int i = 0; i < pMaterial->mNumProperties;++i)
 	{
 		aiMaterialProperty* prop = pMaterial->mProperties[i];
-		if (!::strcmp(prop->mKey.data,"$tex.file") && prop->mSemantic == type)
-		{
+		if (!::strcmp(prop->mKey.data,"$tex.file") && prop->mSemantic == type)	{
 			iIndex = std::max(iIndex, (int) prop->mIndex);
 			++iNumIndices;
 
 			if (aiPTI_String != prop->mType)
-				this->ReportError("Material property %s is expected to be a string",prop->mKey.data);
+				ReportError("Material property %s is expected to be a string",prop->mKey.data);
 		}
 	}
-	if (iIndex +1 != iNumIndices)
-	{
+	if (iIndex +1 != iNumIndices)	{
 		ReportError("%s #%i is set, but there are only %i %s textures",
 			szType,iIndex,iNumIndices,szType);
 	}
@@ -666,8 +664,7 @@ void ValidateDSProcess::SearchForInvalidTextures(const aiMaterial* pMaterial,
 				prop->mIndex, iNumIndices, szType);
 		}
 			
-		if (!::strcmp(prop->mKey.data,"$tex.mapping"))
-		{
+		if (!::strcmp(prop->mKey.data,"$tex.mapping"))	{
 			if (aiPTI_Integer != prop->mType || prop->mDataLength < sizeof(aiTextureMapping))
 			{
 				ReportError("Material property %s%i is expected to be an integer (size is %i)",
@@ -675,8 +672,7 @@ void ValidateDSProcess::SearchForInvalidTextures(const aiMaterial* pMaterial,
 			}
 			mappings[prop->mIndex] = *((aiTextureMapping*)prop->mData);
 		}
-		else if (!::strcmp(prop->mKey.data,"$tex.uvtrafo"))
-		{
+		else if (!::strcmp(prop->mKey.data,"$tex.uvtrafo"))	{
 			if (aiPTI_Float != prop->mType || prop->mDataLength < sizeof(aiUVTransform))
 			{
 				ReportError("Material property %s%i is expected to be 5 floats large (size is %i)",
@@ -684,8 +680,7 @@ void ValidateDSProcess::SearchForInvalidTextures(const aiMaterial* pMaterial,
 			}
 			mappings[prop->mIndex] = *((aiTextureMapping*)prop->mData);
 		}
-		else if (!::strcmp(prop->mKey.data,"$tex.uvwsrc"))
-		{
+		else if (!::strcmp(prop->mKey.data,"$tex.uvwsrc")) {
 			if (aiPTI_Integer != prop->mType || sizeof(int) > prop->mDataLength)
 			{
 				ReportError("Material property %s%i is expected to be an integer (size is %i)",
@@ -709,7 +704,7 @@ void ValidateDSProcess::SearchForInvalidTextures(const aiMaterial* pMaterial,
 					while (mesh->HasTextureCoords(iChannels))++iChannels;
 					if (iIndex >= iChannels)
 					{
-						ReportError("Invalid UV index: %i (key %s). Mesh %i has only %i UV channels",
+						ReportWarning("Invalid UV index: %i (key %s). Mesh %i has only %i UV channels",
 							iIndex,prop->mKey.data,a,iChannels);
 					}
 				}
@@ -742,42 +737,37 @@ void ValidateDSProcess::Validate( const aiMaterial* pMaterial)
 	for (unsigned int i = 0; i < pMaterial->mNumProperties;++i)
 	{
 		const aiMaterialProperty* prop = pMaterial->mProperties[i];
-		if (!prop)
-		{
-			this->ReportError("aiMaterial::mProperties[%i] is NULL (aiMaterial::mNumProperties is %i)",
+		if (!prop)	{
+			ReportError("aiMaterial::mProperties[%i] is NULL (aiMaterial::mNumProperties is %i)",
 				i,pMaterial->mNumProperties);
 		}
-		if (!prop->mDataLength || !prop->mData)
-		{
-			this->ReportError("aiMaterial::mProperties[%i].mDataLength or "
+		if (!prop->mDataLength || !prop->mData)	{
+			ReportError("aiMaterial::mProperties[%i].mDataLength or "
 				"aiMaterial::mProperties[%i].mData is 0",i,i);
 		}
 		// check all predefined types
 		if (aiPTI_String == prop->mType)
 		{
 			// FIX: strings are now stored in a less expensive way ...
-			if (prop->mDataLength < sizeof(size_t) + ((const aiString*)prop->mData)->length + 1)
-			{
-				this->ReportError("aiMaterial::mProperties[%i].mDataLength is "
+			if (prop->mDataLength < sizeof(size_t) + ((const aiString*)prop->mData)->length + 1)	{
+				ReportError("aiMaterial::mProperties[%i].mDataLength is "
 					"too small to contain a string (%i, needed: %i)",
 					i,prop->mDataLength,sizeof(aiString));
 			}
-			this->Validate((const aiString*)prop->mData);
+			Validate((const aiString*)prop->mData);
 		}
 		else if (aiPTI_Float == prop->mType)
 		{
-			if (prop->mDataLength < sizeof(float))
-			{
-				this->ReportError("aiMaterial::mProperties[%i].mDataLength is "
+			if (prop->mDataLength < sizeof(float))	{
+				ReportError("aiMaterial::mProperties[%i].mDataLength is "
 					"too small to contain a float (%i, needed: %i)",
 					i,prop->mDataLength,sizeof(float));
 			}
 		}
 		else if (aiPTI_Integer == prop->mType)
 		{
-			if (prop->mDataLength < sizeof(int))
-			{
-				this->ReportError("aiMaterial::mProperties[%i].mDataLength is "
+			if (prop->mDataLength < sizeof(int))	{
+				ReportError("aiMaterial::mProperties[%i].mDataLength is "
 					"too small to contain an integer (%i, needed: %i)",
 					i,prop->mDataLength,sizeof(int));
 			}
@@ -788,22 +778,19 @@ void ValidateDSProcess::Validate( const aiMaterial* pMaterial)
 	// make some more specific tests 
 	float fTemp;
 	int iShading;
-	if (AI_SUCCESS == aiGetMaterialInteger( pMaterial,AI_MATKEY_SHADING_MODEL,&iShading))
-	{
+	if (AI_SUCCESS == aiGetMaterialInteger( pMaterial,AI_MATKEY_SHADING_MODEL,&iShading))	{
 		switch ((aiShadingMode)iShading)
 		{
 		case aiShadingMode_Blinn:
 		case aiShadingMode_CookTorrance:
 		case aiShadingMode_Phong:
 
-			if (AI_SUCCESS != aiGetMaterialFloat(pMaterial,AI_MATKEY_SHININESS,&fTemp))
-			{
-				this->ReportWarning("A specular shading model is specified but there is no "
+			if (AI_SUCCESS != aiGetMaterialFloat(pMaterial,AI_MATKEY_SHININESS,&fTemp))	{
+				ReportWarning("A specular shading model is specified but there is no "
 					"AI_MATKEY_SHININESS key");
 			}
-			if (AI_SUCCESS == aiGetMaterialFloat(pMaterial,AI_MATKEY_SHININESS_STRENGTH,&fTemp) && !fTemp)
-			{
-				this->ReportWarning("A specular shading model is specified but the value of the "
+			if (AI_SUCCESS == aiGetMaterialFloat(pMaterial,AI_MATKEY_SHININESS_STRENGTH,&fTemp) && !fTemp)	{
+				ReportWarning("A specular shading model is specified but the value of the "
 					"AI_MATKEY_SHININESS_STRENGTH key is 0.0");
 			}
 			break;
@@ -811,13 +798,13 @@ void ValidateDSProcess::Validate( const aiMaterial* pMaterial)
 		};
 	}
 
-	if (AI_SUCCESS == aiGetMaterialFloat( pMaterial,AI_MATKEY_OPACITY,&fTemp))
-	{
-		if (!fTemp)
-			ReportWarning("Material is fully transparent ... are you sure you REALLY want this?");
+	if (AI_SUCCESS == aiGetMaterialFloat( pMaterial,AI_MATKEY_OPACITY,&fTemp) && (!fTemp || fTemp > 1.01f))	{
+		ReportWarning("Invalid opacity value (must be 0 < opacity < 1.0)");
 	}
 
-	// check whether there are invalid texture keys
+	// Check whether there are invalid texture keys
+	// TODO: that's a relict of the past, where texture type and index were baked
+	// into the material string ... we could do that in one single pass.
 	SearchForInvalidTextures(pMaterial,aiTextureType_DIFFUSE);
 	SearchForInvalidTextures(pMaterial,aiTextureType_SPECULAR);
 	SearchForInvalidTextures(pMaterial,aiTextureType_AMBIENT);
@@ -826,6 +813,9 @@ void ValidateDSProcess::Validate( const aiMaterial* pMaterial)
 	SearchForInvalidTextures(pMaterial,aiTextureType_SHININESS);
 	SearchForInvalidTextures(pMaterial,aiTextureType_HEIGHT);
 	SearchForInvalidTextures(pMaterial,aiTextureType_NORMALS);
+	SearchForInvalidTextures(pMaterial,aiTextureType_DISPLACEMENT);
+	SearchForInvalidTextures(pMaterial,aiTextureType_LIGHTMAP);
+	SearchForInvalidTextures(pMaterial,aiTextureType_REFLECTION);
 }
 
 // ------------------------------------------------------------------------------------------------

+ 18 - 0
include/aiConfig.h

@@ -159,6 +159,24 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #define AI_CONFIG_IMPORT_MD3_SKIN_NAME \
 	"IMPORT_MD3_SKIN_NAME"
 
+// ---------------------------------------------------------------------------
+/** @brief  Specify the Quake 3 shader file to be used for a particular
+ *  MD3 file. This can also be a search path.
+ *
+ * By default Assimp's behaviour is as follows: If a MD3 file 
+ * <tt><any_path>/models/<any_q3_subdir>/<model_name>/<file_name>.md3</tt> is 
+ * loaded, the library tries to locate the corresponding shader file in
+ * <tt><any_path>/scripts/<model_name>.shader</tt>. This property overrides this
+ * behaviour. It can either specify a full path to the shader to be loaded
+ * or alternatively the path (relative or absolute) to the directory where
+ * the shaders for all MD3s to be loaded reside. Assimp attempts to open 
+ * <tt><dir>/<model_name>.shader</tt> first, <tt><dir>/<file_name>.shader</tt> 
+ * is the fallback file. Note that <dir> should have a terminal (back)slash.
+ * Property type: String. Default value: n/a.
+ */
+#define AI_CONFIG_IMPORT_MD3_SHADER_SRC \
+	"IMPORT_MD3_SHADER_SRC"
+
 
 // ---------------------------------------------------------------------------
 /** @brief  Configures the LWO loader to load just one layer from the model.

+ 45 - 4
include/aiMaterial.h

@@ -97,7 +97,8 @@ enum aiPropertyTypeInfo
 };
 
 // ---------------------------------------------------------------------------
-/** @brief Defines how the Nth texture is combined with the N-1th texture.
+/** @brief Defines how the Nth texture of a specific type is combined with
+ *  the result of all previous layers.
  *
  *  Example (left: key, right: value): <br>
  *  @code
@@ -418,8 +419,10 @@ enum aiShadingMode
 // ---------------------------------------------------------------------------
 /** @brief Defines some mixed flags for a particular texture.
  *
- *  Usually you'll tell your artists how textures have to look like ...
- *  however, if you use Assimp for completely generic loadeing purposes you
+ *  Usually you'll tell your cg artists how textures have to look like ...
+ *  and hopefully the follow these rules. If they don't, restrict access
+ *  to the coffee machine for them. That should help. 
+ *  However, if you use Assimp for completely generic loading purposes you
  *  might also need to process these flags in order to display as many 
  *  'unknown' 3D models as possible correctly.
  *
@@ -432,6 +435,25 @@ enum aiTextureFlags
 	aiTextureFlags_Invert = 0x1,
 
 
+	/** Explicit request to the application to process the alpha channel
+	 *  of the texture.
+	 *
+	 *  Mutually exclusive with #aiTextureFlags_IgnoreAlpha. These
+	 *  flags are set if the library can say for sure that the alpha
+	 *  channel is used/is not used. If the model format does not
+	 *  define this, it is left to the application to decide whether
+	 *  the texture alpha channel - if any - is evaluated or not.
+	 */
+	aiTextureFlags_UseAlpha = 0x2,
+
+	/** Explicit request to the application to ignore the alpha channel
+	 *  of the texture.
+	 *
+	 *  Mutually exclusive with #aiTextureFlags_IgnoreAlpha. 
+	 */
+	aiTextureFlags_IgnoreAlpha = 0x4,
+
+
 	
 	 /** @cond never 
 	  *  This value is not used. It forces the compiler to use at least
@@ -539,6 +561,25 @@ struct aiUVTransform
 
 // ---------------------------------------------------------------------------
 /** @brief Data structure for a single material property
+ *
+ *  As an user, you'll probably never need to deal with this data structure.
+ *  Just use the provided aiGetMaterialXXX() or aiMaterial::Get() family
+ *  of functions to query material properties easily. Processing them 
+ *  manually is faster, but it is not the recommended way. It isn't worth
+ *  the effort. <br>
+ *  Material property names follow a simple scheme:
+ *  @code
+ *    $<name>
+ *    ?<name>
+ *       A public property, there must be corresponding AI_MATKEY_XXX define
+ *       2nd: Public, but ignored by the #aiProcess_RemoveRedundantMaterials 
+ *       post-processing step.
+ *    ~<name>
+ *       A temporary property for internal use. If someone forgets to
+ *       cleanup, some of these might still be contained in the output.
+ *       Don't complain, if you understood what the first paragraph tried
+ *       to tell you, you wouldn't even know. 
+ *  @endcode
  *  @see aiMaterial
  */
 struct aiMaterialProperty
@@ -710,7 +751,7 @@ extern "C" {
  * <b>Type:</b> string (aiString)<br>
  * <b>Default value:</b> <tt>none</tt> <br>
 */
-#define AI_MATKEY_NAME "$mat.name",0,0
+#define AI_MATKEY_NAME "?mat.name",0,0
 
 // ---------------------------------------------------------------------------
 /** @def AI_MATKEY_TWOSIDED

+ 2 - 5
include/aiScene.h

@@ -171,7 +171,6 @@ struct aiNode
  */
 #define AI_SCENE_FLAGS_INCOMPLETE	0x1
 
-
 /** @def AI_SCENE_FLAGS_VALIDATED
  * This flag is set by the validation postprocess-step (aiPostProcess_ValidateDS)
  * if the validation is successful. In a validated scene you can be sure that
@@ -179,7 +178,6 @@ struct aiNode
  */
 #define AI_SCENE_FLAGS_VALIDATED	0x2
 
-
 /** @def AI_SCENE_FLAGS_VALIDATION_WARNING
  * This flag is set by the validation postprocess-step (aiPostProcess_ValidateDS)
  * if the validation is successful but some issues have been found.
@@ -190,7 +188,6 @@ struct aiNode
  */
 #define AI_SCENE_FLAGS_VALIDATION_WARNING  	0x4
 
-
 /** @def AI_SCENE_FLAGS_NON_VERBOSE_FORMAT
  * This flag is currently only set by the aiProcess_JoinIdenticalVertices step.
  * It indicates that the vertices of the output meshes aren't in the internal
@@ -199,7 +196,6 @@ struct aiNode
  */
 #define AI_SCENE_FLAGS_NON_VERBOSE_FORMAT  	0x8
 
-
  /** @def AI_SCENE_FLAGS_TERRAIN
  * Denotes pure height-map terrain data. Pure terrains usually consist of quads, 
  * sometimes triangles, in a regular grid. The x,y coordinates of all vertex 
@@ -212,7 +208,8 @@ struct aiNode
  * as long as possible (typically you'll do the triangulation when you actually
  * need to render it).
  */
-#define AI_SCENE_FLAGS_TERRAIN 0x16
+#define AI_SCENE_FLAGS_TERRAIN 0x10
+
 
 // -------------------------------------------------------------------------------
 /** The root structure of the imported data. 

+ 5 - 1
tools/assimp_view/AssetHelper.h

@@ -150,7 +150,8 @@ class AssetHelper
 					piShininessTexture	(NULL),
 					piLightmapTexture	(NULL),
 					pvOriginalNormals	(NULL),
-					bSharedFX(false) {}
+					bSharedFX(false),
+					twosided (false){}
 
 				~MeshHelper ()
 					{
@@ -203,6 +204,9 @@ class AssetHelper
 				// strength of the specular highlight
 				float fSpecularStrength;
 
+				// two-sided?
+				bool twosided;
+
 				// Stores a pointer to the original normal set of the asset
 				aiVector3D* pvOriginalNormals;
 			};

+ 210 - 202
tools/assimp_view/Display.cpp

@@ -45,6 +45,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 namespace AssimpView {
 
+struct SVertex
+{
+	float x,y,z,w,u,v;
+};
 
 CDisplay CDisplay::s_cInstance;
 
@@ -91,43 +95,40 @@ void GetNodeCount(aiNode* pcNode, unsigned int* piCnt)
 }
 
 //-------------------------------------------------------------------------------
+int CDisplay::EnableAnimTools(BOOL hm) 
+{
+	EnableWindow(GetDlgItem(g_hDlg,IDC_PLAY),hm);
+	EnableWindow(GetDlgItem(g_hDlg,IDC_SLIDERANIM),hm);
+	return 1;
+}
+
+//-------------------------------------------------------------------------------
+// Fill animation combo box
 int CDisplay::FillAnimList(void)
 {
-	if (0 == g_pcAsset->pcScene->mNumAnimations)
-	{
-		// disable all UI components related to animations
-		EnableWindow(GetDlgItem(g_hDlg,IDC_PLAYANIM),FALSE);
-		EnableWindow(GetDlgItem(g_hDlg,IDC_SPEED),FALSE);
-		EnableWindow(GetDlgItem(g_hDlg,IDC_PINORDER),FALSE);
-
-		EnableWindow(GetDlgItem(g_hDlg,IDC_SSPEED),FALSE);
-		EnableWindow(GetDlgItem(g_hDlg,IDC_SANIMGB),FALSE);
-		EnableWindow(GetDlgItem(g_hDlg,IDC_SANIM),FALSE);
-		EnableWindow(GetDlgItem(g_hDlg,IDC_COMBO1),FALSE);
-	}
-	else
+	if (0 != g_pcAsset->pcScene->mNumAnimations)
 	{
-		// reenable all animation components if they have been
-		// disabled for a previous mesh
-		EnableWindow(GetDlgItem(g_hDlg,IDC_PLAYANIM),TRUE);
-		EnableWindow(GetDlgItem(g_hDlg,IDC_SPEED),TRUE);
-		EnableWindow(GetDlgItem(g_hDlg,IDC_PINORDER),TRUE);
-
-		EnableWindow(GetDlgItem(g_hDlg,IDC_SSPEED),TRUE);
-		EnableWindow(GetDlgItem(g_hDlg,IDC_SANIMGB),TRUE);
-		EnableWindow(GetDlgItem(g_hDlg,IDC_SANIM),TRUE);
-		EnableWindow(GetDlgItem(g_hDlg,IDC_COMBO1),TRUE);
-
 		// now fill in all animation names
-		for (unsigned int i = 0; i < g_pcAsset->pcScene->mNumAnimations;++i)
-		{
+		for (unsigned int i = 0; i < g_pcAsset->pcScene->mNumAnimations;++i)	{
 			SendDlgItemMessage(g_hDlg,IDC_COMBO1,CB_ADDSTRING,0,
 				( LPARAM ) g_pcAsset->pcScene->mAnimations[i]->mName.data);
 		}
+
+		// also add a dummy - 'none'
+		SendDlgItemMessage(g_hDlg,IDC_COMBO1,CB_ADDSTRING,0,(LPARAM)"none");
+
+		// select first
+		SendDlgItemMessage(g_hDlg,IDC_COMBO1,CB_SETCURSEL,0,0);
+
+		EnableAnimTools(TRUE);
 	}
+	else // tools remain disabled
+		EnableAnimTools(FALSE);
+
 	return 1;
 }
 //-------------------------------------------------------------------------------
+// Clear the list of animations
 int CDisplay::ClearAnimList(void)
 {
 	// clear the combo box
@@ -135,6 +136,7 @@ int CDisplay::ClearAnimList(void)
 	return 1;
 }
 //-------------------------------------------------------------------------------
+// Clear the tree view
 int CDisplay::ClearDisplayList(void)
 {
 	// clear the combo box
@@ -143,6 +145,7 @@ int CDisplay::ClearDisplayList(void)
 	return 1;
 }
 //-------------------------------------------------------------------------------
+// Add a specific node to the display list
 int CDisplay::AddNodeToDisplayList(
 	unsigned int iIndex, 
 	unsigned int iDepth,
@@ -154,10 +157,8 @@ int CDisplay::AddNodeToDisplayList(
 
 	char chTemp[512];
 
-	if(0 == pcNode->mName.length)
-	{
-		if (iIndex >= 100)
-		{
+	if(0 == pcNode->mName.length)	{
+		if (iIndex >= 100)	{
 			iIndex += iDepth  * 1000;
 		}
 		else if (iIndex >= 10)
@@ -190,8 +191,7 @@ int CDisplay::AddNodeToDisplayList(
 
 	// recursively add all child nodes
 	++iDepth;
-	for (unsigned int i = 0; i< pcNode->mNumChildren;++i)
-	{
+	for (unsigned int i = 0; i< pcNode->mNumChildren;++i){
 		AddNodeToDisplayList(i,iDepth,pcNode->mChildren[i],hTexture);
 	}
 
@@ -203,6 +203,7 @@ int CDisplay::AddNodeToDisplayList(
 	return 1;
 }
 //-------------------------------------------------------------------------------
+// Replace the currently selected texture by another one
 int CDisplay::ReplaceCurrentTexture(const char* szPath)
 {
 	ai_assert(NULL != szPath);
@@ -214,8 +215,7 @@ int CDisplay::ReplaceCurrentTexture(const char* szPath)
 	szString.length = strlen(szPath);
 	CMaterialManager::Instance().LoadTexture(&piTexture,&szString);
 
-	if (!piTexture)
-	{
+	if (!piTexture)	{
 		CLogDisplay::Instance().AddEntry("[ERROR] Unable to load this texture",
 			D3DCOLOR_ARGB(0xFF,0xFF,0x0,0x0));
 		return 0;
@@ -225,15 +225,15 @@ int CDisplay::ReplaceCurrentTexture(const char* szPath)
 	// view item if the default texture was previously set
 	TVITEMEX tvi; 
 	tvi.mask = TVIF_SELECTEDIMAGE | TVIF_IMAGE;
-	tvi.iImage = this->m_aiImageList[AI_VIEW_IMGLIST_MATERIAL];
-	tvi.iSelectedImage = this->m_aiImageList[AI_VIEW_IMGLIST_MATERIAL];
+	tvi.iImage = m_aiImageList[AI_VIEW_IMGLIST_MATERIAL];
+	tvi.iSelectedImage = m_aiImageList[AI_VIEW_IMGLIST_MATERIAL];
 
 	TreeView_SetItem(GetDlgItem(g_hDlg,IDC_TREE1),
-		this->m_pcCurrentTexture->hTreeItem);
+		m_pcCurrentTexture->hTreeItem);
 
 	// change this in the old aiMaterial structure, too
 	Assimp::MaterialHelper* pcMat = (Assimp::MaterialHelper*)
-		g_pcAsset->pcScene->mMaterials[this->m_pcCurrentTexture->iMatIndex];
+		g_pcAsset->pcScene->mMaterials[m_pcCurrentTexture->iMatIndex];
  	
 	// update all meshes referencing this material
 	for (unsigned int i = 0; i < g_pcAsset->pcScene->mNumMeshes;++i)
@@ -242,137 +242,73 @@ int CDisplay::ReplaceCurrentTexture(const char* szPath)
 			continue;
 
 		AssetHelper::MeshHelper* pcMesh = g_pcAsset->apcMeshes[i];
+		IDirect3DTexture9** tex = NULL;
+		const char* tex_string  = NULL;
+
 		switch (this->m_pcCurrentTexture->iType)
 		{
 		case aiTextureType_DIFFUSE:
-			if (pcMesh->piDiffuseTexture && pcMesh->piDiffuseTexture != piTexture)
-			{
-				pcMesh->piDiffuseTexture->Release();
-				pcMesh->piDiffuseTexture = piTexture;
-				this->m_pcCurrentTexture->piTexture = &pcMesh->piDiffuseTexture;
-
-				if (!pcMesh->bSharedFX)
-				{
-					pcMesh->piEffect->SetTexture("DIFFUSE_TEXTURE",piTexture);
-				}
-			}
+			tex = &pcMesh->piDiffuseTexture;
+			tex_string = "DIFFUSE_TEXTURE";
 			break;
 		case aiTextureType_AMBIENT:
-			if (pcMesh->piAmbientTexture && pcMesh->piAmbientTexture != piTexture)
-			{
-				pcMesh->piAmbientTexture->Release();
-				pcMesh->piAmbientTexture = piTexture;
-				this->m_pcCurrentTexture->piTexture = &pcMesh->piAmbientTexture;
-
-				if (!pcMesh->bSharedFX)
-				{
-					pcMesh->piEffect->SetTexture("AMBIENT_TEXTURE",piTexture);
-				}
-			}
+			tex = &pcMesh->piAmbientTexture;
+			tex_string = "AMBIENT_TEXTURE";
 			break;
 		case aiTextureType_SPECULAR:
-			if (pcMesh->piSpecularTexture && pcMesh->piSpecularTexture != piTexture)
-			{
-				pcMesh->piSpecularTexture->Release();
-				pcMesh->piSpecularTexture = piTexture;
-				this->m_pcCurrentTexture->piTexture = &pcMesh->piSpecularTexture;
-
-				if (!pcMesh->bSharedFX)
-				{
-					pcMesh->piEffect->SetTexture("SPECULAR_TEXTURE",piTexture);
-				}
-			}
+			tex = &pcMesh->piSpecularTexture;
+			tex_string = "SPECULAR_TEXTURE";
 			break;
 		case aiTextureType_EMISSIVE:
-			if (pcMesh->piEmissiveTexture && pcMesh->piEmissiveTexture != piTexture)
-			{
-				pcMesh->piEmissiveTexture->Release();
-				pcMesh->piEmissiveTexture = piTexture;
-				this->m_pcCurrentTexture->piTexture = &pcMesh->piEmissiveTexture;
-
-				if (!pcMesh->bSharedFX)
-				{
-					pcMesh->piEffect->SetTexture("EMISSIVE_TEXTURE",piTexture);
-				}
-			}
+			tex = &pcMesh->piEmissiveTexture;
+			tex_string = "EMISSIVE_TEXTURE";
+			break;
+		case aiTextureType_LIGHTMAP:
+			tex = &pcMesh->piLightmapTexture;
+			tex_string = "LIGHTMAP_TEXTURE";
+			break;
+		case aiTextureType_DISPLACEMENT:
+		case aiTextureType_REFLECTION:
+		case aiTextureType_UNKNOWN:
 			break;
 		case aiTextureType_SHININESS:
-			if (pcMesh->piShininessTexture && pcMesh->piShininessTexture != piTexture)
-			{
-				pcMesh->piShininessTexture->Release();
-				pcMesh->piShininessTexture = piTexture;
-				this->m_pcCurrentTexture->piTexture = &pcMesh->piShininessTexture;
-
-				if (!pcMesh->bSharedFX)
-				{
-					pcMesh->piEffect->SetTexture("SHININESS_TEXTURE",piTexture);
-				}
-			}
+			tex = &pcMesh->piShininessTexture;
+			tex_string = "SHININESS_TEXTURE";
 			break;
 		case aiTextureType_NORMALS:
 		case aiTextureType_HEIGHT:
-			if (pcMesh->piNormalTexture && pcMesh->piNormalTexture != piTexture)
-			{
+
+			// special handling here 
+			if (pcMesh->piNormalTexture && pcMesh->piNormalTexture != piTexture)	{
 				pcMesh->piNormalTexture->Release();
 				pcMesh->piNormalTexture = piTexture;
-				CMaterialManager::Instance().HMtoNMIfNecessary(pcMesh->piNormalTexture,
-					&pcMesh->piNormalTexture,true);
-				this->m_pcCurrentTexture->piTexture = &pcMesh->piNormalTexture;
+				CMaterialManager::Instance().HMtoNMIfNecessary(pcMesh->piNormalTexture,&pcMesh->piNormalTexture,true);
+				m_pcCurrentTexture->piTexture = &pcMesh->piNormalTexture;
 
-				if (!pcMesh->bSharedFX)
-				{
+				if (!pcMesh->bSharedFX)	{
 					pcMesh->piEffect->SetTexture("NORMAL_TEXTURE",piTexture);
 				}
 			}
 			break;
 		default: //case aiTextureType_OPACITY && case aiTextureType_OPACITY | 0x40000000:
-			if (pcMesh->piOpacityTexture && pcMesh->piOpacityTexture != piTexture)
-			{
-				pcMesh->piOpacityTexture->Release();
-				pcMesh->piOpacityTexture = piTexture;
-				this->m_pcCurrentTexture->piTexture = &pcMesh->piOpacityTexture;
-
-				if (!pcMesh->bSharedFX)
-				{
-					pcMesh->piEffect->SetTexture("OPACITY_TEXTURE",piTexture);
-				}
-			}
+			
+			tex = &pcMesh->piOpacityTexture;
+			tex_string = "OPACITY_TEXTURE";
 			break;
 		};
+		if (tex && *tex && *tex != piTexture)
+		{
+			(**tex).Release();
+			*tex = piTexture;
+			m_pcCurrentTexture->piTexture = tex;
+
+			if (!pcMesh->bSharedFX){
+				pcMesh->piEffect->SetTexture(tex_string,piTexture);
+			}
+		}
 	}
 	// now update the material itself
 	aiString szOld;
-	const char* szKey = NULL;
-#if 0
-	switch (this->m_pcCurrentTexture->iType)
-	{
-	case aiTextureType_DIFFUSE:
-		szKey = AI_MATKEY_TEXTURE_DIFFUSE(0);
-		break;
-	case aiTextureType_AMBIENT:
-		szKey = AI_MATKEY_TEXTURE_AMBIENT(0);
-		break;
-	case aiTextureType_SPECULAR:
-		szKey = AI_MATKEY_TEXTURE_SPECULAR(0);
-		break;
-	case aiTextureType_EMISSIVE:
-		szKey = AI_MATKEY_TEXTURE_EMISSIVE(0);
-		break;
-	case aiTextureType_NORMALS:
-		szKey = AI_MATKEY_TEXTURE_NORMALS(0);
-		break;
-	case aiTextureType_HEIGHT:
-		szKey = AI_MATKEY_TEXTURE_HEIGHT(0);
-		break;
-	case aiTextureType_SHININESS:
-		szKey = AI_MATKEY_TEXTURE_SHININESS(0);
-		break;
-	default: //case aiTextureType_OPACITY && case aiTextureType_OPACITY | 0x40000000:
-		szKey = AI_MATKEY_TEXTURE_OPACITY(0);
-		break;
-	};
-#endif
-	ai_assert(NULL != szKey);
 
 	aiGetMaterialString(pcMat,AI_MATKEY_TEXTURE(m_pcCurrentTexture->iType,0),&szOld);
 	pcMat->AddProperty(&szString,AI_MATKEY_TEXTURE(m_pcCurrentTexture->iType,0));
@@ -478,8 +414,7 @@ int CDisplay::AddTextureToDisplayList(unsigned int iType,
 		szType = "Opacity";
 		break;
 	};
-	if (bIsExtraOpacity)
-	{
+	if (bIsExtraOpacity)	{
 		sprintf(chTemp,"%s %i (<copy of diffuse #1>)",szType,iIndex+1);
 	}
 	else 
@@ -494,8 +429,7 @@ int CDisplay::AddTextureToDisplayList(unsigned int iType,
 
 	// find out whether this is the default texture or not
 
-	if (piTexture && *piTexture)
-	{
+	if (piTexture && *piTexture)	{
 		// {9785DA94-1D96-426b-B3CB-BADC36347F5E}
 		static const GUID guidPrivateData = 
 			{ 0x9785da94, 0x1d96, 0x426b, 
@@ -651,6 +585,7 @@ int CDisplay::AddMaterialToDisplayList(HTREEITEM hRoot,
 	return 1;
 }
 //-------------------------------------------------------------------------------
+// Expand all elements in the treeview
 int CDisplay::ExpandTree()
 {
 	// expand all materials
@@ -671,6 +606,7 @@ int CDisplay::ExpandTree()
 	return 1;
 }
 //-------------------------------------------------------------------------------
+// Get image list for tree view
 int CDisplay::LoadImageList(void)
 {
 	if (!m_hImageList)
@@ -708,6 +644,7 @@ int CDisplay::LoadImageList(void)
 	return 1;
 }
 //-------------------------------------------------------------------------------
+// Fill tree view
 int CDisplay::FillDisplayList(void)
 {
 	LoadImageList();
@@ -750,6 +687,7 @@ int CDisplay::FillDisplayList(void)
 	return 1;
 }
 //-------------------------------------------------------------------------------
+// Main render loop
 int CDisplay::OnRender()
 {
 	// update possible animation
@@ -759,10 +697,17 @@ int CDisplay::OnRender()
 
 		ai_assert( g_pcAsset->mAnimator);
 		if (g_bPlay) {
-			
-
 			g_dCurrent += clock()/ double( CLOCKS_PER_SEC)   -lastPlaying;
-			g_pcAsset->mAnimator->Calculate( g_dCurrent );
+
+			double time = g_dCurrent;
+			aiAnimation* mAnim = g_pcAsset->mAnimator->CurrentAnim();
+			if(  mAnim && mAnim->mDuration > 0.0) {
+				double tps = mAnim->mTicksPerSecond ? mAnim->mTicksPerSecond : 25.f;
+				time = fmod( time, mAnim->mDuration/tps);
+				SendDlgItemMessage(g_hDlg,IDC_SLIDERANIM,TBM_SETPOS,TRUE,LPARAM(10000 * (time/(mAnim->mDuration/tps))));			
+			}
+
+			g_pcAsset->mAnimator->Calculate( time );
 			lastPlaying = g_dCurrent;
 		}
 	}
@@ -795,6 +740,7 @@ int CDisplay::OnRender()
 	return 1;
 }	
 //-------------------------------------------------------------------------------
+// Update UI
 void UpdateColorFieldsInUI()
 {
 	InvalidateRect(GetDlgItem(g_hDlg,IDC_LCOLOR1),NULL,TRUE);
@@ -806,6 +752,7 @@ void UpdateColorFieldsInUI()
 	UpdateWindow(GetDlgItem(g_hDlg,IDC_LCOLOR3));
 }
 //-------------------------------------------------------------------------------
+// FIll statistics UI
 int CDisplay::FillDefaultStatistics(void)
 {
 	if (!g_pcAsset)
@@ -858,6 +805,7 @@ int CDisplay::FillDefaultStatistics(void)
 	return 1;
 }
 //-------------------------------------------------------------------------------
+// Reset UI
 int CDisplay::Reset(void)
 {
 	// clear all lists
@@ -870,6 +818,7 @@ int CDisplay::Reset(void)
 	return OnSetupNormalView();
 }
 //-------------------------------------------------------------------------------
+// reset to standard statistics view
 void ShowNormalUIComponents()
 {
 	ShowWindow(GetDlgItem(g_hDlg,IDC_NUMNODES),SW_SHOW);
@@ -1089,60 +1038,45 @@ int CDisplay::OnSetupTextureView(TextureInfo* pcNew)
 int CDisplay::OnSetup(HTREEITEM p_hTreeItem)
 {
 	// search in our list for the item
-
 	union	{
 		TextureInfo* pcNew;
 		NodeInfo* pcNew2;
-		MaterialInfo* pcNew3;	};
+		MaterialInfo* pcNew3;	
+	};
 
 	pcNew = NULL;
-	for (std::vector<TextureInfo>::iterator
-		i =  this->m_asTextures.begin();
-		i != this->m_asTextures.end();++i)
-	{
-		if (p_hTreeItem == (*i).hTreeItem)
-		{
+	for (std::vector<TextureInfo>::iterator i =  m_asTextures.begin();i != m_asTextures.end();++i){
+		if (p_hTreeItem == (*i).hTreeItem)	{
 			pcNew = &(*i);
 			break;
 		}
 	}
-	if (pcNew)
-	{
-		return this->OnSetupTextureView(pcNew);
+	if (pcNew)	{
+		return OnSetupTextureView(pcNew);
 	}
 
 	// seach the node list
-	for (std::vector<NodeInfo>::iterator
-		i =  this->m_asNodes.begin();
-		i != this->m_asNodes.end();++i)
-	{
-		if (p_hTreeItem == (*i).hTreeItem)
-		{
+	for (std::vector<NodeInfo>::iterator i =  m_asNodes.begin(); i != m_asNodes.end();++i){
+		if (p_hTreeItem == (*i).hTreeItem)	{
 			pcNew2 = &(*i);
 			break;
 		}
 	}
-	if (pcNew2)
-	{
-		return this->OnSetupNodeView(pcNew2);
+	if (pcNew2)	{
+		return OnSetupNodeView(pcNew2);
 	}
 
 	// seach the material list
-	for (std::vector<MaterialInfo>::iterator
-		i =  this->m_asMaterials.begin();
-		i != this->m_asMaterials.end();++i)
-	{
-		if (p_hTreeItem == (*i).hTreeItem)
-		{
+	for (std::vector<MaterialInfo>::iterator i =  m_asMaterials.begin();i != m_asMaterials.end();++i){
+		if (p_hTreeItem == (*i).hTreeItem){
 			pcNew3 = &(*i);
 			break;
 		}
 	}
-	if (pcNew3)
-	{
-		return this->OnSetupMaterialView(pcNew3);
+	if (pcNew3)	{
+		return OnSetupMaterialView(pcNew3);
 	}
-	return this->OnSetupNormalView();
+	return OnSetupNormalView();
 }
 //-------------------------------------------------------------------------------
 int CDisplay::ShowTreeViewContextMenu(HTREEITEM hItem)
@@ -1499,6 +1433,7 @@ int CDisplay::HandleTreeViewPopup2(WPARAM wParam,LPARAM lParam)
 	return 0;
 }
 //-------------------------------------------------------------------------------
+// Setup stereo view
 int CDisplay::SetupStereoView()
 {
 	if (NULL != g_pcAsset && NULL != g_pcAsset->pcScene->mRootNode)
@@ -1515,6 +1450,7 @@ int CDisplay::SetupStereoView()
 	return 1;
 }
 //-------------------------------------------------------------------------------
+// Do the actual rendering pass for the stereo view
 int CDisplay::RenderStereoView(const aiMatrix4x4& m)
 {
 	// and rerender the scene
@@ -1550,6 +1486,7 @@ int CDisplay::RenderStereoView(const aiMatrix4x4& m)
 	return 1;
 }
 //-------------------------------------------------------------------------------
+// Process input for the texture view
 int CDisplay::HandleInputTextureView()
 {
 	HandleMouseInputTextureView();
@@ -1557,6 +1494,7 @@ int CDisplay::HandleInputTextureView()
 	return 1;
 }
 //-------------------------------------------------------------------------------
+// Get input for the current state
 int CDisplay::HandleInput()
 {
 	if(CBackgroundPainter::TEXTURE_CUBE == CBackgroundPainter::Instance().GetMode())
@@ -1608,6 +1546,7 @@ int CDisplay::HandleInput()
 	return 1;
 }
 //-------------------------------------------------------------------------------
+// Process input for an empty scen view to allow for skybox rotations
 int CDisplay::HandleInputEmptyScene()
 {
 	if(CBackgroundPainter::TEXTURE_CUBE == CBackgroundPainter::Instance().GetMode())
@@ -1630,6 +1569,7 @@ int CDisplay::HandleInputEmptyScene()
 	return 1;
 }
 //-------------------------------------------------------------------------------
+// Draw the HUD on top of the scene
 int CDisplay::DrawHUD()
 {
   // HACK: (thom) can't get the effect to work on non-shader cards, therefore deactivated for the moment
@@ -1642,21 +1582,16 @@ int CDisplay::DrawHUD()
 	sRect.right -= sRect.left;
 	sRect.bottom -= sRect.top;
 
-	struct SVertex
-	{
-		float x,y,z,w,u,v;
-	};
-
 	// commit the texture to the shader
 	// FIX: Necessary because the texture view is also using this shader
 	g_piPassThroughEffect->SetTexture("TEXTURE_2D",g_pcTexture);
 
 	// NOTE: The shader might be used for other purposes, too.
 	// So ensure the right technique is there
-  if( g_sCaps.PixelShaderVersion < D3DPS_VERSION(2,0))
-    g_piPassThroughEffect->SetTechnique( "PassThrough_FF");
-  else
-    g_piPassThroughEffect->SetTechnique("PassThrough");
+	if( g_sCaps.PixelShaderVersion < D3DPS_VERSION(2,0))
+		g_piPassThroughEffect->SetTechnique( "PassThrough_FF");
+	else
+		g_piPassThroughEffect->SetTechnique("PassThrough");
 
 	// build vertices for drawing from system memory
 	UINT dw;
@@ -1713,11 +1648,18 @@ int CDisplay::DrawHUD()
 	return 1;
 }
 //-------------------------------------------------------------------------------
+// Render the full scene, all nodes
 int CDisplay::RenderFullScene()
 {
 	// reset the color index used for drawing normals
 	g_iCurrentColor = 0;
 
+	aiMatrix4x4 pcProj;
+	GetProjectionMatrix(pcProj);
+
+	vPos = GetCameraMatrix(mViewProjection);
+	mViewProjection = mViewProjection * pcProj;
+
 	// setup wireframe/solid rendering mode
 	if (g_sOptions.eDrawMode == RenderOptions::WIREFRAME)
 		g_piDevice->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME);
@@ -1727,6 +1669,24 @@ int CDisplay::RenderFullScene()
 		g_piDevice->SetRenderState(D3DRS_CULLMODE,D3DCULL_CCW);
 	else g_piDevice->SetRenderState(D3DRS_CULLMODE,D3DCULL_NONE);
 
+	// for high-quality mode, enable anisotropic texture filtering
+	if (g_sOptions.bLowQuality) {
+		for (DWORD d = 0; d < 8;++d) {
+			g_piDevice->SetSamplerState(d,D3DSAMP_MAGFILTER,D3DTEXF_LINEAR);
+			g_piDevice->SetSamplerState(d,D3DSAMP_MINFILTER,D3DTEXF_LINEAR);
+			g_piDevice->SetSamplerState(d,D3DSAMP_MIPFILTER,D3DTEXF_LINEAR);
+		}
+	}
+	else {
+		for (DWORD d = 0; d < 8;++d) {
+			g_piDevice->SetSamplerState(d,D3DSAMP_MAGFILTER,D3DTEXF_ANISOTROPIC);
+			g_piDevice->SetSamplerState(d,D3DSAMP_MINFILTER,D3DTEXF_ANISOTROPIC);
+			g_piDevice->SetSamplerState(d,D3DSAMP_MIPFILTER,D3DTEXF_LINEAR);
+
+			g_piDevice->SetSamplerState(d,D3DSAMP_MAXANISOTROPY,g_sCaps.MaxAnisotropy);
+		}
+	}
+
 	// draw the scene background (clear and texture 2d)
 	CBackgroundPainter::Instance().OnPreRender();
 
@@ -1761,9 +1721,37 @@ int CDisplay::RenderFullScene()
 	}
 
 	// setup the stereo view if necessary
-	if (g_sOptions.bStereoView)
+	if (g_sOptions.bStereoView) 
 		RenderStereoView(m);
 
+	// render the skeleton if necessary
+	if (g_sOptions.bSkeleton && NULL != g_pcAsset && NULL != g_pcAsset->pcScene->mRootNode) {
+		// disable the z-buffer
+		g_piDevice->SetRenderState(D3DRS_ZWRITEENABLE,FALSE);
+
+		if (g_sOptions.eDrawMode != RenderOptions::WIREFRAME) {
+			g_piDevice->SetRenderState(D3DRS_ZENABLE,FALSE);
+		}
+
+		g_piDevice->SetVertexDeclaration( gDefaultVertexDecl);
+		// this is very similar to the code in SetupMaterial()
+		ID3DXEffect* piEnd = g_piNormalsEffect;
+		aiMatrix4x4 pcProj = m * mViewProjection;
+
+		D3DXVECTOR4 vVector(1.f,0.f,0.f,1.f);
+		piEnd->SetVector("OUTPUT_COLOR",&vVector);
+		piEnd->SetMatrix("WorldViewProjection", (const D3DXMATRIX*)&pcProj);
+
+		UINT dwPasses = 0;
+		piEnd->Begin(&dwPasses,0);
+		piEnd->BeginPass(0);
+
+		RenderSkeleton(g_pcAsset->pcScene->mRootNode,m,m);
+
+		piEnd->EndPass();piEnd->End();
+		g_piDevice->SetRenderState(D3DRS_ZWRITEENABLE,TRUE);
+		g_piDevice->SetRenderState(D3DRS_ZENABLE,TRUE);
+	}
 
 	// draw the HUD texture on top of the rendered scene using
 	// pre-projected vertices
@@ -1778,6 +1766,37 @@ int CDisplay::RenderMaterialView()
 	return 1;
 }
 //-------------------------------------------------------------------------------
+// Render animation skeleton
+int CDisplay::RenderSkeleton (aiNode* piNode,const aiMatrix4x4& piMatrix, const aiMatrix4x4& parent)
+{
+	aiMatrix4x4 me = g_pcAsset->mAnimator->GetGlobalTransform( piNode);
+
+	me.Transpose();
+	//me *= piMatrix;
+
+	if (piNode->mParent) {
+		AssetHelper::LineVertex data[2];
+		data[0].dColorDiffuse = data[1].dColorDiffuse = D3DCOLOR_ARGB(0xff,0xff,0,0);
+		
+		data[0].vPosition.x = parent.d1;
+		data[0].vPosition.y = parent.d2;
+		data[0].vPosition.z = parent.d3;
+
+		data[1].vPosition.x = me.d1;
+		data[1].vPosition.y = me.d2;
+		data[1].vPosition.z = me.d3;
+
+		g_piDevice->DrawPrimitiveUP(D3DPT_LINELIST,1,&data,sizeof(AssetHelper::LineVertex));
+	}
+
+	// render all child nodes
+	for (unsigned int i = 0; i < piNode->mNumChildren;++i)
+		RenderSkeleton(piNode->mChildren[i],piMatrix, me );
+
+	return 1;
+}
+//-------------------------------------------------------------------------------
+// Render a single node
 int CDisplay::RenderNode (aiNode* piNode,const aiMatrix4x4& piMatrix,
 	bool bAlpha /*= false*/)
 {
@@ -1801,14 +1820,9 @@ int CDisplay::RenderNode (aiNode* piNode,const aiMatrix4x4& piMatrix,
 		bChangedVM = true;
 	}
 
-	aiMatrix4x4 pcProj;
-	GetProjectionMatrix(pcProj);
+	aiMatrix4x4 pcProj = aiMe * mViewProjection;
 
-	aiMatrix4x4 pcCam;
-	aiVector3D vPos = GetCameraMatrix(pcCam);
-	pcProj = (aiMe * pcCam) * pcProj;
-
-	pcCam = aiMe;
+	aiMatrix4x4 pcCam = aiMe;
 	pcCam.Inverse().Transpose();
 
 	// VERY UNOPTIMIZED, much stuff is redundant. Who cares?
@@ -1911,11 +1925,6 @@ int CDisplay::RenderNode (aiNode* piNode,const aiMatrix4x4& piMatrix,
 			const aiMesh* mesh = g_pcAsset->pcScene->mMeshes[piNode->mMeshes[i]];
 			AssetHelper::MeshHelper* helper = g_pcAsset->apcMeshes[piNode->mMeshes[i]];
 
-
-			// fix: Render triangle meshes only
-			if (mesh->mPrimitiveTypes != aiPrimitiveType_TRIANGLE)
-				continue;
-
 			// don't render the mesh if the render pass is incorrect
 			if (g_sOptions.bRenderMats && (helper->piOpacityTexture || helper->fOpacity != 1.0f) && !mesh->HasBones())
 			{
@@ -1986,7 +1995,6 @@ int CDisplay::RenderNode (aiNode* piNode,const aiMatrix4x4& piMatrix,
 				ID3DXEffect* piEnd = g_piNormalsEffect;
 
 				piEnd->SetVector("OUTPUT_COLOR",&vVector);
-
 				piEnd->SetMatrix("WorldViewProjection", (const D3DXMATRIX*)&pcProj);
 
 				UINT dwPasses = 0;

+ 15 - 0
tools/assimp_view/Display.h

@@ -295,6 +295,10 @@ public:
 	// Event handling for pop-up menus displayed by th tree view
 	int HandleTreeViewPopup(WPARAM wParam,LPARAM lParam);
 
+	//------------------------------------------------------------------
+	// Enable animation-related parts of the UI
+	int EnableAnimTools(BOOL hm) ;
+
 	//------------------------------------------------------------------
 	// setter for m_iViewMode
 	inline void SetViewMode(unsigned int p_iNew)
@@ -444,6 +448,13 @@ private:
 	// Used by HandleTreeViewPopup().
 	int HandleTreeViewPopup2(WPARAM wParam,LPARAM lParam);
 
+	//------------------------------------------------------------------
+	// Render skeleton
+	int RenderSkeleton (aiNode* piNode,const aiMatrix4x4& piMatrix, 
+		const aiMatrix4x4& parent);
+
+	
+
 private:
 
 	// view mode
@@ -485,6 +496,10 @@ private:
 	// Colors used to draw the checker pattern (for the
 	// texture viewer as background )
 	D3DXVECTOR4 m_avCheckerColors[2];
+
+	// View projection matrix
+	aiMatrix4x4 mViewProjection;
+	aiVector3D vPos;
 	};
 
 #endif // AV_DISPLAY_H_INCLUDE

+ 19 - 7
tools/assimp_view/Material.cpp

@@ -825,9 +825,12 @@ int CMaterialManager::CreateMaterial(
 		}
 		else
 		{
+			int flags = 0;
+			aiGetMaterialInteger(pcMat,AI_MATKEY_TEXFLAGS_DIFFUSE(0),&flags);
+
 			// try to find out whether the diffuse texture has any
 			// non-opaque pixels. If we find a few, use it as opacity texture
-			if (pcMesh->piDiffuseTexture && HasAlphaPixels(pcMesh->piDiffuseTexture))
+			if (pcMesh->piDiffuseTexture && !(flags & aiTextureFlags_IgnoreAlpha) && HasAlphaPixels(pcMesh->piDiffuseTexture))
 			{
 				int iVal;
 
@@ -911,11 +914,14 @@ int CMaterialManager::CreateMaterial(
 	// This is a workaround for some meshes in the DX SDK (e.g. tiny.x)
 	// FIX: Added this check to the x-loader, but the line remains to
 	// catch other loader doing the same ...
-	if (0.0f == pcMesh->fShininess)
-	{
+	if (0.0f == pcMesh->fShininess){
 		pcMesh->eShadingMode = aiShadingMode_Gouraud;
 	}
 
+	int two_sided = 0;
+	aiGetMaterialInteger(pcMat,AI_MATKEY_TWOSIDED,&two_sided);
+	pcMesh->twosided = (two_sided != 0);
+
 	// check whether we have already a material using the same
 	// shader. This will decrease loading time rapidly ...
 	for (unsigned int i = 0; i < g_pcAsset->pcScene->mNumMeshes;++i)
@@ -969,7 +975,7 @@ int CMaterialManager::CreateMaterial(
 			return 2;
 		}
 	}
-	this->m_iShaderCount++;
+	m_iShaderCount++;
 
 	// build macros for the HLSL compiler
 	unsigned int iCurrent = 0;
@@ -990,7 +996,6 @@ int CMaterialManager::CreateMaterial(
 		++iCurrent;
 
 
-
 		if (mapV == aiTextureMapMode_Wrap)
 			sMacro[iCurrent].Name = "AV_WRAPV";
 		else if (mapV == aiTextureMapMode_Mirror)
@@ -1303,8 +1308,10 @@ int CMaterialManager::SetupMaterial (
 		}
 	}
 
-
-
+	// disable culling, if necessary
+	if (pcMesh->twosided && g_sOptions.bCulling) {
+		g_piDevice->SetRenderState(D3DRS_CULLMODE,D3DCULL_NONE);
+	}
 
 	// setup the correct shader technique to be used for drawing
 	if( g_sCaps.PixelShaderVersion < D3DPS_VERSION(2,0))
@@ -1340,6 +1347,11 @@ int CMaterialManager::EndMaterial (AssetHelper::MeshHelper* pcMesh)
 	pcMesh->piEffect->EndPass();
 	pcMesh->piEffect->End();
 
+	// reenable culling if necessary
+	if (pcMesh->twosided && g_sOptions.bCulling) {
+		g_piDevice->SetRenderState(D3DRS_CULLMODE,D3DCULL_CCW);
+	}
+
 	return 1;
 }
 }; // end namespace AssimpView

+ 12 - 1
tools/assimp_view/MeshRenderer.cpp

@@ -60,8 +60,17 @@ int CMeshRenderer::DrawUnsorted(unsigned int iIndex)
 
 	g_piDevice->SetIndices(g_pcAsset->apcMeshes[iIndex]->piIB);
 
+	D3DPRIMITIVETYPE type;
+	switch (g_pcAsset->pcScene->mMeshes[iIndex]->mPrimitiveTypes) {
+		case aiPrimitiveType_POINT:
+			type = D3DPT_POINTLIST;break;
+		case aiPrimitiveType_LINE:
+			type = D3DPT_LINELIST;break;
+		case aiPrimitiveType_TRIANGLE:
+			type = D3DPT_TRIANGLELIST;break;
+	}
 	// and draw the mesh
-	g_piDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,
+	g_piDevice->DrawIndexedPrimitive(type,
 		0,0,
 		g_pcAsset->pcScene->mMeshes[iIndex]->mNumVertices,0,
 		g_pcAsset->pcScene->mMeshes[iIndex]->mNumFaces);
@@ -76,6 +85,8 @@ int CMeshRenderer::DrawSorted(unsigned int iIndex,const aiMatrix4x4& mWorld)
 	AssetHelper::MeshHelper* pcHelper = g_pcAsset->apcMeshes[iIndex]; 
 	const aiMesh* pcMesh = g_pcAsset->pcScene->mMeshes[iIndex];
 
+	if (pcMesh->mPrimitiveTypes != aiPrimitiveType_TRIANGLE)
+		return DrawUnsorted(iIndex);
 	if (pcMesh->HasBones())
 		return DrawUnsorted(iIndex);
 

+ 5 - 1
tools/assimp_view/MessageProc.cpp

@@ -1180,7 +1180,7 @@ INT_PTR CALLBACK SMMessageProc(HWND hwndDlg,UINT uMsg,
 		if (IDOK == LOWORD(wParam)) {
 			char s[30];
 			GetDlgItemText(hwndDlg,IDC_EDITSM,s,30);
-			g_smoothAngle = atof(s);
+			g_smoothAngle = (float)atof(s);
 
 			EndDialog(hwndDlg,0);
 		}
@@ -1936,6 +1936,10 @@ __DRUNKEN_ALIEN_FROM_MARS:
 					{
 						g_bPlay = !g_bPlay;
 						SetDlgItemText(g_hDlg,IDC_PLAY,(g_bPlay ? "Stop" : "Play"));
+
+						if (g_bPlay)
+							EnableWindow(GetDlgItem(g_hDlg,IDC_SLIDERANIM),FALSE);
+						else EnableWindow(GetDlgItem(g_hDlg,IDC_SLIDERANIM),TRUE);
 					}
 				}
 			// check the file history

+ 7 - 0
tools/assimp_view/SceneAnimator.h

@@ -186,6 +186,13 @@ public:
 		return mCurrentAnimIndex;
 	}
 
+	// ----------------------------------------------------------------------------
+	/** @brief Get the current animation or NULL
+	 */
+	aiAnimation* CurrentAnim() const {
+		return  mCurrentAnimIndex < mScene->mNumAnimations ? mScene->mAnimations[ mCurrentAnimIndex ] : NULL;
+	}
+
 protected:
 
 	/** Recursively creates an internal node structure matching the 

+ 2 - 24
tools/assimp_view/Shaders.cpp

@@ -548,9 +548,6 @@ std::string g_szMaterialShader = std::string(
 		  "#ifdef AV_CLAMPV\n"
 		  "AddressV = CLAMP;\n"
 		  "#endif\n"
-		  "MinFilter=LINEAR;\n"
-		  "MagFilter=LINEAR;\n"
-		  "MipFilter=LINEAR;\n"
 		"};\n"
 	"#endif // AV_DIFFUSE_TEXTUR\n"
 
@@ -559,9 +556,6 @@ std::string g_szMaterialShader = std::string(
 		"sampler DIFFUSE_SAMPLER2\n"
 		"{\n"
 		  "Texture = <DIFFUSE_TEXTURE2>;\n"
-		  "MinFilter=LINEAR;\n"
-		  "MagFilter=LINEAR;\n"
-		  "MipFilter=LINEAR;\n"
 		"};\n"
 	"#endif // AV_DIFFUSE_TEXTUR2\n"
 
@@ -570,9 +564,6 @@ std::string g_szMaterialShader = std::string(
 		"sampler SPECULAR_SAMPLER\n"
 		"{\n"
 		  "Texture = <SPECULAR_TEXTURE>;\n"
-		  "MinFilter=LINEAR;\n"
-		  "MagFilter=LINEAR;\n"
-		  "MipFilter=LINEAR;\n"
 		"};\n"
 	"#endif // AV_SPECULAR_TEXTUR\n"
 
@@ -581,9 +572,6 @@ std::string g_szMaterialShader = std::string(
 		"sampler AMBIENT_SAMPLER\n"
 		"{\n"
 		  "Texture = <AMBIENT_TEXTURE>;\n"
-		  "MinFilter=LINEAR;\n"
-		  "MagFilter=LINEAR;\n"
-		  "MipFilter=LINEAR;\n"
 		"};\n"
 	"#endif // AV_AMBIENT_TEXTUR\n"
 
@@ -592,9 +580,6 @@ std::string g_szMaterialShader = std::string(
 		"sampler LIGHTMAP_SAMPLER\n"
 		"{\n"
 		  "Texture = <LIGHTMAP_TEXTURE>;\n"
-		  "MinFilter=LINEAR;\n"
-		  "MagFilter=LINEAR;\n"
-		  "MipFilter=LINEAR;\n"
 		"};\n"
 	"#endif // AV_LIGHTMAP_TEXTURE\n"
 
@@ -603,9 +588,6 @@ std::string g_szMaterialShader = std::string(
 		"sampler OPACITY_SAMPLER\n"
 		"{\n"
 		  "Texture = <OPACITY_TEXTURE>;\n"
-		  "MinFilter=LINEAR;\n"
-		  "MagFilter=LINEAR;\n"
-		  "MipFilter=LINEAR;\n"
 		"};\n"
 	"#endif // AV_OPACITY_TEXTURE\n"
 
@@ -614,9 +596,6 @@ std::string g_szMaterialShader = std::string(
 		"sampler EMISSIVE_SAMPLER\n"
 		"{\n"
 		  "Texture = <EMISSIVE_TEXTURE>;\n"
-		  "MinFilter=LINEAR;\n"
-		  "MagFilter=LINEAR;\n"
-		  "MipFilter=LINEAR;\n"
 		"};\n"
 	"#endif // AV_EMISSIVE_TEXTUR\n"
 
@@ -625,9 +604,6 @@ std::string g_szMaterialShader = std::string(
 		"sampler NORMAL_SAMPLER\n"
 		"{\n"
 		  "Texture = <NORMAL_TEXTURE>;\n"
-		  "MinFilter=LINEAR;\n"
-		  "MagFilter=LINEAR;\n"
-		  "MipFilter=LINEAR;\n"
 		"};\n"
 	"#endif // AV_NORMAL_TEXTURE\n"
 
@@ -1278,6 +1254,8 @@ std::string g_szPassThroughShader = std::string(
 		"sampler TEXTURE_SAMPLER = sampler_state\n"
 		"{\n"
 			"Texture = (TEXTURE_2D);\n"
+			"MinFilter = POINT;\n"
+			"MagFilter = POINT;\n"
 		"};\n"
 
     // Vertex Shader output for pixel shader usage

BIN
tools/assimp_view/assimp_view.aps


+ 27 - 37
tools/assimp_view/assimp_view.cpp

@@ -128,10 +128,6 @@ DWORD WINAPI LoadThreadProc(LPVOID lpParameter)
 	// get current time
 	double fCur = (double)timeGetTime();
 
-	// Remove all line and point meshes from the import
-	aiSetImportPropertyInteger(AI_CONFIG_PP_SBP_REMOVE,
-		aiPrimitiveType_LINE | aiPrimitiveType_POINT);
-
 	// Call ASSIMPs C-API to load the file
 	g_pcAsset->pcScene = (aiScene*)aiImportFile(g_szFileName,
 		aiProcess_CalcTangentSpace		   | // calculate tangents and bitangents if possible
@@ -456,24 +452,17 @@ int CreateAssetData()
 		const aiMesh* mesh = g_pcAsset->pcScene->mMeshes[i];
 
 		// create the material for the mesh
-		if (!g_pcAsset->apcMeshes[i]->piEffect)
-		{
+		if (!g_pcAsset->apcMeshes[i]->piEffect)	{
 			CMaterialManager::Instance().CreateMaterial(
 				g_pcAsset->apcMeshes[i],mesh);
 		}
 
-		if (mesh->mPrimitiveTypes != aiPrimitiveType_TRIANGLE)
-		{
-			continue;
-		}
-
 		// create vertex buffer
 		if(FAILED( g_piDevice->CreateVertexBuffer(sizeof(AssetHelper::Vertex) *
 			mesh->mNumVertices,
 			D3DUSAGE_WRITEONLY,
 			0,
-			D3DPOOL_DEFAULT, &g_pcAsset->apcMeshes[i]->piVB,NULL)))
-		{
+			D3DPOOL_DEFAULT, &g_pcAsset->apcMeshes[i]->piVB,NULL)))	{
 			MessageBox(g_hDlg,"Failed to create vertex buffer",
 				"ASSIMP Viewer Utility",MB_OK);
 			return 2;
@@ -483,12 +472,22 @@ int CreateAssetData()
 		if (g_pcAsset->apcMeshes[i]->piOpacityTexture || 1.0f != g_pcAsset->apcMeshes[i]->fOpacity)
 			dwUsage |= D3DUSAGE_DYNAMIC;
 
+		unsigned int nidx;
+		switch (mesh->mPrimitiveTypes) {
+			case aiPrimitiveType_POINT:
+				nidx = 1;break;
+			case aiPrimitiveType_LINE:
+				nidx = 2;break;
+			case aiPrimitiveType_TRIANGLE:
+				nidx = 3;break;
+			default: assert(false);
+		};
+
 		// check whether we can use 16 bit indices
-		if (mesh->mNumFaces * 3 >= 65536)
-		{
+		if (mesh->mNumFaces * 3 >= 65536)	{
 			// create 32 bit index buffer
 			if(FAILED( g_piDevice->CreateIndexBuffer( 4 *
-				mesh->mNumFaces * 3,
+				mesh->mNumFaces * nidx,
 				D3DUSAGE_WRITEONLY | dwUsage,
 				D3DFMT_INDEX32,
 				D3DPOOL_DEFAULT, 
@@ -505,17 +504,16 @@ int CreateAssetData()
 			g_pcAsset->apcMeshes[i]->piIB->Lock(0,0,(void**)&pbData,0);
 			for (unsigned int x = 0; x < mesh->mNumFaces;++x)
 			{
-				for (unsigned int a = 0; a < 3;++a)
+				for (unsigned int a = 0; a < nidx;++a)
 				{
 					*pbData++ = mesh->mFaces[x].mIndices[a];
 				}
 			}
 		}
-		else
-		{
+		else	{
 			// create 16 bit index buffer
 			if(FAILED( g_piDevice->CreateIndexBuffer( 2 *
-				mesh->mNumFaces * 3,
+				mesh->mNumFaces * nidx,
 				D3DUSAGE_WRITEONLY | dwUsage,
 				D3DFMT_INDEX16,
 				D3DPOOL_DEFAULT,
@@ -532,7 +530,7 @@ int CreateAssetData()
 			g_pcAsset->apcMeshes[i]->piIB->Lock(0,0,(void**)&pbData,0);
 			for (unsigned int x = 0; x < mesh->mNumFaces;++x)
 			{
-				for (unsigned int a = 0; a < 3;++a)
+				for (unsigned int a = 0; a < nidx;++a)
 				{
 					*pbData++ = (uint16_t)mesh->mFaces[x].mIndices[a];
 				}
@@ -542,8 +540,7 @@ int CreateAssetData()
 
 		// collect weights on all vertices. Quick and careless
 		std::vector<std::vector<aiVertexWeight> > weightsPerVertex( mesh->mNumVertices);
-		for( unsigned int a = 0; a < mesh->mNumBones; a++)
-		{
+		for( unsigned int a = 0; a < mesh->mNumBones; a++)	{
 			const aiBone* bone = mesh->mBones[a];
 			for( unsigned int b = 0; b < bone->mNumWeights; b++)
 				weightsPerVertex[bone->mWeights[b].mVertexId].push_back( aiVertexWeight( a, bone->mWeights[b].mWeight));
@@ -560,19 +557,16 @@ int CreateAssetData()
 				pbData2->vNormal = aiVector3D(0.0f,0.0f,0.0f);
 			else pbData2->vNormal = mesh->mNormals[x];
 
-			if (NULL == mesh->mTangents)
-			{
+			if (NULL == mesh->mTangents)	{
 				pbData2->vTangent = aiVector3D(0.0f,0.0f,0.0f);
 				pbData2->vBitangent = aiVector3D(0.0f,0.0f,0.0f);
 			}
-			else 
-			{
+			else	{
 				pbData2->vTangent = mesh->mTangents[x];
 				pbData2->vBitangent = mesh->mBitangents[x];
 			}
 
-			if (mesh->HasVertexColors( 0))
-			{
+			if (mesh->HasVertexColors( 0))	{
 				pbData2->dColorDiffuse = D3DCOLOR_ARGB(
 					((unsigned char)std::max( std::min( mesh->mColors[0][x].a * 255.0f, 255.0f),0.0f)),
 					((unsigned char)std::max( std::min( mesh->mColors[0][x].r * 255.0f, 255.0f),0.0f)),
@@ -582,16 +576,14 @@ int CreateAssetData()
 			else pbData2->dColorDiffuse = D3DCOLOR_ARGB(0xFF,0xff,0xff,0xff);
 
 			// ignore a third texture coordinate component
-			if (mesh->HasTextureCoords( 0))
-			{
+			if (mesh->HasTextureCoords( 0))	{
 				pbData2->vTextureUV = aiVector2D(
 					mesh->mTextureCoords[0][x].x,
 					mesh->mTextureCoords[0][x].y);
 			}
 			else pbData2->vTextureUV = aiVector2D(0.5f,0.5f);
 
-			if (mesh->HasTextureCoords( 1))
-			{
+			if (mesh->HasTextureCoords( 1))	{
 				pbData2->vTextureUV2 = aiVector2D(
 					mesh->mTextureCoords[1][x].x,
 					mesh->mTextureCoords[1][x].y);
@@ -599,8 +591,7 @@ int CreateAssetData()
 			else pbData2->vTextureUV2 = aiVector2D(0.5f,0.5f);
 
 			// Bone indices and weights
-			if( mesh->HasBones())
-			{
+			if( mesh->HasBones())	{
 				unsigned char boneIndices[4] = { 0, 0, 0, 0 };
 				unsigned char boneWeights[4] = { 0, 0, 0, 0 };
 				ai_assert( weightsPerVertex[x].size() <= 4);
@@ -623,8 +614,7 @@ int CreateAssetData()
 		g_pcAsset->apcMeshes[i]->piVB->Unlock();
 
 		// now generate the second vertex buffer, holding all normals
-		if (!g_pcAsset->apcMeshes[i]->piVBNormals)
-		{
+		if (!g_pcAsset->apcMeshes[i]->piVBNormals)	{
 			GenerateNormalsAsLineList(g_pcAsset->apcMeshes[i],mesh);
 		}
 	}

+ 1 - 1
tools/assimp_view/assimp_view.rc

@@ -96,7 +96,7 @@ BEGIN
     LTEXT           "[A]",IDC_STATIC,552,348,11,8
     LTEXT           "[Z]",IDC_STATIC,552,358,11,8
     LTEXT           "[R]",IDC_STATIC,552,368,11,8
-    COMBOBOX        IDC_COMBO1,353,361,84,30,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP
+    COMBOBOX        IDC_COMBO1,353,361,84,30,CBS_DROPDOWN | WS_VSCROLL | WS_TABSTOP
     CONTROL         149,IDC_STATIC,"Static",SS_BITMAP | SS_CENTERIMAGE,470,0,114,9
     CONTROL         148,IDC_STATIC,"Static",SS_BITMAP | SS_CENTERIMAGE,470,229,114,9
     CONTROL         147,IDC_STATIC,"Static",SS_BITMAP | SS_CENTERIMAGE,470,336,114,9