Browse Source

General
- Added format auto-detection to most loaders
- Simplified BaseImporter::CanRead() with some utility methods
- improved fast_atof -> no overruns anymore. Fuck you, irrlicht.
- added assimp_cmd tool to allow command line model processing. Mainly adebugging tool for internal purposes, but others might find it useful, too.
- vc8/vc9: revision number is now written to DLL version header
- mkutil: some batch scripts to simplify tagging & building of release versions
- some API cleanup
- fixing some doxygen markup (+now explicit use of @file <filename>)
- Icon for assimp_view and assimp_cmd

3DS
- Normal vectors are not anymore inverted in some cases
- Improved pivot handling
- Improved handling of x-flipped meshes

Collada
- fixed a minor bug (visual_scene element)

LWS
- WIP implementation. No animations yet, some bugs and crashes.
- Animation system remains disabled, WIP code
- many test files for LWS, but most of them test the anim support, which is, read above, currently disabled.

STL
- fixing a log warning which appears for every model
- added binary&ascii test spider, exported from truespace

MD3
- Cleaning up output tags for automatically joined player models.


IRR
- Fixing coordinate system issues.
- Instance handling improved.
- Some of the reported crashes not yet fixed.

PretransformVertices
- Numerous performance improvements.
- Added config option to preserve the hierarchy during the step.

RemoveRedundantMaterials
- Added config option to specify a list of materials which are kept in every case.

UNREAL
- Added support for the old unreal data format (*.a,*.d,*.uc)
- tested only with exports from Milkshape
- more Unreal stuff to come soon



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

aramis_acg 16 years ago
parent
commit
4bbc03332b
100 changed files with 4869 additions and 1608 deletions
  1. 32 7
      INSTALL
  2. 0 6
      clean.bat
  3. 55 48
      code/3DSConverter.cpp
  4. 3 5
      code/3DSHelper.h
  5. 51 60
      code/3DSLoader.cpp
  6. 22 20
      code/3DSLoader.h
  7. 19 10
      code/ACLoader.cpp
  8. 8 7
      code/ACLoader.h
  9. 24 19
      code/ASELoader.cpp
  10. 14 12
      code/ASELoader.h
  11. 5 3
      code/ASEParser.cpp
  12. 1 1
      code/ASEParser.h
  13. 6 4
      code/AssimpPCH.cpp
  14. 8 0
      code/AssimpPCH.h
  15. 4 2
      code/B3DImporter.cpp
  16. 1 1
      code/B3DImporter.h
  17. 11 10
      code/BVHLoader.cpp
  18. 11 4
      code/BVHLoader.h
  19. 120 25
      code/BaseImporter.cpp
  20. 137 57
      code/BaseImporter.h
  21. 1 1
      code/BaseProcess.cpp
  22. 10 6
      code/CalcTangentsProcess.cpp
  23. 16 14
      code/ColladaLoader.cpp
  24. 2 5
      code/ColladaLoader.h
  25. 3 3
      code/ColladaParser.cpp
  26. 1 1
      code/ColladaParser.h
  27. 11 13
      code/DXFLoader.cpp
  28. 6 6
      code/DXFLoader.h
  29. 57 23
      code/DefaultLogger.cpp
  30. 126 65
      code/FindDegenerates.cpp
  31. 30 0
      code/FindDegenerates.h
  32. 1 1
      code/GenFaceNormalsProcess.cpp
  33. 7 9
      code/HMPFileData.h
  34. 24 14
      code/HMPLoader.cpp
  35. 8 9
      code/HMPLoader.h
  36. 4 4
      code/HalfLifeFileData.h
  37. 39 45
      code/IRRLoader.cpp
  38. 9 3
      code/IRRLoader.h
  39. 12 13
      code/IRRMeshLoader.cpp
  40. 7 13
      code/IRRMeshLoader.h
  41. 5 1
      code/IRRShared.cpp
  42. 130 29
      code/Importer.cpp
  43. 80 60
      code/ImproveCacheLocality.cpp
  44. 9 11
      code/ImproveCacheLocality.h
  45. 109 76
      code/JoinVerticesProcess.cpp
  46. 2 0
      code/JoinVerticesProcess.h
  47. 567 0
      code/LWOAnimation.cpp
  48. 336 0
      code/LWOAnimation.h
  49. 47 7
      code/LWOBLoader.cpp
  50. 35 10
      code/LWOFileData.h
  51. 184 45
      code/LWOLoader.cpp
  52. 20 3
      code/LWOLoader.h
  53. 64 32
      code/LWOMaterial.cpp
  54. 791 13
      code/LWSLoader.cpp
  55. 143 14
      code/LWSLoader.h
  56. 7 17
      code/MD2FileData.h
  57. 26 23
      code/MD2Loader.cpp
  58. 8 9
      code/MD2Loader.h
  59. 3 5
      code/MD3FileData.h
  60. 53 24
      code/MD3Loader.cpp
  61. 5 1
      code/MD3Loader.h
  62. 30 30
      code/MD5Loader.cpp
  63. 7 8
      code/MD5Loader.h
  64. 6 5
      code/MD5Parser.cpp
  65. 18 20
      code/MD5Parser.h
  66. 3 5
      code/MDCFileData.h
  67. 18 16
      code/MDCLoader.cpp
  68. 8 9
      code/MDCLoader.h
  69. 13 14
      code/MDLFileData.h
  70. 26 17
      code/MDLLoader.cpp
  71. 2 1
      code/MDLLoader.h
  72. 2 2
      code/MDLMaterialLoader.cpp
  73. 10 16
      code/NFFLoader.cpp
  74. 9 8
      code/NFFLoader.h
  75. 20 12
      code/OFFLoader.cpp
  76. 6 6
      code/OFFLoader.h
  77. 3 16
      code/ObjFileImporter.cpp
  78. 1 4
      code/ObjFileImporter.h
  79. 29 28
      code/PlyLoader.cpp
  80. 10 12
      code/PlyLoader.h
  81. 3 4
      code/PlyParser.cpp
  82. 376 165
      code/PretransformVertices.cpp
  83. 91 13
      code/PretransformVertices.h
  84. 149 32
      code/ProcessHelper.h
  85. 24 18
      code/Q3DLoader.cpp
  86. 6 6
      code/Q3DLoader.h
  87. 12 15
      code/RawLoader.cpp
  88. 8 7
      code/RawLoader.h
  89. 75 8
      code/RemoveRedundantMaterials.cpp
  90. 35 14
      code/RemoveRedundantMaterials.h
  91. 2 5
      code/RemoveVCProcess.cpp
  92. 18 35
      code/SMDLoader.cpp
  93. 11 13
      code/SMDLoader.h
  94. 19 15
      code/STLLoader.cpp
  95. 9 8
      code/STLLoader.h
  96. 148 71
      code/SceneCombiner.cpp
  97. 58 1
      code/SceneCombiner.h
  98. 70 17
      code/ScenePreprocessor.cpp
  99. 3 2
      code/SkeletonMeshBuilder.cpp
  100. 1 1
      code/SortByPTypeProcess.cpp

+ 32 - 7
INSTALL

@@ -1,7 +1,32 @@
-	Open Asset Import Library (Assimp) Install
-	-----------------------------------------
-To take a look into the ASSIMP library just get the code, go to the 
-workspaces-directory and open your prefered build enviroment. Now just build 
-the engine, start the ASSIMP-Viewer application and select one of our basic test-files.
-
-You need boost-1.35 to build the Asset Import Library.
+	
+
+Open Asset Import Library (Assimp) Install
+------------------------------------------------
+
+Please see the doxygen documentation to learn how to build & use Assimp.
+A CHM file is included in the SVN repos: ./doc/lib_htmp/AssimpDoc.chm.
+At least Windows should be able to read it.
+
+To build the doxygen doc on your own follow these steps:
+
+a) download & install latest doxygen 
+b) ensure doxygen is in the executable search path
+c) navigate to ./doc
+d) and run 'doxygen'
+
+Open the generated HTML (lib_htmp/index.html) in the browser of your choice.
+Windows only: To generate the CHM doc install the 'Microsoft HTML Workshop'
+and configure the path to it in the DOXYFILE. Run doxygen again.
+
+
+You can also find a copy of the doc on our web site:
+http://assimp.sourceforge.net/lib_html/index.html
+
+Beware, it could be outdated. If you're in serious doubt it might be,
+rebuilding the doc is probably a wise choice.
+
+
+
+
+
+

+ 0 - 6
clean.bat

@@ -1,6 +0,0 @@
-cd code
-mingw32-make -f makefile.mingw clean
-
-cd ..
-del /Q /S obj bin lib
-

+ 55 - 48
code/3DSConverter.cpp

@@ -54,14 +54,13 @@ using namespace Assimp;
 // Setup final material indices, generae a default material if necessary
 void Discreet3DSImporter::ReplaceDefaultMaterial()
 {
-	//////////////////////////////////////////////////////////////////////////
+	
 	// Try to find an existing material that matches the
 	// typical default material setting:
 	// - no textures
 	// - diffuse color (in grey!)
 	// NOTE: This is here to workaround the fact that some
 	// exporters are writing a default material, too.
-	//////////////////////////////////////////////////////////////////////////
 	unsigned int idx = 0xcdcdcdcd;
 	for (unsigned int i = 0; i < mScene->mMaterials.size();++i)
 	{
@@ -438,57 +437,65 @@ void Discreet3DSImporter::AddNodeToGraph(aiScene* pcSOut,aiNode* pcOut,
 	iArray.reserve(3);
 
 	aiMatrix4x4 abs;
-	/*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")
-	{		
-		// Find all meshes with the same name as the node
-		for (unsigned int a = 0; a < pcSOut->mNumMeshes;++a)
-		{
-			const D3DS::Mesh* pcMesh = (const D3DS::Mesh*)pcSOut->mMeshes[a]->mColors[0];
-			ai_assert(NULL != pcMesh);
 
-			if (pcIn->mName == pcMesh->mName)
-				iArray.push_back(a);
-		}
-		if (!iArray.empty())
-		{
-			// The matrix should be identical for all meshes with the 
-			// same name. It HAS to be identical for all meshes .....
-			aiMatrix4x4 mInv = ((D3DS::Mesh*)pcSOut->mMeshes[iArray[0]]->mColors[0])->mMat;
-			mInv.Inverse();
-			const aiVector3D& pivot = pcIn->vPivot;
-
-			pcOut->mNumMeshes = (unsigned int)iArray.size();
-			pcOut->mMeshes = new unsigned int[iArray.size()];
-			for (unsigned int i = 0;i < iArray.size();++i)
-			{
-				const unsigned int iIndex = iArray[i];
-				aiMesh* const mesh = pcSOut->mMeshes[iIndex];
+	// Find all meshes with the same name as the node
+	for (unsigned int a = 0; a < pcSOut->mNumMeshes;++a)
+	{
+		const D3DS::Mesh* pcMesh = (const D3DS::Mesh*)pcSOut->mMeshes[a]->mColors[0];
+		ai_assert(NULL != pcMesh);
 
-				// Pivot point adjustment
-				// See: http://www.zfx.info/DisplayThread.php?MID=235690#235690
-				const aiVector3D* const pvEnd = mesh->mVertices+mesh->mNumVertices;
-				aiVector3D* pvCurrent = mesh->mVertices;
+		if (pcIn->mName == pcMesh->mName)
+			iArray.push_back(a);
+	}
+	if (!iArray.empty())
+	{
+		// The matrix should be identical for all meshes with the 
+		// same name. It HAS to be identical for all meshes .....
+		D3DS::Mesh* imesh = ((D3DS::Mesh*)pcSOut->mMeshes[iArray[0]]->mColors[0]);
+
+		// Compute the inverse of the transformation matrix to move the
+		// vertices back to their relative and local space
+		aiMatrix4x4 mInv = imesh->mMat, mInvTransposed = imesh->mMat;
+		mInv.Inverse();mInvTransposed.Transpose();
+		aiVector3D pivot = pcIn->vPivot;
+
+		pcOut->mNumMeshes = (unsigned int)iArray.size();
+		pcOut->mMeshes = new unsigned int[iArray.size()];
+		for (unsigned int i = 0;i < iArray.size();++i)	{
+			const unsigned int iIndex = iArray[i];
+			aiMesh* const mesh = pcSOut->mMeshes[iIndex];
+
+			// Transform the vertices back into their local space
+			// fixme: consider computing normals after this, so we don't need to transform them
+			const aiVector3D* const pvEnd = mesh->mVertices+mesh->mNumVertices;
+			aiVector3D* pvCurrent = mesh->mVertices, *t2 = mesh->mNormals;
+
+			for (;pvCurrent != pvEnd;++pvCurrent,++t2) {
+				*pvCurrent = mInv * (*pvCurrent);
+				*t2 = mInvTransposed * (*t2);
+			}
 
-				if(pivot.x || pivot.y || pivot.z)
-				{
-					for (;pvCurrent != pvEnd;++pvCurrent)
-					{
-						*pvCurrent = mInv * (*pvCurrent);
-						*pvCurrent -= pivot;
-					}
+			// Handle negative transformation matrix determinant -> invert vertex x
+			if (imesh->mMat.Determinant() < 0.0f)
+			{
+				/* we *must* have normals */
+				for (pvCurrent = mesh->mVertices,t2 = mesh->mNormals;pvCurrent != pvEnd;++pvCurrent,++t2) {
+					pvCurrent->x *= -1.f;
+					t2->x *= -1.f;
 				}
-				else
-				{
-					for (;pvCurrent != pvEnd;++pvCurrent)
-						*pvCurrent = mInv * (*pvCurrent);
+				DefaultLogger::get()->info("3DS: Flipping mesh X-Axis");
+			}
+
+			// Handle pivot point
+			if(pivot.x || pivot.y || pivot.z)
+			{
+				for (pvCurrent = mesh->mVertices;pvCurrent != pvEnd;++pvCurrent)	{
+					*pvCurrent -= pivot;	
 				}
-				// Setup the mesh index
-				pcOut->mMeshes[i] = iIndex;
 			}
+
+			// Setup the mesh index
+			pcOut->mMeshes[i] = iIndex;
 		}
 	}
 
@@ -526,7 +533,7 @@ void Discreet3DSImporter::AddNodeToGraph(aiScene* pcSOut,aiNode* pcOut,
 	}
 
 	// Generate animation channels for the node
-	if (pcIn->aPositionKeys.size()  > 1  || pcIn->aRotationKeys.size()   > 1   ||
+	if (pcIn->aPositionKeys.size()  > 1  || pcIn->aRotationKeys.size()   > 1 ||
 		pcIn->aScalingKeys.size()   > 1  || pcIn->aCameraRollKeys.size() > 1 ||
 		pcIn->aTargetPositionKeys.size() > 1)
 	{

+ 3 - 5
code/3DSHelper.h

@@ -124,11 +124,10 @@ public:
 		CHUNK_PERCENTF	= 0x0031,		// float4  percentage
 		// ********************************************************************
 
-		// Unknown and ignored. Possibly a chunk used by PROJ (
-		// Discreet 3DS max Project File)?
+		// Prj master chunk
 		CHUNK_PRJ       = 0xC23D,
 
-		// Unknown. Possibly a reference to an external .mli file?
+		// MDLI master chunk
 		CHUNK_MLI       = 0x3DAA,
 
 		// Primary main chunk of the .3ds file
@@ -178,7 +177,6 @@ public:
 		CHUNK_MESHCOLOR = 0x4165,
 		CHUNK_TXTINFO   = 0x4170,
 		CHUNK_LIGHT     = 0x4600,
-		CHUNK_SPOTLIGHT = 0x4610,
 		CHUNK_CAMERA    = 0x4700,
 		CHUNK_HIERARCHY = 0x4F00,
 
@@ -330,7 +328,7 @@ struct Texture
 		, mMapMode	(aiTextureMapMode_Wrap)
 		, iUVSrc	(0)
 	{
-		mTextureBlend = std::numeric_limits<float>::quiet_NaN();
+		mTextureBlend = get_qnan();
 	}
 
 	//! Specifies the blend factor for the texture

+ 51 - 60
code/3DSLoader.cpp

@@ -39,7 +39,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ---------------------------------------------------------------------------
 */
 
-/** @file Implementation of the 3ds importer class */
+/** @file  3DSLoader.cpp
+ *  @brief Implementation of the 3ds importer class
+ *
+ *  http://www.the-labs.com/Blender/3DS-details.html
+ */
 
 #include "AssimpPCH.h"
 #ifndef ASSIMP_BUILD_NO_3DS_IMPORTER
@@ -53,12 +57,14 @@ using namespace Assimp;
 // Begins a new parsing block
 // - Reads the current chunk and validates it
 // - computes its length
-#define ASSIMP_3DS_BEGIN_CHUNK()                                     \
-	Discreet3DS::Chunk chunk;                                        \
-	ReadChunk(&chunk);                                               \
-	int chunkSize = chunk.Size-sizeof(Discreet3DS::Chunk);	         \
-	const int oldReadLimit = stream->GetReadLimit();                 \
-	stream->SetReadLimit(stream->GetCurrentPos() + chunkSize);
+#define ASSIMP_3DS_BEGIN_CHUNK()                                         \
+	if (stream->GetRemainingSizeToLimit() < sizeof(Discreet3DS::Chunk))  \
+		return;                                                          \
+	Discreet3DS::Chunk chunk;                                            \
+	ReadChunk(&chunk);                                                   \
+	int chunkSize = chunk.Size-sizeof(Discreet3DS::Chunk);	             \
+	const int oldReadLimit = stream->GetReadLimit();                     \
+	stream->SetReadLimit(stream->GetCurrentPos() + chunkSize);		 
 	
 
 // ------------------------------------------------------------------------------------------------
@@ -82,18 +88,27 @@ Discreet3DSImporter::~Discreet3DSImporter()
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file. 
-bool Discreet3DSImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
+bool Discreet3DSImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
 {
-	// simple check of file extension is enough for the moment
-	std::string::size_type pos = pFile.find_last_of('.');
-	// no file extension - can't read
-	if( pos == std::string::npos)
-		return false;
-	std::string extension = pFile.substr( pos);
-	for (std::string::iterator i = extension.begin(); i != extension.end();++i)
-		*i = ::tolower(*i);
-
-	return (extension == ".3ds");
+	std::string extension = GetExtension(pFile);
+	if(extension == "3ds" || extension == "prj" ) {
+		return true;
+	}
+	if (!extension.length() || checkSig) {
+		uint16_t token[3];
+		token[0] = 0x4d4d;
+		token[1] = 0x3dc2;
+		//token[2] = 0x3daa;
+		return CheckMagicToken(pIOHandler,pFile,token,2,0,2);
+	}
+	return false;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Get list of all extension supported by this loader
+void Discreet3DSImporter::GetExtensionList(std::string& append)
+{
+	append.append("*.3ds;*.prj");
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -128,6 +143,7 @@ void Discreet3DSImporter::InternReadFile( const std::string& pFile,
 	mMasterScale               = 1.0f;
 	mBackgroundImage           = "";
 	bHasBG                     = false;
+	bIsPrj                     = false;
 
 	// Parse the file
 	ParseMainChunk();
@@ -138,8 +154,7 @@ void Discreet3DSImporter::InternReadFile( const std::string& pFile,
 	// vectors from the smoothing groups we read from the
 	// file.
 	for (std::vector<D3DS::Mesh>::iterator i = mScene->mMeshes.begin(),
-		 end = mScene->mMeshes.end(); i != end;++i)
-	{
+		 end = mScene->mMeshes.end(); i != end;++i)	{
 		CheckIndices(*i);
 		MakeUnique  (*i);
 		ComputeNormalsWithSmoothingsGroups<D3DS::Face>(*i);
@@ -226,6 +241,9 @@ void Discreet3DSImporter::ParseMainChunk()
 	// get chunk type
 	switch (chunk.Flag)
 	{
+	
+	case Discreet3DS::CHUNK_PRJ:
+		bIsPrj = true;
 	case Discreet3DS::CHUNK_MAIN:
 		ParseEditorChunk();
 		break;
@@ -371,9 +389,9 @@ void Discreet3DSImporter::ParseChunk(const char* name, unsigned int num)
 
 		light->mColorDiffuse = aiColor3D(1.f,1.f,1.f);
 
-		// Now check for further subchunks (excluding color)
-		int8_t* p = stream->GetPtr();
-		ParseLightChunk();
+		// Now check for further subchunks
+		if (!bIsPrj) /* fixme */
+			ParseLightChunk();
 
 		// The specular light color is identical the the diffuse light
 		// color. The ambient light color is equal to the ambient base 
@@ -424,6 +442,10 @@ void Discreet3DSImporter::ParseChunk(const char* name, unsigned int num)
 		if (camera->mHorizontalFOV < 0.001f)
 			camera->mHorizontalFOV = AI_DEG_TO_RAD(45.f);
 		}
+
+		// Now check for further subchunks 
+		if (!bIsPrj) /* fixme */
+			ParseCameraChunk();
 		break;
 	};
 	ASSIMP_3DS_END_CHUNK();
@@ -440,7 +462,7 @@ void Discreet3DSImporter::ParseLightChunk()
 	// get chunk type
 	switch (chunk.Flag)
 	{
-	case Discreet3DS::CHUNK_SPOTLIGHT:
+	case Discreet3DS::CHUNK_DL_SPOTLIGHT:
 		// Now we can be sure that the light is a spot light
 		light->mType = aiLightSource_SPOT;
 
@@ -511,7 +533,7 @@ void Discreet3DSImporter::ParseKeyframeChunk()
 	switch (chunk.Flag)
 	{
 	case Discreet3DS::CHUNK_TRACKCAMTGT:
-	case Discreet3DS::CHUNK_SPOTLIGHT:
+	case Discreet3DS::CHUNK_TRACKSPOTL:
 	case Discreet3DS::CHUNK_TRACKCAMERA:
 	case Discreet3DS::CHUNK_TRACKINFO:
 	case Discreet3DS::CHUNK_TRACKLIGHT:
@@ -574,14 +596,11 @@ void Discreet3DSImporter::SkipTCBInfo()
 {
 	unsigned int flags = stream->GetI2();
 
-	if (!flags)
-	{
-		//////////////////////////////////////////////////////////////////////////
+	if (!flags)	{
 		// Currently we can't do anything with these values. They occur
 		// quite rare, so it wouldn't be worth the effort implementing
 		// them. 3DS ist not really suitable for complex animations,
 		// so full support is not required.
-		//////////////////////////////////////////////////////////////////////////
 		DefaultLogger::get()->warn("3DS: Skipping TCB animation info");
 	}
 
@@ -1015,35 +1034,7 @@ void Discreet3DSImporter::ParseMeshChunk()
 		mMesh.mMat.a4 = stream->GetF4();
 		mMesh.mMat.b4 = stream->GetF4();
 		mMesh.mMat.c4 = stream->GetF4();
-
-		// Now check whether the matrix has got a negative determinant
-		// If yes, we need to flip all vertices' Z axis ....
-		// This code has been taken from lib3ds
-		if (mMesh.mMat.Determinant() < 0.0f)	{
-			// Compute the inverse of the matrix
-			aiMatrix4x4 mInv = mMesh.mMat;
-			mInv.Inverse();
-
-			aiMatrix4x4 mMe = mMesh.mMat;
-			mMe.c1 *= -1.0f;
-			mMe.c2 *= -1.0f;
-			mMe.c3 *= -1.0f;
-			mMe.c4 *= -1.0f;
-			mInv = mInv * mMe;
-
-			// Now transform all vertices
-			for (unsigned int i = 0; i < (unsigned int)mMesh.mPositions.size();++i)
-			{
-				aiVector3D a,c;
-				a = mMesh.mPositions[i];
-				c[0]= mInv[0][0]*a[0] + mInv[1][0]*a[1] + mInv[2][0]*a[2] + mInv[3][0];
-				c[1]= mInv[0][1]*a[0] + mInv[1][1]*a[1] + mInv[2][1]*a[2] + mInv[3][1];
-				c[2]= mInv[0][2]*a[0] + mInv[1][2]*a[1] + mInv[2][2]*a[2] + mInv[3][2];
-				mMesh.mPositions[i] = c;
-			}
-
-			DefaultLogger::get()->info("3DS: Flipping mesh Z-Axis");
-		}}
+		}
 		break;
 
 	case Discreet3DS::CHUNK_MAPLIST:
@@ -1353,7 +1344,7 @@ float Discreet3DSImporter::ParsePercentageChunk()
 		return stream->GetF4();
 	else if (Discreet3DS::CHUNK_PERCENTW == chunk.Flag)
 		return (float)((uint16_t)stream->GetI2()) / (float)0xFFFF;
-	return std::numeric_limits<float>::quiet_NaN();
+	return get_qnan();
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -1364,7 +1355,7 @@ void Discreet3DSImporter::ParseColorChunk(aiColor3D* out,
 	ai_assert(out != NULL);
 
 	// error return value
-	const float qnan = std::numeric_limits<float>::quiet_NaN();
+	const float qnan = get_qnan();
 	static const aiColor3D clrError = aiColor3D(qnan,qnan,qnan);
 
 	Discreet3DS::Chunk chunk;

+ 22 - 20
code/3DSLoader.h

@@ -39,7 +39,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ----------------------------------------------------------------------
 */
 
-/** @file Definition of the .3ds importer class. */
+/** @file  3DSLoader.h
+ *  @brief 3DS File format loader
+ */
 #ifndef AI_3DSIMPORTER_H_INC
 #define AI_3DSIMPORTER_H_INC
 
@@ -49,16 +51,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 struct aiNode;
 #include "3DSHelper.h"
 
-namespace Assimp
-{
+namespace Assimp	{
 class MaterialHelper;
 
 using namespace D3DS;
 
-// ---------------------------------------------------------------------------
-/** The Discreet3DSImporter is a worker class capable of importing a scene from a
-* 3ds Max 4/5 Files (.3ds)
-*/
+// ---------------------------------------------------------------------------------
+/** Importer class for 3D Studio r3 and r4 3DS files
+ */
 class Discreet3DSImporter : public BaseImporter
 {
 	friend class Importer;
@@ -74,14 +74,16 @@ public:
 
 	// -------------------------------------------------------------------
 	/** Returns whether the class can handle the format of the given file. 
-	* See BaseImporter::CanRead() for details.	*/
-	bool CanRead( const std::string& pFile, IOSystem* pIOHandler) const;
+	 * See BaseImporter::CanRead() for details.	
+	 */
+	bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
+		bool checkSig) const;
 
 	// -------------------------------------------------------------------
 	/** Called prior to ReadFile().
-	* The function is a request to the importer to update its configuration
-	* basing on the Importer's configuration property list.
-	*/
+	 * The function is a request to the importer to update its configuration
+	 * basing on the Importer's configuration property list.
+	 */
 	void SetupProperties(const Importer* pImp);
 
 protected:
@@ -90,21 +92,18 @@ protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 */
-	void GetExtensionList(std::string& append)
-	{
-		append.append("*.3ds");
-	}
+	void GetExtensionList(std::string& append);
 
 	// -------------------------------------------------------------------
 	/** Imports the given file into the given scene structure. 
-	* See BaseImporter::InternReadFile() for details
-	*/
+	 * See BaseImporter::InternReadFile() for details
+	 */
 	void InternReadFile( const std::string& pFile, aiScene* pScene, 
 		IOSystem* pIOHandler);
 
 	// -------------------------------------------------------------------
 	/** Converts a temporary material to the outer representation 
-	*/
+	 */
 	void ConvertMaterial(D3DS::Material& p_cMat,
 		MaterialHelper& p_pcOut);
 
@@ -112,7 +111,7 @@ protected:
 	/** Read a chunk
 	 *
 	 *  @param pcOut Receives the current chunk
-	*/
+	 */
 	void ReadChunk(Discreet3DS::Chunk* pcOut);
 
 	// -------------------------------------------------------------------
@@ -271,6 +270,9 @@ protected:
 	/** Path to the background image of the scene */
 	std::string mBackgroundImage;
 	bool bHasBG;
+
+	/** true if PRJ file */
+	bool bIsPrj;
 };
 
 } // end of namespace Assimp

+ 19 - 10
code/ACLoader.cpp

@@ -108,28 +108,38 @@ using namespace Assimp;
 // Constructor to be privately used by Importer
 AC3DImporter::AC3DImporter()
 {
+	// nothing to be done here
 }
 
 // ------------------------------------------------------------------------------------------------
 // Destructor, private as well 
 AC3DImporter::~AC3DImporter()
 {
+	// nothing to be done here
 }
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file. 
-bool AC3DImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
+bool AC3DImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
 {
-	// simple check of file extension is enough for the moment
-	std::string::size_type pos = pFile.find_last_of('.');
-	// no file extension - can't read
-	if( pos == std::string::npos)return false;
-	std::string extension = pFile.substr( pos);
+	std::string extension = GetExtension(pFile);
 
-	for( std::string::iterator it = extension.begin(); it != extension.end(); ++it)
-		*it = tolower( *it);
+	// fixme: are acc and ac3d *really* used? Some sources say they are
+	if(extension == "ac" || extension == "ac3d" || extension == "acc") {
+		return true;
+	}
+	if (!extension.length() || checkSig) {
+		uint32_t token = AI_MAKE_MAGIC("AC3D");
+		return CheckMagicToken(pIOHandler,pFile,&token,1,0);
+	}
+	return false;
+}
 
-	return( extension == ".ac3d" || extension == ".ac");
+// ------------------------------------------------------------------------------------------------
+// Get list of file extensions handled by this loader
+void AC3DImporter::GetExtensionList(std::string& append)
+{
+	append.append("*.ac;*.acc;*.ac3d");
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -140,7 +150,6 @@ bool AC3DImporter::GetNextLine( )
 	return SkipSpaces(&buffer);
 }
 
-
 // ------------------------------------------------------------------------------------------------
 // Parse an object section in an AC file
 void AC3DImporter::LoadObjectSection(std::vector<Object>& objects)

+ 8 - 7
code/ACLoader.h

@@ -38,7 +38,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ----------------------------------------------------------------------
 */
 
-/** @file Declaration of the .ac importer class. */
+/** @file  ACLoader.h
+ *  @brief Declaration of the .ac importer class.
+ */
 #ifndef AI_AC3DLOADER_H_INCLUDED
 #define AI_AC3DLOADER_H_INCLUDED
 
@@ -166,8 +168,10 @@ public:
 
 	// -------------------------------------------------------------------
 	/** Returns whether the class can handle the format of the given file. 
-	* See BaseImporter::CanRead() for details.	*/
-	bool CanRead( const std::string& pFile, IOSystem* pIOHandler) const;
+	 * See BaseImporter::CanRead() for details.
+	 */
+	bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
+		bool checkSig) const;
 
 protected:
 
@@ -175,10 +179,7 @@ protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 */
-	void GetExtensionList(std::string& append)
-	{
-		append.append("*.ac;*.acc");
-	}
+	void GetExtensionList(std::string& append);
 
 	// -------------------------------------------------------------------
 	/** Imports the given file into the given scene structure. 

+ 24 - 19
code/ASELoader.cpp

@@ -39,7 +39,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ---------------------------------------------------------------------------
 */
 
-/** @file Implementation of the ASE importer class */
+/** @file  ASELoader.cpp
+ *  @brief Implementation of the ASE importer class
+ */
 
 #include "AssimpPCH.h"
 #ifndef ASSIMP_BUILD_NO_ASE_IMPORTER
@@ -69,23 +71,25 @@ ASEImporter::~ASEImporter()
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file. 
-bool ASEImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
+bool ASEImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool cs) const
 {
-	// simple check of file extension is enough for the moment
-	std::string::size_type pos = pFile.find_last_of('.');
-	// no file extension - can't read
-	if( pos == std::string::npos)
-		return false;
-
-	std::string extension = pFile.substr( pos);
-
-	// Either ASE, ASC or ASK
-	return  !(extension.length() < 4 || extension[0] != '.' ||
-			  extension[1] != 'a' && extension[1] != 'A' ||
-			  extension[2] != 's' && extension[2] != 'S' ||
-			  extension[3] != 'e' && extension[3] != 'E' &&
-			  extension[3] != 'k' && extension[3] != 'K' &&
-			  extension[3] != 'c' && extension[3] != 'C');
+	// check file extension 
+	const std::string extension = GetExtension(pFile);
+	
+	if( extension == "ase" || extension == "ask")
+		return true;
+
+	if ((!extension.length() || cs) && pIOHandler) {
+		const char* tokens[] = {"*3dsmax_asciiexport"};
+		return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1);
+	}
+	return false;
+}
+
+// ------------------------------------------------------------------------------------------------
+void ASEImporter::GetExtensionList(std::string& append)
+{
+	append.append("*.ase;*.ask");
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -108,6 +112,8 @@ void ASEImporter::InternReadFile( const std::string& pFile,
 		throw new ImportErrorException( "Failed to open ASE file " + pFile + ".");
 
 	size_t fileSize = file->FileSize();
+	if (!fileSize)
+		throw new ImportErrorException( "ASE: File is empty");
 
 	// Allocate storage and copy the contents of the file to a memory buffer
 	// (terminate it with zero)
@@ -778,8 +784,7 @@ void ASEImporter::BuildNodes()
 		delete pc;
 	}
 	// The root node should not have at least one child or the file is invalid
-	else if (!pcScene->mRootNode->mNumChildren)
-	{
+	else if (!pcScene->mRootNode->mNumChildren) {
 		throw new ImportErrorException("No nodes loaded. The ASE/ASK file is either empty or corrupt");
 	}
 	return;

+ 14 - 12
code/ASELoader.h

@@ -38,7 +38,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ----------------------------------------------------------------------
 */
 
-/** @file Definition of the .ASE importer class. */
+/** @file  ASELoader.h
+ *  @brief Definition of the .ASE importer class.
+ */
 #ifndef AI_ASELOADER_H_INCLUDED
 #define AI_ASELOADER_H_INCLUDED
 
@@ -48,15 +50,16 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 struct aiNode;
 #include "ASEParser.h"
 
-namespace Assimp
-{
+namespace Assimp {
 class MaterialHelper;
 
 using namespace ASE;
 
-// ---------------------------------------------------------------------------
-/** Used to load ASE files
-*/
+// --------------------------------------------------------------------------------
+/** Importer class for the 3DS ASE ASCII format
+ *
+ * fixme: consider code cleanup
+ */
 class ASEImporter : public BaseImporter
 {
 	friend class Importer;
@@ -72,8 +75,10 @@ public:
 
 	// -------------------------------------------------------------------
 	/** Returns whether the class can handle the format of the given file. 
-	* See BaseImporter::CanRead() for details.	*/
-	bool CanRead( const std::string& pFile, IOSystem* pIOHandler) const;
+	 * See BaseImporter::CanRead() for details.	
+	 */
+	bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
+		bool checkSig) const;
 
 protected:
 
@@ -81,10 +86,7 @@ protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 */
-	void GetExtensionList(std::string& append)
-	{
-		append.append("*.ase;*.ask;*.asc");
-	}
+	void GetExtensionList(std::string& append);
 
 	// -------------------------------------------------------------------
 	/** Imports the given file into the given scene structure. 

+ 5 - 3
code/ASEParser.cpp

@@ -39,7 +39,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ---------------------------------------------------------------------------
 */
 
-/** @file Implementation of the ASE parser class */
+/** @file  ASEParser.cpp
+ *  @brief Implementation of the ASE parser class 
+ */
 
 #include "AssimpPCH.h"
 
@@ -120,8 +122,8 @@ Parser::Parser (const char* szFile, unsigned int fileFormatDefault)
 	iFileFormat = fileFormatDefault;
 
 	// make sure that the color values are invalid
-	m_clrBackground.r = std::numeric_limits<float>::quiet_NaN();
-	m_clrAmbient.r    = std::numeric_limits<float>::quiet_NaN();
+	m_clrBackground.r = get_qnan();
+	m_clrAmbient.r    = get_qnan();
 
 	// setup some default values
 	iLineNumber = 0;

+ 1 - 1
code/ASEParser.h

@@ -227,7 +227,7 @@ struct BaseNode
 		mName = szTemp;
 
 		// Set mTargetPosition to qnan
-		const float qnan = std::numeric_limits<float>::quiet_NaN();
+		const float qnan = get_qnan();
 		mTargetPosition.x = qnan;
 	}
 

+ 6 - 4
code/AssimpPCH.cpp

@@ -1,5 +1,5 @@
 
-// Actually just a dummyy, used by the compiler to build the precompiled header.
+// Actually just a dummy, used by the compiler to build the precompiled header.
 
 #include "AssimpPCH.h"
 #include "./../include/aiVersion.h"
@@ -12,7 +12,7 @@ static const char* LEGAL_INFORMATION =
 "A free C/C++ library to import various 3D file formats into applications\n\n"
 
 "(c) ASSIMP Development Team, 2008-2009\n"
-"License: BSD\n"
+"License: 3-clause BSD license\n"
 "Website: http://assimp.sourceforge.net\n"
 ;
 
@@ -59,10 +59,12 @@ ASSIMP_API unsigned int aiGetCompileFlags ()	{
 	return flags;
 }
 
+// include current build revision
+#include "../mkutil/revision.h"
+
 // ------------------------------------------------------------------------------------------------
 ASSIMP_API unsigned int aiGetVersionRevision ()
 {
-	// TODO: find a way to update the revision number automatically
-	return 306;
+	return SVNRevision;
 }
 

+ 8 - 0
code/AssimpPCH.h

@@ -67,6 +67,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #	define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1
 #endif
 
+// size_t to unsigned int, possible loss of data.
+// Yes, the compiler is right with his warning, but this loss of data
+// won't be a problem for us. So shut up little boy.
+#ifdef _MSC_VER
+#	pragma warning (disable : 4267)
+#endif
+
 // Actually that's not required for MSVC (it is included somewhere in 
 // the STL ..) but it is necessary for build with STLport.
 #include <ctype.h>
@@ -75,6 +82,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <vector>
 #include <list>
 #include <map>
+#include <set>
 #include <string>
 #include <sstream>
 #include <iomanip>

+ 4 - 2
code/B3DImporter.cpp

@@ -39,7 +39,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ---------------------------------------------------------------------------
 */
 
-/** @file Implementation of the b3d importer class */
+/** @file  B3DImporter.cpp
+ *  @brief Implementation of the b3d importer class
+ */
 
 #include "AssimpPCH.h"
 #ifndef ASSIMP_BUILD_NO_B3D_IMPORTER
@@ -52,7 +54,7 @@ using namespace Assimp;
 using namespace std;
 
 // ------------------------------------------------------------------------------------------------
-bool B3DImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const{
+bool B3DImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const{
 
 	int pos=pFile.find_last_of( '.' );
 	if( pos==string::npos ) return false;

+ 1 - 1
code/B3DImporter.h

@@ -56,7 +56,7 @@ namespace Assimp{
 class B3DImporter : public BaseImporter{
 public:
 
-	virtual bool CanRead( const std::string& pFile, IOSystem* pIOHandler) const;
+	virtual bool CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const;
 
 protected:
 

+ 11 - 10
code/BVHLoader.cpp

@@ -61,18 +61,19 @@ BVHLoader::~BVHLoader()
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file. 
-bool BVHLoader::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
+bool BVHLoader::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool cs) const
 {
 	// check file extension 
-	std::string::size_type pos = pFile.find_last_of('.');
-	// no file extension - can't read
-	if( pos == std::string::npos)
-		return false;
-	std::string extension = pFile.substr( pos);
-	for( std::string::iterator it = extension.begin(); it != extension.end(); ++it)
-		*it = tolower( *it);
-
-	return ( extension == ".bvh");
+	const std::string extension = GetExtension(pFile);
+	
+	if( extension == "bvh")
+		return true;
+
+	if ((!extension.length() || cs) && pIOHandler) {
+		const char* tokens[] = {"HIERARCHY"};
+		return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1);
+	}
+	return false;
 }
 
 // ------------------------------------------------------------------------------------------------

+ 11 - 4
code/BVHLoader.h

@@ -40,6 +40,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ----------------------------------------------------------------------
 */
 
+/** @file BVHLoader.h
+ *  @brief Biovision BVH import
+ */
+
 #ifndef AI_BVHLOADER_H_INC
 #define AI_BVHLOADER_H_INC
 
@@ -48,9 +52,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 namespace Assimp
 {
 
-/** Loader class to read Motion Capturing data from a .bvh file. This format only contains a 
-* hierarchy of joints and a series of keyframes for the hierarchy. It contains no actual mesh data,
-* but we generate a dummy mesh inside the loader just to be able to see something.
+// --------------------------------------------------------------------------------
+/** Loader class to read Motion Capturing data from a .bvh file. 
+ *
+ * This format only contains a hierarchy of joints and a series of keyframes for
+ * the hierarchy. It contains no actual mesh data, but we generate a dummy mesh
+ * inside the loader just to be able to see something.
 */
 class BVHLoader : public BaseImporter
 {
@@ -88,7 +95,7 @@ protected:
 public:
 	/** Returns whether the class can handle the format of the given file. 
 	 * See BaseImporter::CanRead() for details.	*/
-	bool CanRead( const std::string& pFile, IOSystem* pIOHandler) const;
+	bool CanRead( const std::string& pFile, IOSystem* pIOHandler, bool cs) const;
 
 protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.

+ 120 - 25
code/BaseImporter.cpp

@@ -39,12 +39,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ---------------------------------------------------------------------------
 */
 
-/** @file Implementation of BaseImporter */
+/** @file  BaseImporter.cpp
+ *  @brief Implementation of BaseImporter 
+ */
 
 #include "AssimpPCH.h"
 #include "BaseImporter.h"
 
-
 using namespace Assimp;
 
 
@@ -98,7 +99,7 @@ void BaseImporter::SetupProperties(const Importer* pImp)
 }
 
 // ------------------------------------------------------------------------------------------------
-bool BaseImporter::SearchFileHeaderForToken(IOSystem* pIOHandler,
+/*static*/ bool BaseImporter::SearchFileHeaderForToken(IOSystem* pIOHandler,
 	const std::string&	pFile,
 	const char**		tokens, 
 	unsigned int		numTokens,
@@ -142,17 +143,105 @@ bool BaseImporter::SearchFileHeaderForToken(IOSystem* pIOHandler,
 	return false;
 }
 
+// ------------------------------------------------------------------------------------------------
+// Simple check for file extension
+/*static*/ bool BaseImporter::SimpleExtensionCheck (const std::string& pFile, 
+	const char* ext0,
+	const char* ext1,
+	const char* ext2)
+{
+	std::string::size_type pos = pFile.find_last_of('.');
+
+	// no file extension - can't read
+	if( pos == std::string::npos)
+		return false;
+	
+	const char* ext_real = & pFile[ pos+1 ];
+	if( !ASSIMP_stricmp(ext_real,ext0) )
+		return true;
+
+	// check for other, optional, file extensions
+	if (ext1 && !ASSIMP_stricmp(ext_real,ext1))
+		return true;
+
+	if (ext2 && !ASSIMP_stricmp(ext_real,ext2))
+		return true;
+
+	return false;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Get file extension from path
+/*static*/ std::string BaseImporter::GetExtension (const std::string& pFile)
+{
+	std::string::size_type pos = pFile.find_last_of('.');
+
+	// no file extension at all
+	if( pos == std::string::npos)
+		return "";
+
+	std::string ret = pFile.substr(pos+1);
+	std::transform(ret.begin(),ret.end(),ret.begin(),::tolower); // thanks to Andy Maloney for the hint
+	return ret;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Check for magic bytes at the beginning of the file.
+/* static */ bool BaseImporter::CheckMagicToken(IOSystem* pIOHandler, const std::string& pFile, 
+	const void* _magic, unsigned int num, unsigned int offset, unsigned int size)
+{
+	ai_assert(size <= 16 && _magic && num && pIOHandler);
+
+	const char* magic = (const char*)_magic;
+	boost::scoped_ptr<IOStream> pStream (pIOHandler->Open(pFile));
+	if (pStream.get() )
+	{
+		// skip to offset
+		pStream->Seek(offset,aiOrigin_SET);
+
+		// read 'size' characters from the file
+		char data[16];
+		if(size != pStream->Read(data,1,size))
+			return false;
+
+		for (unsigned int i = 0; i < num; ++i) {
+			// also check against big endian versions of tokens with size 2,4
+			// that's just for convinience, the chance that we cause conflicts
+			// is quite low and it can save some lines and prevent nasty bugs
+			if (2 == size) {
+				int16_t rev = *((int16_t*)magic);
+				ByteSwap::Swap(&rev);
+				if (*((int16_t*)data) == ((int16_t*)magic)[i] || *((int16_t*)data) == rev)
+					return true;
+			}
+			else if (4 == size) {
+				int32_t rev = *((int32_t*)magic);
+				ByteSwap::Swap(&rev);
+				if (*((int32_t*)data) == ((int32_t*)magic)[i] || *((int32_t*)data) == rev)
+					return true;
+			}
+			else {
+				// any length ... just compare
+				if(!::memcmp(magic,data,size))
+					return true;
+			}
+			magic += size;
+		}
+	}
+	return false;
+}
 
 // ------------------------------------------------------------------------------------------------
 // Represents an import request
 struct LoadRequest
 {
-	LoadRequest(const std::string& _file, unsigned int _flags,const BatchLoader::PropertyMap* _map)
+	LoadRequest(const std::string& _file, unsigned int _flags,const BatchLoader::PropertyMap* _map, unsigned int _id)
 		:	file	(_file)
 		,	flags	(_flags)
 		,	refCnt	(1)
 		,	scene	(NULL)            
 		,	loaded	(false)
+		,	id		(_id)
 	{
 		if (_map)
 			map = *_map;
@@ -164,6 +253,7 @@ struct LoadRequest
 	aiScene* scene;
 	bool loaded;
 	BatchLoader::PropertyMap map;
+	unsigned int id;
 
 	bool operator== (const std::string& f) {
 		return file == f;
@@ -174,6 +264,10 @@ struct LoadRequest
 // BatchLoader::pimpl data structure
 struct Assimp::BatchData
 {
+	BatchData()
+		:	next_id(0xffff)
+	{}
+
 	// IO system to be used for all imports
 	IOSystem* pIOSystem;
 
@@ -185,6 +279,9 @@ struct Assimp::BatchData
 
 	// Base path
 	std::string pathBase;
+
+	// Id for next item
+	unsigned int next_id;
 };
 
 // ------------------------------------------------------------------------------------------------
@@ -219,13 +316,15 @@ void BatchLoader::SetBasePath (const std::string& pBase)
 	std::string::size_type ss,ss2;
 	if (std::string::npos != (ss = data->pathBase.find_first_of('.')))
 	{
-		if (std::string::npos != (ss2 = data->pathBase.find_last_of('\\')) ||
-			std::string::npos != (ss2 = data->pathBase.find_last_of('/')))
+		if (std::string::npos != (ss2 = data->pathBase.find_last_of("\\/")))
 		{
 			if (ss > ss2)
 				data->pathBase.erase(ss2,data->pathBase.length()-ss2);
 		}
-		else return;
+		else {
+			data->pathBase = "";
+			return;
+		}
 	}
 
 	// make sure the directory is terminated properly
@@ -235,7 +334,7 @@ void BatchLoader::SetBasePath (const std::string& pBase)
 }
 
 // ------------------------------------------------------------------------------------------------
-void BatchLoader::AddLoadRequest	(const std::string& file,
+unsigned int BatchLoader::AddLoadRequest	(const std::string& file,
 	unsigned int steps /*= 0*/, const PropertyMap* map /*= NULL*/)
 {
 	ai_assert(!file.empty());
@@ -245,8 +344,7 @@ void BatchLoader::AddLoadRequest	(const std::string& file,
 
 	// build a full path if this is a relative path and 
 	// we have a new base directory given
-	if (file.length() > 2 && file[1] != ':' && data->pathBase.length())
-	{
+	if (file.length() > 2 && file[1] != ':' && data->pathBase.length()) {
 		real = data->pathBase + file;
 	}
 	else real = file;
@@ -258,32 +356,29 @@ void BatchLoader::AddLoadRequest	(const std::string& file,
 		// Call IOSystem's path comparison function here
 		if (data->pIOSystem->ComparePaths((*it).file,real))
 		{
+			if (map) {
+				if (!((*it).map == *map))
+					continue;
+			}
+			else if (!(*it).map.empty())
+				continue;
+
 			(*it).refCnt++;
-			return;
+			return (*it).id;
 		}
 	}
 
 	// no, we don't have it. So add it to the queue ...
-	data->requests.push_back(LoadRequest(real,steps,map));
+	data->requests.push_back(LoadRequest(real,steps,map,data->next_id));
+	return data->next_id++;
 }
 
 // ------------------------------------------------------------------------------------------------
-aiScene* BatchLoader::GetImport		(const std::string& file)
+aiScene* BatchLoader::GetImport		(unsigned int which)
 {
-	// no threaded implementation for the moment
-	std::string real;
-
-	// build a full path if this is a relative path and 
-	// we have a new base directory given
-	if (file.length() > 2 && file[1] != ':' && data->pathBase.length())
-	{
-		real = data->pathBase + file;
-	}
-	else real = file;
 	for (std::list<LoadRequest>::iterator it = data->requests.begin();it != data->requests.end(); ++it)
 	{
-		// Call IOSystem's path comparison function here
-		if (data->pIOSystem->ComparePaths((*it).file,real) && (*it).loaded)
+		if ((*it).id == which && (*it).loaded)
 		{
 			aiScene* sc = (*it).scene;
 			if (!(--(*it).refCnt))

+ 137 - 57
code/BaseImporter.h

@@ -52,6 +52,10 @@ namespace Assimp	{
 class IOSystem;
 class Importer;
 
+// utility to do char4 to uint32 in a portable manner
+#define AI_MAKE_MAGIC(string) ((uint32_t)((string[0] << 24) + \
+	(string[1] << 16) + (string[2] << 8) + string[3]))
+
 // ---------------------------------------------------------------------------
 /** Simple exception class to be thrown if an error occurs while importing. */
 class ASSIMP_API ImportErrorException 
@@ -97,36 +101,48 @@ protected:
 public:
 	// -------------------------------------------------------------------
 	/** Returns whether the class can handle the format of the given file.
-	* @param pFile Path and file name of the file to be examined.
-	* @param pIOHandler The IO handler to use for accessing any file.
-	* @return true if the class can read this file, false if not.
-	*
-	* @note Sometimes ASSIMP uses this method to determine whether a
-	* a given file extension is generally supported. In this case the
-	* file extension is passed in the pFile parameter, pIOHandler is NULL
-	*/
+	 *
+	 * The implementation should be as quick as possible. A check for
+	 * the file extension is enough. If no suitable loader is found with
+	 * this strategy, CanRead() is called again, the 'checkSig' parameter
+	 * set to true this time. Now the implementation is expected to
+	 * perform a full check of the file format, possibly searching the
+	 * first bytes of the file for magic identifiers or keywords.
+	 *
+	 * @param pFile Path and file name of the file to be examined.
+	 * @param pIOHandler The IO handler to use for accessing any file.
+	 * @param checkSig Set to true if this method is called a second time.
+	 *   This time, the implementation may take more time to examine the
+	 *   contents of the file to be loaded for magic bytes, keywords, etc
+	 *   to be able to load files with unknown/not existent file extensions.
+	 * @return true if the class can read this file, false if not.
+	 *
+	 * @note Sometimes ASSIMP uses this method to determine whether a
+	 * a given file extension is generally supported. In this case the
+	 * file extension is passed in the pFile parameter, pIOHandler is NULL
+	 */
 	virtual bool CanRead( const std::string& pFile, 
-		IOSystem* pIOHandler) const = 0;
+		IOSystem* pIOHandler, bool checkSig) const = 0;
 
 
 	// -------------------------------------------------------------------
 	/** Imports the given file and returns the imported data.
-	* If the import succeeds, ownership of the data is transferred to 
-	* the caller. If the import fails, NULL is returned. The function
-	* takes care that any partially constructed data is destroyed
-	* beforehand.
-	*
-	* @param pFile Path of the file to be imported. 
-	* @param pIOHandler IO-Handler used to open this and possible other files.
-	* @return The imported data or NULL if failed. If it failed a 
-	* human-readable error description can be retrieved by calling 
-	* GetErrorText()
-	*
-	* @note This function is not intended to be overridden. Implement 
-	* InternReadFile() to do the import. If an exception is thrown somewhere 
-	* in InternReadFile(), this function will catch it and transform it into
-	*  a suitable response to the caller.
-	*/
+	 * If the import succeeds, ownership of the data is transferred to 
+	 * the caller. If the import fails, NULL is returned. The function
+	 * takes care that any partially constructed data is destroyed
+	 * beforehand.
+	 *
+	 * @param pFile Path of the file to be imported. 
+	 * @param pIOHandler IO-Handler used to open this and possible other files.
+	 * @return The imported data or NULL if failed. If it failed a 
+	 * human-readable error description can be retrieved by calling 
+	 * GetErrorText()
+	 *
+	 * @note This function is not intended to be overridden. Implement 
+	 * InternReadFile() to do the import. If an exception is thrown somewhere 
+	 * in InternReadFile(), this function will catch it and transform it into
+	 *  a suitable response to the caller.
+	 */
 	aiScene* ReadFile( const std::string& pFile, IOSystem* pIOHandler);
 
 
@@ -135,19 +151,20 @@ public:
 	 * @return A description of the last error that occured. An empty
 	 * string if there was no error.
 	 */
-	inline const std::string& GetErrorText() const 
-		{ return mErrorText; }
+	const std::string& GetErrorText() const {
+		return mErrorText;
+	}
 
 
 	// -------------------------------------------------------------------
 	/** Called prior to ReadFile().
-	* The function is a request to the importer to update its configuration
-	* basing on the Importer's configuration property list.
-	* @param pImp Importer instance
-	* @param ppFlags Post-processing steps to be executed on the data
-	*  returned by the loaders. This value is provided to allow some
-	* internal optimizations.
-	*/
+	 * The function is a request to the importer to update its configuration
+	 * basing on the Importer's configuration property list.
+	 * @param pImp Importer instance
+	 * @param ppFlags Post-processing steps to be executed on the data
+	 *  returned by the loaders. This value is provided to allow some
+	 * internal optimizations.
+	 */
 	virtual void SetupProperties(const Importer* pImp /*,
 		unsigned int ppFlags*/);
 
@@ -157,7 +174,7 @@ protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 *  Importer implementations should append all file extensions
 	 *  which they supported to the passed string.
-	 *  Example: "*.blabb;*.quak;*.gug;*.foo" (no comma after the last!)
+	 *  Example: "*.blabb;*.quak;*.gug;*.foo" (no delimiter after the last!)
 	 * @param append Output string
 	 */
 	virtual void GetExtensionList(std::string& append) = 0;
@@ -169,23 +186,36 @@ protected:
 	 * expected to be correct. Override this function to implement the 
 	 * actual importing.
 	 * <br>
-	 * The output scene must meet the following requirements:<br>
-	 * - at least a root node must be there<br>
-	 * - aiMesh::mPrimitiveTypes may be 0. The types of primitives
-	 *   in the mesh are determined automatically in this case.<br>
-	 * - the vertex data is stored in a pseudo-indexed "verbose" format.
+	 *  The output scene must meet the following requirements:<br>
+	 * <ul>
+	 * <li>At least a root node must be there, even if its only purpose
+	 *     is to reference one mesh.</li>
+	 * <li>aiMesh::mPrimitiveTypes may be 0. The types of primitives
+	 *   in the mesh are determined automatically in this case.</li>
+	 * <li>the vertex data is stored in a pseudo-indexed "verbose" format.
 	 *   In fact this means that every vertex that is referenced by
 	 *   a face is unique. Or the other way round: a vertex index may
-	 *   not occur twice in a single aiMesh.
-	 * - aiAnimation::mDuration may be -1. Assimp determines the length
+	 *   not occur twice in a single aiMesh.</li>
+	 * <li>aiAnimation::mDuration may be -1. Assimp determines the length
 	 *   of the animation automatically in this case as the length of
-	 *   the longest animation channel.
-	 *
-	 * If the AI_SCENE_FLAGS_INCOMPLETE-Flag is not set:<br>
-	 * - at least one mesh must be there<br>
-	 * - at least one material must be there<br>
-	 * - there may be no meshes with 0 vertices or faces<br>
-	 * This won't be checked (except by the validation step), Assimp will
+	 *   the longest animation channel.</li>
+	 * <li>aiMesh::mBitangents may be NULL if tangents and normals are
+	 *   given. In this case bitangents are computed as the cross product
+	 *   between normal and tangent.</li>
+	 * <li>There needn't be a material. If none is there a default material
+	 *   is generated. However, it is recommended practice for loaders
+	 *   to generate a default material for yourself that matches the
+	 *   default material setting for the file format better than Assimp's
+	 *   generic default material. Note that default materials *should*
+	 *   be named AI_DEFAULT_MATERIAL_NAME if they're just color-shaded
+	 *   or AI_DEFAULT_TEXTURED_MATERIAL_NAME if they define a (dummy) 
+	 *   texture. </li>
+	 * </ul>
+	 * If the AI_SCENE_FLAGS_INCOMPLETE-Flag is <b>not</b> set:<ul>
+	 * <li> at least one mesh must be there</li>
+	 * <li> there may be no meshes with 0 vertices or faces</li>
+	 * </ul>
+	 * This won't be checked (except by the validation step): Assimp will
 	 * crash if one of the conditions is not met!
 	 *
 	 * @param pFile Path of the file to be imported.
@@ -218,12 +248,53 @@ protected:
 		unsigned int		numTokens,
 		unsigned int		searchBytes = 200);
 
+
+	// -------------------------------------------------------------------
+	/** @brief Check whether a file has a specific file extension
+	 *  @param pFile Input file
+	 *  @param ext0 Extension to check for. Lowercase characters only, no dot!
+	 *  @param ext1 Optional second extension
+	 *  @param ext2 Optional third extension
+	 *  @note Case-insensitive
+	 */
+	static bool SimpleExtensionCheck (const std::string& pFile, 
+		const char* ext0,
+		const char* ext1 = NULL,
+		const char* ext2 = NULL);
+
+	// -------------------------------------------------------------------
+	/** @brief Extract file extension from a string
+	 *  @param pFile Input file
+	 *  @return Extension without trailing dot, all lowercase
+	 */
+	static std::string GetExtension (const std::string& pFile);
+
+	// -------------------------------------------------------------------
+	/** @brief Check whether a file starts with one or more magic tokens
+	 *  @param pFile Input file
+	 *  @param pIOHandler IO system to be used
+	 *  @param magic n magic tokens
+	 *  @params num Size of magic
+	 *  @param offset Offset from file start where tokens are located
+	 *  @param Size of one token, in bytes. Maximally 16 bytes.
+	 *  @return true if one of the given tokens was found
+	 *
+	 *  @note For convinence, the check is also performed for the
+	 *  byte-swapped variant of all tokens (big endian). Only for
+	 *  tokens of size 2,4.
+	 */
+	static bool CheckMagicToken(IOSystem* pIOHandler, const std::string& pFile, 
+		const void* magic,
+		unsigned int num,
+		unsigned int offset = 0,
+		unsigned int size   = 4);
+
 #if 0 /** TODO **/
 	// -------------------------------------------------------------------
 	/** An utility for all text file loaders. It converts a file to our
-	 *  ASCII/UTF8 character set. Special unicode characters are lost.
-	 *
-	 *  @param buffer Input buffer. Needn't be terminated with zero.
+	*  ASCII/UTF8 character set. Special unicode characters are lost.
+	*
+	*  @param buffer Input buffer. Needn't be terminated with zero.
 	 *  @param length Length of the input buffer, in bytes. Receives the
 	 *    number of output characters, excluding the terminal char.
 	 *  @return true if the source format did not match our internal
@@ -266,10 +337,18 @@ public:
 		Importer::IntPropertyMap     ints;
 		Importer::FloatPropertyMap   floats;
 		Importer::StringPropertyMap  strings;
-	};
 
+		bool operator == (const PropertyMap& prop) const {
+			return ints == prop.ints && floats == prop.floats && strings == prop.strings; 
+		}
+
+		bool empty () const {
+			return ints.empty() && floats.empty() && strings.empty();
+		}
+	};
 
 public:
+	
 
 	/** Construct a batch loader from a given IO system
 	 */
@@ -293,8 +372,10 @@ public:
 	 *  @param file File to be loaded
 	 *  @param steps Steps to be executed on the file
 	 *  @param map Optional configuration properties
+	 *  @return 'Load request channel' - an unique ID that can later
+	 *    be used to access the imported file data.
 	 */
-	void AddLoadRequest	(const std::string& file,
+	unsigned int AddLoadRequest	(const std::string& file,
 		unsigned int steps = 0, const PropertyMap* map = NULL);
 
 
@@ -304,11 +385,11 @@ public:
 	 *  If an import is requested several times, this function
 	 *  can be called several times, too.
 	 *
-	 *  @param file File name of the scene
+	 *  @param which LRWC returned by AddLoadRequest().
 	 *  @return NULL if there is no scene with this file name
 	 *  in the queue of the scene hasn't been loaded yet.
 	 */
-	aiScene* GetImport		(const std::string& file);
+	aiScene* GetImport		(unsigned int which);
 
 
 	/** Waits until all scenes have been loaded.
@@ -321,7 +402,6 @@ private:
 	BatchData* data;
 };
 
-
 } // end of namespace Assimp
 
 #endif // AI_BASEIMPORTER_H_INC

+ 1 - 1
code/BaseProcess.cpp

@@ -69,7 +69,7 @@ void BaseProcess::ExecuteOnScene( Importer* pImp)
 	// catch exceptions thrown inside the PostProcess-Step
 	try
 	{
-		this->Execute(pImp->mScene);
+		Execute(pImp->mScene);
 
 	} catch( ImportErrorException* exception)
 	{

+ 10 - 6
code/CalcTangentsProcess.cpp

@@ -126,7 +126,7 @@ bool CalcTangentsProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex)
 	const float angleEpsilon = 0.9999f;
 
 	std::vector<bool> vertexDone( pMesh->mNumVertices, false);
-	const float qnan = std::numeric_limits<float>::quiet_NaN();
+	const float qnan = get_qnan();
 
 	// create space for the tangents and bitangents
 	pMesh->mTangents = new aiVector3D[pMesh->mNumVertices];
@@ -149,8 +149,10 @@ bool CalcTangentsProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex)
 			// their tangent vectors are set to qnan.
 			for (unsigned int i = 0; i < face.mNumIndices;++i)
 			{
-				vertexDone[face.mIndices[i]] = true;
-				meshTang  [face.mIndices[i]] = qnan;
+				register unsigned int idx = face.mIndices[i];
+				vertexDone  [idx] = true;
+				meshTang    [idx] = qnan;
+				meshBitang  [idx] = qnan;
 			}
 
 			continue;
@@ -218,10 +220,10 @@ bool CalcTangentsProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex)
 		vertexFinder = &_vertexFinder;
 		posEpsilon = ComputePositionEpsilon(pMesh);
 	}
-
 	std::vector<unsigned int> verticesFound;
 
 	const float fLimit = cosf(this->configMaxAngle); 
+	std::vector<unsigned int> closeVertices;
 
 	// in the second pass we now smooth out all tangents and bitangents at the same local position 
 	// if they are not too far off.
@@ -234,12 +236,14 @@ bool CalcTangentsProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex)
 		const aiVector3D& origNorm = pMesh->mNormals[a];
 		const aiVector3D& origTang = pMesh->mTangents[a];
 		const aiVector3D& origBitang = pMesh->mBitangents[a];
-		std::vector<unsigned int> closeVertices;
-		closeVertices.push_back( a);
+		closeVertices.clear();
 
 		// find all vertices close to that position
 		vertexFinder->FindPositions( origPos, posEpsilon, verticesFound);
 
+		closeVertices.reserve (verticesFound.size()+5);
+		closeVertices.push_back( a);
+
 		// look among them for other vertices sharing the same normal and a close-enough tangent/bitangent
 		for( unsigned int b = 0; b < verticesFound.size(); b++)
 		{

+ 16 - 14
code/ColladaLoader.cpp

@@ -67,22 +67,16 @@ ColladaLoader::~ColladaLoader()
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file. 
-bool ColladaLoader::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
+bool ColladaLoader::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
 {
 	// check file extension 
-	std::string::size_type pos = pFile.find_last_of('.');
-	// no file extension - can't read
-	if( pos == std::string::npos)
-		return false;
-	std::string extension = pFile.substr( pos);
-	for( std::string::iterator it = extension.begin(); it != extension.end(); ++it)
-		*it = tolower( *it);
-
-	if( extension == ".dae")
+	std::string extension = GetExtension(pFile);
+	
+	if( extension == "dae")
 		return true;
 
 	// XML - too generic, we need to open the file and search for typical keywords
-	if( extension == ".xml")	{
+	if( extension == "xml" || !extension.length() || checkSig)	{
 		/*  If CanRead() is called in order to check whether we
 		 *  support a specific file extension in general pIOHandler
 		 *  might be NULL and it's our duty to return true here.
@@ -94,6 +88,13 @@ bool ColladaLoader::CanRead( const std::string& pFile, IOSystem* pIOHandler) con
 	return false;
 }
 
+// ------------------------------------------------------------------------------------------------
+// Get file extension list
+void ColladaLoader::GetExtensionList( std::string& append)
+{
+	append.append("*.dae");
+}
+
 // ------------------------------------------------------------------------------------------------
 // Imports the given file into the given scene structure. 
 void ColladaLoader::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler)
@@ -166,7 +167,8 @@ aiNode* ColladaLoader::BuildHierarchy( const ColladaParser& pParser, const Colla
 	aiNode* node = new aiNode();
 
 	// now setup the name of the node. We take the name if not empty, otherwise the collada ID
-	if (!pNode->mName.empty())
+	// FIX: Workaround for XSI calling the instanced visual scene 'untitled' by default.
+	if (!pNode->mName.empty() && pNode->mName != "untitled")
 		node->mName.Set(pNode->mName);
 	else if (!pNode->mID.empty())
 		node->mName.Set(pNode->mID);
@@ -632,7 +634,7 @@ void ColladaLoader::AddTexture ( Assimp::MaterialHelper& mat, const ColladaParse
 		_AI_MATKEY_TEXTURE_BASE,type,idx);
 
 	// mapping mode
-	int map = map = aiTextureMapMode_Clamp;
+	int map = aiTextureMapMode_Clamp;
 	if (sampler.mWrapU)
 		map = aiTextureMapMode_Wrap;
 	if (sampler.mWrapU && sampler.mMirrorU)
@@ -896,4 +898,4 @@ void ColladaLoader::ConvertPath (aiString& ss)
 	}
 }
 
-#endif // !! ASSIMP_BUILD_NO_DAE_IMPORTER
+#endif // !! ASSIMP_BUILD_NO_DAE_IMPORTER

+ 2 - 5
code/ColladaLoader.h

@@ -90,16 +90,13 @@ protected:
 public:
 	/** Returns whether the class can handle the format of the given file. 
 	 * See BaseImporter::CanRead() for details.	*/
-	bool CanRead( const std::string& pFile, IOSystem* pIOHandler) const;
+	bool CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const;
 
 protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 */
-	void GetExtensionList( std::string& append)
-	{
-		append.append("*.dae");
-	}
+	void GetExtensionList( std::string& append);
 
 	/** Imports the given file into the given scene structure. 
 	 * See BaseImporter::InternReadFile() for details

+ 3 - 3
code/ColladaParser.cpp

@@ -1618,8 +1618,8 @@ void ColladaParser::ReadSceneLibrary()
 		}
 		else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
 		{
-			if( strcmp( mReader->getNodeName(), "library_visual_scenes") != 0)
-				ThrowException( "Expected end of \"library_visual_scenes\" element.");
+			if( strcmp( mReader->getNodeName(), "library_visual_scenes") == 0)
+				//ThrowException( "Expected end of \"library_visual_scenes\" element.");
 
 			break;
 		}
@@ -2106,4 +2106,4 @@ Collada::InputType ColladaParser::GetTypeForSemantic( const std::string& pSemant
 	return IT_Invalid;
 }
 
-#endif // !! ASSIMP_BUILD_NO_DAE_IMPORTER
+#endif // !! ASSIMP_BUILD_NO_DAE_IMPORTER

+ 1 - 1
code/ColladaParser.h

@@ -202,7 +202,7 @@ protected:
 
 	/** Reads the text contents of an element, returns NULL if not given.
 	    Skips leading whitespace. */
-	const char* ColladaParser::TestTextContent();
+	const char* TestTextContent();
 
 	/** Reads a single bool from current text content */
 	bool ReadBoolFromTextContent();

+ 11 - 13
code/DXFLoader.cpp

@@ -39,7 +39,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ---------------------------------------------------------------------------
 */
 
-/** @file Implementation of the DXF importer class */
+/** @file  DXFLoader.cpp
+ *  @brief Implementation of the DXF importer class
+ */
 
 #include "AssimpPCH.h"
 #ifndef ASSIMP_BUILD_NO_DXF_IMPORTER
@@ -77,7 +79,7 @@ static aiColor4D g_aclrDxfIndexColors[] =
 #define AI_DXF_NUM_INDEX_COLORS (sizeof(g_aclrDxfIndexColors)/sizeof(g_aclrDxfIndexColors[0]))
 
 // invalid/unassigned color value
-aiColor4D g_clrInvalid = aiColor4D(std::numeric_limits<float>::quiet_NaN(),0.f,0.f,1.f);
+aiColor4D g_clrInvalid = aiColor4D(get_qnan(),0.f,0.f,1.f);
 
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
@@ -95,19 +97,15 @@ DXFImporter::~DXFImporter()
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file. 
-bool DXFImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
+bool DXFImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
 {
-	// simple check of file extension is enough for the moment
-	std::string::size_type pos = pFile.find_last_of('.');
-	// no file extension - can't read
-	if( pos == std::string::npos)
-		return false;
-	std::string extension = pFile.substr( pos);
+	return SimpleExtensionCheck(pFile,"dxf");
+}
 
-	return !(extension.length() != 4 || extension[0] != '.' ||
-			 extension[1] != 'd' && extension[1] != 'D' ||
-			 extension[2] != 'x' && extension[2] != 'X' ||
-			 extension[3] != 'f' && extension[3] != 'F');
+// ------------------------------------------------------------------------------------------------
+void DXFImporter::GetExtensionList(std::string& append)
+{
+	append.append("*.dxf");
 }
 
 // ------------------------------------------------------------------------------------------------

+ 6 - 6
code/DXFLoader.h

@@ -38,7 +38,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ----------------------------------------------------------------------
 */
 
-/** @file Declaration of the .dxf importer class. */
+/** @file  DXFLoader.h 
+ *  @brief Declaration of the .dxf importer class.
+ */
 #ifndef AI_DXFLOADER_H_INCLUDED
 #define AI_DXFLOADER_H_INCLUDED
 
@@ -85,7 +87,8 @@ public:
 	// -------------------------------------------------------------------
 	/** Returns whether the class can handle the format of the given file. 
 	* See BaseImporter::CanRead() for details.	*/
-	bool CanRead( const std::string& pFile, IOSystem* pIOHandler) const;
+	bool CanRead( const std::string& pFile, IOSystem* pIOHandler, 
+		bool checkSig) const;
 
 protected:
 
@@ -93,10 +96,7 @@ protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 */
-	void GetExtensionList(std::string& append)
-	{
-		append.append("*.dxf");
-	}
+	void GetExtensionList(std::string& append);
 
 	// -------------------------------------------------------------------
 	/** Imports the given file into the given scene structure. 

+ 57 - 23
code/DefaultLogger.cpp

@@ -51,6 +51,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "StdOStreamLogStream.h"
 #include "FileLogStream.h"
 
+#ifndef ASSIMP_BUILD_SINGLETHREADED
+#	include <boost/thread/thread.hpp>
+#	include <boost/thread/mutex.hpp>
+
+boost::mutex loggerMutex;
+#endif
+
 namespace Assimp	{
 
 // ----------------------------------------------------------------------------------
@@ -119,6 +126,11 @@ Logger *DefaultLogger::create(const char* name /*= "AssimpLog.txt"*/,
 	unsigned int defStreams                    /*= DLS_DEBUGGER | DLS_FILE*/,
 	IOSystem* io		                       /*= NULL*/)
 {
+	// enter the mutex here to avoid concurrency problems
+#ifndef ASSIMP_BUILD_SINGLETHREADED
+	boost::mutex::scoped_lock lock(loggerMutex);
+#endif
+
 	if (m_pLogger && !isNullLogger() )
 		delete m_pLogger;
 
@@ -146,31 +158,56 @@ Logger *DefaultLogger::create(const char* name /*= "AssimpLog.txt"*/,
 
 // ----------------------------------------------------------------------------------
 void Logger::debug(const std::string &message)	{
-	ai_assert(message.length()<=Logger::MAX_LOG_MESSAGE_LENGTH);
+
+	// SECURITY FIX: otherwise it's easy to produce overruns ...
+	if (message.length()>MAX_LOG_MESSAGE_LENGTH) {
+		ai_assert(false);
+		return;
+	}
 	return OnDebug(message.c_str());
 }
 
 // ----------------------------------------------------------------------------------
 void Logger::info(const std::string &message)	{
-	ai_assert(message.length()<=Logger::MAX_LOG_MESSAGE_LENGTH);
+	
+	// SECURITY FIX: otherwise it's easy to produce overruns ...
+	if (message.length()>MAX_LOG_MESSAGE_LENGTH) {
+		ai_assert(false);
+		return;
+	}
 	return OnInfo(message.c_str());
 }
 	
 // ----------------------------------------------------------------------------------
 void Logger::warn(const std::string &message)	{
-	ai_assert(message.length()<=Logger::MAX_LOG_MESSAGE_LENGTH);
+	
+	// SECURITY FIX: otherwise it's easy to produce overruns ...
+	if (message.length()>MAX_LOG_MESSAGE_LENGTH) {
+		ai_assert(false);
+		return;
+	}
 	return OnWarn(message.c_str());
 }
 
 // ----------------------------------------------------------------------------------
 void Logger::error(const std::string &message)	{
-	ai_assert(message.length()<=Logger::MAX_LOG_MESSAGE_LENGTH);
+	
+	// SECURITY FIX: otherwise it's easy to produce overruns ...
+	if (message.length()>MAX_LOG_MESSAGE_LENGTH) {
+		ai_assert(false);
+		return;
+	}
 	return OnError(message.c_str());
 }
 
 // ----------------------------------------------------------------------------------
 void DefaultLogger::set( Logger *logger )
 {
+	// enter the mutex here to avoid concurrency problems
+#ifndef ASSIMP_BUILD_SINGLETHREADED
+	boost::mutex::scoped_lock lock(loggerMutex);
+#endif
+
 	if (!logger)logger = &s_pNullLogger;
 	if (m_pLogger && !isNullLogger() )
 		delete m_pLogger;
@@ -195,6 +232,11 @@ Logger *DefaultLogger::get()
 //	Kills the only instance
 void DefaultLogger::kill()
 {
+	// enter the mutex here to avoid concurrency problems
+#ifndef ASSIMP_BUILD_SINGLETHREADED
+	boost::mutex::scoped_lock lock(loggerMutex);
+#endif
+
 	if (m_pLogger != &s_pNullLogger)return;
 	delete m_pLogger;
 	m_pLogger = &s_pNullLogger;
@@ -243,23 +285,14 @@ void DefaultLogger::OnError( const char* message )
 	WriteToStreams( msg, Logger::ERR );
 }
 
-// ----------------------------------------------------------------------------------
-//	Severity setter
-void DefaultLogger::setLogSeverity( LogSeverity log_severity )
-{
-	m_Severity = log_severity;
-}
-
 // ----------------------------------------------------------------------------------
 //	Attachs a new stream
-void DefaultLogger::attachStream( LogStream *pStream, unsigned int severity )
+bool DefaultLogger::attachStream( LogStream *pStream, unsigned int severity )
 {
 	if (!pStream)
-		return;
+		return false;
 
-	// fix (Aramis)
-	if (0 == severity)
-	{
+	if (0 == severity)	{
 		severity = Logger::INFO | Logger::ERR | Logger::WARN | Logger::DEBUGGING;
 	}
 
@@ -270,24 +303,23 @@ void DefaultLogger::attachStream( LogStream *pStream, unsigned int severity )
 		if ( (*it)->m_pStream == pStream )
 		{
 			(*it)->m_uiErrorSeverity |= severity;
-			return;
+			return true;
 		}
 	}
 	
 	LogStreamInfo *pInfo = new LogStreamInfo( severity, pStream );
 	m_StreamArray.push_back( pInfo );
+	return true;
 }
 
 // ----------------------------------------------------------------------------------
 //	Detatch a stream
-void DefaultLogger::detatchStream( LogStream *pStream, unsigned int severity )
+bool DefaultLogger::detatchStream( LogStream *pStream, unsigned int severity )
 {
 	if (!pStream)
-		return;
+		return false;
 
-	// fix (Aramis)
-	if (0 == severity)
-	{
+	if (0 == severity)	{
 		severity = Logger::INFO | Logger::ERR | Logger::WARN | Logger::DEBUGGING;
 	}
 	
@@ -303,15 +335,17 @@ void DefaultLogger::detatchStream( LogStream *pStream, unsigned int severity )
 				m_StreamArray.erase( it );
 				break;
 			}
+			return true;
 		}
 	}
+	return false;
 }
 
 // ----------------------------------------------------------------------------------
 //	Constructor
 DefaultLogger::DefaultLogger(LogSeverity severity) 
 
-	:	m_Severity	( severity )
+	:	Logger	( severity )
 	,	noRepeatMsg	(false)
 	,	lastLen( 0 )
 {

+ 126 - 65
code/FindDegenerates.cpp

@@ -39,8 +39,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ---------------------------------------------------------------------------
 */
 
-/** @file Implementation of the DeterminePTypeHelperProcess and
- *  SortByPTypeProcess post-process steps.
+/** @file  FindDegenerates.cpp
+ *  @brief Implementation of the FindDegenerates post-process step.
 */
 
 #include "AssimpPCH.h"
@@ -54,8 +54,8 @@ using namespace Assimp;
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
 FindDegeneratesProcess::FindDegeneratesProcess()
-{
-}
+: configRemoveDegenerates (false)
+{}
 
 // ------------------------------------------------------------------------------------------------
 // Destructor, private as well
@@ -71,83 +71,144 @@ bool FindDegeneratesProcess::IsActive( unsigned int pFlags) const
 	return 0 != (pFlags & aiProcess_FindDegenerates);
 }
 
+// ------------------------------------------------------------------------------------------------
+// Setup import configuration
+void FindDegeneratesProcess::SetupProperties(const Importer* pImp)
+{
+	// Get the current value of AI_CONFIG_PP_FD_REMOVE
+	configRemoveDegenerates = (0 != pImp->GetPropertyInteger(AI_CONFIG_PP_FD_REMOVE,0));
+}
+
 // ------------------------------------------------------------------------------------------------
 // Executes the post processing step on the given imported data.
 void FindDegeneratesProcess::Execute( aiScene* pScene)
 {
 	DefaultLogger::get()->debug("FindDegeneratesProcess begin");
-	for (unsigned int i = 0; i < pScene->mNumMeshes;++i)
+	for (unsigned int i = 0; i < pScene->mNumMeshes;++i){
+		ExecuteOnMesh( pScene->mMeshes[i]);
+	}
+	DefaultLogger::get()->debug("FindDegeneratesProcess finished");
+}
+
+// ------------------------------------------------------------------------------------------------
+// Executes the post processing step on the given imported mesh
+void FindDegeneratesProcess::ExecuteOnMesh( aiMesh* mesh)
+{
+	mesh->mPrimitiveTypes = 0;
+
+	std::vector<bool> remove_me; 
+	if (configRemoveDegenerates)
+		remove_me.resize(mesh->mNumFaces,false);
+
+	unsigned int deg = 0;
+	for (unsigned int a = 0; a < mesh->mNumFaces; ++a)
 	{
-		aiMesh* mesh = pScene->mMeshes[i];
-		mesh->mPrimitiveTypes = 0;
+		aiFace& face = mesh->mFaces[a];
+		bool first = true;
 
-		unsigned int deg = 0;
-		for (unsigned int a = 0; a < mesh->mNumFaces; ++a)
+		// check whether the face contains degenerated entries
+		for (register unsigned int i = 0; i < face.mNumIndices; ++i)
 		{
-			aiFace& face = mesh->mFaces[a];
-			bool first = true;
-
-			// check whether the face contains degenerated entries
-			for (register unsigned int i = 0; i < face.mNumIndices; ++i)
+			for (register unsigned int t = i+1; t < face.mNumIndices; ++t)
 			{
-				for (register unsigned int a = i+1; a < face.mNumIndices; ++a)
+				if (mesh->mVertices[face.mIndices[i]] == mesh->mVertices[face.mIndices[t]])
 				{
-					if (mesh->mVertices[face.mIndices[i]] == mesh->mVertices[face.mIndices[a]])
+					// we have found a matching vertex position
+					// remove the corresponding index from the array
+					--face.mNumIndices;
+					for (unsigned int m = t; m < face.mNumIndices; ++m)
+					{
+						face.mIndices[m] = face.mIndices[m+1];
+					}
+					--t; 
+
+					// NOTE: we set the removed vertex index to an unique value
+					// to make sure the developer gets notified when his
+					// application attemps to access this data.
+					face.mIndices[face.mNumIndices] = 0xdeadbeef;
+
+					if(first)
 					{
-						// we have found a matching vertex position
-						// remove the corresponding index from the array
-						for (unsigned int m = a; m < face.mNumIndices-1; ++m)
-						{
-							face.mIndices[m] = face.mIndices[m+1];
-						}
-						--a;
-						--face.mNumIndices;
-
-						// NOTE: we set the removed vertex index to an unique value
-						// to make sure the developer gets notified when his
-						// application attemps to access this data.
-						face.mIndices[face.mNumIndices] = 0xdeadbeef;
-
-
-						if(first)
-						{
-							++deg;
-							first = false;
-						}
+						++deg;
+						first = false;
+					}
+
+					if (configRemoveDegenerates) {
+						remove_me[a] = true;
+						goto evil_jump_outside; // hrhrhrh ... yeah, this rocks baby!
 					}
 				}
 			}
-
-			// We need to update the primitive flags array of the mesh.
-			// Unfortunately it is not possible to execute
-			// FindDegenerates before DeterminePType. The latter does
-			// nothing if the primitive flags have already been set by
-			// the loader - our changes would be ignored. Although
-			// we could use some tricks regarding - i.e setting 
-			// mPrimitiveTypes to 0 in every case - but this is the cleanest 
-			//  way and causes no additional dependencies in the pipeline.
-			switch (face.mNumIndices)
-			{
-			case 1u:
-				mesh->mPrimitiveTypes |= aiPrimitiveType_POINT;
-				break;
-			case 2u:
-				mesh->mPrimitiveTypes |= aiPrimitiveType_LINE;
-				break;
-			case 3u:
-				mesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE;
-				break;
-			default:
-				mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON;
-				break;
-			};
 		}
-		if (deg && !DefaultLogger::isNullLogger())
+
+		// We need to update the primitive flags array of the mesh.
+		// Unfortunately it is not possible to execute
+		// FindDegenerates before DeterminePType. The latter does
+		// nothing if the primitive flags have already been set by
+		// the loader - our changes would be ignored. Although
+		// we could use some tricks regarding - i.e setting 
+		// mPrimitiveTypes to 0 in every case - but this is the cleanest 
+		//  way and causes no additional dependencies in the pipeline.
+		switch (face.mNumIndices)
+		{
+		case 1u:
+			mesh->mPrimitiveTypes |= aiPrimitiveType_POINT;
+			break;
+		case 2u:
+			mesh->mPrimitiveTypes |= aiPrimitiveType_LINE;
+			break;
+		case 3u:
+			mesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE;
+			break;
+		default:
+			mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON;
+			break;
+		};
+evil_jump_outside:
+		continue;
+	}
+
+	// If AI_CONFIG_PP_FD_REMOVE is true, remove degenerated faces from the import
+	if (configRemoveDegenerates && deg) {
+		unsigned int n = 0;
+		for (unsigned int a = 0; a < mesh->mNumFaces; ++a)
 		{
-			char s[64];
-			ASSIMP_itoa10(s,deg); 
-			DefaultLogger::get()->warn(std::string("Found ") + s + " degenerated primitives");
+			aiFace& face_src = mesh->mFaces[a];
+			if (!remove_me[a]) {
+				aiFace& face_dest = mesh->mFaces[n++];
+
+				// Do a manual copy, keep the index array
+				face_dest.mNumIndices = face_src.mNumIndices;
+				face_dest.mIndices    = face_src.mIndices;
+
+				// clear source
+				face_src.mNumIndices = 0;
+				face_src.mIndices = NULL;
+			}
+			else {
+				// Otherwise delete it if we don't need this face
+				delete[] face_src.mIndices;
+				face_src.mIndices = NULL;
+				face_src.mNumIndices = 0;
+			}
+		}
+		// Just leave the rest of the array unreferenced, we don't care for now
+		mesh->mNumFaces = n;
+		if (!mesh->mNumFaces) {
+			// WTF!? 
+			// OK ... for completeness and because I'm not yet tired,
+			// let's write code that willl hopefully never be called
+			// (famous last words)
+
+			// OK ... bad idea.
+			throw new ImportErrorException("Mesh is empty after removal of degenerated primitives ... WTF!?");
 		}
 	}
-	DefaultLogger::get()->debug("FindDegeneratesProcess finished");
+
+	if (deg && !DefaultLogger::isNullLogger())
+	{
+		char s[64];
+		ASSIMP_itoa10(s,deg); 
+		DefaultLogger::get()->warn(std::string("Found ") + s + " degenerated primitives");
+	}
 }

+ 30 - 0
code/FindDegenerates.h

@@ -66,14 +66,44 @@ protected:
 	~FindDegeneratesProcess();
 
 public:
+	
 	// -------------------------------------------------------------------
+	// Check whether step is active
 	bool IsActive( unsigned int pFlags) const;
 
 	// -------------------------------------------------------------------
+	// Execute step on a given scene
 	void Execute( aiScene* pScene);
 
+	// -------------------------------------------------------------------
+	// Setup import settings
+	void SetupProperties(const Importer* pImp);
+
+	// -------------------------------------------------------------------
+	// Execute step on a given mesh
+	void ExecuteOnMesh( aiMesh* mesh);
+
+
+	// -------------------------------------------------------------------
+	/** @brief Enable the instant removal of degenerated primitives
+	 *  @param d hm ... difficult to guess what this means, hu!?
+	 */
+	void EnableInstantRemoval(bool d) {
+		configRemoveDegenerates = d;
+	}
+
+	// -------------------------------------------------------------------
+	/** @brief Check whether instant removal is currently enabled
+	 *  @return ...
+	 */
+	bool IsInstantRemoval() const {
+		return configRemoveDegenerates;
+	}
+
 private:
 
+	//! Configuration option: remove degenerates faces immediately
+	bool configRemoveDegenerates;
 };
 }
 

+ 1 - 1
code/GenFaceNormalsProcess.cpp

@@ -111,7 +111,7 @@ bool GenFaceNormalsProcess::GenMeshFaceNormals (aiMesh* pMesh)
 
 	// allocate an array to hold the output normals
 	pMesh->mNormals = new aiVector3D[pMesh->mNumVertices];
-	const float qnan = std::numeric_limits<float>::quiet_NaN();
+	const float qnan = get_qnan();
 
 	// iterate through all faces and compute per-face normals but store
 	// them per-vertex. 

+ 7 - 9
code/HMPFileData.h

@@ -46,17 +46,15 @@ namespace HMP	{
 
 #include "./../include/Compiler/pushpack1.h"
 
-// to make it easier for ourselfes, we test the magic word against both "endianesses"
-#define HMP_MAKE(string) ((uint32_t)((string[0] << 24) + (string[1] << 16) + (string[2] << 8) + string[3]))
+// to make it easier for us, we test the magic word against both "endianesses"
+#define AI_HMP_MAGIC_NUMBER_BE_4	AI_MAKE_MAGIC("HMP4")
+#define AI_HMP_MAGIC_NUMBER_LE_4	AI_MAKE_MAGIC("4PMH")
 
-#define AI_HMP_MAGIC_NUMBER_BE_4	HMP_MAKE("HMP4")
-#define AI_HMP_MAGIC_NUMBER_LE_4	HMP_MAKE("4PMH")
+#define AI_HMP_MAGIC_NUMBER_BE_5	AI_MAKE_MAGIC("HMP5")
+#define AI_HMP_MAGIC_NUMBER_LE_5	AI_MAKE_MAGIC("5PMH")
 
-#define AI_HMP_MAGIC_NUMBER_BE_5	HMP_MAKE("HMP5")
-#define AI_HMP_MAGIC_NUMBER_LE_5	HMP_MAKE("5PMH")
-
-#define AI_HMP_MAGIC_NUMBER_BE_7	HMP_MAKE("HMP7")
-#define AI_HMP_MAGIC_NUMBER_LE_7	HMP_MAKE("7PMH")
+#define AI_HMP_MAGIC_NUMBER_BE_7	AI_MAKE_MAGIC("HMP7")
+#define AI_HMP_MAGIC_NUMBER_LE_7	AI_MAKE_MAGIC("7PMH")
 
 // ---------------------------------------------------------------------------
 /** Data structure for the header of a HMP5 file.

+ 24 - 14
code/HMPLoader.cpp

@@ -67,25 +67,34 @@ HMPImporter::~HMPImporter()
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file. 
-bool HMPImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
+bool HMPImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool cs) const
 {
-	(void)pIOHandler; //this avoids the compiler warning of unused element
-	// simple check of file extension is enough for the moment
-	std::string::size_type pos = pFile.find_last_of('.');
-	// no file extension - can't read
-	if( pos == std::string::npos)return false;
-	std::string extension = pFile.substr( pos);
-
-	for( std::string::iterator it = extension.begin(); it != extension.end(); ++it)
-		*it = tolower( *it);
+	const std::string extension = GetExtension(pFile);
+	if (extension == "hmp" )
+		return true;
+
+	// if check for extension is not enough, check for the magic tokens 
+	if (!extension.length() || cs) {
+		uint32_t tokens[3]; 
+		tokens[0] = AI_HMP_MAGIC_NUMBER_LE_4;
+		tokens[1] = AI_HMP_MAGIC_NUMBER_LE_5;
+		tokens[2] = AI_HMP_MAGIC_NUMBER_LE_7;
+		return CheckMagicToken(pIOHandler,pFile,tokens,3,0);
+	}
+	return false;
+}
 
-	return extension == ".hmp";
+// ------------------------------------------------------------------------------------------------
+// Get list of all file extensions that are handled by this loader
+void HMPImporter::GetExtensionList(std::string& append)
+{
+	append.append("*.hmp");
 }
 
 // ------------------------------------------------------------------------------------------------
 // Imports the given file into the given scene structure. 
 void HMPImporter::InternReadFile( const std::string& pFile, 
-	aiScene* _pScene, IOSystem* _pIOHandler)
+								 aiScene* _pScene, IOSystem* _pIOHandler)
 {
 	pScene     = _pScene;
 	pIOHandler = _pIOHandler;
@@ -225,7 +234,8 @@ void HMPImporter::InternReadFile_HMP5( )
 	}
 
 	// generate texture coordinates if necessary
-	if (pcHeader->numskins)GenerateTextureCoords(width,height);
+	if (pcHeader->numskins)
+		GenerateTextureCoords(width,height);
 
 	// now build a list of faces
 	CreateOutputFaceList(width,height);	
@@ -477,7 +487,7 @@ void HMPImporter::GenerateTextureCoords(
 
 	for (unsigned int y = 0; y < height;++y)	{
 		for (unsigned int x = 0; x < width;++x,++uv)	{
-			uv->y = 1.0f-fY*y;
+			uv->y = fY*y;
 			uv->x = fX*x;
 			uv->z = 0.0f;
 		}

+ 8 - 9
code/HMPLoader.h

@@ -37,9 +37,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 ----------------------------------------------------------------------
 */
-//!
-//! @file Declaration of the HMP importer class
-//!
+/** @file  HMPLoader.h
+ *  @brief Declaration of the HMP importer class
+ */
 
 #ifndef AI_HMPLOADER_H_INCLUDED
 #define AI_HMPLOADER_H_INCLUDED
@@ -75,8 +75,10 @@ public:
 
 	// -------------------------------------------------------------------
 	/** Returns whether the class can handle the format of the given file. 
-	* See BaseImporter::CanRead() for details.	*/
-	bool CanRead( const std::string& pFile, IOSystem* pIOHandler) const;
+	 * See BaseImporter::CanRead() for details.
+	 */
+	bool CanRead( const std::string& pFile, IOSystem* pIOHandler, 
+		bool checkSig) const;
 
 protected:
 
@@ -85,10 +87,7 @@ protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 */
-	void GetExtensionList(std::string& append)
-	{
-		append.append("*.hmp");
-	}
+	void GetExtensionList(std::string& append);
 
 	// -------------------------------------------------------------------
 	/** Imports the given file into the given scene structure. 

+ 4 - 4
code/HalfLifeFileData.h

@@ -57,10 +57,10 @@ namespace Assimp	{
 namespace MDL	{
 
 // magic bytes used in Half Life 2 MDL models
-#define AI_MDL_MAGIC_NUMBER_BE_HL2a	MDL_MAKE("IDST")
-#define AI_MDL_MAGIC_NUMBER_LE_HL2a	MDL_MAKE("TSDI")
-#define AI_MDL_MAGIC_NUMBER_BE_HL2b	MDL_MAKE("IDSQ")
-#define AI_MDL_MAGIC_NUMBER_LE_HL2b	MDL_MAKE("QSDI")
+#define AI_MDL_MAGIC_NUMBER_BE_HL2a	AI_MAKE_MAGIC("IDST")
+#define AI_MDL_MAGIC_NUMBER_LE_HL2a	AI_MAKE_MAGIC("TSDI")
+#define AI_MDL_MAGIC_NUMBER_BE_HL2b	AI_MAKE_MAGIC("IDSQ")
+#define AI_MDL_MAGIC_NUMBER_LE_HL2b	AI_MAKE_MAGIC("QSDI")
 
 // ---------------------------------------------------------------------------
 /** \struct Header_HL2

+ 39 - 45
code/IRRLoader.cpp

@@ -39,7 +39,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ---------------------------------------------------------------------------
 */
 
-/** @file Implementation of the Irr importer class */
+/** @file  IRRLoader.cpp
+ *  @brief Implementation of the Irr importer class 
+ */
 
 #include "AssimpPCH.h"
 
@@ -81,26 +83,17 @@ IRRImporter::~IRRImporter()
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file. 
-bool IRRImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
+bool IRRImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
 {
 	/* NOTE: A simple check for the file extension is not enough
 	 * here. Irrmesh and irr are easy, but xml is too generic
 	 * and could be collada, too. So we need to open the file and
 	 * search for typical tokens.
 	 */
-
-	std::string::size_type pos = pFile.find_last_of('.');
-
-	// no file extension - can't read
-	if( pos == std::string::npos)
-		return false;
-
-	std::string extension = pFile.substr( pos);
-	for (std::string::iterator i = extension.begin(); i != extension.end();++i)
-		*i = ::tolower(*i);
-
-	if (extension == ".irr")return true;
-	else if (extension == ".xml")
+	const std::string extension = GetExtension(pFile);
+	
+	if (extension == "irr")return true;
+	else if (extension == "xml" || checkSig)
 	{
 		/*  If CanRead() is called in order to check whether we
 		 *  support a specific file extension in general pIOHandler
@@ -128,11 +121,13 @@ void IRRImporter::SetupProperties(const Importer* pImp)
 {
 	// read the output frame rate of all node animation channels
 	fps = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_IRR_ANIM_FPS,100);
-	if (!fps)
-	{
+	if (fps < 10.)	{
 		DefaultLogger::get()->error("IRR: Invalid FPS configuration");
 		fps = 100;
 	}
+
+	// AI_CONFIG_FAVOUR_SPEED
+	configSpeedFlag = (0 != pImp->GetPropertyInteger(AI_CONFIG_FAVOUR_SPEED,0));
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -595,7 +590,7 @@ void IRRImporter::ComputeAnimations(Node* root, aiNode* real, std::vector<aiNode
 
 // ------------------------------------------------------------------------------------------------
 // This function is maybe more generic than we'd need it here
-void SetupMapping (MaterialHelper* mat, aiTextureMapping mode, const aiVector3D& axis = aiVector3D(0.f,1.f,0.f))
+void SetupMapping (MaterialHelper* mat, aiTextureMapping mode, const aiVector3D& axis = aiVector3D(0.f,0.f,-1.f))
 {
 	// Check whether there are texture properties defined - setup
 	// the desired texture mapping mode for all of them and ignore
@@ -650,7 +645,9 @@ void SetupMapping (MaterialHelper* mat, aiTextureMapping mode, const aiVector3D&
 	// rebuild the output array
 	if (p.size() > mat->mNumAllocated)	{
 		delete[] mat->mProperties;
-		mat->mProperties = new aiMaterialProperty*[p.size()];
+		mat->mProperties = new aiMaterialProperty*[p.size()*2];
+
+		mat->mNumAllocated = p.size()*2;
 	}
 	mat->mNumProperties = (unsigned int)p.size();
 	::memcpy(mat->mProperties,&p[0],sizeof(void*)*mat->mNumProperties);
@@ -680,7 +677,7 @@ void IRRImporter::GenerateGraph(Node* root,aiNode* rootOut ,aiScene* scene,
 			// Get the loaded mesh from the scene and add it to
 			// the list of all scenes to be attached to the 
 			// graph we're currently building
-			aiScene* scene = batch.GetImport(root->meshPath);
+			aiScene* scene = batch.GetImport(root->id);
 			if (!scene)
 			{
 				DefaultLogger::get()->error("IRR: Unable to load external file: " 
@@ -688,6 +685,8 @@ void IRRImporter::GenerateGraph(Node* root,aiNode* rootOut ,aiScene* scene,
 				break;
 			}
 			attach.push_back(AttachmentInfo(scene,rootOut));
+
+#if 0
 			meshTrafoAssign = 1;
 
 			// If the root node of the scene is animated - and *this* node
@@ -721,8 +720,9 @@ void IRRImporter::GenerateGraph(Node* root,aiNode* rootOut ,aiScene* scene,
 					}
 				}
 			}
-			if (1 == meshTrafoAssign)
-				scene->mRootNode->mTransformation *= AI_TO_IRR_MATRIX;
+#endif
+		//	if (1 == meshTrafoAssign)
+		//		scene->mRootNode->mTransformation = AI_TO_IRR_MATRIX * scene->mRootNode->mTransformation;
 	
 
 			// Now combine the material we've loaded for this mesh
@@ -748,7 +748,7 @@ void IRRImporter::GenerateGraph(Node* root,aiNode* rootOut ,aiScene* scene,
 
 			// NOTE: Each mesh should have exactly one material assigned,
 			// but we do it in a separate loop if this behaviour changes
-			// in the future.
+			// in future.
 			for (unsigned int i = 0; i < scene->mNumMeshes;++i)
 			{
 				// Process material flags 
@@ -901,8 +901,7 @@ void IRRImporter::GenerateGraph(Node* root,aiNode* rootOut ,aiScene* scene,
 		rootOut->mNumMeshes = (unsigned int)meshes.size() - oldMeshSize;
 		rootOut->mMeshes    = new unsigned int[rootOut->mNumMeshes];
 
-		for (unsigned int a = 0; a  < rootOut->mNumMeshes;++a)
-		{
+		for (unsigned int a = 0; a  < rootOut->mNumMeshes;++a)	{
 			rootOut->mMeshes[a] = oldMeshSize+a;
 		}
 	}
@@ -913,6 +912,7 @@ void IRRImporter::GenerateGraph(Node* root,aiNode* rootOut ,aiScene* scene,
 	// Now compute the final local transformation matrix of the
 	// node from the given translation, rotation and scaling values.
 	// (the rotation is given in Euler angles, XYZ order)
+	std::swap((float&)root->rotation.z,(float&)root->rotation.y);
 	rootOut->mTransformation.FromEulerAnglesXYZ(AI_DEG_TO_RAD(root->rotation) );
 
 	// apply scaling
@@ -920,20 +920,20 @@ void IRRImporter::GenerateGraph(Node* root,aiNode* rootOut ,aiScene* scene,
 	mat.a1 *= root->scaling.x;
 	mat.b1 *= root->scaling.x; 
 	mat.c1 *= root->scaling.x;
-	mat.a2 *= root->scaling.y; 
-	mat.b2 *= root->scaling.y; 
-	mat.c2 *= root->scaling.y;
-	mat.a3 *= root->scaling.z;
-	mat.b3 *= root->scaling.z; 
-	mat.c3 *= root->scaling.z;
+	mat.a2 *= root->scaling.z; 
+	mat.b2 *= root->scaling.z; 
+	mat.c2 *= root->scaling.z;
+	mat.a3 *= root->scaling.y;
+	mat.b3 *= root->scaling.y; 
+	mat.c3 *= root->scaling.y;
 
 	// apply translation
 	mat.a4 += root->position.x; 
-	mat.b4 += root->position.y; 
-	mat.c4 += root->position.z;
+	mat.b4 += root->position.z; 
+	mat.c4 += root->position.y;
 
-	if (meshTrafoAssign == 2)
-		mat *= AI_TO_IRR_MATRIX;
+	//if (meshTrafoAssign == 2)
+	//	mat *= AI_TO_IRR_MATRIX;
 
 	// now compute animations for the node
 	ComputeAnimations(root,rootOut, anims);
@@ -1446,7 +1446,7 @@ void IRRImporter::InternReadFile( const std::string& pFile,
 										}
 										else
 										{
-											batch.AddLoadRequest(prop.value,pp,&map);
+											curNode->id = batch.AddLoadRequest(prop.value,pp,&map);
 											curNode->meshPath = prop.value;
 										}
 									}
@@ -1528,8 +1528,7 @@ void IRRImporter::InternReadFile( const std::string& pFile,
 	/*  Now iterate through all cameras and compute their final (horizontal) FOV
 	 */
 	for (std::vector<aiCamera*>::iterator it = cameras.begin(), end = cameras.end();
-		 it != end; ++it)
-	{
+		 it != end; ++it)	{
 		aiCamera* cam = *it;
 		if (cam->mAspect) // screen aspect could be missing
 		{
@@ -1625,8 +1624,8 @@ void IRRImporter::InternReadFile( const std::string& pFile,
 	 *  attachment points in the scenegraph.
 	 */
 	SceneCombiner::MergeScenes(&pScene,tempScene,attach,
-		AI_INT_MERGE_SCENE_GEN_UNIQUE_MATNAMES |
-		AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES);
+		AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES | (!configSpeedFlag ? (
+		AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY | AI_INT_MERGE_SCENE_GEN_UNIQUE_MATNAMES) : 0));
 
 
 	/*  If we have no meshes | no materials now set the INCOMPLETE
@@ -1639,11 +1638,6 @@ void IRRImporter::InternReadFile( const std::string& pFile,
 		pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
 	}
 
-
-	// transformation matrix to convert from IRRMESH to ASSIMP coordinates
-	pScene->mRootNode->mTransformation *= AI_TO_IRR_MATRIX;
-
-
 	/* Finished ... everything destructs automatically and all 
 	 * temporary scenes have already been deleted by MergeScenes()
 	 */

+ 9 - 3
code/IRRLoader.h

@@ -39,8 +39,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
 
 
-/** @file Declaration of the .irrMesh (Irrlight Engine Mesh Format)
-    importer class.
+/** @file IRRLoader.h
+ *  @brief Declaration of the .irrMesh (Irrlight Engine Mesh Format)
+ *  importer class.
  */
 #ifndef AI_IRRLOADER_H_INCLUDED
 #define AI_IRRLOADER_H_INCLUDED
@@ -75,7 +76,8 @@ public:
 	/** Returns whether the class can handle the format of the given file. 
 	 *  See BaseImporter::CanRead() for details.	
 	 */
-	bool CanRead( const std::string& pFile, IOSystem* pIOHandler) const;
+	bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
+		bool checkSig) const;
 
 protected:
 
@@ -203,6 +205,7 @@ private:
 
 		// Meshes: path to the mesh to be loaded
 		std::string meshPath;
+		unsigned int id;
 
 		// Meshes: List of materials to be assigned
 		// along with their corresponding material flags
@@ -297,8 +300,11 @@ private:
 
 private:
 
+	/** Configuration option: desired output FPS */
 	double fps;
 
+	/** Configuration option: speed flag was set? */
+	bool configSpeedFlag;
 };
 
 } // end of namespace Assimp

+ 12 - 13
code/IRRMeshLoader.cpp

@@ -67,26 +67,17 @@ IRRMeshImporter::~IRRMeshImporter()
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file. 
-bool IRRMeshImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
+bool IRRMeshImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
 {
 	/* NOTE: A simple check for the file extension is not enough
 	 * here. Irrmesh and irr are easy, but xml is too generic
 	 * and could be collada, too. So we need to open the file and
 	 * search for typical tokens.
 	 */
+	const std::string extension = GetExtension(pFile);
 
-	std::string::size_type pos = pFile.find_last_of('.');
-
-	// no file extension - can't read
-	if( pos == std::string::npos)
-		return false;
-
-	std::string extension = pFile.substr( pos);
-	for (std::string::iterator i = extension.begin(); i != extension.end();++i)
-		*i = ::tolower(*i);
-
-	if (extension == ".irrmesh")return true;
-	else if (extension == ".xml")
+	if (extension == "irrmesh")return true;
+	else if (extension == "xml" || checkSig)
 	{
 		/*  If CanRead() is called to check whether the loader
 		 *  supports a specific file extension in general we
@@ -99,6 +90,14 @@ bool IRRMeshImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) c
 	return false;
 }
 
+// ------------------------------------------------------------------------------------------------
+// Get a list of all file extensions which are handled by this class
+void IRRMeshImporter::GetExtensionList(std::string& append)
+{
+	// fixme: consider cleaner handling of xml extension
+	append.append("*.xml;*.irrmesh");
+}
+
 // ------------------------------------------------------------------------------------------------
 // Imports the given file into the given scene structure. 
 void IRRMeshImporter::InternReadFile( const std::string& pFile, 

+ 7 - 13
code/IRRMeshLoader.h

@@ -38,8 +38,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ----------------------------------------------------------------------
 */
 
-/** @file Declaration of the .irrMesh (Irrlight Engine Mesh Format)
-    importer class. */
+/** @file IRRMeshLoader.h
+ *  @brief Declaration of the .irrMesh (Irrlight Engine Mesh Format)
+ *  importer class. 
+ */
 #ifndef AI_IRRMESHLOADER_H_INCLUDED
 #define AI_IRRMESHLOADER_H_INCLUDED
 
@@ -72,7 +74,8 @@ public:
 	/** Returns whether the class can handle the format of the given file. 
 	 *  See BaseImporter::CanRead() for details.	
 	 */
-	bool CanRead( const std::string& pFile, IOSystem* pIOHandler) const;
+	bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
+		bool checkSig) const;
 
 protected:
 
@@ -80,16 +83,7 @@ protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 */
-	void GetExtensionList(std::string& append)
-	{
-
-		/*  NOTE: The file extenxsion .xml is too generic. We'll 
-		 *  need to open the file in CanRead() and check whether it is 
-		 *  a real irrlicht file
-		 */
-
-		append.append("*.xml;*.irrmesh");
-	}
+	void GetExtensionList(std::string& append);
 
 	// -------------------------------------------------------------------
 	/** Imports the given file into the given scene structure. 

+ 5 - 1
code/IRRShared.cpp

@@ -294,7 +294,11 @@ aiMaterial* IrrlichtBase::ParseMaterial(unsigned int& matFlags)
 					// material type (shader)
 					if (prop.name == "Type")
 					{
-						if (prop.value == "trans_vertex_alpha")
+						if (prop.value == "solid")
+						{
+							// default material ...
+						}
+						else if (prop.value == "trans_vertex_alpha")
 						{
 							matFlags = AI_IRRMESH_MAT_trans_vertex_alpha;
 						}

+ 130 - 29
code/Importer.cpp

@@ -43,6 +43,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #include "AssimpPCH.h"
 
+/* Uncomment this line to prevent Assimp from catching unknown exceptions.
+ *
+ * Note that any Exception except ImportErrorException may lead to 
+ * undefined behaviour -> loaders could remain in an unusable state and
+ * further imports with the same Importer instance could fail/crash/burn ...
+ */
+#define ASSIMP_CATCH_GLOBAL_EXCEPTIONS
+
 // =======================================================================================
 // Internal headers
 // =======================================================================================
@@ -135,6 +143,20 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #ifndef AI_BUILD_NO_TERRAGEN_IMPORTER
 #	include "TerragenLoader.h"
 #endif
+//#ifndef AI_BUILD_NO_CSM_IMPORTER
+//#	include "CSMLoader.h"
+//#endif
+#ifndef AI_BUILD_NO_3D_IMPORTER
+#	include "UnrealLoader.h"
+#endif
+
+
+
+
+#ifndef AI_BUILD_NO_LWS_IMPORTER
+#	include "LWSLoader.h"
+#endif
+
 
 // =======================================================================================
 // PostProcess-Steps
@@ -325,6 +347,18 @@ Importer::Importer()
 #if (!defined AI_BUILD_NO_TERRAGEN_IMPORTER)
 	mImporter.push_back( new TerragenImporter());
 #endif
+//#if (!defined AI_BUILD_NO_CSM_IMPORTER)
+//	mImporter.push_back( new CSMImporter());
+//#endif
+#if (!defined AI_BUILD_NO_3D_IMPORTER)
+	mImporter.push_back( new UnrealImporter());
+#endif
+
+
+
+#if (!defined AI_BUILD_NO_LWS_IMPORTER)
+	mImporter.push_back( new LWSImporter());
+#endif
 
 	// ======================================================================
 	// Add an instance of each post processing step here in the order 
@@ -333,10 +367,6 @@ Importer::Importer()
 	// ======================================================================
 	mPostProcessingSteps.reserve(25);
 
-#if (!defined AI_BUILD_NO_VALIDATEDS_PROCESS)
-	mPostProcessingSteps.push_back( new ValidateDSProcess()); 
-#endif
-
 #if (!defined AI_BUILD_NO_REMOVEVC_PROCESS)
 	mPostProcessingSteps.push_back( new RemoveVCProcess());
 #endif
@@ -548,10 +578,9 @@ bool Importer::IsDefaultIOHandler()
 	return mIsDefaultHandler;
 }
 
-#ifdef _DEBUG
 // ------------------------------------------------------------------------------------------------
 // Validate post process step flags 
-bool ValidateFlags(unsigned int pFlags)
+bool _ValidateFlags(unsigned int pFlags)
 {
 	if (pFlags & aiProcess_GenSmoothNormals &&
 		pFlags & aiProcess_GenNormals)
@@ -562,7 +591,6 @@ bool ValidateFlags(unsigned int pFlags)
 	}
 	return true;
 }
-#endif // ! DEBUG
 
 // ------------------------------------------------------------------------------------------------
 // Free the current scene
@@ -602,12 +630,50 @@ aiScene* Importer::GetOrphanedScene()
 	return s;
 }
 
+// ------------------------------------------------------------------------------------------------
+// Validate post-processing flags
+bool Importer::ValidateFlags(unsigned int pFlags)
+{
+	// run basic checks for mutually exclusive flags
+	if(!_ValidateFlags(pFlags))
+		return false;
+
+	// ValidateDS does not anymore occur in the pp list, it plays
+	// an awesome extra role ...
+#ifdef AI_BUILD_NO_VALIDATEDS_PROCESS
+	if (pFlags & aiProcess_ValidateDataStructure)
+		return false;
+#endif
+	pFlags &= ~aiProcess_ValidateDataStructure;
+
+	// Now iterate through all bits which are set in
+	// the flags and check whether we find at least
+	// one pp plugin which handles it.
+	for (unsigned int mask = 1; mask < (1 << (sizeof(unsigned int)*8-1));mask <<= 1) {
+		
+		if (pFlags & mask) {
+		
+			bool have = false;
+			for( unsigned int a = 0; a < mPostProcessingSteps.size(); a++)	{
+				if (mPostProcessingSteps[a]-> IsActive(mask) ) {
+				
+					have = true;
+					break;
+				}
+			}
+			if (!have)
+				return false;
+		}
+	}
+	return true;
+}
+
 // ------------------------------------------------------------------------------------------------
 // Reads the given file and returns its contents if successful. 
 const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags)
 {
-	// Validate the flags
-	ai_assert(ValidateFlags(pFlags));
+	// In debug builds, run a basic flag validation
+	ai_assert(_ValidateFlags(pFlags));
 
 	const std::string pFile(_pFile);
 
@@ -616,7 +682,9 @@ const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags)
 	// that might be thrown by STL containers or by new(). 
 	// ImportErrorException's are throw by ourselves and caught elsewhere.
 	// ======================================================================
+#ifdef ASSIMP_CATCH_GLOBAL_EXCEPTIONS
 	try
+#endif // ! ASSIMP_CATCH_GLOBAL_EXCEPTIONS
 	{
 		// Check whether this Importer instance has already loaded
 		// a scene. In this case we need to delete the old one
@@ -636,21 +704,35 @@ const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags)
 
 		// Find an worker class which can handle the file
 		BaseImporter* imp = NULL;
-		for( unsigned int a = 0; a < mImporter.size(); a++)
-		{
-			if( mImporter[a]->CanRead( pFile, mIOHandler))
-			{
+		for( unsigned int a = 0; a < mImporter.size(); a++)	{
+
+			if( mImporter[a]->CanRead( pFile, mIOHandler, false)) {
 				imp = mImporter[a];
 				break;
 			}
 		}
 
-		// Put a proper error message if no suitable importer was found
-		if( !imp)
+		if (!imp)
 		{
-			mErrorString = "No suitable reader found for the file format of file \"" + pFile + "\".";
-			DefaultLogger::get()->error(mErrorString);
-			return NULL;
+			// not so bad yet ... try format auto detection.
+			std::string::size_type s = pFile.find_last_of('.');
+			if (s != std::string::npos) {
+				DefaultLogger::get()->info("File extension now known, trying signature-based detection");
+				for( unsigned int a = 0; a < mImporter.size(); a++)	{
+
+					if( mImporter[a]->CanRead( pFile, mIOHandler, true)) {
+						imp = mImporter[a];
+						break;
+					}
+				}
+			}
+			// Put a proper error message if no suitable importer was found
+			if( !imp)
+			{
+				mErrorString = "No suitable reader found for the file format of file \"" + pFile + "\".";
+				DefaultLogger::get()->error(mErrorString);
+				return NULL;
+			}
 		}
 
 		// Dispatch the reading to the worker class for this format
@@ -661,7 +743,19 @@ const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags)
 		// If successful, apply all active post processing steps to the imported data
 		if( mScene)
 		{
-			// FIRST of all - preprocess the scene 
+#ifndef AI_BUILD_NO_VALIDATEDS_PROCESS
+			// The ValidateDS process is an exception. It is executed first,
+			// even before ScenePreprocessor is called.
+			if (pFlags & aiProcess_ValidateDataStructure)
+			{
+				ValidateDSProcess ds;
+				ds.ExecuteOnScene (this);
+				if (!mScene)
+					return NULL;
+			}
+#endif // no validation
+
+			// Preprocess the scene 
 			ScenePreprocessor pre(mScene);
 			pre.ProcessScene();
 
@@ -669,11 +763,11 @@ const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags)
 #ifdef _DEBUG
 			if (bExtraVerbose)
 			{
-#if (!defined AI_BUILD_NO_VALIDATEDS_PROCESS)
+#ifndef AI_BUILD_NO_VALIDATEDS_PROCESS
 
 				DefaultLogger::get()->error("Extra verbose mode not available, library"
 					" wasn't build with the ValidateDS-Step");
-#endif
+#endif  // no validation
 
 
 				pFlags |= aiProcess_ValidateDataStructure;
@@ -694,14 +788,16 @@ const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags)
 
 #ifndef AI_BUILD_NO_VALIDATEDS_PROCESS
 				continue;
-#endif
+#endif  // no validation
 
-				// if the extra verbose mode is active execute the
+				// If the extra verbose mode is active execute the
 				// VaidateDataStructureStep again after each step
-				if (bExtraVerbose && a)
+				if (bExtraVerbose)
 				{
 					DefaultLogger::get()->debug("Extra verbose: revalidating data structures");
-					((ValidateDSProcess*)mPostProcessingSteps[0])->ExecuteOnScene (this);
+					
+					ValidateDSProcess ds; 
+					ds.ExecuteOnScene (this);
 					if( !mScene)
 					{
 						DefaultLogger::get()->error("Extra verbose: failed to revalidate data structures");
@@ -712,11 +808,13 @@ const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags)
 			}
 		}
 		// if failed, extract the error string
-		else if( !mScene)mErrorString = imp->GetErrorText();
+		else if( !mScene)
+			mErrorString = imp->GetErrorText();
 
 		// clear any data allocated by post-process steps
 		mPPShared->Clean();
 	}
+#ifdef ASSIMP_CATCH_GLOBAL_EXCEPTIONS
 	catch (std::exception &e)
 	{
 #if (defined _MSC_VER) &&	(defined _CPPRTTI) 
@@ -729,6 +827,7 @@ const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags)
 		DefaultLogger::get()->error(mErrorString);
 		delete mScene;mScene = NULL;
 	}
+#endif // ! ASSIMP_CATCH_GLOBAL_EXCEPTIONS
 
 	// either successful or failure - the pointer expresses it anyways
 	return mScene;
@@ -750,7 +849,7 @@ BaseImporter* Importer::FindLoader (const char* _szExtension)
 		i != mImporter.end();++i)
 	{
 		// pass the file extension to the CanRead(..,NULL)-method
-		if ((*i)->CanRead(szExtension,NULL))return *i;
+		if ((*i)->CanRead(szExtension,NULL,false))return *i;
 	}
 	return NULL;
 }
@@ -765,8 +864,10 @@ void Importer::GetExtensionList(aiString& szOut)
 		i =  mImporter.begin();
 		i != mImporter.end();++i,++iNum)
 	{
-		// insert a comma as delimiter character
-		if (0 != iNum)
+		// Insert a comma as delimiter character
+		// FIX: to take lazy loader implementations into account, we are
+		// slightly more tolerant here than we'd need to be.
+		if (0 != iNum && ';' != *(tmp.end()-1))
 			tmp.append(";");
 
 		(*i)->GetExtensionList(tmp);

+ 80 - 60
code/ImproveCacheLocality.cpp

@@ -44,6 +44,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  * <br>
  * The algorithm is roughly basing on this paper:
  * http://www.cs.princeton.edu/gfx/pubs/Sander_2007_%3ETR/tipsy.pdf
+ *   ... TODO: implement overdraw reduction
  */
 
 #include "AssimpPCH.h"
@@ -54,16 +55,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 using namespace Assimp;
 
-#if _MSC_VER >= 1400
-#	define sprintf sprintf_s
-#endif
-
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
-ImproveCacheLocalityProcess::ImproveCacheLocalityProcess()
-{
-	// nothing to do here
-	configCacheDepth = 12; // hardcoded to 12 at the moment
+ImproveCacheLocalityProcess::ImproveCacheLocalityProcess() {
+	configCacheDepth = PP_ICL_PTCACHE_SIZE;
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -80,28 +75,48 @@ bool ImproveCacheLocalityProcess::IsActive( unsigned int pFlags) const
 	return (pFlags & aiProcess_ImproveCacheLocality) != 0;
 }
 
+// ------------------------------------------------------------------------------------------------
+// Setup configuration
+void ImproveCacheLocalityProcess::SetupProperties(const Importer* pImp)
+{
+	// AI_CONFIG_PP_ICL_PTCACHE_SIZE
+	configCacheDepth = pImp->GetPropertyInteger(AI_CONFIG_PP_ICL_PTCACHE_SIZE,PP_ICL_PTCACHE_SIZE);
+}
+
 // ------------------------------------------------------------------------------------------------
 // Executes the post processing step on the given imported data.
 void ImproveCacheLocalityProcess::Execute( aiScene* pScene)
 {
-	if (!pScene->mNumMeshes)
-	{
+	if (!pScene->mNumMeshes) {
 		DefaultLogger::get()->debug("ImproveCacheLocalityProcess skipped; there are no meshes");
 		return;
 	}
 
 	DefaultLogger::get()->debug("ImproveCacheLocalityProcess begin");
 
-	for( unsigned int a = 0; a < pScene->mNumMeshes; a++)
-	{
-		this->ProcessMesh( pScene->mMeshes[a],a);
+	float out = 0.f;
+	unsigned int numf = 0, numm = 0;
+	for( unsigned int a = 0; a < pScene->mNumMeshes; a++){
+		const float res = ProcessMesh( pScene->mMeshes[a],a);
+		if (res) {
+			numf += pScene->mMeshes[a]->mNumFaces;
+			out  += res;
+			++numm;
+		}
+	}
+	if (!DefaultLogger::isNullLogger()) {
+		char szBuff[128]; // should be sufficiently large in every case
+		::sprintf(szBuff,"Cache relevant are %i meshes (%i faces). Average output ACMR is %f",
+			numm,numf,out/numf);
+
+		DefaultLogger::get()->info(szBuff);
+		DefaultLogger::get()->debug("ImproveCacheLocalityProcess finished. ");
 	}
-	DefaultLogger::get()->debug("ImproveCacheLocalityProcess finished. ");
 }
 
 // ------------------------------------------------------------------------------------------------
 // Improves the cache coherency of a specific mesh
-void ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshNum)
+float ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshNum)
 {
 	ai_assert(NULL != pMesh);
 
@@ -109,52 +124,50 @@ void ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshN
 	// - there must be vertices and faces (haha)
 	// - all faces must be triangulated
 	if (!pMesh->HasFaces() || !pMesh->HasPositions())
-		return;
+		return 0.f;
 
-	if (pMesh->mPrimitiveTypes != aiPrimitiveType_TRIANGLE)
-	{
+	if (pMesh->mPrimitiveTypes != aiPrimitiveType_TRIANGLE)	{
 		DefaultLogger::get()->error("This algorithm works on triangle meshes only");
-		return;
+		return 0.f;
 	}
 
-	// find the input ACMR ...
-	unsigned int* piFIFOStack = new unsigned int[this->configCacheDepth];
-	::memset(piFIFOStack,0xff,this->configCacheDepth*sizeof(unsigned int));
-	unsigned int* piCur = piFIFOStack;
-	const unsigned int* const piCurEnd = piFIFOStack + this->configCacheDepth;
-
-	// count the number of cache misses
-	unsigned int iCacheMisses = 0;
-
+	float fACMR = 3.f;
 	const aiFace* const pcEnd = pMesh->mFaces+pMesh->mNumFaces;
-	for (const aiFace* pcFace = pMesh->mFaces;pcFace != pcEnd;++pcFace)
+
+	// Input ACMR is for logging purposes only
+	if (!DefaultLogger::isNullLogger()) 
 	{
-		for (unsigned int qq = 0; qq < 3;++qq)
+		unsigned int* piFIFOStack = new unsigned int[configCacheDepth];
+		::memset(piFIFOStack,0xff,configCacheDepth*sizeof(unsigned int));
+		unsigned int* piCur = piFIFOStack;
+		const unsigned int* const piCurEnd = piFIFOStack + configCacheDepth;
+
+		// count the number of cache misses
+		unsigned int iCacheMisses = 0;
+		for (const aiFace* pcFace = pMesh->mFaces;pcFace != pcEnd;++pcFace)
 		{
-			bool bInCache = false;
-			for (unsigned int* pp = piFIFOStack;pp < piCurEnd;++pp)
+			for (unsigned int qq = 0; qq < 3;++qq)
 			{
-				if (*pp == pcFace->mIndices[qq])
+				bool bInCache = false;
+				for (unsigned int* pp = piFIFOStack;pp < piCurEnd;++pp)
 				{
-					// the vertex is in cache
-					bInCache = true;
-					break;
+					if (*pp == pcFace->mIndices[qq])	{
+						// the vertex is in cache
+						bInCache = true;
+						break;
+					}
+				}
+				if (!bInCache)
+				{
+					++iCacheMisses;
+					if (piCurEnd == piCur)piCur = piFIFOStack;
+					*piCur++ = pcFace->mIndices[qq];
 				}
-			}
-			if (!bInCache)
-			{
-				++iCacheMisses;
-				if (piCurEnd == piCur)piCur = piFIFOStack;
-				*piCur++ = pcFace->mIndices[qq];
 			}
 		}
-	}
-	delete[] piFIFOStack;
-	float fACMR = (float)iCacheMisses / pMesh->mNumFaces;
-	if (3.0 == fACMR)
-	{
-		if (!DefaultLogger::isNullLogger())
-		{
+		delete[] piFIFOStack;
+		fACMR = (float)iCacheMisses / pMesh->mNumFaces;
+		if (3.0 == fACMR)	{
 			char szBuff[128]; // should be sufficiently large in every case
 
 			// the JoinIdenticalVertices process has not been executed on this
@@ -162,8 +175,8 @@ void ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshN
 			// smaller than 3.0 ...
 			::sprintf(szBuff,"Mesh %i: Not suitable for vcache optimization",meshNum);
 			DefaultLogger::get()->warn(szBuff);
+			return 0.f;
 		}
-		return;
 	}
 
 	// first we need to build a vertex-triangle adjacency list
@@ -204,7 +217,7 @@ void ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshN
 			iMaxRefTris = std::max(iMaxRefTris,*piCur);
 	}
 	unsigned int* piCandidates = new unsigned int[iMaxRefTris*3];
-	iCacheMisses = 0;
+	unsigned int iCacheMisses = 0;
 
 	/** PSEUDOCODE for the algorithm
 
@@ -236,7 +249,7 @@ void ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshN
 
 	int ivdx = 0;
 	int ics = 1;
-	int iStampCnt = this->configCacheDepth+1;
+	int iStampCnt = configCacheDepth+1;
 	while (ivdx >= 0)
 	{
 		unsigned int icnt = piNumTriPtrNoModify[ivdx]; 
@@ -274,7 +287,7 @@ void ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshN
 					*piCSIter++ = dp;
 
 					// if the vertex is not yet in cache, set its cache count
-					if (iStampCnt-piCachingStamps[dp] > this->configCacheDepth)
+					if (iStampCnt-piCachingStamps[dp] > configCacheDepth)
 					{
 						piCachingStamps[dp] = iStampCnt++;
 						++iCacheMisses;
@@ -302,7 +315,7 @@ void ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshN
 
 				// will the vertex be in cache, even after fanning occurs?
 				unsigned int tmp;
-				if ((tmp = iStampCnt-piCachingStamps[dp]) + 2*piNumTriPtr[dp] <= this->configCacheDepth)
+				if ((tmp = iStampCnt-piCachingStamps[dp]) + 2*piNumTriPtr[dp] <= configCacheDepth)
 					priority = tmp;
 				// keep best candidate
 				if (priority > max_priority)
@@ -344,18 +357,24 @@ void ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshN
 			}
 		}
 	}
+	float fACMR2 = 0.0f;
 	if (!DefaultLogger::isNullLogger())
 	{
-		char szBuff[128]; // should be sufficiently large in every case
-		float fACMR2 = (float)iCacheMisses / pMesh->mNumFaces;
+		fACMR2 = (float)iCacheMisses / pMesh->mNumFaces;
 
-		::sprintf(szBuff,"Mesh %i | ACMR in: %f out: %f | ~%.1f%%",meshNum,fACMR,fACMR2,
-			((fACMR - fACMR2) / fACMR) * 100.f);
-		DefaultLogger::get()->info(szBuff);
+		// very intense verbose logging ... prepare for much text if there are many meshes
+		if ( DefaultLogger::get()->getLogSeverity() == Logger::VERBOSE) {
+			char szBuff[128]; // should be sufficiently large in every case
+
+			::sprintf(szBuff,"Mesh %i | ACMR in: %f out: %f | ~%.1f%%",meshNum,fACMR,fACMR2,
+				((fACMR - fACMR2) / fACMR) * 100.f);
+			DefaultLogger::get()->debug(szBuff);
+		}
+
+		fACMR2 *= pMesh->mNumFaces;
 	}
 	// sort the output index buffer back to the input array
 	piCSIter = piIBOutput;
-
 	for (aiFace* pcFace = pMesh->mFaces; pcFace != pcEnd;++pcFace)
 	{
 		pcFace->mIndices[0] = *piCSIter++;
@@ -368,4 +387,5 @@ void ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshN
 	delete[] piIBOutput;
 	delete[] piCandidates;
 	delete[] piNumTriPtrNoModify;
+	return fACMR2;
 }

+ 9 - 11
code/ImproveCacheLocality.h

@@ -70,30 +70,28 @@ protected:
 	~ImproveCacheLocalityProcess();
 
 public:
+
 	// -------------------------------------------------------------------
-	/** Returns whether the processing step is present in the given flag field.
-	 * @param pFlags The processing flags the importer was called with. A bitwise
-	 *   combination of #aiPostProcessSteps.
-	 * @return true if the process is present in this flag fields, false if not.
-	*/
+	// Check whether the pp step is active
 	bool IsActive( unsigned int pFlags) const;
 
 	// -------------------------------------------------------------------
-	/** Executes the post processing step on the given imported data.
-	* At the moment a process is not supposed to fail.
-	* @param pScene The imported data to work at.
-	*/
+	// Executes the pp step on a given scene
 	void Execute( aiScene* pScene);
 
+	// -------------------------------------------------------------------
+	// Configures the pp step
+	void SetupProperties(const Importer* pImp);
+
 protected:
 	// -------------------------------------------------------------------
 	/** Executes the postprocessing step on the given mesh
 	 * @param pMesh The mesh to process.
 	 * @param meshNum Index of the mesh to process
 	 */
-	void ProcessMesh( aiMesh* pMesh, unsigned int meshNum);
-
+	float ProcessMesh( aiMesh* pMesh, unsigned int meshNum);
 
+private:
 	//! Configuration parameter: specifies the size of the cache to
 	//! optimize the vertex data for.
 	unsigned int configCacheDepth;

+ 109 - 76
code/JoinVerticesProcess.cpp

@@ -51,9 +51,15 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 using namespace Assimp;
 
-#if _MSC_VER >= 1400
-#	define sprintf sprintf_s
-#endif
+// Data structure to keep a vertex in an interlaced format
+struct Vertex
+{
+	aiVector3D mPosition;
+	aiVector3D mNormal;
+	aiVector3D mTangent, mBitangent;
+	aiColor4D  mColors     [AI_MAX_NUMBER_OF_COLOR_SETS];
+	aiVector3D mTexCoords  [AI_MAX_NUMBER_OF_TEXTURECOORDS];
+};
 
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
@@ -75,7 +81,6 @@ bool JoinVerticesProcess::IsActive( unsigned int pFlags) const
 {
 	return (pFlags & aiProcess_JoinIdenticalVertices) != 0;
 }
-
 // ------------------------------------------------------------------------------------------------
 // Executes the post processing step on the given imported data.
 void JoinVerticesProcess::Execute( aiScene* pScene)
@@ -84,18 +89,19 @@ void JoinVerticesProcess::Execute( aiScene* pScene)
 
 	// get the total number of vertices BEFORE the step is executed
 	int iNumOldVertices = 0;
-	for( unsigned int a = 0; a < pScene->mNumMeshes; a++)
-	{
-		iNumOldVertices +=	pScene->mMeshes[a]->mNumVertices;
+	if (!DefaultLogger::isNullLogger()) {
+		for( unsigned int a = 0; a < pScene->mNumMeshes; a++)	{
+			iNumOldVertices +=	pScene->mMeshes[a]->mNumVertices;
+		}
 	}
 
 	// execute the step
 	int iNumVertices = 0;
-	for( unsigned int a = 0; a < pScene->mNumMeshes; a++)
-	{
-		iNumVertices +=	this->ProcessMesh( pScene->mMeshes[a],a);
+	for( unsigned int a = 0; a < pScene->mNumMeshes; a++)	{
+		iNumVertices +=	ProcessMesh( pScene->mMeshes[a],a);
 	}
-	// if logging is active, print detailled statistics
+
+	// if logging is active, print detailed statistics
 	if (!DefaultLogger::isNullLogger())
 	{
 		if (iNumOldVertices == iNumVertices)DefaultLogger::get()->debug("JoinVerticesProcess finished ");
@@ -117,81 +123,103 @@ void JoinVerticesProcess::Execute( aiScene* pScene)
 // Unites identical vertices in the given mesh
 int JoinVerticesProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex)
 {
-	// helper structure to hold all the data a single vertex can possibly have
-	typedef struct Vertex vertex;
+	BOOST_STATIC_ASSERT( AI_MAX_NUMBER_OF_COLOR_SETS    == 4);
+	BOOST_STATIC_ASSERT( AI_MAX_NUMBER_OF_TEXTURECOORDS == 4);
 
+	// Return early if we don't have any positions
 	if (!pMesh->HasPositions() || !pMesh->HasFaces())
 		return 0;
-	
-	struct Vertex
-	{
-		aiVector3D mPosition;
-		aiVector3D mNormal;
-		aiVector3D mTangent, mBitangent;
-		aiColor4D mColors[AI_MAX_NUMBER_OF_COLOR_SETS];
-		aiVector3D mTexCoords[AI_MAX_NUMBER_OF_TEXTURECOORDS];
-	};
+
+	// We'll never have more vertices afterwards.
 	std::vector<Vertex> uniqueVertices;
 	uniqueVertices.reserve( pMesh->mNumVertices);
 
-	//unsigned int iOldVerts = pMesh->mNumVertices;
-
 	// For each vertex the index of the vertex it was replaced by. 
 	std::vector<unsigned int> replaceIndex( pMesh->mNumVertices, 0xffffffff);
 	// for each vertex whether it was replaced by an existing unique vertex (true) or a new vertex was created for it (false)
 	std::vector<bool> isVertexUnique( pMesh->mNumVertices, false);
 
-	// a little helper to find locally close vertices faster
+	// A little helper to find locally close vertices faster
 	// FIX: check whether we can reuse the SpatialSort of a previous step
-	const float epsilon = 1e-5f;
-	float posEpsilon;
-	SpatialSort* vertexFinder = NULL;
+	const static float epsilon = 1e-5f;
+	float posEpsilonSqr;
+	SpatialSort*  vertexFinder = NULL;
 	SpatialSort  _vertexFinder;
 	if (shared)
 	{
 		std::vector<std::pair<SpatialSort,float> >* avf;
 		shared->GetProperty(AI_SPP_SPATIAL_SORT,avf);
-		if (avf)
-		{
+		if (avf)	{
 			std::pair<SpatialSort,float>& blubb = avf->operator [] (meshIndex);
-			vertexFinder = &blubb.first;
-			posEpsilon = blubb.second;
+			vertexFinder  = &blubb.first;
+			posEpsilonSqr = blubb.second;
 		}
 	}
-	if (!vertexFinder)
-	{
+	if (!vertexFinder)	{
 		_vertexFinder.Fill(pMesh->mVertices, pMesh->mNumVertices, sizeof( aiVector3D));
 		vertexFinder = &_vertexFinder;
-		posEpsilon = ComputePositionEpsilon(pMesh);
+		posEpsilonSqr = ComputePositionEpsilon(pMesh);
 	}
 
 	// squared because we check against squared length of the vector difference
-	const float squareEpsilon = epsilon * epsilon;
-	std::vector<unsigned int> verticesFound;
+	static const float squareEpsilon = epsilon * epsilon;
 
-	// now check each vertex if it brings something new to the table
+	// again, better waste some bytes than a realloc ...
+	std::vector<unsigned int> verticesFound;
+	verticesFound.reserve(10);
+
+	// run an optimized code path if we don't have multiple UVs or vertex colors
+	const bool complex = (
+		pMesh->mTextureCoords[1]	||
+		pMesh->mTextureCoords[2]	|| 
+		pMesh->mTextureCoords[3]	||
+		pMesh->mColors[0]			|| 
+		pMesh->mColors[1]			|| 
+		pMesh->mColors[2]			||
+		pMesh->mColors[3] );
+
+	// Now check each vertex if it brings something new to the table
 	for( unsigned int a = 0; a < pMesh->mNumVertices; a++)
 	{
 		// collect the vertex data
 		Vertex v;
 		v.mPosition = pMesh->mVertices[a];
-		v.mNormal = (pMesh->mNormals != NULL) ? pMesh->mNormals[a] : aiVector3D( 0, 0, 0);
-		v.mTangent = (pMesh->mTangents != NULL) ? pMesh->mTangents[a] : aiVector3D( 0, 0, 0);
-		v.mBitangent = (pMesh->mBitangents != NULL) ? pMesh->mBitangents[a] : aiVector3D( 0, 0, 0);
-		for( unsigned int b = 0; b < AI_MAX_NUMBER_OF_COLOR_SETS; b++)
-			v.mColors[b] = (pMesh->mColors[b] != NULL) ? pMesh->mColors[b][a] : aiColor4D( 0, 0, 0, 0);
-		for( unsigned int b = 0; b < AI_MAX_NUMBER_OF_TEXTURECOORDS; b++)
-			v.mTexCoords[b] = (pMesh->mTextureCoords[b] != NULL) ? pMesh->mTextureCoords[b][a] : aiVector3D( 0, 0, 0);
+
+		if (pMesh->mNormals)
+			v.mNormal = pMesh->mNormals[a];
+		if (pMesh->mTangents)
+			v.mTangent = pMesh->mTangents[a];
+		if (pMesh->mBitangents)
+			v.mBitangent = pMesh->mBitangents[a];
+
+		if (pMesh->mColors[0]) { // manually unrolled here
+			v.mColors[0] = pMesh->mColors[0][a];
+			if (pMesh->mColors[1]) {
+				v.mColors[1] = pMesh->mColors[1][a];
+				if (pMesh->mColors[2]) {
+					v.mColors[2] = pMesh->mColors[2][a];
+					if (pMesh->mColors[3]) {
+						v.mColors[3] = pMesh->mColors[3][a];
+		}}}}
+		if (pMesh->mTextureCoords[0]) { // manually unrolled here
+			v.mTexCoords[0] = pMesh->mTextureCoords[0][a];
+			if (pMesh->mTextureCoords[1]) {
+				v.mTexCoords[1] = pMesh->mTextureCoords[1][a];
+				if (pMesh->mTextureCoords[2]) {
+					v.mTexCoords[2] = pMesh->mTextureCoords[2][a];
+					if (pMesh->mTextureCoords[3]) {
+						v.mTexCoords[3] = pMesh->mTextureCoords[3][a];
+		}}}}
 
 		// collect all vertices that are close enough to the given position
-		vertexFinder->FindPositions( v.mPosition, posEpsilon, verticesFound);
+		vertexFinder->FindPositions( v.mPosition, posEpsilonSqr, verticesFound);
 
 		unsigned int matchIndex = 0xffffffff;
 		// check all unique vertices close to the position if this vertex is already present among them
 		for( unsigned int b = 0; b < verticesFound.size(); b++)
 		{
-			unsigned int vidx = verticesFound[b];
-			unsigned int uidx = replaceIndex[ vidx];
+			const unsigned int vidx = verticesFound[b];
+			const unsigned int uidx = replaceIndex[ vidx];
 			if( uidx == 0xffffffff || !isVertexUnique[ vidx])
 				continue;
 
@@ -201,33 +229,38 @@ int JoinVerticesProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex)
 			// We just test the other attributes even if they're not present in the mesh.
 			// In this case they're initialized to 0 so the comparision succeeds. 
 			// By this method the non-present attributes are effectively ignored in the comparision.
-			
 			if( (uv.mNormal - v.mNormal).SquareLength() > squareEpsilon)
 				continue;
 			if( (uv.mTangent - v.mTangent).SquareLength() > squareEpsilon)
 				continue;
 			if( (uv.mBitangent - v.mBitangent).SquareLength() > squareEpsilon)
 				continue;
-			// manually unrolled because continue wouldn't work as desired in an inner loop
-			BOOST_STATIC_ASSERT(4 == AI_MAX_NUMBER_OF_COLOR_SETS);
-			if( GetColorDifference( uv.mColors[0], v.mColors[0]) > squareEpsilon)
-				continue;
-			if( GetColorDifference( uv.mColors[1], v.mColors[1]) > squareEpsilon)
-				continue;
-			if( GetColorDifference( uv.mColors[2], v.mColors[2]) > squareEpsilon)
-				continue;
-			if( GetColorDifference( uv.mColors[3], v.mColors[3]) > squareEpsilon)
-				continue;
-			// texture coord matching manually unrolled as well
-			BOOST_STATIC_ASSERT(4 == AI_MAX_NUMBER_OF_TEXTURECOORDS);
+
 			if( (uv.mTexCoords[0] - v.mTexCoords[0]).SquareLength() > squareEpsilon)
 				continue;
-			if( (uv.mTexCoords[1] - v.mTexCoords[1]).SquareLength() > squareEpsilon)
-				continue;
-			if( (uv.mTexCoords[2] - v.mTexCoords[2]).SquareLength() > squareEpsilon)
-				continue;
-			if( (uv.mTexCoords[3] - v.mTexCoords[3]).SquareLength() > squareEpsilon)
-				continue;
+
+			// Usually we won't have vertex colors or multiple UVs, so we can skip from here
+			// Actually this increases runtime performance slightly.
+			if (complex) 
+			{
+				// manually unrolled because continue wouldn't work as desired in an inner loop
+				if( GetColorDifference( uv.mColors[0], v.mColors[0]) > squareEpsilon)
+					continue;
+				if( GetColorDifference( uv.mColors[1], v.mColors[1]) > squareEpsilon)
+					continue;
+				if( GetColorDifference( uv.mColors[2], v.mColors[2]) > squareEpsilon)
+					continue;
+				if( GetColorDifference( uv.mColors[3], v.mColors[3]) > squareEpsilon)
+					continue;
+
+				// texture coord matching manually unrolled as well
+				if( (uv.mTexCoords[1] - v.mTexCoords[1]).SquareLength() > squareEpsilon)
+					continue;
+				if( (uv.mTexCoords[2] - v.mTexCoords[2]).SquareLength() > squareEpsilon)
+					continue;
+				if( (uv.mTexCoords[3] - v.mTexCoords[3]).SquareLength() > squareEpsilon)
+					continue;
+			}
 
 			// we're still here -> this vertex perfectly matches our given vertex
 			matchIndex = uidx;
@@ -250,24 +283,26 @@ int JoinVerticesProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex)
 		}
 	}
 
-	if (!DefaultLogger::isNullLogger())
+	if (!DefaultLogger::isNullLogger() && DefaultLogger::get()->getLogSeverity() == Logger::VERBOSE)
 	{
 		char szBuff[128]; // should be sufficiently large in every case
-		sprintf(szBuff,"Mesh %i | Verts in: %i out: %i | ~%.1f%%",
+		::sprintf(szBuff,"Mesh %i | Verts in: %i out: %i | ~%.1f%%",
 			meshIndex,
 			pMesh->mNumVertices,
 			(int)uniqueVertices.size(),
 			((pMesh->mNumVertices - uniqueVertices.size()) / (float)pMesh->mNumVertices) * 100.f);
-		DefaultLogger::get()->info(szBuff);
+		DefaultLogger::get()->debug(szBuff);
 	}
 
 	// replace vertex data with the unique data sets
 	pMesh->mNumVertices = (unsigned int)uniqueVertices.size();
+
 	// Position
 	delete [] pMesh->mVertices;
 	pMesh->mVertices = new aiVector3D[pMesh->mNumVertices];
 	for( unsigned int a = 0; a < pMesh->mNumVertices; a++)
 		pMesh->mVertices[a] = uniqueVertices[a].mPosition;
+
 	// Normals, if present
 	if( pMesh->mNormals)
 	{
@@ -296,7 +331,7 @@ int JoinVerticesProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex)
 	for( unsigned int a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; a++)
 	{
 		if( !pMesh->mColors[a])
-			continue;
+			break;
 
 		delete [] pMesh->mColors[a];
 		pMesh->mColors[a] = new aiColor4D[pMesh->mNumVertices];
@@ -307,7 +342,7 @@ int JoinVerticesProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex)
 	for( unsigned int a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; a++)
 	{
 		if( !pMesh->mTextureCoords[a])
-			continue;
+			break;
 
 		delete [] pMesh->mTextureCoords[a];
 		pMesh->mTextureCoords[a] = new aiVector3D[pMesh->mNumVertices];
@@ -319,10 +354,8 @@ int JoinVerticesProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex)
 	for( unsigned int a = 0; a < pMesh->mNumFaces; a++)
 	{
 		aiFace& face = pMesh->mFaces[a];
-		for( unsigned int b = 0; b < face.mNumIndices; b++)
-		{
-			const size_t index = face.mIndices[b];
-			face.mIndices[b] = replaceIndex[index];
+		for( unsigned int b = 0; b < face.mNumIndices; b++)	{
+			face.mIndices[b] = replaceIndex[face.mIndices[b]];
 		}
 	}
 

+ 2 - 0
code/JoinVerticesProcess.h

@@ -95,6 +95,8 @@ protected:
 	 * @param meshIndex Index of the mesh to process
 	 */
 	int ProcessMesh( aiMesh* pMesh, unsigned int meshIndex);
+
+private:
 };
 
 } // end of namespace Assimp

+ 567 - 0
code/LWOAnimation.cpp

@@ -0,0 +1,567 @@
+/*
+Open Asset Import Library (ASSIMP)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2008, ASSIMP Development Team
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms, 
+with or without modification, are permitted provided that the 
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the ASSIMP team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the ASSIMP Development Team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file  LWOAnimation.cpp
+ *  @brief LWOAnimationResolver utility class 
+ *
+ *  It's a very generic implementation of LightWave's system of
+ *  componentwise-animated stuff. The one and only fully free
+ *  implementation of LightWave envelopes of which I know.
+*/
+
+#include "AssimpPCH.h"
+#if (!defined ASSIMP_BUILD_NO_LWO_IMPORTER) && (!defined ASSIMP_BUILD_NO_LWS_IMPORTER)
+
+// internal headers
+#include "LWOFileData.h"
+
+using namespace Assimp;
+using namespace Assimp::LWO;
+
+// ------------------------------------------------------------------------------------------------
+// Construct an animation resolver from a given list of envelopes
+AnimResolver::AnimResolver(std::list<Envelope>& _envelopes,double tick)
+	: envelopes   (_envelopes)
+	, sample_rate (0.)
+{
+	trans_x = trans_y = trans_z = NULL;
+	rotat_x = rotat_y = rotat_z = NULL;
+	scale_x = scale_y = scale_z = NULL;
+
+	first = last = 150392.;
+
+	// find transformation envelopes
+	for (std::list<LWO::Envelope>::iterator it = envelopes.begin(); it != envelopes.end(); ++it) {
+
+		(*it).old_first = 0;
+		(*it).old_last  = (*it).keys.size()-1;
+
+		if ((*it).keys.empty()) continue;
+		switch ((*it).type) {
+
+			// translation
+			case LWO::EnvelopeType_Position_X:
+				trans_x = &*it;break;
+			case LWO::EnvelopeType_Position_Y:
+				trans_y = &*it;break;
+			case LWO::EnvelopeType_Position_Z:
+				trans_z = &*it;break;
+
+				// rotation
+			case LWO::EnvelopeType_Rotation_Heading:
+				rotat_x = &*it;break;
+			case LWO::EnvelopeType_Rotation_Pitch:
+				rotat_y = &*it;break;
+			case LWO::EnvelopeType_Rotation_Bank:
+				rotat_z = &*it;break;
+
+				// scaling
+			case LWO::EnvelopeType_Scaling_X:
+				scale_x = &*it;break;
+			case LWO::EnvelopeType_Scaling_Y:
+				scale_y = &*it;break;
+			case LWO::EnvelopeType_Scaling_Z:
+				scale_z = &*it;break;
+			default:
+				continue;
+		};
+
+		// convert from seconds to ticks
+		for (std::vector<LWO::Key>::iterator d = (*it).keys.begin(); d != (*it).keys.end(); ++d)
+			(*d).time *= tick;
+
+		// set default animation range (minimum and maximum time value for which we have a keyframe)
+		first = std::min(first, (*it).keys.front().time );
+		last  = std::max(last,  (*it).keys.back().time );
+	}
+
+	// deferred setup of animation range to increase performance.
+	// typically the application will want to specify its own.
+	need_to_setup = true;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reset all envelopes to their original contents
+void AnimResolver::ClearAnimRangeSetup()
+{
+	for (std::list<LWO::Envelope>::iterator it = envelopes.begin(); it != envelopes.end(); ++it) {
+		
+		(*it).keys.erase((*it).keys.begin(),(*it).keys.begin()+(*it).old_first);
+		(*it).keys.erase((*it).keys.begin()+(*it).old_last+1,(*it).keys.end());
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+// Insert additional keys to match LWO's pre& post behaviours.
+void AnimResolver::UpdateAnimRangeSetup()
+{
+	for (std::list<LWO::Envelope>::iterator it = envelopes.begin(); it != envelopes.end(); ++it) {
+		if ((*it).keys.empty()) continue;
+	
+		const double my_first = (*it).keys.front().time;
+		const double my_last  = (*it).keys.back().time;
+
+		const double delta = my_last-my_first;
+		const size_t old_size = (*it).keys.size();
+
+		const float value_delta = (*it).keys.back().value - (*it).keys.front().value; 
+
+		// NOTE: We won't handle reset, linear and constant here.
+		// See DoInterpolation() for their implementation.
+
+		// process pre behaviour
+		switch ((*it).pre) {
+			case LWO::PrePostBehaviour_OffsetRepeat:
+			case LWO::PrePostBehaviour_Repeat:
+			case LWO::PrePostBehaviour_Oscillate:
+
+				const double start_time = delta - fmod(my_first-first,delta);
+				std::vector<LWO::Key>::iterator n = std::find_if((*it).keys.begin(),(*it).keys.end(), 
+					std::bind1st(std::greater<double>(),start_time)),m;
+
+				size_t ofs = 0;
+				if (n != (*it).keys.end()) {
+					// copy from here - don't use iterators, insert() would invalidate them
+					ofs = (*it).keys.end()-n;
+					(*it).keys.insert((*it).keys.begin(),ofs,LWO::Key());
+
+					std::copy((*it).keys.end()-ofs,(*it).keys.end(),(*it).keys.begin());
+				}
+
+				// do full copies. again, no iterators
+				const unsigned int num = (unsigned int)((my_first-first) / delta);
+				(*it).keys.resize((*it).keys.size() + num*old_size);
+
+				n = (*it).keys.begin()+ofs;
+				bool reverse = false;
+				for (unsigned int i = 0; i < num; ++i) {
+					m = n+old_size*(i+1);
+					std::copy(n,n+old_size,m);
+
+					if ((*it).pre == LWO::PrePostBehaviour_Oscillate && (reverse = !reverse))
+						std::reverse(m,m+old_size-1);
+				}
+
+				// update time values 
+				n = (*it).keys.end() - (old_size+1);
+				double cur_minus = delta;
+				unsigned int tt = 1;
+				for (const double tmp =  delta*(num+1);cur_minus <= tmp;cur_minus += delta,++tt) {
+					m = (delta == tmp ? (*it).keys.begin() :  n - (old_size+1));
+					for (;m != n; --n) {
+						(*n).time -= cur_minus;
+					
+						// offset repeat? add delta offset to key value
+						if ((*it).pre == LWO::PrePostBehaviour_OffsetRepeat) {
+							(*n).value += tt * value_delta;
+						}
+					}
+				}
+				break;
+		}
+
+		// process post behaviour
+		switch ((*it).post) {
+			
+			case LWO::PrePostBehaviour_OffsetRepeat:
+			case LWO::PrePostBehaviour_Repeat:
+			case LWO::PrePostBehaviour_Oscillate:
+
+				break;
+		}
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+// Extract bind pose matrix
+void AnimResolver::ExtractBindPose(aiMatrix4x4& out)
+{
+	// If we have no envelopes, return identity
+	if (envelopes.empty()) {
+		out = aiMatrix4x4();
+		return;
+	}
+	aiVector3D angles, scaling(1.f,1.f,1.f), translation;
+
+	if (trans_x) translation.x = trans_x->keys[0].value;
+	if (trans_y) translation.y = trans_y->keys[0].value;
+	if (trans_z) translation.z = trans_z->keys[0].value;
+
+	if (rotat_x) angles.x = rotat_x->keys[0].value;
+	if (rotat_y) angles.y = rotat_y->keys[0].value;
+	if (rotat_z) angles.z = rotat_z->keys[0].value;
+
+	if (scale_x) scaling.x = scale_x->keys[0].value;
+	if (scale_y) scaling.y = scale_y->keys[0].value;
+	if (scale_z) scaling.z = scale_z->keys[0].value;
+
+	// build the final matrix
+	aiMatrix4x4 s,r,t;
+	
+	r.FromEulerAnglesXYZ(angles);
+	//aiMatrix4x4::RotationY(angles.y,r);
+	// fixme: make FromEulerAngles static, too
+	aiMatrix4x4::Translation(translation,t);
+	aiMatrix4x4::Scaling(scaling,s);
+	out = s*r*t;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Do a single interpolation on a channel 
+void AnimResolver::DoInterpolation(std::vector<LWO::Key>::const_iterator cur, 
+	LWO::Envelope* envl,double time, float& fill)
+{
+	if (envl->keys.size() == 1) {
+		fill = envl->keys[0].value;
+		return;
+	}
+
+	// check whether we're at the beginning of the animation track
+	if (cur == envl->keys.begin()) {
+	
+		// ok ... this depends on pre behaviour now
+		// we don't need to handle repeat&offset repeat&oszillate here, see UpdateAnimRangeSetup()
+		switch (envl->pre)
+		{
+		case LWO::PrePostBehaviour_Linear:
+			DoInterpolation2(cur,cur+1,time,fill);
+			return;
+
+		case LWO::PrePostBehaviour_Reset:
+			fill = 0.f;
+			return;
+
+		default : //case LWO::PrePostBehaviour_Constant:
+			fill = (*cur).value;
+			return;
+		}
+	}
+	// check whether we're at the end of the animation track
+	else if (cur == envl->keys.end()-1 && time > envl->keys.rbegin()->time) {
+		// ok ... this depends on post behaviour now
+		switch (envl->post)
+		{
+		case LWO::PrePostBehaviour_Linear:
+			DoInterpolation2(cur,cur-1,time,fill);
+			return;
+
+		case LWO::PrePostBehaviour_Reset:
+			fill = 0.f;
+			return;
+
+		default : //case LWO::PrePostBehaviour_Constant:
+			fill = (*cur).value;
+			return;
+		}
+	}
+
+	// Otherwise do a simple interpolation
+	DoInterpolation2(cur-1,cur,time,fill);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Almost the same, except we won't handle pre/post conditions here
+void AnimResolver::DoInterpolation2(std::vector<LWO::Key>::const_iterator beg, 
+	std::vector<LWO::Key>::const_iterator end,double time, float& fill)
+{
+	switch ((*end).inter) {
+		
+		case LWO::IT_STEP:
+			// no interpolation at all - take the value of the last key
+			fill = (*beg).value;
+			return;
+
+	}
+	// linear interpolation - default
+	fill = (*beg).value + ((*end).value - (*beg).value)*(float)(((time - (*beg).time) / ((*end).time - (*beg).time)));
+}
+
+// ------------------------------------------------------------------------------------------------
+// Subsample animation track by given key values
+void AnimResolver::SubsampleAnimTrack(std::vector<aiVectorKey>& out,
+	double time,double sample_delta)
+{
+	ai_assert(!out.empty() && sample_delta);
+
+	const double time_start = out.back().mTime;
+//	for ()
+}
+
+// ------------------------------------------------------------------------------------------------
+// Track interpolation
+void AnimResolver::InterpolateTrack(std::vector<aiVectorKey>& out,aiVectorKey& fill,double time)
+{
+	// subsample animation track?
+	if (flags & AI_LWO_ANIM_FLAG_SAMPLE_ANIMS) {
+		SubsampleAnimTrack(out,time, sample_delta);
+	}
+
+	fill.mTime = time;
+
+	// get x
+	if ((*cur_x).time == time) {
+		fill.mValue.x = (*cur_x).value;
+
+		if (cur_x != envl_x->keys.end()-1) /* increment x */
+			++cur_x;
+		else end_x = true;
+	}
+	else DoInterpolation(cur_x,envl_x,time,(float&)fill.mValue.x);
+
+	// get y
+	if ((*cur_y).time == time) {
+		fill.mValue.y = (*cur_y).value;
+
+		if (cur_y != envl_y->keys.end()-1) /* increment y */
+			++cur_y;
+		else end_y = true;
+	}
+	else DoInterpolation(cur_y,envl_y,time,(float&)fill.mValue.y);
+
+	// get z
+	if ((*cur_z).time == time) {
+		fill.mValue.z = (*cur_z).value;
+
+		if (cur_z != envl_z->keys.end()-1) /* increment z */
+			++cur_z;
+		else end_x = true;
+	}
+	else DoInterpolation(cur_z,envl_z,time,(float&)fill.mValue.z);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Build linearly subsampled keys from three single envelopes, one for each component (x,y,z)
+void AnimResolver::GetKeys(std::vector<aiVectorKey>& out, 
+	LWO::Envelope* _envl_x,
+	LWO::Envelope* _envl_y,
+	LWO::Envelope* _envl_z,
+	unsigned int _flags)
+{
+	envl_x = _envl_x;
+	envl_y = _envl_y;
+	envl_z = _envl_z;
+	flags  = _flags;
+
+	// generate default channels if none are given
+	LWO::Envelope def_x, def_y, def_z;
+	LWO::Key key_dummy;
+	key_dummy.time = 0.f;
+	if (envl_x && envl_x->type == LWO::EnvelopeType_Scaling_X ||
+		envl_y && envl_y->type == LWO::EnvelopeType_Scaling_Y || 
+		envl_z && envl_z->type == LWO::EnvelopeType_Scaling_Z) {
+		key_dummy.value = 1.f;
+	}
+	else key_dummy.value = 0.f;
+
+	if (!envl_x) {
+		envl_x = &def_x;
+		envl_x->keys.push_back(key_dummy);
+	}
+	if (!envl_y) {
+		envl_y = &def_y;
+		envl_y->keys.push_back(key_dummy);
+	}
+	if (!envl_z) {
+		envl_z = &def_z;
+		envl_z->keys.push_back(key_dummy);
+	}
+
+	// guess how many keys we'll get
+	size_t reserve;
+	double sr = 1.;
+	if (flags & AI_LWO_ANIM_FLAG_SAMPLE_ANIMS) {
+		if (!sample_rate)
+			sr = 100.f;
+		else sr = sample_rate;
+		sample_delta = 1.f / sr; 
+
+		reserve = (size_t)(
+			std::max( envl_x->keys.end()->time,
+			std::max( envl_y->keys.end()->time, envl_z->keys.end()->time )) * sr);
+	}
+	else reserve = std::max(envl_x->keys.size(),std::max(envl_x->keys.size(),envl_z->keys.size()));
+	out.reserve(reserve+(reserve>>1));
+
+	// Iterate through all three arrays at once - it's tricky, but 
+	// rather interesting to implement.
+	double lasttime = std::min(envl_x->keys[0].time,std::min(envl_y->keys[0].time,envl_z->keys[0].time));
+	
+	cur_x = envl_x->keys.begin();
+	cur_y = envl_y->keys.begin();
+	cur_z = envl_z->keys.begin();
+
+	end_x = end_y = end_z = false;
+	while (1) {
+
+		aiVectorKey fill;
+
+		if ((*cur_x).time == (*cur_y).time && (*cur_x).time == (*cur_z).time ) {
+
+			// we have a keyframe for all of them defined .. great,
+			// we don't need to fucking interpolate here ...
+			fill.mTime = (*cur_x).time;
+
+			fill.mValue.x = (*cur_x).value;
+			fill.mValue.y = (*cur_y).value;
+			fill.mValue.z = (*cur_z).value;
+
+			// subsample animation track
+			if (flags & AI_LWO_ANIM_FLAG_SAMPLE_ANIMS) {
+				//SubsampleAnimTrack(out,cur_x, cur_y, cur_z, d, sample_delta);
+			}
+
+			if (cur_x != envl_x->keys.end()-1)
+				++cur_x;
+			else end_x = true;
+			if (cur_y != envl_y->keys.end()-1)
+				++cur_y;
+			else end_y = true;
+			if (cur_z != envl_z->keys.end()-1)
+				++cur_z;
+			else end_z = true;
+		}
+
+		// Find key with lowest time value
+		else if ((*cur_x).time <= (*cur_y).time && !end_x) {
+
+			if ((*cur_z).time <= (*cur_x).time && !end_z) {
+				InterpolateTrack(out,fill,(*cur_z).time);
+			}
+			else {
+				InterpolateTrack(out,fill,(*cur_x).time);
+			}
+		}
+		else if ((*cur_z).time <= (*cur_y).time && !end_z)	{
+			InterpolateTrack(out,fill,(*cur_z).time);
+		}
+		else if (!end_y) {
+			// welcome on the server, y
+			InterpolateTrack(out,fill,(*cur_y).time);
+		}
+		else {
+			// we have reached the end of at least 2 channels,
+			// only one is remaining. Extrapolate the 2.
+			if (end_y) {
+				InterpolateTrack(out,fill,(end_x ? (*cur_z) : (*cur_x)).time);
+			}
+			else if (end_x) {
+				InterpolateTrack(out,fill,(end_z ? (*cur_y) : (*cur_z)).time);
+			}
+			else { // if (end_z) 
+				InterpolateTrack(out,fill,(end_y ? (*cur_x) : (*cur_y)).time);
+			}
+		}
+		lasttime = fill.mTime;
+		out.push_back(fill);
+
+		if( end_x && end_y && end_z ) /* finished? */
+			break;
+	}
+
+	if (flags & AI_LWO_ANIM_FLAG_START_AT_ZERO) {
+		for (std::vector<aiVectorKey>::iterator it = out.begin(); it != out.end(); ++it)
+			(*it).mTime -= first;
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+// Extract animation channel
+void AnimResolver::ExtractAnimChannel(aiNodeAnim** out, unsigned int flags /*= 0*/)
+{
+	*out = NULL;
+
+	// If we have no envelopes, return NULL
+	if (envelopes.empty()) {
+		return;
+	}
+
+	// We won't spawn an animation channel if we don't have at least one
+	// envelope with more than one keyframe defined.
+	const bool trans = (trans_x && trans_x->keys.size() > 1 || trans_y && trans_y->keys.size() > 1 || trans_z && trans_z->keys.size() > 1);
+	const bool rotat = (rotat_x && rotat_x->keys.size() > 1 || rotat_y && rotat_y->keys.size() > 1 || rotat_z && rotat_z->keys.size() > 1);
+	const bool scale = (scale_x && scale_x->keys.size() > 1 || scale_y && scale_y->keys.size() > 1 || scale_z && scale_z->keys.size() > 1);
+	if (!trans && !rotat && !scale)
+		return;
+
+	// Allocate the output animation 
+	aiNodeAnim* anim = *out = new aiNodeAnim();
+
+	// Setup default animation setup if necessary
+	if (need_to_setup) {
+		UpdateAnimRangeSetup();
+		need_to_setup = false;
+	}
+
+	// copy translation keys
+	if (trans) {
+		std::vector<aiVectorKey> keys;
+		GetKeys(keys,trans_x,trans_y,trans_z,flags);
+
+		anim->mPositionKeys = new aiVectorKey[ anim->mNumPositionKeys = keys.size() ];
+		std::copy(keys.begin(),keys.end(),anim->mPositionKeys);
+	}
+
+	// copy rotation keys
+	if (rotat) {
+		std::vector<aiVectorKey> keys;
+		GetKeys(keys,rotat_x,rotat_y,rotat_z,flags);
+
+		anim->mRotationKeys = new aiQuatKey[ anim->mNumRotationKeys = keys.size() ];
+		
+		// convert heading, pitch, bank to quaternion
+		for (unsigned int i = 0; i < anim->mNumRotationKeys; ++i) {
+			aiQuatKey& qk = anim->mRotationKeys[i];
+			qk.mTime  = keys[i].mTime;
+			qk.mValue = aiQuaternion( keys[i].mValue.x ,keys[i].mValue.y ,keys[i].mValue.z );
+		}
+	}
+
+	// copy scaling keys
+	if (scale) {
+		std::vector<aiVectorKey> keys;
+		GetKeys(keys,scale_x,scale_y,scale_z,flags);
+
+		anim->mScalingKeys = new aiVectorKey[ anim->mNumScalingKeys = keys.size() ];
+		std::copy(keys.begin(),keys.end(),anim->mScalingKeys);
+	}
+}
+
+
+#endif // no lwo or no lws

+ 336 - 0
code/LWOAnimation.h

@@ -0,0 +1,336 @@
+/*
+Open Asset Import Library (ASSIMP)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2008, ASSIMP Development Team
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms, 
+with or without modification, are permitted provided that the 
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the ASSIMP team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the ASSIMP Development Team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file  LWOAnimation.h
+ *  @brief LWOAnimationResolver utility class 
+ *
+ *  This is for all lightwave-related file format, not only LWO.
+ *  LWS isthe main purpose.
+*/
+#ifndef AI_LWO_ANIMATION_INCLUDED
+#define AI_LWO_ANIMATION_INCLUDED
+
+namespace Assimp {
+namespace LWO {
+
+// ---------------------------------------------------------------------------
+/** \brief List of recognized LWO envelopes
+ */
+enum EnvelopeType
+{
+	EnvelopeType_Position_X = 0x1,
+	EnvelopeType_Position_Y = 0x2,
+	EnvelopeType_Position_Z = 0x3,
+
+	EnvelopeType_Rotation_Heading = 0x4,
+	EnvelopeType_Rotation_Pitch = 0x5,
+	EnvelopeType_Rotation_Bank = 0x6,
+
+	EnvelopeType_Scaling_X = 0x7,
+	EnvelopeType_Scaling_Y = 0x8,
+	EnvelopeType_Scaling_Z = 0x9,
+
+	// -- currently not yet handled
+	EnvelopeType_Color_R = 0xa,
+	EnvelopeType_Color_G = 0xb,
+	EnvelopeType_Color_B = 0xc,
+
+	EnvelopeType_Falloff_X = 0xd,
+	EnvelopeType_Falloff_Y = 0xe,
+	EnvelopeType_Falloff_Z = 0xf,
+
+	EnvelopeType_Unknown
+};
+
+// ---------------------------------------------------------------------------
+/** \brief List of recognized LWO interpolation modes
+ */
+enum InterpolationType
+{
+	IT_STEP, IT_LINE, IT_TCB, IT_HERM, IT_BEZI, IT_BEZ2	
+};
+
+
+// ---------------------------------------------------------------------------
+/** \brief List of recognized LWO pre or post range behaviours
+ */
+enum PrePostBehaviour
+{
+	PrePostBehaviour_Reset        = 0x0,
+	PrePostBehaviour_Constant     = 0x1,
+	PrePostBehaviour_Repeat       = 0x2,
+	PrePostBehaviour_Oscillate    = 0x3,
+	PrePostBehaviour_OffsetRepeat = 0x4,
+	PrePostBehaviour_Linear       = 0x5
+};
+
+// ---------------------------------------------------------------------------
+/** \brief Data structure for a LWO animation keyframe
+ */
+struct Key
+{
+	Key()
+		:	inter	(IT_LINE)
+	{}
+
+	//! Current time
+	double time;
+
+	//! Current value
+	float value;
+
+	//! How to interpolate this key with previous key?
+	InterpolationType inter;
+
+	//! Interpolation parameters
+	float params[5];
+
+
+	// for std::find()
+	operator double () {
+		return time;
+	}
+};
+
+// ---------------------------------------------------------------------------
+/** \brief Data structure for a LWO animation envelope
+ */
+struct Envelope
+{
+	Envelope()
+		:	type	(EnvelopeType_Unknown)
+		,	pre		(PrePostBehaviour_Constant)
+		,	post	(PrePostBehaviour_Constant)
+		
+		,	old_first (0)
+		,	old_last  (0)
+	{}
+
+	//! Index of this envelope
+	unsigned int index;
+
+	//! Type of envelope
+	EnvelopeType type;
+
+	//! Pre and post-behaviour
+	PrePostBehaviour pre,post;
+
+	//! Keyframes for this envelope
+	std::vector<Key> keys;
+
+
+	// temporary data for AnimResolver
+	size_t old_first,old_last;
+};
+
+// ---------------------------------------------------------------------------
+//! @def AI_LWO_ANIM_FLAG_SAMPLE_ANIMS 
+//! Flag for AnimResolver, subsamples the input data with the rate specified
+//! by AnimResolver::SetSampleRate().
+#define AI_LWO_ANIM_FLAG_SAMPLE_ANIMS 0x1
+
+
+// ---------------------------------------------------------------------------
+//! @def AI_LWO_ANIM_FLAG_START_AT_ZERO
+//! Flag for AnimResolver, ensures that the animations starts at zero.
+#define AI_LWO_ANIM_FLAG_START_AT_ZERO 0x2
+
+// ---------------------------------------------------------------------------
+/** @brief Utility class to build Assimp animations from LWO envelopes.
+ *
+ *  Used for both LWO and LWS (MOT also).
+ */
+class AnimResolver
+{
+public:
+
+	// ------------------------------------------------------------------
+	/** @brief Construct an AnimResolver from a given list of envelopes
+	 *  @param envelopes Input envelopes. May be empty.
+	 *  @param Output tick rate, per second
+	 *  @note The input envelopes are possibly modified.
+	 */
+	AnimResolver(std::list<Envelope>& envelopes,
+		double tick);
+
+public:
+
+	// ------------------------------------------------------------------
+	/** @brief Extract the bind-pose transformation matrix.
+	 *  @param out Receives bind-pose transformation matrix
+	 */
+	void ExtractBindPose(aiMatrix4x4& out);
+
+	// ------------------------------------------------------------------
+	/** @brief Extract a node animation channel
+	 *  @param out Receives a pointer to a newly allocated node anim.
+	 *    If there's just one keyframe defined, *out is set to NULL and
+	 *    no animation channel is computed.
+	 *  @param flags Any combination of the AI_LWO_ANIM_FLAG_XXX flags.
+	 */
+	void ExtractAnimChannel(aiNodeAnim** out, unsigned int flags = 0);
+
+
+	// ------------------------------------------------------------------
+	/** @brief Set the sampling rate for ExtractAnimChannel().
+	 *
+	 *  Non-linear interpolations are subsampled with this rate (keys 
+	 *  per second). Closer sampling positions, if existent, are kept.
+	 *  The sampling rate defaults to 0, if this value is not changed and
+	 *  AI_LWO_ANIM_FLAG_SAMPLE_ANIMS is specified for ExtractAnimChannel(),
+	 *  the class finds a suitable sample rate by itself.
+	 */
+	void SetSampleRate(double sr) {
+		sample_rate = sr;
+	}
+
+	// ------------------------------------------------------------------
+	/** @brief Getter for SetSampleRate()
+	 */
+	double GetSampleRate() const {
+		return sample_rate;
+	}
+
+	// ------------------------------------------------------------------
+	/** @brief Set the animation time range
+	 *
+	 *  @param first Time where the animation starts, in ticks
+	 *  @param last  Time where the animation ends, in ticks
+	 */
+	void SetAnimationRange(double _first, double _last) {
+		first = _first;
+		last  = _last;
+
+		ClearAnimRangeSetup();
+		UpdateAnimRangeSetup();
+	}
+
+protected:
+
+	// ------------------------------------------------------------------
+	/** @brief Build linearly subsampled keys from 3 single envelopes
+	 *  @param out Receives output keys
+	 *  @param envl_x X-component envelope
+	 *  @param envl_y Y-component envelope
+	 *  @param envl_z Z-component envelope
+	 *  @param flags Any combination of the AI_LWO_ANIM_FLAG_XXX flags.
+	 *  @note Up to two input envelopes may be NULL
+	 */
+	void GetKeys(std::vector<aiVectorKey>& out, 
+		LWO::Envelope* envl_x,
+		LWO::Envelope* envl_y,
+		LWO::Envelope* envl_z,
+		unsigned int flags);
+
+	// ------------------------------------------------------------------
+	/** @brief Resolve a single animation key by applying the right
+	 *  interpolation to it.
+	 *  @param cur Current key
+	 *  @param envl Envelope working on
+	 *  @param time time to be interpolated
+	 *  @param fill Receives the interpolated output value.
+	 */
+	void DoInterpolation(std::vector<LWO::Key>::const_iterator cur, 
+		LWO::Envelope* envl,double time, float& fill);
+
+	// ------------------------------------------------------------------
+	/** @brief Almost the same, except we won't handle pre/post 
+	 *  conditions here.
+	 *  @see DoInterpolation
+	 */
+	void DoInterpolation2(std::vector<LWO::Key>::const_iterator beg, 
+		std::vector<LWO::Key>::const_iterator end,double time, float& fill);
+
+	// ------------------------------------------------------------------
+	/** @brief Interpolate 2 tracks if one is given
+	 * 
+	 *  @param out Receives extra output keys
+	 *  @param key_out Primary output key
+	 *  @param time Time to interpolate for
+	 */
+	void InterpolateTrack(std::vector<aiVectorKey>& out,
+		aiVectorKey& key_out,double time);
+
+	// ------------------------------------------------------------------
+	/** @brief Subsample an animation track by a given sampling rate
+	 *
+	 *  @param out Receives output keys. Last key at input defines the
+	 *    time where subsampling starts.
+	 *  @param time Time to end subsampling at
+	 *  @param sample_delta Time delta between two samples
+	 */
+	void SubsampleAnimTrack(std::vector<aiVectorKey>& out,
+		double time,double sample_delta);
+
+	// ------------------------------------------------------------------
+	/** @brief Delete all keys which we inserted to match anim setup
+	 */
+	void ClearAnimRangeSetup();
+
+	// ------------------------------------------------------------------
+	/** @brief Insert extra keys to match LWO's pre and post behaviours
+	 *  in a given time range [first...last]
+	 */
+	void UpdateAnimRangeSetup();
+
+private:
+	std::list<Envelope>& envelopes;
+	double sample_rate;
+
+	LWO::Envelope* trans_x, *trans_y, *trans_z;
+	LWO::Envelope* rotat_x, *rotat_y, *rotat_z;
+	LWO::Envelope* scale_x, *scale_y, *scale_z;
+
+	double first, last;
+	bool need_to_setup;
+
+	// temporary storage
+	LWO::Envelope* envl_x, * envl_y, * envl_z;
+	std::vector<LWO::Key>::const_iterator cur_x,cur_y,cur_z;
+	bool end_x, end_y, end_z;
+
+	unsigned int flags;
+	double sample_delta;
+};
+
+} // end namespace LWO
+} // end namespace Assimp
+
+#endif // !! AI_LWO_ANIMATION_INCLUDED

+ 47 - 7
code/LWOBLoader.cpp

@@ -178,8 +178,8 @@ void LWOImporter::CopyFaceIndicesLWOB(FaceList::iterator& it,
 		{
 			surface = -surface;
 
-			// there are detail polygons
-			uint16_t numPolygons = *cursor++;
+			// there are detail polygons. 
+			const uint16_t numPolygons = *cursor++;
 			if (cursor < end)CopyFaceIndicesLWOB(it,cursor,end,numPolygons);
 		}
 		face.surfaceIndex = surface-1;
@@ -194,6 +194,27 @@ LWO::Texture* LWOImporter::SetupNewTextureLWOB(LWO::TextureList& list,unsigned i
 
 	std::string type;
 	GetS0(type,size);
+	const char* s = type.c_str();
+
+	if(strstr(s, "Image Map"))
+	{
+		// Determine mapping type
+		if(strstr(s, "Planar"))
+			tex->mapMode = LWO::Texture::Planar;
+		else if(strstr(s, "Cylindrical"))
+			tex->mapMode = LWO::Texture::Cylindrical;
+		else if(strstr(s, "Spherical"))
+			tex->mapMode = LWO::Texture::Spherical;
+		else if(strstr(s, "Cubic"))
+			tex->mapMode = LWO::Texture::Cubic;
+		else if(strstr(s, "Front"))
+			tex->mapMode = LWO::Texture::FrontProjection;
+	}
+	else
+	{
+		// procedural or gradient, not supported
+		DefaultLogger::get()->error("LWOB: Unsupported legacy texture: " + type);
+	}
 
 	return tex;
 }
@@ -271,7 +292,7 @@ void LWOImporter::LoadLWOBSurface(unsigned int size)
 		case AI_LWO_SMAN:
 			{
 				AI_LWO_VALIDATE_CHUNK_LENGTH(head->length,SMAN,4);
-				surf.mMaximumSmoothAngle = GetF4();
+				surf.mMaximumSmoothAngle = fabs( GetF4() );
 				break;
 			}
 		// glossiness
@@ -323,8 +344,7 @@ void LWOImporter::LoadLWOBSurface(unsigned int size)
 				{
 					GetS0(pTex->mFileName,head->length);	
 				}
-				else DefaultLogger::get()->warn("LWOB: TIMG tag was encuntered although "
-					"there was no xTEX tag before");
+				else DefaultLogger::get()->warn("LWOB: Unexpected TIMG chunk");
 				break;
 			}
 		// texture strength
@@ -332,8 +352,28 @@ void LWOImporter::LoadLWOBSurface(unsigned int size)
 			{
 				AI_LWO_VALIDATE_CHUNK_LENGTH(head->length,TVAL,1);
 				if (pTex)pTex->mStrength = (float)GetU1()/ 255.f;
-				else DefaultLogger::get()->warn("LWOB: TVAL tag was encuntered "
-					"although there was no xTEX tag before");
+				else DefaultLogger::get()->warn("LWOB: Unexpected TVAL chunk");
+				break;
+			}
+		// texture flags
+		case AI_LWO_TFLG:
+			{
+				AI_LWO_VALIDATE_CHUNK_LENGTH(head->length,TFLG,2);
+
+				if (pTex) 
+				{
+					const uint16_t s = GetU2();
+					if (s & 1)
+						pTex->majorAxis = LWO::Texture::AXIS_X;
+					else if (s & 2)
+						pTex->majorAxis = LWO::Texture::AXIS_Y;
+					else if (s & 4)
+						pTex->majorAxis = LWO::Texture::AXIS_Z;
+
+					if (s & 16)
+						DefaultLogger::get()->warn("LWOB: Ignoring \'negate\' flag on texture");
+				} 
+				else DefaultLogger::get()->warn("LWOB: Unexpected TFLG chunk");
 				break;
 			}
 		}

+ 35 - 10
code/LWOFileData.h

@@ -38,10 +38,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ----------------------------------------------------------------------
 */
 
-/** @file Defines chunk constants used by the LWO file format
+/** @file LWOFileData.h
+ *  @brief Defines chunk constants used by the LWO file format
 
 The chunks are taken from the official LightWave SDK headers.
-Original copyright notice: "Ernie Wright  17 Sep 00" 
  
 */
 #ifndef AI_LWO_FILEDATA_INCLUDED
@@ -56,6 +56,7 @@ Original copyright notice: "Ernie Wright  17 Sep 00"
 
 // internal headers
 #include "IFF.h"
+#include "LWOAnimation.h"
 
 namespace Assimp {
 namespace LWO {
@@ -254,29 +255,45 @@ namespace LWO {
  */
 struct Face : public aiFace
 {
+	//! Default construction
 	Face() 
 		: surfaceIndex	(0)
 		, smoothGroup	(0)
+		, type			(AI_LWO_FACE)
 	{}
 
-	Face(const Face& f)
-	{
+	//! Construction from given type
+	Face(uint32_t _type) 
+		: surfaceIndex	(0)
+		, smoothGroup	(0)
+		, type			(_type)
+	{}
+
+	//! Copy construction
+	Face(const Face& f)	{
 		*this = f;
 	}
 
+	//! Zero-based index into tags chunk
 	unsigned int surfaceIndex;
+
+	//! Smooth group this face is assigned to
 	unsigned int smoothGroup;
 
-	Face& operator=(const LWO::Face& f)
-	{
+	//! Type of face
+	uint32_t type;
+
+
+	//! Assignment operator
+	Face& operator=(const LWO::Face& f)	{
 		aiFace::operator =(f);
 		surfaceIndex	= f.surfaceIndex;
 		smoothGroup		= f.smoothGroup;
+		type            = f.type;
 		return *this;
 	}
 };
 
-
 // ---------------------------------------------------------------------------
 /** \brief Base structure for all vertex map representations
  */
@@ -473,8 +490,9 @@ struct Clip
 	} type;
 
 	Clip()
-		: type	(UNSUPPORTED)
-		, idx	(0)
+		: type	 (UNSUPPORTED)
+		, idx	 (0)
+		, negate (false)
 	{}
 
 	//! path to the base texture -
@@ -485,6 +503,9 @@ struct Clip
 
 	//! index of the clip
 	unsigned int idx;
+
+	//! Negate the clip?
+	bool negate;
 };
 
 
@@ -529,6 +550,7 @@ struct Surface
 		, mIOR					(1.f) // vakuum
 		, mBumpIntensity		(1.f)
 		, mWireframe			(false)
+		, mAdditiveTransparency (10e10f)
 	{}
 
 	//! Name of the surface
@@ -571,6 +593,9 @@ struct Surface
 
 	//! Wireframe flag
 	bool mWireframe;
+
+	//! Intensity of additive blending
+	float mAdditiveTransparency;
 };
 
 // ---------------------------------------------------------------------------
@@ -592,7 +617,7 @@ typedef std::vector	<	WeightChannel	>	WeightChannelList;
 typedef std::vector	<	VColorChannel	>	VColorChannelList;
 typedef std::vector	<	UVChannel		>	UVChannelList;
 typedef std::vector	<	Clip			>	ClipList;
-
+typedef std::vector	<	Envelope		>	EnvelopeList;
 
 // ---------------------------------------------------------------------------
 /** \brief Represents a layer in the file

+ 184 - 45
code/LWOLoader.cpp

@@ -68,21 +68,21 @@ LWOImporter::~LWOImporter()
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file. 
-bool LWOImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
+bool LWOImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
 {
-	// simple check of file extension is enough for the moment
-	std::string::size_type pos = pFile.find_last_of('.');
-	// no file extension - can't read
-	if( pos == std::string::npos)return false;
-	std::string extension = pFile.substr( pos);
-
-	if (extension.length() < 4)return false;
-	if (extension[0] != '.')return false;
-
-	return ! (extension[1] != 'l' && extension[1] != 'L' ||
-			  extension[2] != 'w' && extension[2] != 'W' &&
-			  extension[2] != 'x' && extension[2] != 'X' ||
-			  extension[3] != 'o' && extension[3] != 'O');
+	const std::string extension = GetExtension(pFile);
+	if (extension == "lwo" || extension == "lxo")
+		return true;
+
+	// if check for extension is not enough, check for the magic tokens 
+	if (!extension.length() || checkSig) {
+		uint32_t tokens[3]; 
+		tokens[0] = AI_LWO_FOURCC_LWOB;
+		tokens[1] = AI_LWO_FOURCC_LWO2;
+		tokens[2] = AI_LWO_FOURCC_LXOB;
+		return CheckMagicToken(pIOHandler,pFile,tokens,3,8);
+	}
+	return false;
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -109,21 +109,23 @@ void LWOImporter::InternReadFile( const std::string& pFile,
 	if((this->fileSize = (unsigned int)file->FileSize()) < 12)
 		throw new ImportErrorException("LWO: The file is too small to contain the IFF header");
 
-	// allocate storage and copy the contents of the file to a memory buffer
+	// Allocate storage and copy the contents of the file to a memory buffer
 	std::vector< uint8_t > mBuffer(fileSize);
 	file->Read( &mBuffer[0], 1, fileSize);
 	this->pScene = pScene;
 
-	// determine the type of the file
+	// Determine the type of the file
 	uint32_t fileType;
 	const char* sz = IFF::ReadHeader(&mBuffer[0],fileType);
 	if (sz)throw new ImportErrorException(sz);
 
 	mFileBuffer = &mBuffer[0] + 12;
 	fileSize -= 12;
-	hasNamedLayer = false;
 
-	// create temporary storage on the stack but store pointers to it in the class 
+	// Initialize some members with their default values
+	hasNamedLayer   = false;
+
+	// Create temporary storage on the stack but store pointers to it in the class 
 	// instance. Therefore everything will be destructed properly if an exception 
 	// is thrown and we needn't take care of that.
 	LayerList		_mLayers;
@@ -218,6 +220,11 @@ void LWOImporter::InternReadFile( const std::string& pFile,
 			for (FaceList::iterator it = layer.mFaces.begin(), end = layer.mFaces.end();
 				it != end;++it,++i)
 			{
+				// Check whether we support this face's type
+				if ((*it).type != AI_LWO_FACE && (*it).type != AI_LWO_PTCH) {
+					continue;
+				}
+
 				unsigned int idx = (*it).surfaceIndex;
 				if (idx >= mTags->size())
 				{
@@ -282,7 +289,7 @@ void LWOImporter::InternReadFile( const std::string& pFile,
 					if (0xffffffff == vUVChannelIndices[mui])break;
 					pvUV[mui] = mesh->mTextureCoords[mui] = new aiVector3D[mesh->mNumVertices];
 
-					// LightWave doesn't support more than 2 UV components
+					// LightWave doesn't support more than 2 UV components (?)
 					// so we can directly setup this value
 					mesh->mNumUVComponents[0] = 2;
 				}
@@ -318,6 +325,7 @@ void LWOImporter::InternReadFile( const std::string& pFile,
 						register unsigned int idx = face.mIndices[q];
 						*pv = layer.mTempPoints[idx] + layer.mPivot;
 						pv->z *= -1.0f; // DX to OGL
+						//std::swap(pv->z,pv->y);
 						pv++;
 
 						// process UV coordinates
@@ -585,7 +593,8 @@ void LWOImporter::GenerateNodeGraph(std::vector<aiNode*>& apcNodes)
 		pScene->mRootNode->mChildren	= apcNewNodes;
 		pScene->mRootNode->mNumChildren	= newSize;
 	}
-	if (!pScene->mRootNode->mNumChildren)throw new ImportErrorException("LWO: Unable to build a valid node graph");
+	if (!pScene->mRootNode->mNumChildren)
+		throw new ImportErrorException("LWO: Unable to build a valid node graph");
 
 	// remove a single root node
 	// TODO: implement directly in the above loop, no need to deallocate here
@@ -717,31 +726,44 @@ void LWOImporter::LoadLWOPoints(unsigned int length)
 void LWOImporter::LoadLWO2Polygons(unsigned int length)
 {
 	LE_NCONST uint16_t* const end	= (LE_NCONST uint16_t*)(mFileBuffer+length);
-	uint32_t type = GetU4();
+	const uint32_t type = GetU4();
 
 	// Determine the type of the polygons
 	switch (type)
 	{
+		// read unsupported stuff too (although we wont process it)
+	case  AI_LWO_BONE:
+		DefaultLogger::get()->warn("LWO2: Encountered unsupported primitive chunk (BONE)");
+		break;
+	case  AI_LWO_MBAL:
+		DefaultLogger::get()->warn("LWO2: Encountered unsupported primitive chunk (METABALL)");
+		break;
+	case  AI_LWO_CURV:
+		DefaultLogger::get()->warn("LWO2: Encountered unsupported primitive chunk (SPLINE)");;
+		break;
+
+		// These are ok with no restrictions
 	case  AI_LWO_PTCH:
 	case  AI_LWO_FACE:
-
 		break;
 	default:
-		DefaultLogger::get()->warn("LWO2: Unsupported polygon type (PTCH and FACE are supported)");
+
+		// hm!? wtf is this? ok ...
+		DefaultLogger::get()->error("LWO2: Encountered unknown polygon type");
+		break;
 	}
 
 	// first find out how many faces and vertices we'll finally need
-	uint16_t* cursor		= (uint16_t*)mFileBuffer;
+	uint16_t* cursor= (uint16_t*)mFileBuffer;
 
 	unsigned int iNumFaces = 0,iNumVertices = 0;
 	CountVertsAndFacesLWO2(iNumVertices,iNumFaces,cursor,end);
 
 	// allocate the output array and copy face indices
-	if (iNumFaces)
-	{
+	if (iNumFaces)	{
 		cursor = (uint16_t*)mFileBuffer;
 
-		mCurLayer->mFaces.resize(iNumFaces);
+		mCurLayer->mFaces.resize(iNumFaces,LWO::Face(type));
 		FaceList::iterator it = mCurLayer->mFaces.begin();
 		CopyFaceIndicesLWO2(it,cursor,end);
 	}
@@ -898,6 +920,7 @@ inline void AddToSingleLinkedList(ReferrerList& refList, unsigned int srcIdx, un
 }
 
 // ------------------------------------------------------------------------------------------------
+// Load LWO2 vertex map
 void LWOImporter::LoadLWO2VertexMap(unsigned int length, bool perPoly)
 {
 	LE_NCONST uint8_t* const end = mFileBuffer+length;
@@ -915,24 +938,24 @@ void LWOImporter::LoadLWO2VertexMap(unsigned int length, bool perPoly)
 	switch (type)
 	{
 	case AI_LWO_TXUV:
-		if (dims != 2)
-		{
+		if (dims != 2)	{
 			DefaultLogger::get()->warn("LWO2: Found UV channel with != 2 components"); 
+			return;
 		}
 		base = FindEntry(mCurLayer->mUVChannels,name,perPoly);
 		break;
 	case AI_LWO_WGHT:
-		if (dims != 1)
-		{
+		if (dims != 1)	{
 			DefaultLogger::get()->warn("LWO2: found vertex weight map with != 1 components"); 
+			return;
 		}
 		base = FindEntry(mCurLayer->mWeightChannels,name,perPoly);
 		break;
 	case AI_LWO_RGB:
 	case AI_LWO_RGBA:
-		if (dims != 3 && dims != 4)
-		{
+		if (dims != 3 && dims != 4)	{
 			DefaultLogger::get()->warn("LWO2: found vertex color map with != 3&4 components"); 
+			return;
 		}
 		base = FindEntry(mCurLayer->mVColorChannels,name,perPoly);
 		break;
@@ -1028,6 +1051,7 @@ void LWOImporter::LoadLWO2VertexMap(unsigned int length, bool perPoly)
 }
 
 // ------------------------------------------------------------------------------------------------
+// Load LWO2 clip
 void LWOImporter::LoadLWO2Clip(unsigned int length)
 {
 	AI_LWO_VALIDATE_CHUNK_LENGTH(length,CLIP,10);
@@ -1042,6 +1066,7 @@ void LWOImporter::LoadLWO2Clip(unsigned int length)
 	switch (head->type)
 	{
 	case AI_LWO_STIL:
+		AI_LWO_VALIDATE_CHUNK_LENGTH(head->length,STIL,1);
 
 		// "Normal" texture
 		GetS0(clip.path,head->length);
@@ -1049,7 +1074,7 @@ void LWOImporter::LoadLWO2Clip(unsigned int length)
 		break;
 
 	case AI_LWO_ISEQ:
-
+		AI_LWO_VALIDATE_CHUNK_LENGTH(head->length,ISEQ,16);
 		// Image sequence. We'll later take the first.
 		{
 			uint8_t digits = GetU1();  mFileBuffer++;
@@ -1078,18 +1103,124 @@ void LWOImporter::LoadLWO2Clip(unsigned int length)
 		break;
 
 	case AI_LWO_XREF:
+		AI_LWO_VALIDATE_CHUNK_LENGTH(head->length,XREF,4);
 
 		// Just a cross-reference to another CLIp
 		clip.type = Clip::REF;
 		clip.clipRef = GetU4();
 		break;
 
+	case AI_LWO_NEGA:
+		AI_LWO_VALIDATE_CHUNK_LENGTH(head->length,NEGA,2);
+		clip.negate = (0 != GetU2());
+		break;
+
 	default:
 		DefaultLogger::get()->warn("LWO2: Encountered unknown CLIP subchunk");
 	}
 }
 
 // ------------------------------------------------------------------------------------------------
+// Load envelope description
+void LWOImporter::LoadLWO2Envelope(unsigned int length)
+{
+	LE_NCONST uint8_t* const end = mFileBuffer + length;
+	AI_LWO_VALIDATE_CHUNK_LENGTH(length,ENVL,4);
+
+	mEnvelopes.push_back(LWO::Envelope());
+	LWO::Envelope& envelope = mEnvelopes.back();
+
+	// Get the index of the envelope
+	envelope.index = ReadVSizedIntLWO2(mFileBuffer);
+
+	// ... and read all subchunks
+	while (true)
+	{
+		if (mFileBuffer + 6 >= end)break;
+		LE_NCONST IFF::SubChunkHeader* const head = IFF::LoadSubChunk(mFileBuffer);
+
+		if (mFileBuffer + head->length > end)
+			throw new ImportErrorException("LWO2: Invalid envelope chunk length");
+
+		uint8_t* const next = mFileBuffer+head->length;
+		switch (head->type)
+		{
+			// Type & representation of the envelope
+		case AI_LWO_TYPE:
+			AI_LWO_VALIDATE_CHUNK_LENGTH(head->length,TYPE,2);
+			mFileBuffer++; // skip user format
+
+			// Determine type of envelope
+			envelope.type  = (LWO::EnvelopeType)*mFileBuffer;
+			++mFileBuffer;
+			break;
+
+			// precondition
+		case AI_LWO_PRE:
+			AI_LWO_VALIDATE_CHUNK_LENGTH(head->length,PRE,2);
+			envelope.pre = (LWO::PrePostBehaviour)GetU2();
+			break;
+		
+			// postcondition
+		case AI_LWO_POST:
+			AI_LWO_VALIDATE_CHUNK_LENGTH(head->length,POST,2);
+			envelope.post = (LWO::PrePostBehaviour)GetU2();
+			break;
+
+			// keyframe
+		case AI_LWO_KEY: 
+			{
+			AI_LWO_VALIDATE_CHUNK_LENGTH(head->length,KEY,8);
+			
+			envelope.keys.push_back(LWO::Key());
+			LWO::Key& key = envelope.keys.back();
+
+			key.time = GetF4();
+			key.value = GetF4();
+			break;
+			}
+
+			// interval interpolation
+		case AI_LWO_SPAN: 
+			{
+				AI_LWO_VALIDATE_CHUNK_LENGTH(head->length,SPAN,4);
+				if (envelope.keys.size()<2)
+					DefaultLogger::get()->warn("LWO2: Unexpected SPAN chunk");
+				else {
+					LWO::Key& key = envelope.keys.back();
+					switch (GetU4())
+					{
+						case AI_LWO_STEP:
+							key.inter = LWO::IT_STEP;break;
+						case AI_LWO_LINE:
+							key.inter = LWO::IT_LINE;break;
+						case AI_LWO_TCB:
+							key.inter = LWO::IT_TCB;break;
+						case AI_LWO_HERM:
+							key.inter = LWO::IT_HERM;break;
+						case AI_LWO_BEZI:
+							key.inter = LWO::IT_BEZI;break;
+						case AI_LWO_BEZ2:
+							key.inter = LWO::IT_BEZ2;break;
+						default:
+							DefaultLogger::get()->warn("LWO2: Unknown interval interpolation mode");
+					};
+
+					// todo ... read params
+				}
+				break;
+			}
+
+		default:
+			DefaultLogger::get()->warn("LWO2: Encountered unknown ENVL subchunk");
+		}
+		// regardless how much we did actually read, go to the next chunk
+		mFileBuffer = next;
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+// Load file - master function
 void LWOImporter::LoadLWO2File()
 {
 	bool skip = false;
@@ -1121,8 +1252,7 @@ void LWOImporter::LoadLWO2File()
 				// load this layer or ignore it? Check the layer index property
 				// NOTE: The first layer is the default layer, so the layer
 				// index is one-based now
-				if (0xffffffff != configLayerIndex && configLayerIndex != mLayers->size())
-				{
+				if (0xffffffff != configLayerIndex && configLayerIndex != mLayers->size()-1)	{
 					skip = true;
 				}
 				else skip = false;
@@ -1138,16 +1268,14 @@ void LWOImporter::LoadLWO2File()
 				GetS0(layer.mName,head->length-16);
 
 				// if the name is empty, generate a default name
-				if (layer.mName.empty())
-				{
+				if (layer.mName.empty())	{
 					char buffer[128]; // should be sufficiently large
 					::sprintf(buffer,"Layer_%i", iUnnamed++);
 					layer.mName = buffer;
 				}
 
 				// load this layer or ignore it? Check the layer name property
-				if (configLayerName.length() && configLayerName != layer.mName)
-				{
+				if (configLayerName.length() && configLayerName != layer.mName)	{
 					skip = true;
 				}
 				else hasNamedLayer = true;
@@ -1161,7 +1289,8 @@ void LWOImporter::LoadLWO2File()
 			// vertex list
 		case AI_LWO_PNTS:
 			{
-				if (skip)break;
+				if (skip)
+					break;
 
 				unsigned int old = (unsigned int)mCurLayer->mTempPoints.size();
 				LoadLWOPoints(head->length);
@@ -1178,7 +1307,8 @@ void LWOImporter::LoadLWO2File()
 			// --- intentionally no break here
 		case AI_LWO_VMAP:
 			{
-				if (skip)break;
+				if (skip)
+					break;
 
 				if (mCurLayer->mTempPoints.empty())
 					DefaultLogger::get()->warn("LWO2: Unexpected VMAP chunk");
@@ -1188,7 +1318,8 @@ void LWOImporter::LoadLWO2File()
 			// face list
 		case AI_LWO_POLS:
 			{
-				if (skip)break;
+				if (skip)
+					break;
 
 				unsigned int old = (unsigned int)mCurLayer->mFaces.size();
 				LoadLWO2Polygons(head->length);
@@ -1198,7 +1329,8 @@ void LWOImporter::LoadLWO2File()
 			// polygon tags 
 		case AI_LWO_PTAG:
 			{
-				if (skip)break;
+				if (skip)
+					break;
 
 				if (mCurLayer->mFaces.empty())
 					DefaultLogger::get()->warn("LWO2: Unexpected PTAG");
@@ -1227,9 +1359,16 @@ void LWOImporter::LoadLWO2File()
 				LoadLWO2Clip(head->length);
 				break;
 			}
+
+			// envelope chunk
+		case AI_LWO_ENVL:
+			{
+				LoadLWO2Envelope(head->length);
+				break;
+			}
 		}
 		mFileBuffer = next;
 	}
 }
 
-#endif // !! ASSIMP_BUILD_NO_LWO_IMPORTER
+#endif // !! ASSIMP_BUILD_NO_LWO_IMPORTER

+ 20 - 3
code/LWOLoader.h

@@ -62,7 +62,7 @@ using namespace LWO;
  *         Methods named "xxxLWOB[xxx]" are used with the older LWOB format.
  *         Methods named "xxxLWO[xxx]" are used with both formats.
  *         Methods named "xxx" are used to preprocess the loaded data -
- *         they aren't specific to one format version, either
+ *         they aren't specific to one format version
 */
 // ---------------------------------------------------------------------------
 class LWOImporter : public BaseImporter
@@ -81,8 +81,10 @@ public:
 
 	// -------------------------------------------------------------------
 	/** Returns whether the class can handle the format of the given file. 
-	* See BaseImporter::CanRead() for details.	*/
-	bool CanRead( const std::string& pFile, IOSystem* pIOHandler) const;
+	 * See BaseImporter::CanRead() for details.	
+	 */
+	bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
+		bool checkSig) const;
 
 
 	// -------------------------------------------------------------------
@@ -211,6 +213,11 @@ private:
 	*/
 	void LoadLWO2Clip(unsigned int length);
 
+	// -------------------------------------------------------------------
+	/** Load an envelope from an EVL chunk
+	 *  @param length Size of the chunk
+	*/
+	void LoadLWO2Envelope(unsigned int length);
 
 	// -------------------------------------------------------------------
 	/** Count vertices and faces in a LWOB/LWO2 file
@@ -372,6 +379,9 @@ protected:
 	/** Temporary clip list from the file */
 	ClipList mClips;
 
+	/** Temporary envelope list from the file */
+	EnvelopeList mEnvelopes;
+
 	/** file buffer */
 	uint8_t* mFileBuffer;
 
@@ -381,9 +391,16 @@ protected:
 	/** Output scene */
 	aiScene* pScene;
 
+	/** Configuration option: speed flag set? */
 	bool configSpeedFlag;
+
+	/** Configuration option: index of layer to be loaded */
 	unsigned int configLayerIndex;
+
+	/** Configuration option: name of layer to be loaded */
 	std::string  configLayerName;
+
+	/** True if we have a named layer */
 	bool hasNamedLayer;
 };
 

+ 64 - 32
code/LWOMaterial.cpp

@@ -183,13 +183,30 @@ bool LWOImporter::HandleTextures(MaterialHelper* pcMat, const TextureList& in, a
 			if (mClips.end() == clip)	{
 				DefaultLogger::get()->error("LWO2: Clip index is out of bounds");
 				temp = 0;
+
+				// fixme: appearently some LWO files shipping with Doom3 don't
+				// have clips at all ... check whether that's true or whether
+				// it's a bug in the loader.
+
+				s.Set("$texture.png");
+
+				//continue;
 			}
-			if (Clip::UNSUPPORTED == (*clip).type)	{
-				DefaultLogger::get()->error("LWO2: Clip type is not supported");
-				continue;
+			else {
+				if (Clip::UNSUPPORTED == (*clip).type)	{
+					DefaultLogger::get()->error("LWO2: Clip type is not supported");
+					continue;
+				}
+				AdjustTexturePath((*clip).path);
+				s.Set((*clip).path);
+
+				// Additional image settings
+				int flags = 0;
+				if ((*clip).negate) {
+					flags |= aiTextureFlags_Invert;
+				}
+				pcMat->AddProperty(&flags,1,AI_MATKEY_TEXFLAGS(type,cur));
 			}
-			AdjustTexturePath((*clip).path);
-			s.Set((*clip).path);
 		}
 		else 
 		{
@@ -232,6 +249,7 @@ bool LWOImporter::HandleTextures(MaterialHelper* pcMat, const TextureList& in, a
 				DefaultLogger::get()->warn("LWO2: Unsupported texture blend mode: alpha or displacement");
 
 		}
+		// Setup texture operation
 		pcMat->AddProperty<int>((int*)&temp,1,AI_MATKEY_TEXOP(type,cur));
 
 		// setup the mapping mode
@@ -258,12 +276,12 @@ void LWOImporter::ConvertMaterial(const LWO::Surface& surf,MaterialHelper* pcMat
 	st.Set(surf.mName);
 	pcMat->AddProperty(&st,AI_MATKEY_NAME);
 
-	int i = surf.bDoubleSided ? 1 : 0;
-	pcMat->AddProperty<int>(&i,1,AI_MATKEY_TWOSIDED);
+	const int i = surf.bDoubleSided ? 1 : 0;
+	pcMat->AddProperty(&i,1,AI_MATKEY_TWOSIDED);
 
 	// add the refraction index and the bump intensity
-	pcMat->AddProperty<float>(&surf.mIOR,1,AI_MATKEY_REFRACTI);
-	pcMat->AddProperty<float>(&surf.mBumpIntensity,1,AI_MATKEY_BUMPSCALING);
+	pcMat->AddProperty(&surf.mIOR,1,AI_MATKEY_REFRACTI);
+	pcMat->AddProperty(&surf.mBumpIntensity,1,AI_MATKEY_BUMPSCALING);
 	
 	aiShadingMode m;
 	if (surf.mSpecularValue && surf.mGlossiness)
@@ -281,16 +299,16 @@ void LWOImporter::ConvertMaterial(const LWO::Surface& surf,MaterialHelper* pcMat
 			else fGloss = 80.0f;
 		}
 
-		pcMat->AddProperty<float>(&surf.mSpecularValue,1,AI_MATKEY_SHININESS_STRENGTH);
-		pcMat->AddProperty<float>(&fGloss,1,AI_MATKEY_SHININESS);
+		pcMat->AddProperty(&surf.mSpecularValue,1,AI_MATKEY_SHININESS_STRENGTH);
+		pcMat->AddProperty(&fGloss,1,AI_MATKEY_SHININESS);
 		m = aiShadingMode_Phong;
 	}
 	else m = aiShadingMode_Gouraud;
 
 	// specular color
 	aiColor3D clr = lerp( aiColor3D(1.f,1.f,1.f), surf.mColor, surf.mColorHighlights );
-	pcMat->AddProperty<aiColor3D>(&clr,1,AI_MATKEY_COLOR_SPECULAR);
-	pcMat->AddProperty<float>(&surf.mSpecularValue,1,AI_MATKEY_SHININESS_STRENGTH);
+	pcMat->AddProperty(&clr,1,AI_MATKEY_COLOR_SPECULAR);
+	pcMat->AddProperty(&surf.mSpecularValue,1,AI_MATKEY_SHININESS_STRENGTH);
 
 	// emissive color
 	// (luminosity is not really the same but it affects the surface in 
@@ -298,12 +316,21 @@ void LWOImporter::ConvertMaterial(const LWO::Surface& surf,MaterialHelper* pcMat
 	clr.g = clr.b = clr.r = surf.mLuminosity*0.8f;
 	pcMat->AddProperty<aiColor3D>(&clr,1,AI_MATKEY_COLOR_EMISSIVE);
 
-	// opacity
-	if (10e10f != surf.mTransparency)
+	// opacity ... either additive or default-blended, please
+	if (10e10f != surf.mAdditiveTransparency)
+	{
+		const int add = aiBlendMode_Additive;
+		pcMat->AddProperty(&surf.mAdditiveTransparency,1,AI_MATKEY_OPACITY);
+		pcMat->AddProperty(&add,1,AI_MATKEY_BLEND_FUNC);
+	}
+	else if (10e10f != surf.mTransparency)
 	{
-		float f = 1.0f-surf.mTransparency;
-		pcMat->AddProperty<float>(&f,1,AI_MATKEY_OPACITY);
+		const int def = aiBlendMode_Default;
+		const float f = 1.0f-surf.mTransparency;
+		pcMat->AddProperty(&f,1,AI_MATKEY_OPACITY);
+		pcMat->AddProperty(&def,1,AI_MATKEY_BLEND_FUNC);
 	}
+	
 
 	// ADD TEXTURES to the material
 	// TODO: find out how we can handle COLOR textures correctly...
@@ -315,25 +342,21 @@ void LWOImporter::ConvertMaterial(const LWO::Surface& surf,MaterialHelper* pcMat
 	HandleTextures(pcMat,surf.mOpacityTextures,aiTextureType_OPACITY);
 	HandleTextures(pcMat,surf.mReflectionTextures,aiTextureType_REFLECTION);
 
-	// now we need to know which shader we must use
+	// Now we need to know which shader we must use
 	// iterate through the shader list of the surface and 
-	// search for a name we know 
+	// search for a name which we know ... 
 	for (ShaderList::const_iterator it = surf.mShaders.begin(), end = surf.mShaders.end();
 		 it != end;++it)
 	{
 		//if (!(*it).enabled)continue;
-		if ((*it).functionName == "LW_SuperCelShader" ||
-			(*it).functionName == "AH_CelShader")
-		{
+		if ((*it).functionName == "LW_SuperCelShader" || (*it).functionName == "AH_CelShader")	{
 			DefaultLogger::get()->info("LWO2: Mapping LW_SuperCelShader/AH_CelShader "
 				"to aiShadingMode_Toon");
 
 			m = aiShadingMode_Toon;
 			break;
 		}
-		else if ((*it).functionName == "LW_RealFresnel" ||
-			(*it).functionName == "LW_FastFresnel")
-		{
+		else if ((*it).functionName == "LW_RealFresnel" || (*it).functionName == "LW_FastFresnel")	{
 			DefaultLogger::get()->info("LWO2: Mapping LW_RealFresnel/LW_FastFresnel "
 				"to aiShadingMode_Fresnel");
 
@@ -345,12 +368,13 @@ void LWOImporter::ConvertMaterial(const LWO::Surface& surf,MaterialHelper* pcMat
 			DefaultLogger::get()->warn("LWO2: Unknown surface shader: " + (*it).functionName);
 		}
 	}
-	if (surf.mMaximumSmoothAngle <= 0.0f)m = aiShadingMode_Flat;
+	if (surf.mMaximumSmoothAngle <= 0.0f)
+		m = aiShadingMode_Flat;
 	pcMat->AddProperty((int*)&m,1,AI_MATKEY_SHADING_MODEL);
 
 	// (the diffuse value is just a scaling factor)
 	// If a diffuse texture is set, we set this value to 1.0
-	clr = (b ? aiColor3D(1.f,1.f,1.f) : surf.mColor);
+	clr = (b && false ? aiColor3D(1.f,1.f,1.f) : surf.mColor);
 	clr.r *= surf.mDiffuseValue;
 	clr.g *= surf.mDiffuseValue;
 	clr.b *= surf.mDiffuseValue;
@@ -365,9 +389,7 @@ void LWOImporter::FindUVChannels(LWO::TextureList& list, LWO::Layer& layer,
 		 it != end;++it)
 	{
 		// Ignore textures with non-UV mappings for the moment.
-		if (!(*it).enabled || !(*it).bCanUse || 0xffffffff != (*it).mRealUVIndex ||
-			(*it).mapMode != LWO::Texture::UV)
-		{
+		if (!(*it).enabled || !(*it).bCanUse || 0xffffffff != (*it).mRealUVIndex || (*it).mapMode != LWO::Texture::UV)	{
 			continue;
 		}
 		for (unsigned int i = 0; i < layer.mUVChannels.size();++i)
@@ -379,6 +401,7 @@ void LWOImporter::FindUVChannels(LWO::TextureList& list, LWO::Layer& layer,
 				{
 					if (i == out[m])	{
 						(*it).mRealUVIndex = m;
+						break;
 					}
 				}
 				if (0xffffffff == (*it).mRealUVIndex)
@@ -710,7 +733,9 @@ void LWOImporter::LoadLWO2Surface(unsigned int size)
 			// transparency
 		case AI_LWO_TRAN:
 			{
-				if (surf.mTransparency == 10e10f)break;
+				// transparency explicitly disabled?
+				if (surf.mTransparency == 10e10f)
+					break;
 
 				AI_LWO_VALIDATE_CHUNK_LENGTH(head->length,TRAN,4);
 				surf.mTransparency = GetF4();
@@ -741,6 +766,13 @@ void LWOImporter::LoadLWO2Surface(unsigned int size)
 				}
 				break;
 			}
+			// additive transparency
+		case AI_LWO_ADTR:
+			{
+				AI_LWO_VALIDATE_CHUNK_LENGTH(head->length,ADTR,4);
+				surf.mAdditiveTransparency = GetF4();
+				break;
+			}
 			// wireframe mode
 		case AI_LWO_LINE:
 			{
@@ -788,7 +820,7 @@ void LWOImporter::LoadLWO2Surface(unsigned int size)
 		case AI_LWO_SMAN:
 			{
 				AI_LWO_VALIDATE_CHUNK_LENGTH(head->length,SMAN,4);
-				surf.mMaximumSmoothAngle = GetF4();
+				surf.mMaximumSmoothAngle = fabs( GetF4() );
 				break;
 			}
 			// vertex color channel to be applied to the surface

+ 791 - 13
code/LWSLoader.cpp

@@ -39,7 +39,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ---------------------------------------------------------------------------
 */
 
-/** @file Implementation of the LWS importer class */
+/** @file  LWSLoader.cpp
+ *  @brief Implementation of the LWS importer class 
+ */
 
 #include "AssimpPCH.h"
 
@@ -48,9 +50,60 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "fast_atof.h"
 
 #include "SceneCombiner.h"
+#include "GenericProperty.h"
+#include "SkeletonMeshBuilder.h"
 
 using namespace Assimp;
 
+// ------------------------------------------------------------------------------------------------
+// Recursive parsing of LWS files
+void LWS::Element::Parse (const char*& buffer)
+{
+	for (;SkipSpacesAndLineEnd(&buffer);SkipLine(&buffer)) {
+	
+		// begin of a new element with children
+		bool sub = false;
+		if (*buffer == '{') {
+			++buffer;
+			SkipSpaces(&buffer);
+			sub = true;
+		}
+		else if (*buffer == '}')
+			return;
+
+		children.push_back(Element());
+
+		// copy data line - read token per token
+
+		const char* cur = buffer;
+		while (!IsSpaceOrNewLine(*buffer)) ++buffer;
+		children.back().tokens[0] = std::string(cur,(size_t) (buffer-cur));
+		SkipSpaces(&buffer);
+
+		if (children.back().tokens[0] == "Plugin") 
+		{
+			DefaultLogger::get()->debug("LWS: Skipping over plugin-specific data");
+
+			// strange stuff inside Plugin/Endplugin blocks. Needn't
+			// follow LWS syntax, so we skip over it
+			for (;SkipSpacesAndLineEnd(&buffer);SkipLine(&buffer)) {
+				if (!::strncmp(buffer,"EndPlugin",9)) {
+					//SkipLine(&buffer);
+					break;
+				}
+			}
+			continue;
+		}
+
+		cur = buffer;
+		while (!IsLineEnd(*buffer)) ++buffer;
+		children.back().tokens[1] = std::string(cur,(size_t) (buffer-cur));
+
+		// parse more elements recursively
+		if (sub)
+			children.back().Parse(buffer);
+	}
+}
 
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
@@ -68,30 +121,755 @@ LWSImporter::~LWSImporter()
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file. 
-bool LWSImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
+bool LWSImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler,bool checkSig) const
 {
-	std::string::size_type pos = pFile.find_last_of('.');
+	const std::string extension = GetExtension(pFile);
+	if (extension == "lws" || extension == "mot")
+		return true;
 
-	// no file extension - can't read
-	if( pos == std::string::npos)
-		return false;
+	// if check for extension is not enough, check for the magic tokens LWSC and LWMO
+	if (!extension.length() || checkSig) {
+		uint32_t tokens[2]; 
+		tokens[0] = AI_MAKE_MAGIC("LWSC");
+		tokens[1] = AI_MAKE_MAGIC("LWMO");
+		return CheckMagicToken(pIOHandler,pFile,tokens,2);
+	}
+	return false;
+}
 
-	std::string extension = pFile.substr( pos);
-	for (std::string::iterator i = extension.begin(); i != extension.end();++i)
-		*i = ::tolower(*i);
+// ------------------------------------------------------------------------------------------------
+// Get list of file extensions
+void LWSImporter::GetExtensionList(std::string& append)
+{
+	append.append("*.lws;*.mot");
+}
+
+// ------------------------------------------------------------------------------------------------
+// Setup configuration properties
+void LWSImporter::SetupProperties(const Importer* pImp)
+{
+	// AI_CONFIG_FAVOUR_SPEED
+	configSpeedFlag = (0 != pImp->GetPropertyInteger(AI_CONFIG_FAVOUR_SPEED,0));
 
-	return extension == ".lws";
+	// AI_CONFIG_IMPORT_LWS_ANIM_START
+	first = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_LWS_ANIM_START,
+		150392 /* magic hack */);
+
+	// AI_CONFIG_IMPORT_LWS_ANIM_END
+	last = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_LWS_ANIM_END,
+		150392 /* magic hack */);
+
+	if (last < first) {
+		std::swap(last,first);
+	}
 }
 
 // ------------------------------------------------------------------------------------------------
-void LWSImporter::GetExtensionList(std::string& append)
+// Read an envelope description
+void LWSImporter::ReadEnvelope(const LWS::Element& dad, LWO::Envelope& fill )
+{
+	if (dad.children.empty()) {
+		DefaultLogger::get()->error("LWS: Envelope descriptions must not be empty");	
+		return;
+	}
+
+	// reserve enough storage
+	std::list< LWS::Element >::const_iterator it = dad.children.begin();;
+	fill.keys.reserve(strtol10(it->tokens[1].c_str()));
+
+	for (++it; it != dad.children.end(); ++it) {
+		const char* c = (*it).tokens[1].c_str();
+
+		if ((*it).tokens[0] == "Key") {
+			fill.keys.push_back(LWO::Key());
+			LWO::Key& key = fill.keys.back();
+
+			float f;
+			SkipSpaces(&c);
+			c = fast_atof_move(c,key.value);
+			SkipSpaces(&c);
+			c = fast_atof_move(c,f);
+
+			key.time = f;
+
+			unsigned int span = strtol10(c,&c), num = 0;
+			switch (span) {
+			
+				case 0:
+					key.inter = LWO::IT_TCB;
+					num = 5;
+					break;
+				case 1:
+				case 2:
+					key.inter = LWO::IT_HERM;
+					num = 5;
+					break;
+				case 3:
+					key.inter = LWO::IT_LINE;
+					num = 0;
+					break;
+				case 4:
+					key.inter = LWO::IT_STEP;
+					num = 0;
+					break;
+				case 5:
+					key.inter = LWO::IT_BEZ2;
+					num = 4;
+					break;
+				default:
+					DefaultLogger::get()->error("LWS: Unknown span type");
+			}
+			for (unsigned int i = 0; i < num;++i) {
+				SkipSpaces(&c);
+				c = fast_atof_move(c,key.params[i]);
+			}
+		}
+		else if ((*it).tokens[0] == "Behaviors") {
+			SkipSpaces(&c);
+			fill.pre = (LWO::PrePostBehaviour) strtol10(c,&c);
+			SkipSpaces(&c);
+			fill.post = (LWO::PrePostBehaviour) strtol10(c,&c);
+		}
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+// Read animation channels in the old LightWave animation format
+void LWSImporter::ReadEnvelope_Old(
+	std::list< LWS::Element >::const_iterator& it, 
+	const std::list< LWS::Element >::const_iterator& end,
+	LWS::NodeDesc& nodes,
+	unsigned int version)
+{
+	unsigned int num,sub_num;
+	if (++it == end)goto unexpected_end;
+
+	num = strtol10((*it).tokens[0].c_str());
+	for (unsigned int i = 0; i < num; ++i) {
+	
+		nodes.channels.push_back(LWO::Envelope());
+		LWO::Envelope& envl = nodes.channels.back();
+
+		envl.index = i;
+		envl.type  = (LWO::EnvelopeType)(i+1);
+	
+		if (++it == end)goto unexpected_end;
+		sub_num = strtol10((*it).tokens[0].c_str());
+
+		for (unsigned int n = 0; n < sub_num;++n) {
+
+			if (++it == end)goto unexpected_end;
+
+			// parse value and time, skip the rest for the moment.
+			LWO::Key key;
+			const char* c = fast_atof_move((*it).tokens[0].c_str(),key.value);
+			SkipSpaces(&c);
+			float f;
+			fast_atof_move((*it).tokens[0].c_str(),f);
+			key.time = f;
+
+			envl.keys.push_back(key);
+		}
+	}
+	return;
+
+unexpected_end:
+	DefaultLogger::get()->error("LWS: Encountered unexpected end of file while parsing object motion");
+}
+
+// ------------------------------------------------------------------------------------------------
+// Setup a nice name for a node 
+void LWSImporter::SetupNodeName(aiNode* nd, LWS::NodeDesc& src)
 {
-	append.append("*.lws");
+	const unsigned int combined = src.number | ((unsigned int)src.type) << 28u;
+
+	// the name depends on the type. We break LWS's strange naming convention
+	// and return human-readable, but still machine-parsable and unique, strings.
+	if (src.type == LWS::NodeDesc::OBJECT)	{
+
+		if (src.path.length()) {
+			std::string::size_type s = src.path.find_last_of("\\/");
+			if (s == std::string::npos)
+				s = 0;
+			else ++s;
+
+			nd->mName.length = ::sprintf(nd->mName.data,"%s_(%08X)",src.path.substr(s).c_str(),combined);
+			return;
+		}
+	}
+	nd->mName.length = ::sprintf(nd->mName.data,"%s_(%08X)",src.name,combined);
 }
 
 // ------------------------------------------------------------------------------------------------
+// Recursively build the scenegraph
+void LWSImporter::BuildGraph(aiNode* nd, LWS::NodeDesc& src, std::vector<AttachmentInfo>& attach,
+	BatchLoader& batch,
+	aiCamera**& camOut,
+	aiLight**& lightOut, 
+	std::vector<aiNodeAnim*>& animOut)
+{
+	// Setup a very cryptic name for the node, we want the user to be happy
+	SetupNodeName(nd,src);
+
+	// If this is an object from an external file - get the scene
+	// and setup proper attachment tags
+	if (src.type == LWS::NodeDesc::OBJECT && src.path.length() ) {
+		aiScene* obj = batch.GetImport(src.id);
+		if (!obj) {
+			DefaultLogger::get()->error("LWS: Failed to read external file " + src.path);
+		}
+		else {
+			attach.push_back(AttachmentInfo(obj,nd));
+		}
+	}
+
+	// If object is a light source - setup a corresponding ai structure
+	else if (src.type == LWS::NodeDesc::LIGHT) {
+		aiLight* lit = *lightOut++ = new aiLight();
+
+		// compute final light color
+		lit->mColorDiffuse = lit->mColorSpecular = src.lightColor*src.lightIntensity;
+
+		// name to attach light to node -> unique due to LWs indexing system
+		lit->mName = nd->mName;
+
+		// detemine light type and setup additional members
+		if (src.lightType == 2) { /* spot light */
+
+			lit->mType = aiLightSource_SPOT;
+			lit->mAngleInnerCone = (float)AI_DEG_TO_RAD( src.lightConeAngle );
+			lit->mAngleOuterCone = lit->mAngleInnerCone+(float)AI_DEG_TO_RAD( src.lightEdgeAngle );
+
+		}
+		else if (src.lightType == 1) { /* directional light source */
+			lit->mType = aiLightSource_DIRECTIONAL;
+		}
+		else lit->mType = aiLightSource_POINT;
+
+		// fixme: no proper handling of light falloffs yet
+		if (src.lightFalloffType == 1)
+			lit->mAttenuationConstant = 1.f;
+		else if (src.lightFalloffType == 1)
+			lit->mAttenuationLinear = 1.f;
+		else 
+			lit->mAttenuationQuadratic = 1.f;
+	}
+
+	// If object is a camera - setup a corresponding ai structure
+	else if (src.type == LWS::NodeDesc::CAMERA) {
+		aiCamera* cam = *camOut++ = new aiCamera();
+
+		// name to attach cam to node -> unique due to LWs indexing system
+		cam->mName = nd->mName;
+	}
+
+	// Get the node transformation from the LWO key
+	LWO::AnimResolver resolver(src.channels,fps);
+	resolver.ExtractBindPose(nd->mTransformation);
+
+	// .. and construct animation channels
+	aiNodeAnim* anim = NULL;
+#if 0 /* not yet */
+	if (first != last) {
+		resolver.SetAnimationRange(first,last);
+		resolver.ExtractAnimChannel(&anim,AI_LWO_ANIM_FLAG_SAMPLE_ANIMS|AI_LWO_ANIM_FLAG_START_AT_ZERO);
+		if (anim) {
+			anim->mNodeName = nd->mName;
+			animOut.push_back(anim);
+		}
+	}
+#endif
+
+	// process pivot point, if any
+	if (src.pivotPos != aiVector3D()) {
+		aiMatrix4x4 tmp;
+		aiMatrix4x4::Translation(-src.pivotPos,tmp);
+
+		if (anim) {
+		
+			// We have an animation channel for this node. Problem: to combine the pivot
+			// point with the node anims, we'd need to interpolate *all* keys, get 
+			// transformation matrices from them, apply the translation and decompose
+			// the resulting matrices again in order to reconstruct the keys. This 
+			// solution here is *much* easier ... we're just inserting an extra node
+			// in the hierarchy.
+			// Maybe the final optimization here will be done during postprocessing.
+
+			aiNode* pivot = new aiNode();
+			pivot->mName.Set("$Pivot");
+			pivot->mTransformation = tmp;
+
+			pivot->mChildren = new aiNode*[pivot->mNumChildren = 1];
+			pivot->mChildren[0] = nd;
+
+			pivot->mParent = nd->mParent;
+			nd->mParent    = pivot;
+			
+			// swap children ad hope the parents wont see a huge difference
+			pivot->mParent->mChildren[pivot->mParent->mNumChildren-1] = pivot;
+		}
+		else {
+			nd->mTransformation = tmp * nd->mTransformation;
+		}
+	}
+
+	// Add children
+	if (src.children.size()) {
+		nd->mChildren = new aiNode*[src.children.size()];
+		for (std::list<LWS::NodeDesc*>::iterator it = src.children.begin(); it != src.children.end(); ++it) {
+			aiNode* ndd = nd->mChildren[nd->mNumChildren++] = new aiNode();
+			ndd->mParent = nd;
+
+			BuildGraph(ndd,**it,attach,batch,camOut,lightOut,animOut);
+		}
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+// Determine the exact location of a LWO file
+std::string LWSImporter::FindLWOFile(const std::string& in)
+{
+	// insert missing directory seperator if necessary
+	std::string tmp;
+	if (in.length() > 3 && in[1] == ':'&& in[2] != '\\' && in[2] != '/')
+	{
+		tmp = in[0] + ":\\" + in.substr(2);
+	}
+	else tmp = in;
+
+	if (io->Exists(tmp))
+		return in;
+
+	// file is not accessible for us ... maybe it's packed by 
+	// LightWave's 'Package Scene' command?
+
+	// Relevant for us are the following two directories:
+	// <folder>\Objects\<hh>\<*>.lwo
+	// <folder>\Scenes\<hh>\<*>.lws
+	// where <hh> is optional.
+
+	std::string test = ".." + io->getOsSeparator() + tmp; 
+	if (io->Exists(test))
+		return test;
+
+	test = ".." + io->getOsSeparator() + test; 
+	if (io->Exists(test))
+		return test;
+
+	// return original path, maybe the IOsystem knows better
+	return tmp;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Read file into given scene data structure
 void LWSImporter::InternReadFile( const std::string& pFile, aiScene* pScene, 
 	IOSystem* pIOHandler)
 {
-	return;
+	io = pIOHandler;
+	boost::scoped_ptr<IOStream> file( pIOHandler->Open( pFile, "rb"));
+
+	// Check whether we can read from the file
+	if( file.get() == NULL)
+		throw new ImportErrorException( "Failed to open LWS file " + pFile + ".");
+
+	// Allocate storage and copy the contents of the file to a memory buffer
+	const size_t fileSize = file->FileSize();
+	std::vector< char > mBuffer(fileSize);
+	file->Read( &mBuffer[0], 1, fileSize);
+	
+	// Parse the file structure
+	LWS::Element root; const char* dummy = &mBuffer[0];
+	root.Parse(dummy);
+
+	// Construct a Batchimporter to read more files recursively
+	BatchLoader batch(pIOHandler);
+	batch.SetBasePath(pFile);
+
+	// Construct an array to receive the flat output graph
+	std::list<LWS::NodeDesc> nodes;
+
+	unsigned int cur_light = 0, cur_camera = 0, cur_object = 0;
+	unsigned int num_light = 0, num_camera = 0, num_object = 0;
+
+	// check magic identifier, 'LWSC'
+	bool motion_file = false;
+	std::list< LWS::Element >::const_iterator it = root.children.begin();
+	
+	if ((*it).tokens[0] == "LWMO")
+		motion_file = true;
+
+	if ((*it).tokens[0] != "LWSC" && !motion_file)
+		throw new ImportErrorException("LWS: Not a LightWave scene, magic tag LWSC not found");
+
+	// get file format version and print to log
+	++it;
+	unsigned int version = strtol10((*it).tokens[0].c_str());
+	DefaultLogger::get()->info("LWS file format version is " + (*it).tokens[0]);
+	first = 0.;
+	last  = 60.;
+	fps   = 25.; /* seems to be a good default frame rate */
+
+	// Now read all elements in a very straghtforward manner
+	for (; it != root.children.end(); ++it) {
+		const char* c = (*it).tokens[1].c_str();
+
+		// 'FirstFrame': begin of animation slice
+		if ((*it).tokens[0] == "FirstFrame") {
+			if (150392. != first           /* see SetupProperties() */)
+				first = strtol10(c,&c)-1.; /* we're zero-based */
+		}
+
+		// 'LastFrame': end of animation slice
+		else if ((*it).tokens[0] == "LastFrame") {
+			if (150392. != last      /* see SetupProperties() */)
+				last = strtol10(c,&c)-1.; /* we're zero-based */
+		}
+
+		// 'FramesPerSecond': frames per second
+		else if ((*it).tokens[0] == "FramesPerSecond") {
+			fps = strtol10(c,&c);
+		}
+
+		// 'LoadObjectLayer': load a layer of a specific LWO file
+		else if ((*it).tokens[0] == "LoadObjectLayer") {
+
+			// get layer index
+			const int layer = strtol10(c,&c);
+
+			// setup the layer to be loaded
+			BatchLoader::PropertyMap props;
+			SetGenericProperty(props.ints,AI_CONFIG_IMPORT_LWO_ONE_LAYER_ONLY,layer);
+
+			// add node to list
+			LWS::NodeDesc d;
+			d.type = LWS::NodeDesc::OBJECT;
+			if (version >= 4) { // handle LWSC 4 explicit ID
+				SkipSpaces(&c);
+				d.number = strtol16(c,&c) & AI_LWS_MASK;
+			}
+			else d.number = cur_object++;
+
+			// and add the file to the import list
+			SkipSpaces(&c);
+			std::string path = FindLWOFile( c );
+			d.path = path;
+			d.id = batch.AddLoadRequest(path,0,&props);
+
+			nodes.push_back(d);
+			num_object++;
+		}
+		// 'LoadObject': load a LWO file into the scenegraph
+		else if ((*it).tokens[0] == "LoadObject") {
+			
+			// add node to list
+			LWS::NodeDesc d;
+			d.type = LWS::NodeDesc::OBJECT;
+			
+			if (version >= 4) { // handle LWSC 4 explicit ID
+				d.number = strtol16(c,&c) & AI_LWS_MASK;
+				SkipSpaces(&c);
+			}
+			else d.number = cur_object++;
+			std::string path = FindLWOFile( c );
+			d.id = batch.AddLoadRequest(path,0,NULL);
+
+			d.path = path;
+			nodes.push_back(d);
+			num_object++;
+		}
+		// 'AddNullObject': add a dummy node to the hierarchy
+		else if ((*it).tokens[0] == "AddNullObject") {
+
+			// add node to list
+			LWS::NodeDesc d;
+			d.type = LWS::NodeDesc::OBJECT;
+			d.name = c;
+			if (version >= 4) { // handle LWSC 4 explicit ID
+				d.number = strtol16(c,&c) & AI_LWS_MASK;
+			}
+			else d.number = cur_object++;
+			nodes.push_back(d);
+
+			num_object++;
+		}
+		// 'NumChannels': Number of envelope channels assigned to last layer
+		else if ((*it).tokens[0] == "NumChannels") {
+			// ignore for now
+		}
+		// 'Channel': preceedes any envelope description
+		else if ((*it).tokens[0] == "Channel") {
+			if (nodes.empty()) {
+				if (motion_file) {
+
+					// LightWave motion file. Add dummy node
+					LWS::NodeDesc d;
+					d.type = LWS::NodeDesc::OBJECT;
+					d.name = c;
+					d.number = cur_object++;
+					nodes.push_back(d);
+				}
+				else DefaultLogger::get()->error("LWS: Unexpected keyword: \'Channel\'");
+			}
+
+			// important: index of channel
+			nodes.back().channels.push_back(LWO::Envelope());
+			LWO::Envelope& env = nodes.back().channels.back();
+			
+			env.index = strtol10(c);
+
+			// currently we can just interpret the standard channels 0...9
+			// (hack) assume that index-i yields the binary channel type from LWO
+			env.type = (LWO::EnvelopeType)(env.index+1);
+
+		}
+		// 'Envelope': a single animation channel
+		else if ((*it).tokens[0] == "Envelope") {
+			if (nodes.empty() || nodes.back().channels.empty())
+				DefaultLogger::get()->error("LWS: Unexpected keyword: \'Envelope\'");
+			else {
+				ReadEnvelope((*it),nodes.back().channels.back());
+			}
+		}
+		// 'ObjectMotion': animation information for older lightwave formats
+		else if (version < 3  && ((*it).tokens[0] == "ObjectMotion" ||
+			(*it).tokens[0] == "CameraMotion" ||
+			(*it).tokens[0] == "LightMotion")) {
+
+			if (nodes.empty())
+				DefaultLogger::get()->error("LWS: Unexpected keyword: \'<Light|Object|Camera>Motion\'");
+			else {
+				ReadEnvelope_Old(it,root.children.end(),nodes.back(),version);
+			}
+		}
+		// 'Pre/PostBehavior': pre/post animation behaviour for LWSC 2
+		else if (version == 2 && (*it).tokens[0] == "Pre/PostBehavior") {
+			if (nodes.empty())
+				DefaultLogger::get()->error("LWS: Unexpected keyword: \'Pre/PostBehavior'");
+			else {
+				for (std::list<LWO::Envelope>::iterator it = nodes.back().channels.begin(); it != nodes.back().channels.end(); ++it) {
+					// two ints per envelope
+					LWO::Envelope& env = *it;
+					env.pre  = (LWO::PrePostBehaviour) strtol10(c,&c); SkipSpaces(&c);
+					env.post = (LWO::PrePostBehaviour) strtol10(c,&c); SkipSpaces(&c);
+				}
+			}
+		}
+		// 'ParentItem': specifies the parent of the current element
+		else if ((*it).tokens[0] == "ParentItem") {
+			if (nodes.empty())
+				DefaultLogger::get()->error("LWS: Unexpected keyword: \'ParentItem\'");
+
+			else nodes.back().parent = strtol16(c,&c);
+		}
+		// 'ParentObject': deprecated one for older formats
+		else if (version < 3 && (*it).tokens[0] == "ParentObject") {
+			if (nodes.empty())
+				DefaultLogger::get()->error("LWS: Unexpected keyword: \'ParentObject\'");
+
+			else { 
+				nodes.back().parent = strtol10(c,&c) | (1u << 28u);
+			}
+		}
+		// 'AddCamera': add a camera to the scenegraph
+		else if ((*it).tokens[0] == "AddCamera") {
+
+			// add node to list
+			LWS::NodeDesc d;
+			d.type = LWS::NodeDesc::CAMERA;
+
+			if (version >= 4) { // handle LWSC 4 explicit ID
+				d.number = strtol16(c,&c) & AI_LWS_MASK;
+			}
+			else d.number = cur_camera++;
+			nodes.push_back(d);
+
+			num_camera++;
+		}
+		// 'CameraName': set name of currently active camera
+		else if ((*it).tokens[0] == "CameraName") {
+			if (nodes.empty() || nodes.back().type != LWS::NodeDesc::CAMERA)
+				DefaultLogger::get()->error("LWS: Unexpected keyword: \'CameraName\'");
+
+			else nodes.back().name = c;
+		}
+		// 'AddLight': add a light to the scenegraph
+		else if ((*it).tokens[0] == "AddLight") {
+
+			// add node to list
+			LWS::NodeDesc d;
+			d.type = LWS::NodeDesc::LIGHT;
+
+			if (version >= 4) { // handle LWSC 4 explicit ID
+				d.number = strtol16(c,&c) & AI_LWS_MASK;
+			}
+			else d.number = cur_light++;
+			nodes.push_back(d);
+
+			num_light++;
+		}
+		// 'LightName': set name of currently active light
+		else if ((*it).tokens[0] == "LightName") {
+			if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT)
+				DefaultLogger::get()->error("LWS: Unexpected keyword: \'LightName\'");
+
+			else nodes.back().name = c;
+		}
+		// 'LightIntensity': set intensity of currently active light
+		else if ((*it).tokens[0] == "LightIntensity" || (*it).tokens[0] == "LgtIntensity" ) {
+			if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT)
+				DefaultLogger::get()->error("LWS: Unexpected keyword: \'LightIntensity\'");
+
+			else fast_atof_move(c, nodes.back().lightIntensity );
+			
+		}
+		// 'LightType': set type of currently active light
+		else if ((*it).tokens[0] == "LightType") {
+			if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT)
+				DefaultLogger::get()->error("LWS: Unexpected keyword: \'LightType\'");
+
+			else nodes.back().lightType = strtol10(c);
+			
+		}
+		// 'LightFalloffType': set falloff type of currently active light
+		else if ((*it).tokens[0] == "LightFalloffType") {
+			if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT)
+				DefaultLogger::get()->error("LWS: Unexpected keyword: \'LightFalloffType\'");
+
+			else nodes.back().lightFalloffType = strtol10(c);
+			
+		}
+		// 'LightConeAngle': set cone angle of currently active light
+		else if ((*it).tokens[0] == "LightConeAngle") {
+			if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT)
+				DefaultLogger::get()->error("LWS: Unexpected keyword: \'LightConeAngle\'");
+
+			else nodes.back().lightConeAngle = fast_atof(c);
+			
+		}
+		// 'LightEdgeAngle': set area where we're smoothing from min to max intensity
+		else if ((*it).tokens[0] == "LightEdgeAngle") {
+			if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT)
+				DefaultLogger::get()->error("LWS: Unexpected keyword: \'LightEdgeAngle\'");
+
+			else nodes.back().lightEdgeAngle = fast_atof(c);
+			
+		}
+		// 'LightColor': set color of currently active light
+		else if ((*it).tokens[0] == "LightColor") {
+			if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT)
+				DefaultLogger::get()->error("LWS: Unexpected keyword: \'LightColor\'");
+
+			else {
+				c = fast_atof_move(c, (float&) nodes.back().lightColor.r );
+				SkipSpaces(&c);
+				c = fast_atof_move(c, (float&) nodes.back().lightColor.g );
+				SkipSpaces(&c);
+				c = fast_atof_move(c, (float&) nodes.back().lightColor.b );
+			}
+		}
+
+		// 'PivotPosition': position of local transformation origin
+		else if ((*it).tokens[0] == "PivotPosition" || (*it).tokens[0] == "PivotPoint") {
+			if (nodes.empty())
+				DefaultLogger::get()->error("LWS: Unexpected keyword: \'PivotPosition\'");
+			else {
+				c = fast_atof_move(c, (float&) nodes.back().pivotPos.x );
+				SkipSpaces(&c);
+				c = fast_atof_move(c, (float&) nodes.back().pivotPos.y );
+				SkipSpaces(&c);
+				c = fast_atof_move(c, (float&) nodes.back().pivotPos.z );
+			}
+		}
+	}
+
+	// resolve parenting
+	for (std::list<LWS::NodeDesc>::iterator it = nodes.begin(); it != nodes.end(); ++it) {
+	
+		// check whether there is another node which calls us a parent
+		for (std::list<LWS::NodeDesc>::iterator dit = nodes.begin(); dit != nodes.end(); ++dit) {
+			if (dit != it && *it == (*dit).parent) {
+				if ((*dit).parent_resolved) {
+					// fixme: it's still possible to produce an overflow due to cross references ..
+					DefaultLogger::get()->error("LWS: Found cross reference in scenegraph");
+					continue;
+				}
+
+				(*it).children.push_back(&*dit);
+				(*dit).parent_resolved = &*it;
+			}
+		}
+	}
+
+	// find out how many nodes have no parent yet
+	unsigned int no_parent = 0;
+	for (std::list<LWS::NodeDesc>::iterator it = nodes.begin(); it != nodes.end(); ++it) {
+		if (!(*it).parent_resolved)
+			++ no_parent;
+	}
+	if (!no_parent)
+		throw new ImportErrorException("LWS: Unable to find scene root node");
+
+
+	// Load all subsequent files
+	batch.LoadAll();
+
+	// and build the final output graph by attaching the loaded external
+	// files to ourselves. first build a master graph 
+	aiScene* master = new aiScene();
+	aiNode* nd = master->mRootNode = new aiNode();
+
+	// allocate storage for cameras&lights
+	if (num_camera) {
+		master->mCameras = new aiCamera*[master->mNumCameras = num_camera];
+	}
+	aiCamera** cams = master->mCameras;
+	if (num_light) {
+		master->mLights = new aiLight*[master->mNumLights = num_light];
+	}
+	aiLight** lights = master->mLights;
+
+	std::vector<AttachmentInfo> attach;
+	std::vector<aiNodeAnim*> anims;
+
+	nd->mName.Set("<LWSRoot>");
+	nd->mChildren = new aiNode*[no_parent];
+	for (std::list<LWS::NodeDesc>::iterator it = nodes.begin(); it != nodes.end(); ++it) {
+		if (!(*it).parent_resolved) {
+			aiNode* ro = nd->mChildren[ nd->mNumChildren++ ] = new aiNode();
+			ro->mParent = nd;
+
+			// ... and build the scene graph. If we encounter object nodes,
+			// add then to our attachment table.
+			BuildGraph(ro,*it, attach, batch, cams, lights, anims);
+		}
+	}
+
+	// create a master animation channel for us
+	if (anims.size()) {
+		master->mAnimations = new aiAnimation*[master->mNumAnimations = 1];
+		aiAnimation* anim = master->mAnimations[0] = new aiAnimation();
+		anim->mName.Set("LWSMasterAnim");
+
+		// LWS uses seconds as time units, but we convert to frames
+		anim->mTicksPerSecond = fps;
+		anim->mDuration = last-(first-1); /* fixme ... zero or one-based?*/
+
+		anim->mChannels = new aiNodeAnim*[anim->mNumChannels = anims.size()];
+		std::copy(anims.begin(),anims.end(),anim->mChannels);
+	}
+
+	// OK ... finally build the output graph
+	SceneCombiner::MergeScenes(&pScene,master,attach,
+		AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES    | (!configSpeedFlag ? (
+		AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY | AI_INT_MERGE_SCENE_GEN_UNIQUE_MATNAMES) : 0));
+
+	// Check flags
+	if (!pScene->mNumMeshes || !pScene->mNumMaterials) {
+		pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
+
+		if (pScene->mNumAnimations) {
+			// construct skeleton mesh
+			SkeletonMeshBuilder builder(pScene);
+		}
+	}
 }

+ 143 - 14
code/LWSLoader.h

@@ -38,10 +38,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ----------------------------------------------------------------------
 */
 
-/** @file Declaration of the .LWS (LightWave Scene Format) importer class. */
+/** @file  LWSLoader.h
+ *  @brief Declaration of the LightWave scene importer class. 
+ */
 #ifndef AI_LWSLOADER_H_INCLUDED
 #define AI_LWSLOADER_H_INCLUDED
 
+#include "LWOFileData.h"
+#include "SceneCombiner.h"
 
 namespace Assimp	{
 	namespace LWS	{
@@ -49,17 +53,107 @@ namespace Assimp	{
 // ---------------------------------------------------------------------------
 /** Represents an element in a LWS file.
  *
- *  This can either be a single data line - <name> <value> or it can
- *  be a data group - { name <data_line0> ... n }
+ *  This can either be a single data line - <name> <value> or a data
+ *  group - { name <data_line0> ... n }
  */
 class Element
 {
-	std::string name, data;
+public:
+	Element()
+	{}
+
+	// first: name, second: rest
+	std::string tokens[2];
 	std::list<Element> children;
 
-	void Parse (const char* buffer);
+	//! Recursive parsing function
+	void Parse (const char*& buffer);
 };
 
+#define AI_LWS_MASK (0xffffffff >> 4u)
+
+// ---------------------------------------------------------------------------
+/** Represents a LWS scenegraph element
+ */
+struct NodeDesc
+{
+	NodeDesc()
+		:	number	(0)
+		,	parent	(0)
+		,	name	("")
+		,	parent_resolved (NULL)
+		,	lightIntensity (1.f)
+		,	lightColor (1.f,1.f,1.f)
+		,	lightType (0)
+		,	lightFalloffType (0)
+		,	lightConeAngle (45.f)
+	{}
+
+	enum {
+	
+		OBJECT = 1,
+		LIGHT  = 2,
+		CAMERA = 3,
+		BONE   = 4,
+	} type; // type of node
+
+	// if object: path
+	std::string path;
+	unsigned int id;
+
+	// number of object
+	unsigned int number;
+
+	// index of parent index
+	unsigned int parent;
+
+	// lights & cameras & dummies: name
+	const char* name;
+
+	// animation channels
+	std::list< LWO::Envelope > channels;
+
+	// position of pivot point
+	aiVector3D pivotPos;
+
+
+
+	// color of light source
+	aiColor3D lightColor;
+
+	// intensity of light source
+	float lightIntensity;
+
+	// type of light source
+	unsigned int lightType;
+
+	// falloff type of light source
+	unsigned int lightFalloffType;
+
+	// cone angle of (spot) light source
+	float lightConeAngle;
+
+	// soft cone angle of (spot) light source
+	float lightEdgeAngle;
+
+
+
+	// list of resolved children
+	std::list< NodeDesc* > children;
+
+	// resolved parent node
+	NodeDesc* parent_resolved;
+
+
+	// for std::find()
+	bool operator == (unsigned int num)  const {
+		if (!num)
+			return false;
+		unsigned int _type = num >> 28u;
+		
+		return _type == type && (num & AI_LWS_MASK) == number;
+	}
+};
 
 } // end namespace LWS
 
@@ -84,28 +178,63 @@ protected:
 public:
 
 	// -------------------------------------------------------------------
-	/** Returns whether the class can handle the format of the given file. 
-	* See BaseImporter::CanRead() for details.	*/
-	bool CanRead( const std::string& pFile, IOSystem* pIOHandler) const;
+	// Check whether we can read a specific file
+	bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
+		bool checkSig) const;
 
 protected:
 
 	// -------------------------------------------------------------------
-	/** Called by Importer::GetExtensionList() for each loaded importer.
-	 * See BaseImporter::GetExtensionList() for details
-	 */
+	// Get list of supported extensions
 	void GetExtensionList(std::string& append);
 
 	// -------------------------------------------------------------------
-	/** Imports the given file into the given scene structure. 
-	 * See BaseImporter::InternReadFile() for details
-	 */
+	// Import file into given scene data structure
 	void InternReadFile( const std::string& pFile, aiScene* pScene, 
 		IOSystem* pIOHandler);
 
+	// -------------------------------------------------------------------
+	// Setup import properties
+	void SetupProperties(const Importer* pImp);
+
+private:
+
+
+	// -------------------------------------------------------------------
+	// Read an envelope description
+	void ReadEnvelope(const LWS::Element& dad, LWO::Envelope& out );
+
+	// -------------------------------------------------------------------
+	// Read an envelope description for the older LW file format
+	void ReadEnvelope_Old(std::list< LWS::Element >::const_iterator& it, 
+		const std::list< LWS::Element >::const_iterator& end,
+		LWS::NodeDesc& nodes,
+		unsigned int version);
+
+	// -------------------------------------------------------------------
+	// Setup a nice name for a node 
+	void SetupNodeName(aiNode* nd, LWS::NodeDesc& src);
+
+	// -------------------------------------------------------------------
+	// Recursively build the scenegraph
+	void BuildGraph(aiNode* nd, 
+		LWS::NodeDesc& src, 
+		std::vector<AttachmentInfo>& attach,
+		BatchLoader& batch,
+		aiCamera**& camOut,
+		aiLight**& lightOut, 
+		std::vector<aiNodeAnim*>& animOut);
+
+	// -------------------------------------------------------------------
+	// Try several dirs until we find the right location of a LWS file.
+	std::string FindLWOFile(const std::string& in);
+
 private:
 
+	bool configSpeedFlag;
+	IOSystem* io;
 
+	double first,last,fps;
 };
 
 } // end of namespace Assimp

+ 7 - 17
code/MD2FileData.h

@@ -38,21 +38,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ----------------------------------------------------------------------
 */
 
-/** @file Defines the helper data structures for importing MD2 files  
-
-**********************************************************************
-File format specification: 
-//http://linux.ucla.edu/~phaethon/q3/formats/md2-schoenblum.html
-**********************************************************************
-
-*/
+/** @file  MD2FileData.h
+ *  @brief Defines helper data structures for importing MD2 files  
+ *  http://linux.ucla.edu/~phaethon/q3/formats/md2-schoenblum.html
+ */
 #ifndef AI_MD2FILEHELPER_H_INC
 #define AI_MD2FILEHELPER_H_INC
 
-#include <string>
-#include <vector>
-#include <sstream>
-
 #include "../include/aiTypes.h"
 #include "../include/aiMesh.h"
 #include "../include/aiAnim.h"
@@ -62,11 +54,9 @@ File format specification:
 namespace Assimp	{
 namespace MD2	{
 
-// to make it easier for ourselfes, we test the magic word against both "endianesses"
-#define MD2_MAKE(string) ((uint32_t)((string[0] << 24) + (string[1] << 16) + (string[2] << 8) + string[3]))
-
-#define AI_MD2_MAGIC_NUMBER_BE	MD2_MAKE("IDP2")
-#define AI_MD2_MAGIC_NUMBER_LE	MD2_MAKE("2PDI")
+// to make it easier for us, we test the magic word against both "endianesses"
+#define AI_MD2_MAGIC_NUMBER_BE	AI_MAKE_MAGIC("IDP2")
+#define AI_MD2_MAGIC_NUMBER_LE	AI_MAKE_MAGIC("2PDI")
 
 // common limitations
 #define AI_MD2_VERSION			15

+ 26 - 23
code/MD2Loader.cpp

@@ -62,11 +62,8 @@ using namespace Assimp::MD2;
 void MD2::LookupNormalIndex(uint8_t iNormalIndex,aiVector3D& vOut)
 {
 	// make sure the normal index has a valid value
-	if (iNormalIndex >= ARRAYSIZE(g_avNormals))
-	{
-		DefaultLogger::get()->warn("Index overflow in Quake II normal vector list (the "
-			" LUT has only 162 entries). ");
-
+	if (iNormalIndex >= ARRAYSIZE(g_avNormals))	{
+		DefaultLogger::get()->warn("Index overflow in Quake II normal vector list");
 		iNormalIndex = ARRAYSIZE(g_avNormals) - 1;
 	}
 	vOut = *((const aiVector3D*)(&g_avNormals[iNormalIndex]));
@@ -76,31 +73,37 @@ void MD2::LookupNormalIndex(uint8_t iNormalIndex,aiVector3D& vOut)
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
 MD2Importer::MD2Importer()
-{
-}
+{}
 
 // ------------------------------------------------------------------------------------------------
 // Destructor, private as well 
 MD2Importer::~MD2Importer()
+{}
+
+// ------------------------------------------------------------------------------------------------
+// Returns whether the class can handle the format of the given file. 
+bool MD2Importer::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
 {
+	const std::string extension = GetExtension(pFile);
+	if (extension == "md2")
+		return true;
+
+	// if check for extension is not enough, check for the magic tokens 
+	if (!extension.length() || checkSig) {
+		uint32_t tokens[1]; 
+		tokens[0] = AI_MD2_MAGIC_NUMBER_LE;
+		return CheckMagicToken(pIOHandler,pFile,tokens,1);
+	}
+	return false;
 }
 
 // ------------------------------------------------------------------------------------------------
-// Returns whether the class can handle the format of the given file. 
-bool MD2Importer::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
+// Get a list of all extensions supported by this loader
+void MD2Importer::GetExtensionList(std::string& append)
 {
-	// simple check of file extension is enough for the moment
-	std::string::size_type pos = pFile.find_last_of('.');
-	// no file extension - can't read
-	if( pos == std::string::npos)
-		return false;
-	
-	std::string extension = pFile.substr( pos);
-	for( std::string::iterator it = extension.begin(); it != extension.end(); ++it)
-		*it = tolower( *it);
-
-	return ( extension == ".md2");
+	append.append("*.md2");
 }
+
 // ------------------------------------------------------------------------------------------------
 // Setup configuration properties
 void MD2Importer::SetupProperties(const Importer* pImp)
@@ -161,8 +164,8 @@ void MD2Importer::ValidateHeader( )
 
 	if (m_pcHeader->numFrames <= configFrameID )
 		throw new ImportErrorException("The requested frame is not existing the file");
-
 }
+
 // ------------------------------------------------------------------------------------------------
 // Imports the given file into the given scene structure. 
 void MD2Importer::InternReadFile( const std::string& pFile, 
@@ -319,14 +322,14 @@ void MD2Importer::InternReadFile( const std::string& pFile,
 		pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_AMBIENT);
 
 		aiString szName;
-		szName.Set("MD2Default");
+		szName.Set(AI_DEFAULT_TEXTURED_MATERIAL_NAME);
 		pcHelper->AddProperty(&szName,AI_MATKEY_NAME);
 
 		aiString sz;
 
 		// TODO: Try to guess the name of the texture file from the model file name
 
-		sz.Set("texture_dummmy.bmp");
+		sz.Set("$texture_dummy.bmp");
 		pcHelper->AddProperty(&sz,AI_MATKEY_TEXTURE_DIFFUSE(0));
 	}
 

+ 8 - 9
code/MD2Loader.h

@@ -38,7 +38,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ----------------------------------------------------------------------
 */
 
-/** @file Definition of the .MD2 importer class. */
+/** @file  MD2Loader.h
+ *  @brief Declaration of the .MD2 importer class.
+ */
 #ifndef AI_MD2LOADER_H_INCLUDED
 #define AI_MD2LOADER_H_INCLUDED
 
@@ -48,14 +50,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 struct aiNode;
 #include "MD2FileData.h"
 
-namespace Assimp
-{
+namespace Assimp	{
 class MaterialHelper;
 
 using namespace MD2;
 
 // ---------------------------------------------------------------------------
-/** Used to load MD2 files
+/** Importer class for MD2
 */
 class MD2Importer : public BaseImporter
 {
@@ -73,7 +74,8 @@ public:
 	// -------------------------------------------------------------------
 	/** Returns whether the class can handle the format of the given file. 
 	* See BaseImporter::CanRead() for details.	*/
-	bool CanRead( const std::string& pFile, IOSystem* pIOHandler) const;
+	bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
+		bool checkSig) const;
 
 
 	// -------------------------------------------------------------------
@@ -89,10 +91,7 @@ protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 */
-	void GetExtensionList(std::string& append)
-	{
-		append.append("*.md2");
-	}
+	void GetExtensionList(std::string& append);
 
 	// -------------------------------------------------------------------
 	/** Imports the given file into the given scene structure. 

+ 3 - 5
code/MD3FileData.h

@@ -59,11 +59,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 namespace Assimp	{
 namespace MD3	{
 
-// to make it easier for ourselfes, we test the magic word against both "endianesses"
-#define MD3_MAKE(string) ((uint32_t)((string[0] << 24) + (string[1] << 16) + (string[2] << 8) + string[3]))
-
-#define AI_MD3_MAGIC_NUMBER_BE	MD3_MAKE("IDP3")
-#define AI_MD3_MAGIC_NUMBER_LE	MD3_MAKE("3PDI")
+// to make it easier for us, we test the magic word against both "endianesses"
+#define AI_MD3_MAGIC_NUMBER_BE	AI_MAKE_MAGIC("IDP3")
+#define AI_MD3_MAGIC_NUMBER_LE	AI_MAKE_MAGIC("3PDI")
 
 // common limitations
 #define AI_MD3_VERSION			15

+ 53 - 24
code/MD3Loader.cpp

@@ -344,18 +344,19 @@ MD3Importer::~MD3Importer()
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file. 
-bool MD3Importer::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
+bool MD3Importer::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
 {
-	// simple check of file extension is enough for the moment
-	std::string::size_type pos = pFile.find_last_of('.');
-	// no file extension - can't read
-	if( pos == std::string::npos)
-		return false;
-	std::string extension = pFile.substr( pos);
-	for( std::string::iterator it = extension.begin(); it != extension.end(); ++it)
-		*it = tolower( *it);
+	const std::string extension = GetExtension(pFile);
+	if (extension == "md3")
+		return true;
 
-	return ( extension == ".md3");
+	// if check for extension is not enough, check for the magic tokens 
+	if (!extension.length() || checkSig) {
+		uint32_t tokens[1]; 
+		tokens[0] = AI_MD3_MAGIC_NUMBER_LE;
+		return CheckMagicToken(pIOHandler,pFile,tokens,1);
+	}
+	return false;
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -436,6 +437,9 @@ void MD3Importer::SetupProperties(const Importer* pImp)
 
 	// AI_CONFIG_IMPORT_MD3_SHADER_SRC
 	configShaderFile = (pImp->GetPropertyString(AI_CONFIG_IMPORT_MD3_SHADER_SRC,""));
+
+	// AI_CONFIG_FAVOUR_SPEED
+	configSpeedFlag = (0 != pImp->GetPropertyInteger(AI_CONFIG_FAVOUR_SPEED,0));
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -458,10 +462,7 @@ void MD3Importer::ReadSkin(Q3Shader::SkinData& fill) const
 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);
-
+	std::string::size_type s = path.find_last_of("\\/",path.length()-2);
 	const std::string model_file = path.substr(s+1,path.length()-(s+2));
 
 	// If no specific dir or file is given, use our default search behaviour
@@ -486,6 +487,24 @@ void MD3Importer::ReadShader(Q3Shader::ShaderData& fill) const
 	}
 }
 
+// ------------------------------------------------------------------------------------------------
+// Tiny helper to remove a single node from its parent' list
+void RemoveSingleNodeFromList(aiNode* nd)
+{
+	if (!nd || nd->mNumChildren || !nd->mParent)return;
+	aiNode* par = nd->mParent;
+	for (unsigned int i = 0; i < par->mNumChildren;++i) {
+		if (par->mChildren[i] == nd) { 
+			--par->mNumChildren;
+			for (;i < par->mNumChildren;++i) {
+				par->mChildren[i] = par->mChildren[i+1];
+			}
+			delete nd;
+			break;
+		}
+	}
+}
+
 // ------------------------------------------------------------------------------------------------
 // Read a multi-part Q3 player model
 bool MD3Importer::ReadMultipartFile()
@@ -520,32 +539,32 @@ bool MD3Importer::ReadMultipartFile()
 
 		// now read these three files
 		BatchLoader batch(mIOHandler);
-		batch.AddLoadRequest(lower,0,&props);
-		batch.AddLoadRequest(upper,0,&props);
-		batch.AddLoadRequest(head,0,&props);
+		unsigned int _lower = batch.AddLoadRequest(lower,0,&props);
+		unsigned int _upper = batch.AddLoadRequest(upper,0,&props);
+		unsigned int _head  = batch.AddLoadRequest(head,0,&props);
 		batch.LoadAll();
 
 		// now construct a dummy scene to place these three parts in
 		aiScene* master   = new aiScene();
 		aiNode* nd = master->mRootNode = new aiNode();
-		nd->mName.Set("<M3D_Player>");
+		nd->mName.Set("<MD3_Player>");
 
 		// ... and get them. We need all of them.
-		scene_lower = batch.GetImport(lower);
+		scene_lower = batch.GetImport(_lower);
 		if (!scene_lower) {
 			DefaultLogger::get()->error("M3D: Failed to read multipart model, lower.md3 fails to load");
 			failure = "lower";
 			goto error_cleanup;
 		}
 
-		scene_upper = batch.GetImport(upper);
+		scene_upper = batch.GetImport(_upper);
 		if (!scene_upper) {
 			DefaultLogger::get()->error("M3D: Failed to read multipart model, upper.md3 fails to load");
 			failure = "upper";
 			goto error_cleanup;
 		}
 
-		scene_head  = batch.GetImport(head);
+		scene_head  = batch.GetImport(_head);
 		if (!scene_head) {
 			DefaultLogger::get()->error("M3D: Failed to read multipart model, head.md3 fails to load");
 			failure = "head";
@@ -555,6 +574,7 @@ bool MD3Importer::ReadMultipartFile()
 		// build attachment infos. search for typical Q3 tags
 
 		// original root
+		scene_lower->mRootNode->mName.Set("lower");
 		attach.push_back(AttachmentInfo(scene_lower, nd));
 
 		// tag_torso
@@ -563,6 +583,7 @@ bool MD3Importer::ReadMultipartFile()
 			DefaultLogger::get()->error("M3D: Failed to find attachment tag for multipart model: tag_torso expected");
 			goto error_cleanup;
 		}
+		scene_upper->mRootNode->mName.Set("upper");
 		attach.push_back(AttachmentInfo(scene_upper,tag_torso));
 
 		// tag_head
@@ -571,13 +592,21 @@ bool MD3Importer::ReadMultipartFile()
 			DefaultLogger::get()->error("M3D: Failed to find attachment tag for multipart model: tag_head expected");
 			goto error_cleanup;
 		}
+		scene_head->mRootNode->mName.Set("head");
 		attach.push_back(AttachmentInfo(scene_head,tag_head));
 
+		// Remove tag_head and tag_torso from all other model parts ...
+		// this ensures (together with AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY)
+		// that tag_torso/tag_head is also the name of the (unique) output node
+		RemoveSingleNodeFromList (scene_upper->mRootNode->FindNode("tag_torso"));
+		RemoveSingleNodeFromList (scene_head-> mRootNode->FindNode("tag_head" ));
+
 		// and merge the scenes
 		SceneCombiner::MergeScenes(&mScene,master, attach,
-			AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES |
-			AI_INT_MERGE_SCENE_GEN_UNIQUE_MATNAMES |
-			AI_INT_MERGE_SCENE_RESOLVE_CROSS_ATTACHMENTS);
+			AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES          |
+			AI_INT_MERGE_SCENE_GEN_UNIQUE_MATNAMES       |
+			AI_INT_MERGE_SCENE_RESOLVE_CROSS_ATTACHMENTS |
+			(!configSpeedFlag ? AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY : 0));
 
 		return true;
 

+ 5 - 1
code/MD3Loader.h

@@ -225,7 +225,8 @@ public:
 	// -------------------------------------------------------------------
 	/** Returns whether the class can handle the format of the given file. 
 	* See BaseImporter::CanRead() for details.	*/
-	bool CanRead( const std::string& pFile, IOSystem* pIOHandler) const;
+	bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
+		bool checkSig) const;
 
 
 	// -------------------------------------------------------------------
@@ -297,6 +298,9 @@ protected:
 	/** Configuration option: name or path of shader */
 	std::string configShaderFile;
 
+	/** Configuration option: speed flag was set? */
+	bool configSpeedFlag;
+
 	/** Header of the MD3 file */
 	BE_NCONST MD3::Header* pcHeader;
 

+ 30 - 30
code/MD5Loader.cpp

@@ -39,7 +39,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ---------------------------------------------------------------------------
 */
 
-/** @file Implementation of the MD5 importer class */
+/** @file  MD5Loader.cpp
+ *  @brief Implementation of the MD5 importer class 
+ */
 
 #include "AssimpPCH.h"
 #ifndef ASSIMP_BUILD_NO_MD5_IMPORTER
@@ -56,34 +58,35 @@ using namespace Assimp;
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
 MD5Importer::MD5Importer()
-{
-	// nothing to do here
-}
+{}
 
 // ------------------------------------------------------------------------------------------------
 // Destructor, private as well 
 MD5Importer::~MD5Importer()
+{}
+
+// ------------------------------------------------------------------------------------------------
+// Returns whether the class can handle the format of the given file. 
+bool MD5Importer::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
 {
-	// nothing to do here
+	const std::string extension = GetExtension(pFile);
+
+	if (extension == "md5anim" || extension == "md5mesh")
+		return true;
+	else if (!extension.length() || checkSig)	{
+		if (!pIOHandler)
+			return true;
+		const char* tokens[] = {"MD5Version"};
+		return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1);
+	}
+	return false;
 }
+
 // ------------------------------------------------------------------------------------------------
-// Returns whether the class can handle the format of the given file. 
-bool MD5Importer::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
+// Get list of all supported extensions
+void MD5Importer::GetExtensionList(std::string& append)
 {
-	// simple check of file extension is enough for the moment
-	std::string::size_type pos = pFile.find_last_of('.');
-	// no file extension - can't read
-	if( pos == std::string::npos)
-		return false;
-	std::string extension = pFile.substr( pos);
-
-	if (extension.length() < 4)return false;
-	if (extension[0] != '.')return false;
-
-	if (extension[1] != 'm' && extension[1] != 'M')return false;
-	if (extension[2] != 'd' && extension[2] != 'D')return false;
-	if (extension[3] != '5')return false;
-	return true;
+	append.append("*.md5mesh;*.md5anim");
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -93,17 +96,17 @@ void MD5Importer::InternReadFile(
 {
 	// remove the file extension
 	std::string::size_type pos = pFile.find_last_of('.');
-	this->mFile = pFile.substr(0,pos+1);
+	mFile = pFile.substr(0,pos+1);
 	this->pIOHandler = pIOHandler;
 	this->pScene = pScene;
 
 	bHadMD5Mesh = bHadMD5Anim = false;
 
 	// load the animation keyframes
-	this->LoadMD5AnimFile();
+	LoadMD5AnimFile();
 
 	// load the mesh vertices and bones
-	this->LoadMD5MeshFile();
+	LoadMD5MeshFile();
 
 	// make sure we return no incomplete data
 	if (!bHadMD5Mesh && !bHadMD5Anim)
@@ -260,7 +263,7 @@ void MD5Importer::LoadMD5MeshFile ()
 	bHadMD5Mesh = true;
 
 	// now load the file into memory
-	this->LoadFileIntoMemory(file.get());
+	LoadFileIntoMemory(file.get());
 
 	// now construct a parser and parse the file
 	MD5::MD5Parser parser(mBuffer,fileSize);
@@ -341,10 +344,7 @@ void MD5Importer::LoadMD5MeshFile ()
 		unsigned int* piCount = new unsigned int[meshParser.mJoints.size()];
 		::memset(piCount,0,sizeof(unsigned int)*meshParser.mJoints.size());
 
-		for (MD5::VertexList::const_iterator
-			iter =  meshSrc.mVertices.begin();
-			iter != meshSrc.mVertices.end();++iter,++pv)
-		{
+		for (MD5::VertexList::const_iterator iter =  meshSrc.mVertices.begin();iter != meshSrc.mVertices.end();++iter,++pv) {
 			for (unsigned int jub = (*iter).mFirstWeight, w = jub; w < jub + (*iter).mNumWeights;++w)
 			{
 				MD5::WeightDesc& desc = meshSrc.mWeights[w];
@@ -444,7 +444,7 @@ void MD5Importer::LoadMD5MeshFile ()
 // ------------------------------------------------------------------------------------------------
 void MD5Importer::LoadMD5AnimFile ()
 {
-	std::string pFile = this->mFile + "MD5ANIM";
+	std::string pFile = mFile + "MD5ANIM";
 	boost::scoped_ptr<IOStream> file( pIOHandler->Open( pFile, "rb"));
 
 	// Check whether we can read from the file

+ 7 - 8
code/MD5Loader.h

@@ -39,8 +39,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
 
 
-/** @file Definition of the .MD5 importer class.
-http://www.modwiki.net/wiki/MD5_(file_format)
+/** @file   MD5Loader.h
+ *  @brief Definition of the .MD5 importer class.
+ *  http://www.modwiki.net/wiki/MD5_(file_format)
 */
 #ifndef AI_MD5LOADER_H_INCLUDED
 #define AI_MD5LOADER_H_INCLUDED
@@ -74,7 +75,8 @@ public:
 	// -------------------------------------------------------------------
 	/** Returns whether the class can handle the format of the given file. 
 	* See BaseImporter::CanRead() for details.	*/
-	bool CanRead( const std::string& pFile, IOSystem* pIOHandler) const;
+	bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
+		bool checkSig) const;
 
 protected:
 
@@ -82,11 +84,8 @@ protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 */
-	void GetExtensionList(std::string& append)
-	{
-		append.append("*.md5mesh;*.md5anim");
-	}
-
+	void GetExtensionList(std::string& append);
+	
 	// -------------------------------------------------------------------
 	/** Imports the given file into the given scene structure. 
 	* See BaseImporter::InternReadFile() for details

+ 6 - 5
code/MD5Parser.cpp

@@ -112,21 +112,22 @@ void MD5Parser::ParseHeader()
 	if (0 != ASSIMP_strincmp(buffer,"MD5Version",10) ||
 		!IsSpace(*(buffer+=10)))
 	{
-		this->ReportError("Invalid MD5 file: MD5Version tag has not been found");
+		ReportError("Invalid MD5 file: MD5Version tag has not been found");
 	}
 	SkipSpaces();
 	unsigned int iVer = ::strtol10(buffer,(const char**)&buffer);
 	if (10 != iVer)
 	{
-		this->ReportWarning("MD5 version tag is unknown (10 is expected)");
+		ReportWarning("MD5 version tag is unknown (10 is expected)");
 	}
-	this->SkipLine();
+	SkipLine();
 
 	// print the command line options to the console
+	// fix: can break the log length limit ...
 	char* sz = buffer;
 	while (!IsLineEnd( *buffer++));
-	DefaultLogger::get()->info(std::string(sz,(uintptr_t)(buffer-sz)));
-	this->SkipSpacesAndLineEnd();
+	DefaultLogger::get()->info(std::string(sz,std::min((uintptr_t)MAX_LOG_MESSAGE_LENGTH, (uintptr_t)(buffer-sz))));
+	SkipSpacesAndLineEnd();
 }
 // ------------------------------------------------------------------------------------------------
 bool MD5Parser::ParseSection(Section& out)

+ 18 - 20
code/MD5Parser.h

@@ -39,21 +39,20 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
 
 
-/** @file Definition of the .MD5 parser class.
-http://www.modwiki.net/wiki/MD5_(file_format)
-*/
+/** @file  MD5Parser.h
+ *  @brief Definition of the .MD5 parser class.
+ *  http://www.modwiki.net/wiki/MD5_(file_format)
+ */
 #ifndef AI_MD5PARSER_H_INCLUDED
 #define AI_MD5PARSER_H_INCLUDED
 
 #include "../include/aiTypes.h"
 #include "ParsingUtils.h"
-#include <vector>
 
 struct aiFace;
 
 namespace Assimp	{
-namespace MD5		{
-
+namespace MD5			{
 
 // ---------------------------------------------------------------------------
 /** Represents a single element in a MD5 file
@@ -335,12 +334,13 @@ public:
 	static void ReportWarning (const char* warn, unsigned int line);
 
 
-	inline void ReportError (const char* error)
-		{return ReportError(error, this->lineNumber);}
-
-	inline void ReportWarning (const char* warn)
-		{return ReportWarning(warn, this->lineNumber);}
+	void ReportError (const char* error) {
+		return ReportError(error, lineNumber);
+	}
 
+	void ReportWarning (const char* warn) {
+		return ReportWarning(warn, lineNumber);
+	}
 
 public:
 
@@ -367,24 +367,22 @@ private:
 
 	// override these functions to make sure the line counter gets incremented
 	// -------------------------------------------------------------------
-	inline bool SkipLine( const char* in, const char** out)
+	bool SkipLine( const char* in, const char** out)
 	{
 		++lineNumber;
 		return Assimp::SkipLine(in,out);
 	}
 	// -------------------------------------------------------------------
-	inline bool SkipLine( )
+	bool SkipLine( )
 	{
 		return SkipLine(buffer,(const char**)&buffer);
 	}
 	// -------------------------------------------------------------------
-	inline bool SkipSpacesAndLineEnd( const char* in, const char** out)
+	bool SkipSpacesAndLineEnd( const char* in, const char** out)
 	{
 		bool bHad = false;
-		while (true) 
-		{
-			if( *in == '\r' || *in == '\n')
-			{
+		while (true)	{
+			if( *in == '\r' || *in == '\n')	{
 				if (!bHad) // we open files in binary mode, so there could be \r\n sequences ...
 				{
 					bHad = true;
@@ -400,12 +398,12 @@ private:
 		return *in != '\0';
 	}
 	// -------------------------------------------------------------------
-	inline bool SkipSpacesAndLineEnd( )
+	bool SkipSpacesAndLineEnd( )
 	{
 		return SkipSpacesAndLineEnd(buffer,(const char**)&buffer);
 	}
 	// -------------------------------------------------------------------
-	inline bool SkipSpaces( )
+	bool SkipSpaces( )
 	{
 		return Assimp::SkipSpaces((const char**)&buffer);
 	}

+ 3 - 5
code/MDCFileData.h

@@ -60,11 +60,9 @@ namespace Assimp {
 namespace MDC {
 
 
-// to make it easier for ourselfes, we test the magic word against both "endianesses"
-#define MDC_MAKE(string) ((uint32_t)((string[0] << 24) + (string[1] << 16) + (string[2] << 8) + string[3]))
-
-#define AI_MDC_MAGIC_NUMBER_BE	MDC_MAKE("CPDI")
-#define AI_MDC_MAGIC_NUMBER_LE	MDC_MAKE("IDPC")
+// to make it easier for us, we test the magic word against both "endianesses"
+#define AI_MDC_MAGIC_NUMBER_BE	AI_MAKE_MAGIC("CPDI")
+#define AI_MDC_MAGIC_NUMBER_LE	AI_MAKE_MAGIC("IDPC")
 
 // common limitations
 #define AI_MDC_VERSION			2

+ 18 - 16
code/MDCLoader.cpp

@@ -87,23 +87,25 @@ MDCImporter::~MDCImporter()
 }
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file. 
-bool MDCImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
+bool MDCImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
 {
-	// simple check of file extension is enough for the moment
-	std::string::size_type pos = pFile.find_last_of('.');
-	// no file extension - can't read
-	if( pos == std::string::npos)
-		return false;
-	std::string extension = pFile.substr( pos);
-
-	if (extension.length() < 4)return false;
-	if (extension[0] != '.')return false;
-	
-	if( extension[1] != 'M' && extension[1] != 'm')return false; 
-	if( extension[2] != 'D' && extension[2] != 'd')return false; 
-	if( extension[3] != 'C' && extension[3] != 'c')return false; 
-
-	return true;
+	const std::string extension = GetExtension(pFile);
+	if (extension == "mdc")
+		return true;
+
+	// if check for extension is not enough, check for the magic tokens 
+	if (!extension.length() || checkSig) {
+		uint32_t tokens[1]; 
+		tokens[0] = AI_MDC_MAGIC_NUMBER_LE;
+		return CheckMagicToken(pIOHandler,pFile,tokens,1);
+	}
+	return false;
+}
+
+// ------------------------------------------------------------------------------------------------
+void MDCImporter::GetExtensionList(std::string& append)
+{
+	append.append("*.mdc");
 }
 
 // ------------------------------------------------------------------------------------------------

+ 8 - 9
code/MDCLoader.h

@@ -38,7 +38,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ----------------------------------------------------------------------
 */
 
-/** @file Definition of the MDC importer class. */
+/** @file  MDCLoader.h
+ *  @brief Definition of the MDC importer class. 
+ */
 #ifndef AI_MDCLOADER_H_INCLUDED
 #define AI_MDCLOADER_H_INCLUDED
 
@@ -48,12 +50,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "MDCFileData.h"
 #include "ByteSwap.h"
 
-namespace Assimp
-{
+namespace Assimp	{
 using namespace MDC;
 
 // ---------------------------------------------------------------------------
-/** Used to load MDC files
+/** Importer class to load the RtCW MDC file format
 */
 class MDCImporter : public BaseImporter
 {
@@ -71,7 +72,8 @@ public:
 	// -------------------------------------------------------------------
 	/** Returns whether the class can handle the format of the given file. 
 	* See BaseImporter::CanRead() for details.	*/
-	bool CanRead( const std::string& pFile, IOSystem* pIOHandler) const;
+	bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
+		bool checkSig) const;
 
 	// -------------------------------------------------------------------
 	/** Called prior to ReadFile().
@@ -86,10 +88,7 @@ protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 */
-	void GetExtensionList(std::string& append)
-	{
-		append.append("*.mdc");
-	}
+	void GetExtensionList(std::string& append);
 
 	// -------------------------------------------------------------------
 	/** Imports the given file into the given scene structure. 

+ 13 - 14
code/MDLFileData.h

@@ -58,30 +58,29 @@ namespace Assimp	{
 namespace MDL	{
 
 // -------------------------------------------------------------------------------------
-// to make it easier for ourselfes, we test the magic word against both "endianesses"
-#define MDL_MAKE(string) ((uint32_t)((string[0] << 24) + (string[1] << 16) + (string[2] << 8) + string[3]))
+// to make it easier for us, we test the magic word against both "endianesses"
 
 // magic bytes used in Quake 1 MDL meshes
-#define AI_MDL_MAGIC_NUMBER_BE	MDL_MAKE("IDPO")
-#define AI_MDL_MAGIC_NUMBER_LE	MDL_MAKE("OPDI")
+#define AI_MDL_MAGIC_NUMBER_BE	AI_MAKE_MAGIC("IDPO")
+#define AI_MDL_MAGIC_NUMBER_LE	AI_MAKE_MAGIC("OPDI")
 
 // magic bytes used in GameStudio A<very  low> MDL meshes
-#define AI_MDL_MAGIC_NUMBER_BE_GS3	MDL_MAKE("MDL2")
-#define AI_MDL_MAGIC_NUMBER_LE_GS3	MDL_MAKE("2LDM")
+#define AI_MDL_MAGIC_NUMBER_BE_GS3	AI_MAKE_MAGIC("MDL2")
+#define AI_MDL_MAGIC_NUMBER_LE_GS3	AI_MAKE_MAGIC("2LDM")
 
 // magic bytes used in GameStudio A4 MDL meshes
-#define AI_MDL_MAGIC_NUMBER_BE_GS4	MDL_MAKE("MDL3")
-#define AI_MDL_MAGIC_NUMBER_LE_GS4	MDL_MAKE("3LDM")
+#define AI_MDL_MAGIC_NUMBER_BE_GS4	AI_MAKE_MAGIC("MDL3")
+#define AI_MDL_MAGIC_NUMBER_LE_GS4	AI_MAKE_MAGIC("3LDM")
 
 // magic bytes used in GameStudio A5+ MDL meshes
-#define AI_MDL_MAGIC_NUMBER_BE_GS5a	MDL_MAKE("MDL4")
-#define AI_MDL_MAGIC_NUMBER_LE_GS5a	MDL_MAKE("4LDM")
-#define AI_MDL_MAGIC_NUMBER_BE_GS5b	MDL_MAKE("MDL5")
-#define AI_MDL_MAGIC_NUMBER_LE_GS5b	MDL_MAKE("5LDM")
+#define AI_MDL_MAGIC_NUMBER_BE_GS5a	AI_MAKE_MAGIC("MDL4")
+#define AI_MDL_MAGIC_NUMBER_LE_GS5a	AI_MAKE_MAGIC("4LDM")
+#define AI_MDL_MAGIC_NUMBER_BE_GS5b	AI_MAKE_MAGIC("MDL5")
+#define AI_MDL_MAGIC_NUMBER_LE_GS5b	AI_MAKE_MAGIC("5LDM")
 
 // magic bytes used in GameStudio A7+ MDL meshes
-#define AI_MDL_MAGIC_NUMBER_BE_GS7	MDL_MAKE("MDL7")
-#define AI_MDL_MAGIC_NUMBER_LE_GS7	MDL_MAKE("7LDM")
+#define AI_MDL_MAGIC_NUMBER_BE_GS7	AI_MAKE_MAGIC("MDL7")
+#define AI_MDL_MAGIC_NUMBER_LE_GS7	AI_MAKE_MAGIC("7LDM")
 
 
 // common limitations for Quake1 meshes. The loader does not check them,

+ 26 - 17
code/MDLLoader.cpp

@@ -77,32 +77,41 @@ MDLImporter::~MDLImporter()
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file. 
-bool MDLImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
+bool MDLImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
 {
-	(void)pIOHandler; //this avoids the compiler warning of unused element
-	// simple check of file extension is enough for the moment
-	std::string::size_type pos = pFile.find_last_of('.');
-	// no file extension - can't read
-	if( pos == std::string::npos)
-		return false;
-
-	std::string extension = pFile.substr( pos);
-	for( std::string::iterator it = extension.begin(); it != extension.end(); ++it)
-		*it = tolower( *it);
-
-	return extension == ".mdl";
+	const std::string extension = GetExtension(pFile);
+	if (extension == "mdl" )
+		return true;
+
+	// if check for extension is not enough, check for the magic tokens 
+	if (!extension.length() || checkSig) {
+		uint32_t tokens[8]; 
+		tokens[0] = AI_MDL_MAGIC_NUMBER_LE_HL2a;
+		tokens[1] = AI_MDL_MAGIC_NUMBER_LE_HL2b;
+		tokens[2] = AI_MDL_MAGIC_NUMBER_LE_GS7;
+		tokens[3] = AI_MDL_MAGIC_NUMBER_LE_GS5b;
+		tokens[4] = AI_MDL_MAGIC_NUMBER_LE_GS5a;
+		tokens[5] = AI_MDL_MAGIC_NUMBER_LE_GS4;
+		tokens[6] = AI_MDL_MAGIC_NUMBER_LE_GS3;
+		tokens[7] = AI_MDL_MAGIC_NUMBER_LE;
+		return CheckMagicToken(pIOHandler,pFile,tokens,7,0);
+	}
+	return false;
 }
 // ------------------------------------------------------------------------------------------------
 // Setup configuration properties
 void MDLImporter::SetupProperties(const Importer* pImp)
 {
-	// The AI_CONFIG_IMPORT_MDL_KEYFRAME option overrides the
+	configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MDL_KEYFRAME,0xffffffff);
+
+	// The 
+	// AI_CONFIG_IMPORT_MDL_KEYFRAME option overrides the
 	// AI_CONFIG_IMPORT_GLOBAL_KEYFRAME option.
-	if(0xffffffff == (configFrameID = pImp->GetPropertyInteger(
-		AI_CONFIG_IMPORT_MDL_KEYFRAME,0xffffffff)))
-	{
+	if(0xffffffff == configFrameID)	{
 		configFrameID =  pImp->GetPropertyInteger(AI_CONFIG_IMPORT_GLOBAL_KEYFRAME,0);
 	}
+
+	// AI_CONFIG_IMPORT_MDL_COLORMAP - pallette file
 	configPalette =  pImp->GetPropertyString(AI_CONFIG_IMPORT_MDL_COLORMAP,"colormap.lmp");
 }
 

+ 2 - 1
code/MDLLoader.h

@@ -96,7 +96,8 @@ public:
 	// -------------------------------------------------------------------
 	/** Returns whether the class can handle the format of the given file. 
 	* See BaseImporter::CanRead() for details.	*/
-	bool CanRead( const std::string& pFile, IOSystem* pIOHandler) const;
+	bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
+		bool checkSig) const;
 
 
 	// -------------------------------------------------------------------

+ 2 - 2
code/MDLMaterialLoader.cpp

@@ -89,7 +89,7 @@ aiColor4D MDLImporter::ReplaceTextureWithColor(const aiTexture* pcTexture)
 	ai_assert(NULL != pcTexture);
 
 	aiColor4D clrOut;
-	clrOut.r = std::numeric_limits<float>::quiet_NaN();
+	clrOut.r = get_qnan();
 	if (!pcTexture->mHeight || !pcTexture->mWidth)
 		return clrOut;
 
@@ -572,7 +572,7 @@ void MDLImporter::ParseSkinLump_3DGS_MDL7(
 	// been converted to MDL7 from other formats, such as MDL5
 	aiColor4D clrTexture;
 	if (pcNew)clrTexture = this->ReplaceTextureWithColor(pcNew);
-	else clrTexture.r = std::numeric_limits<float>::quiet_NaN();
+	else clrTexture.r = get_qnan();
 	
 	// check whether a material definition is contained in the skin
 	if (iType & AI_MDL7_SKINTYPE_MATERIAL)

+ 10 - 16
code/NFFLoader.cpp

@@ -65,22 +65,16 @@ NFFImporter::~NFFImporter()
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file. 
-bool NFFImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
+bool NFFImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
 {
-	// simple check of file extension is enough for the moment
-	std::string::size_type pos = pFile.find_last_of('.');
-	// no file extension - can't read
-	if( pos == std::string::npos)return false;
-	std::string extension = pFile.substr( pos);
-
-	// extensions: enff and nff
-	for( std::string::iterator it = extension.begin(); it != extension.end(); ++it)
-		*it = tolower( *it);
-
-	if( extension == ".nff" || extension == ".enff")
-		return true;
+	return SimpleExtensionCheck(pFile,"nff","enff");
+}
 
-	return false;
+// ------------------------------------------------------------------------------------------------
+// Get the list of all supported file extensions
+void NFFImporter::GetExtensionList(std::string& append)
+{
+	append.append("*.nff;*.enff");
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -96,7 +90,7 @@ bool NFFImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
 
 // ------------------------------------------------------------------------------------------------
 #define AI_NFF_PARSE_SHAPE_INFORMATION() \
-	aiVector3D center, radius(1.0f,std::numeric_limits<float>::quiet_NaN(),std::numeric_limits<float>::quiet_NaN()); \
+	aiVector3D center, radius(1.0f,get_qnan(),get_qnan()); \
 	AI_NFF_PARSE_TRIPLE(center); \
 	AI_NFF_PARSE_TRIPLE(radius); \
 	if (is_qnan(radius.z))radius.z = radius.x; \
@@ -282,7 +276,7 @@ void NFFImporter::InternReadFile( const std::string& pFile,
 	// check whether this is the NFF2 file format
 	if (TokenMatch(buffer,"nff",3))
 	{
-		const float qnan = std::numeric_limits<float>::quiet_NaN();
+		const float qnan = get_qnan();
 		const aiColor4D  cQNAN = aiColor4D (qnan,0.f,0.f,1.f);
 		const aiVector3D vQNAN = aiVector3D(qnan,0.f,0.f);
 

+ 9 - 8
code/NFFLoader.h

@@ -38,7 +38,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ----------------------------------------------------------------------
 */
 
-/** @file Declaration of the NFF importer class. */
+/** @file NFFLoader.h
+ *  @brief Declaration of the NFF importer class.
+ */
 #ifndef AI_NFFLOADER_H_INCLUDED
 #define AI_NFFLOADER_H_INCLUDED
 
@@ -49,7 +51,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 namespace Assimp	{
 
-// ---------------------------------------------------------------------------
+// ----------------------------------------------------------------------------------
 /** NFF (Neutral File Format) Importer class.
  *
  * The class implements both Eric Haynes NFF format and Sense8's NFF (NFF2) format.
@@ -71,8 +73,10 @@ public:
 
 	// -------------------------------------------------------------------
 	/** Returns whether the class can handle the format of the given file. 
-	* See BaseImporter::CanRead() for details.	*/
-	bool CanRead( const std::string& pFile, IOSystem* pIOHandler) const;
+	 * See BaseImporter::CanRead() for details.
+	 */
+	bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
+		bool checkSig) const;
 
 protected:
 
@@ -80,10 +84,7 @@ protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 */
-	void GetExtensionList(std::string& append)
-	{
-		append.append("*.nff;*.enff");
-	}
+	void GetExtensionList(std::string& append);
 
 	// -------------------------------------------------------------------
 	/** Imports the given file into the given scene structure. 

+ 20 - 12
code/OFFLoader.cpp

@@ -39,7 +39,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ---------------------------------------------------------------------------
 */
 
-/** @file Implementation of the OFF importer class */
+/** @file  OFFLoader.cpp
+ *  @brief Implementation of the OFF importer class 
+ */
 
 #include "AssimpPCH.h"
 #ifndef ASSIMP_BUILD_NO_OFF_IMPORTER
@@ -64,25 +66,31 @@ OFFImporter::~OFFImporter()
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file. 
-bool OFFImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
+bool OFFImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
 {
-	// simple check of file extension is enough for the moment
-	std::string::size_type pos = pFile.find_last_of('.');
-	// no file extension - can't read
-	if( pos == std::string::npos)return false;
-	std::string extension = pFile.substr( pos);
+	const std::string extension = GetExtension(pFile);
 
+	if (extension == "off")
+		return true;
+	else if (!extension.length() || checkSig)
+	{
+		if (!pIOHandler)return true;
+		const char* tokens[] = {"off"};
+		return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1);
+	}
+	return false;
+}
 
-	return !(extension.length() != 4 || extension[0] != '.' ||
-			 extension[1] != 'o' && extension[1] != 'O' ||
-			 extension[2] != 'f' && extension[2] != 'F' ||
-			 extension[3] != 'f' && extension[3] != 'F');
+// ------------------------------------------------------------------------------------------------
+void OFFImporter::GetExtensionList(std::string& append)
+{
+	append.append("*.off");
 }
 
 // ------------------------------------------------------------------------------------------------
 // Imports the given file into the given scene structure. 
 void OFFImporter::InternReadFile( const std::string& pFile, 
-	aiScene* pScene, IOSystem* pIOHandler)
+								 aiScene* pScene, IOSystem* pIOHandler)
 {
 	boost::scoped_ptr<IOStream> file( pIOHandler->Open( pFile, "rb"));
 

+ 6 - 6
code/OFFLoader.h

@@ -38,7 +38,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ----------------------------------------------------------------------
 */
 
-/** @file Declaration of the OFF importer class. */
+/** @file  OFFLoader.h
+ *  @brief Declaration of the OFF importer class. 
+ */
 #ifndef AI_OFFLOADER_H_INCLUDED
 #define AI_OFFLOADER_H_INCLUDED
 
@@ -67,7 +69,8 @@ public:
 	// -------------------------------------------------------------------
 	/** Returns whether the class can handle the format of the given file. 
 	* See BaseImporter::CanRead() for details.	*/
-	bool CanRead( const std::string& pFile, IOSystem* pIOHandler) const;
+	bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
+		bool checkSig) const;
 
 protected:
 
@@ -75,10 +78,7 @@ protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 */
-	void GetExtensionList(std::string& append)
-	{
-		append.append("*.off");
-	}
+	void GetExtensionList(std::string& append);
 
 	// -------------------------------------------------------------------
 	/** Imports the given file into the given scene structure. 

+ 3 - 16
code/ObjFileImporter.cpp

@@ -51,9 +51,6 @@ namespace Assimp	{
 
 using namespace std;
 
-//!	Obj-file-format extention
-const string ObjFileImporter::OBJ_EXT = "obj";
-
 // ------------------------------------------------------------------------------------------------
 //	Default constructor
 ObjFileImporter::ObjFileImporter() :
@@ -76,20 +73,10 @@ ObjFileImporter::~ObjFileImporter()
 
 // ------------------------------------------------------------------------------------------------
 //	Returns true, fi file is an obj file
-bool ObjFileImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
+bool ObjFileImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
 {
-	if (pFile.empty())
-		return false;
-
-	string::size_type pos = pFile.find_last_of(".");
-	if (string::npos == pos)
-		return false;
-	
-	const string ext = pFile.substr(pos+1, pFile.size() - pos - 1);
-	if (ext == OBJ_EXT)
-		return true;
-
-	return false;
+	// fixme: auto detection
+	return SimpleExtensionCheck(pFile,"obj");
 }
 
 // ------------------------------------------------------------------------------------------------

+ 1 - 4
code/ObjFileImporter.h

@@ -66,9 +66,6 @@ class ObjFileImporter :
 {	
 	friend class Importer;
 
-	//!	OB file extention
-	static const std::string OBJ_EXT;
-
 protected:
 	///	\brief	Default constructor
 	ObjFileImporter();
@@ -79,7 +76,7 @@ protected:
 public:
 	/// \brief	Returns whether the class can handle the format of the given file. 
 	/// \remark	See BaseImporter::CanRead() for details.
-	bool CanRead( const std::string& pFile, IOSystem* pIOHandler) const;
+	bool CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const;
 
 private:
 

+ 29 - 28
code/PlyLoader.cpp

@@ -64,27 +64,31 @@ PLYImporter::~PLYImporter()
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file. 
-bool PLYImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
+bool PLYImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
 {
-	// simple check of file extension is enough for the moment
-	std::string::size_type pos = pFile.find_last_of('.');
-	// no file extension - can't read
-	if( pos == std::string::npos)
-		return false;
-	std::string extension = pFile.substr( pos);
-
-	if (extension.length() < 4)return false;
-	if (extension[0] != '.')   return false;
-	if (extension[1] != 'p' && extension[1] != 'P')return false;
-	if (extension[2] != 'l' && extension[2] != 'L')return false;
-	if (extension[3] != 'y' && extension[3] != 'Y')return false;
-	return true;
+	const std::string extension = GetExtension(pFile);
+
+	if (extension == "ply")
+		return true;
+	else if (!extension.length() || checkSig)
+	{
+		if (!pIOHandler)return true;
+		const char* tokens[] = {"ply"};
+		return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1);
+	}
+	return false;
+}
+
+// ------------------------------------------------------------------------------------------------
+void PLYImporter::GetExtensionList(std::string& append)
+{
+	append.append("*.ply");
 }
 
 // ------------------------------------------------------------------------------------------------
 // Imports the given file into the given scene structure. 
 void PLYImporter::InternReadFile( 
-	const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler)
+								 const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler)
 {
 	boost::scoped_ptr<IOStream> file( pIOHandler->Open( pFile));
 
@@ -108,8 +112,7 @@ void PLYImporter::InternReadFile(
 	// the beginning of the file must be PLY - magic, magic
 	if (mBuffer[0] != 'P' && mBuffer[0] != 'p' ||
 		mBuffer[1] != 'L' && mBuffer[1] != 'l' ||
-		mBuffer[2] != 'Y' && mBuffer[2] != 'y')
-	{
+		mBuffer[2] != 'Y' && mBuffer[2] != 'y')	{
 		throw new ImportErrorException( "Invalid .ply file: Magic number \'ply\' is no there");
 	}
 
@@ -789,19 +792,17 @@ void PLYImporter::LoadFaces(std::vector<PLY::Face>* pvOut)
 		}
 		else // triangle strips
 		{
-			// HUGE TODO: MAKE THAT FUCK WORK!
-
 			// normally we have only one triangle strip instance where
 			// a value of -1 indicates a restart of the strip
 			for (std::vector<ElementInstance>::const_iterator i =  pcList->alInstances.begin();
 				i != pcList->alInstances.end();++i)
 			{
+				const std::vector<PLY::PropertyInstance::ValueUnion>& quak = (*i).alProperties[iProperty].avList;
+				pvOut->reserve(pvOut->size() + quak.size() + (quak.size()>>2u));
+
 				int aiTable[2] = {-1,-1};
-				for (std::vector<PLY::PropertyInstance::ValueUnion>::const_iterator 
-					a =  (*i).alProperties[iProperty].avList.begin();
-					a != (*i).alProperties[iProperty].avList.end();++a)
-				{
-					int p = PLY::PropertyInstance::ConvertTo<int>(*a,eType);
+				for (std::vector<PLY::PropertyInstance::ValueUnion>::const_iterator a =  quak.begin();a != quak.end();++a)	{
+					const int p = PLY::PropertyInstance::ConvertTo<int>(*a,eType);
 					if (-1 == p)
 					{
 						// restart the strip ...
@@ -819,12 +820,12 @@ void PLYImporter::LoadFaces(std::vector<PLY::Face>* pvOut)
 						continue;
 					}
 				
-					PLY::Face sFace;
+					pvOut->push_back(PLY::Face());
+					PLY::Face& sFace = pvOut->back();
 					sFace.mIndices.push_back((unsigned int)aiTable[0]);
 					sFace.mIndices.push_back((unsigned int)aiTable[1]);
 					sFace.mIndices.push_back((unsigned int)p);
-					pvOut->push_back(sFace);
-
+		
 					aiTable[0] = aiTable[1];
 					aiTable[1] = p;
 				}
@@ -1054,4 +1055,4 @@ void PLYImporter::LoadMaterial(std::vector<MaterialHelper*>* pvOut)
 	}
 }
 
-#endif // !! ASSIMP_BUILD_NO_PLY_IMPORTER
+#endif // !! ASSIMP_BUILD_NO_PLY_IMPORTER

+ 10 - 12
code/PlyLoader.h

@@ -38,8 +38,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ----------------------------------------------------------------------
 */
 
-
-/** @file Declaration of the .ply importer class. */
+/** @file  PLYLoader.h
+ *  @brief Declaration of the .ply importer class.
+ */
 #ifndef AI_PLYLOADER_H_INCLUDED
 #define AI_PLYLOADER_H_INCLUDED
 
@@ -50,15 +51,13 @@ struct aiNode;
 
 #include "PlyParser.h"
 
-namespace Assimp
-{
+namespace Assimp	{
 class MaterialHelper;
 
-
 using namespace PLY;
 
 // ---------------------------------------------------------------------------
-/** Used to load PLY files
+/** Importer class to load the stanford PLY file format
 */
 class PLYImporter : public BaseImporter
 {
@@ -75,8 +74,10 @@ public:
 
 	// -------------------------------------------------------------------
 	/** Returns whether the class can handle the format of the given file. 
-	* See BaseImporter::CanRead() for details.	*/
-	bool CanRead( const std::string& pFile, IOSystem* pIOHandler) const;
+	 * See BaseImporter::CanRead() for details.
+ 	 */
+	bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
+		bool checkSig) const;
 
 protected:
 
@@ -84,10 +85,7 @@ protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 */
-	void GetExtensionList(std::string& append)
-	{
-		append.append("*.ply");
-	}
+	void GetExtensionList(std::string& append);
 
 	// -------------------------------------------------------------------
 	/** Imports the given file into the given scene structure. 

+ 3 - 4
code/PlyParser.cpp

@@ -744,10 +744,9 @@ bool PLY::PropertyInstance::ParseInstanceBinary (
 		unsigned int iNum = PLY::PropertyInstance::ConvertTo<unsigned int>(v,prop->eFirstType);
 
 		// parse all list elements
-		for (unsigned int i = 0; i < iNum;++i)
-		{
-			PLY::PropertyInstance::ParseValueBinary(pCur, &pCur,prop->eType,&v,p_bBE);
-			p_pcOut->avList.push_back(v);
+		p_pcOut->avList.resize(iNum);
+		for (unsigned int i = 0; i < iNum;++i){
+			PLY::PropertyInstance::ParseValueBinary(pCur, &pCur,prop->eType,&p_pcOut->avList[i],p_bBE);
 		}
 	}
 	else

+ 376 - 165
code/PretransformVertices.cpp

@@ -39,12 +39,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ---------------------------------------------------------------------------
 */
 
-/** @file Implementation of the "PretransformVertices" post processing step 
+/** @file PretransformVertices.cpp
+ *  @brief Implementation of the "PretransformVertices" post processing step 
 */
 
 #include "AssimpPCH.h"
 #include "PretransformVertices.h"
 #include "ProcessHelper.h"
+#include "SceneCombiner.h"
 
 using namespace Assimp;
 
@@ -55,6 +57,7 @@ using namespace Assimp;
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
 PretransformVertices::PretransformVertices()
+:	configKeepHierarchy (false)
 {
 }
 
@@ -72,9 +75,17 @@ bool PretransformVertices::IsActive( unsigned int pFlags) const
 	return	(pFlags & aiProcess_PreTransformVertices) != 0;
 }
 
+// ------------------------------------------------------------------------------------------------
+// Setup import configuration
+void PretransformVertices::SetupProperties(const Importer* pImp)
+{
+	// Get the current value of AI_CONFIG_PP_PTV_KEEP_HIERARCHY
+	configKeepHierarchy = (0 != pImp->GetPropertyInteger(AI_CONFIG_PP_PTV_KEEP_HIERARCHY,0));
+}
+
 // ------------------------------------------------------------------------------------------------
 // Count the number of nodes
-unsigned int CountNodes( aiNode* pcNode )
+unsigned int PretransformVertices::CountNodes( aiNode* pcNode )
 {
 	unsigned int iRet = 1;
 	for (unsigned int i = 0;i < pcNode->mNumChildren;++i)
@@ -86,7 +97,7 @@ unsigned int CountNodes( aiNode* pcNode )
 
 // ------------------------------------------------------------------------------------------------
 // Get a bitwise combination identifying the vertex format of a mesh
-unsigned int GetMeshVFormat(aiMesh* pcMesh)
+unsigned int PretransformVertices::GetMeshVFormat(aiMesh* pcMesh)
 {
 	// the vertex format is stored in aiMesh::mBones for later retrieval.
 	// there isn't a good reason to compute it a few hundred times
@@ -106,7 +117,7 @@ unsigned int GetMeshVFormat(aiMesh* pcMesh)
 // ------------------------------------------------------------------------------------------------
 // Count the number of vertices in the whole scene and a given
 // material index
-void CountVerticesAndFaces( aiScene* pcScene, aiNode* pcNode, unsigned int iMat,
+void PretransformVertices::CountVerticesAndFaces( aiScene* pcScene, aiNode* pcNode, unsigned int iMat,
 	unsigned int iVFormat, unsigned int* piFaces, unsigned int* piVertices)
 {
 	for (unsigned int i = 0; i < pcNode->mNumMeshes;++i)
@@ -127,39 +138,73 @@ void CountVerticesAndFaces( aiScene* pcScene, aiNode* pcNode, unsigned int iMat,
 
 // ------------------------------------------------------------------------------------------------
 // Collect vertex/face data
-void CollectData( aiScene* pcScene, aiNode* pcNode, unsigned int iMat,
+void PretransformVertices::CollectData( aiScene* pcScene, aiNode* pcNode, unsigned int iMat,
 	unsigned int iVFormat, aiMesh* pcMeshOut, 
-	unsigned int aiCurrent[2])
+	unsigned int aiCurrent[2], unsigned int* num_refs)
 {
+	// No need to multiply if there's no transformation
+	const bool identity = pcNode->mTransformation.IsIdentity();
 	for (unsigned int i = 0; i < pcNode->mNumMeshes;++i)
 	{
 		aiMesh* pcMesh = pcScene->mMeshes[ pcNode->mMeshes[i] ]; 
 		if (iMat == pcMesh->mMaterialIndex && iVFormat == GetMeshVFormat(pcMesh))
 		{
-			// copy positions, transform them to worldspace
-			for (unsigned int n = 0; n < pcMesh->mNumVertices;++n)
-				pcMeshOut->mVertices[aiCurrent[AI_PTVS_VERTEX]+n] = pcNode->mTransformation * pcMesh->mVertices[n];
-			
-
-			aiMatrix4x4 mWorldIT = pcNode->mTransformation;
-			mWorldIT.Inverse().Transpose();
-
-			// TODO: implement Inverse() for aiMatrix3x3
-			aiMatrix3x3 m = aiMatrix3x3(mWorldIT);
+			// Decrement mesh reference counter
+			unsigned int& num_ref = num_refs[pcNode->mMeshes[i]];
+			ai_assert(0 != num_ref);
+			--num_ref;
+
+			if (identity)	{
+				// copy positions without modifying them
+				::memcpy(pcMeshOut->mVertices + aiCurrent[AI_PTVS_VERTEX],
+					pcMesh->mVertices,
+					pcMesh->mNumVertices * sizeof(aiVector3D));
 
-			if (iVFormat & 0x2)
-			{		
-				// copy normals, transform them to worldspace
-				for (unsigned int n = 0; n < pcMesh->mNumVertices;++n)	{
-					pcMeshOut->mNormals[aiCurrent[AI_PTVS_VERTEX]+n] = m * pcMesh->mNormals[n];
+				if (iVFormat & 0x2) {
+					// copy normals without modifying them
+					::memcpy(pcMeshOut->mNormals + aiCurrent[AI_PTVS_VERTEX],
+						pcMesh->mNormals,
+						pcMesh->mNumVertices * sizeof(aiVector3D));
+				}
+				if (iVFormat & 0x4)
+				{
+					// copy tangents without modifying them
+					::memcpy(pcMeshOut->mTangents + aiCurrent[AI_PTVS_VERTEX],
+						pcMesh->mTangents,
+						pcMesh->mNumVertices * sizeof(aiVector3D));
+					// copy bitangents without modifying them
+					::memcpy(pcMeshOut->mBitangents + aiCurrent[AI_PTVS_VERTEX],
+						pcMesh->mBitangents,
+						pcMesh->mNumVertices * sizeof(aiVector3D));
 				}
 			}
-			if (iVFormat & 0x4)
+			else
 			{
-				// copy tangents and bitangents, transform them to worldspace
+				// copy positions, transform them to worldspace
 				for (unsigned int n = 0; n < pcMesh->mNumVertices;++n)	{
-					pcMeshOut->mTangents  [aiCurrent[AI_PTVS_VERTEX]+n] = m * pcMesh->mTangents[n];
-					pcMeshOut->mBitangents[aiCurrent[AI_PTVS_VERTEX]+n] = m * pcMesh->mBitangents[n];
+					pcMeshOut->mVertices[aiCurrent[AI_PTVS_VERTEX]+n] = pcNode->mTransformation * pcMesh->mVertices[n];
+				}
+				aiMatrix4x4 mWorldIT = pcNode->mTransformation;
+				mWorldIT.Inverse().Transpose();
+
+				// TODO: implement Inverse() for aiMatrix3x3
+				aiMatrix3x3 m = aiMatrix3x3(mWorldIT);
+
+				if (iVFormat & 0x2)
+				{
+					// copy normals, transform them to worldspace
+					for (unsigned int n = 0; n < pcMesh->mNumVertices;++n)	{
+						pcMeshOut->mNormals[aiCurrent[AI_PTVS_VERTEX]+n] = 
+							m * pcMesh->mNormals[n];
+					}
+				}
+				if (iVFormat & 0x4)
+				{
+					// copy tangents and bitangents, transform them to worldspace
+					for (unsigned int n = 0; n < pcMesh->mNumVertices;++n)	{
+						pcMeshOut->mTangents  [aiCurrent[AI_PTVS_VERTEX]+n] = m * pcMesh->mTangents[n];
+						pcMeshOut->mBitangents[aiCurrent[AI_PTVS_VERTEX]+n] = m * pcMesh->mBitangents[n];
+					}
 				}
 			}
 			unsigned int p = 0;
@@ -180,26 +225,35 @@ void CollectData( aiScene* pcScene, aiNode* pcNode, unsigned int iMat,
 					pcMesh->mNumVertices * sizeof(aiColor4D));
 				++p;
 			}
-			// now we need to copy all faces
-			// since we will delete the source mesh afterwards,
-			// we don't need to reallocate the array of indices
-			for (unsigned int planck = 0;planck<pcMesh->mNumFaces;++planck)
+			// now we need to copy all faces. since we will delete the source mesh afterwards,
+			// we don't need to reallocate the array of indices except if this mesh is 
+			// referenced multiple times.
+			for (unsigned int planck = 0;planck < pcMesh->mNumFaces;++planck)
 			{
-				pcMeshOut->mFaces[aiCurrent[AI_PTVS_FACE]+planck].mNumIndices =
-					pcMesh->mFaces[planck].mNumIndices; 
+				aiFace& f_src = pcMesh->mFaces[planck];
+				aiFace& f_dst = pcMeshOut->mFaces[aiCurrent[AI_PTVS_FACE]+planck];
 
-				unsigned int* pi = pcMeshOut->mFaces[aiCurrent[AI_PTVS_FACE]+planck].
-					mIndices = pcMesh->mFaces[planck].mIndices; 
+				const unsigned int num_idx = f_src.mNumIndices;
 
-				// offset all vrtex indices
-				for (unsigned int hahn = 0; hahn < pcMesh->mFaces[planck].mNumIndices;++hahn)
-				{
-					pi[hahn] += aiCurrent[AI_PTVS_VERTEX];
-				}
+				f_dst.mNumIndices = num_idx; 
 
-				// just make sure the array won't be deleted by the
-				// aiFace destructor ...
-				pcMesh->mFaces[planck].mIndices = NULL;
+				unsigned int* pi;
+				if (!num_ref) { /* if last time the mesh is referenced -> no reallocation */
+					pi = f_dst.mIndices = f_src.mIndices; 
+
+					// offset all vertex indices
+					for (unsigned int hahn = 0; hahn < num_idx;++hahn){
+						pi[hahn] += aiCurrent[AI_PTVS_VERTEX];
+					}
+				}
+				else {
+					pi = f_dst.mIndices = new unsigned int[num_idx];
+					
+					// copy and offset all vertex indices
+					for (unsigned int hahn = 0; hahn < num_idx;++hahn){
+						pi[hahn] = f_src.mIndices[hahn] + aiCurrent[AI_PTVS_VERTEX];
+					}
+				}
 
 				// Update the mPrimitiveTypes member of the mesh
 				switch (pcMesh->mFaces[planck].mNumIndices)
@@ -222,24 +276,24 @@ void CollectData( aiScene* pcScene, aiNode* pcNode, unsigned int iMat,
 			aiCurrent[AI_PTVS_FACE]   += pcMesh->mNumFaces;
 		}
 	}
-	for (unsigned int i = 0;i < pcNode->mNumChildren;++i)
-	{
+
+	// append all children of us
+	for (unsigned int i = 0;i < pcNode->mNumChildren;++i) {
 		CollectData(pcScene,pcNode->mChildren[i],iMat,
-			iVFormat,pcMeshOut,aiCurrent);
+			iVFormat,pcMeshOut,aiCurrent,num_refs);
 	}
 }
 
 // ------------------------------------------------------------------------------------------------
 // Get a list of all vertex formats that occur for a given material index
 // The output list contains duplicate elements
-void GetVFormatList( aiScene* pcScene, unsigned int iMat,
+void PretransformVertices::GetVFormatList( aiScene* pcScene, unsigned int iMat,
 	std::list<unsigned int>& aiOut)
 {
 	for (unsigned int i = 0; i < pcScene->mNumMeshes;++i)
 	{
 		aiMesh* pcMesh = pcScene->mMeshes[ i ]; 
-		if (iMat == pcMesh->mMaterialIndex)
-		{
+		if (iMat == pcMesh->mMaterialIndex)	{
 			aiOut.push_back(GetMeshVFormat(pcMesh));
 		}
 	}
@@ -247,7 +301,7 @@ void GetVFormatList( aiScene* pcScene, unsigned int iMat,
 
 // ------------------------------------------------------------------------------------------------
 // Compute the absolute transformation matrices of each node
-void ComputeAbsoluteTransform( aiNode* pcNode )
+void PretransformVertices::ComputeAbsoluteTransform( aiNode* pcNode )
 {
 	if (pcNode->mParent)	{
 		pcNode->mTransformation = pcNode->mParent->mTransformation*pcNode->mTransformation;
@@ -258,12 +312,126 @@ void ComputeAbsoluteTransform( aiNode* pcNode )
 	}
 }
 
+// ------------------------------------------------------------------------------------------------
+// Apply the node transformation to a mesh
+void PretransformVertices::ApplyTransform(aiMesh* mesh, const aiMatrix4x4& mat)
+{
+	// Check whether we need to transform the coordinates at all
+	if (!mat.IsIdentity()) {
+		
+		if (mesh->HasPositions()) {
+			for (unsigned int i = 0; i < mesh->mNumVertices; ++i) {
+				mesh->mVertices[i] = mat * mesh->mVertices[i];
+			}
+		}
+		if (mesh->HasNormals() || mesh->HasTangentsAndBitangents()) {
+			aiMatrix4x4 mWorldIT = mat;
+			mWorldIT.Inverse().Transpose();
+
+			// TODO: implement Inverse() for aiMatrix3x3
+			aiMatrix3x3 m = aiMatrix3x3(mWorldIT);
+
+			if (mesh->HasNormals()) {
+				for (unsigned int i = 0; i < mesh->mNumVertices; ++i) {
+					mesh->mNormals[i] = m * mesh->mNormals[i];
+				}
+			}
+			if (mesh->HasTangentsAndBitangents()) {
+				for (unsigned int i = 0; i < mesh->mNumVertices; ++i) {
+					mesh->mTangents[i]   = m * mesh->mTangents[i];
+					mesh->mBitangents[i] = m * mesh->mBitangents[i];
+				}
+			}
+		}
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+// Simple routine to build meshes in worldspace, no further optimization
+void PretransformVertices::BuildWCSMeshes(std::vector<aiMesh*>& out, aiMesh** in,
+	unsigned int numIn, aiNode* node)
+{
+	// NOTE:
+	//  aiMesh::mNumBones store original source mesh, or 0xffffffff if not a copy
+	//  aiMesh::mBones store reference to abs. transform we multiplied with
+
+	// process meshes
+	for (unsigned int i = 0; i < node->mNumMeshes;++i) {
+		aiMesh* mesh = in[node->mMeshes[i]];
+
+		// check whether we can operate on this mesh
+		if (!mesh->mBones || *reinterpret_cast<aiMatrix4x4*>(mesh->mBones) == node->mTransformation) {
+			// yes, we can.
+			mesh->mBones = reinterpret_cast<aiBone**> (&node->mTransformation);
+			mesh->mNumBones = 0xffffffff;
+		}
+		else {
+		
+			// try to find us in the list of newly created meshes
+			for (unsigned int n = 0; n < out.size(); ++n) {
+				aiMesh* ctz = out[n];
+				if (ctz->mNumBones == node->mMeshes[i] && *reinterpret_cast<aiMatrix4x4*>(ctz->mBones) ==  node->mTransformation) {
+					
+					// ok, use this one. Update node mesh index
+					node->mMeshes[i] = numIn + n;
+				}
+			}
+			if (node->mMeshes[i] < numIn) {
+				// Worst case. Need to operate on a full copy of the mesh
+				DefaultLogger::get()->info("PretransformVertices: Copying mesh due to mismatching transforms");
+				aiMesh* ntz;
+
+				const unsigned int tmp = mesh->mNumBones; //
+				mesh->mNumBones = 0;
+				SceneCombiner::Copy(&ntz,mesh);
+				mesh->mNumBones = tmp;
+
+				ntz->mNumBones = node->mMeshes[i];
+				ntz->mBones = reinterpret_cast<aiBone**> (&node->mTransformation);
+
+				out.push_back(ntz);
+			}
+		}
+	}
+
+	// call children
+	for (unsigned int i = 0; i < node->mNumChildren;++i)
+		BuildWCSMeshes(out,in,numIn,node->mChildren[i]);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reset transformation matrices to identity
+void PretransformVertices::MakeIdentityTransform(aiNode* nd)
+{
+	nd->mTransformation = aiMatrix4x4();
+
+	// call children
+	for (unsigned int i = 0; i < nd->mNumChildren;++i)
+		MakeIdentityTransform(nd->mChildren[i]);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Build reference counters for all meshes
+void PretransformVertices::BuildMeshRefCountArray(aiNode* nd, unsigned int * refs)
+{
+	for (unsigned int i = 0; i< nd->mNumMeshes;++i)
+		refs[nd->mMeshes[i]]++;
+
+	// call children
+	for (unsigned int i = 0; i < nd->mNumChildren;++i)
+		BuildMeshRefCountArray(nd->mChildren[i],refs);
+}
+
 // ------------------------------------------------------------------------------------------------
 // Executes the post processing step on the given imported data.
 void PretransformVertices::Execute( aiScene* pScene)
 {
 	DefaultLogger::get()->debug("PretransformVerticesProcess begin");
 
+	// Return immediately if we have no meshes
+	if (!pScene->mNumMeshes)
+		return;
+
 	const unsigned int iOldMeshes = pScene->mNumMeshes;
 	const unsigned int iOldAnimationChannels = pScene->mNumAnimations;
 	const unsigned int iOldNodes = CountNodes(pScene->mRootNode);
@@ -271,11 +439,10 @@ void PretransformVertices::Execute( aiScene* pScene)
 	// first compute absolute transformation matrices for all nodes
 	ComputeAbsoluteTransform(pScene->mRootNode);
 
-	// delete aiMesh::mBones for all meshes. The bones are
+	// Delete aiMesh::mBones for all meshes. The bones are
 	// removed during this step and we need the pointer as
 	// temporary storage
-	for (unsigned int i = 0; i < pScene->mNumMeshes;++i)
-	{
+	for (unsigned int i = 0; i < pScene->mNumMeshes;++i)	{
 		aiMesh* mesh = pScene->mMeshes[i];
 
 		for (unsigned int a = 0; a < mesh->mNumBones;++a)
@@ -287,49 +454,122 @@ void PretransformVertices::Execute( aiScene* pScene)
 
 	// now build a list of output meshes
 	std::vector<aiMesh*> apcOutMeshes;
-	apcOutMeshes.reserve(pScene->mNumMaterials<<1u);
-	std::list<unsigned int> aiVFormats;
-	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)	{
-			unsigned int iVertices = 0;
-			unsigned int iFaces = 0; 
-			CountVerticesAndFaces(pScene,pScene->mRootNode,i,*j,&iFaces,&iVertices);
-			if (iFaces && iVertices)
-			{
-				apcOutMeshes.push_back(new aiMesh());
-				aiMesh* pcMesh = apcOutMeshes.back();
-				pcMesh->mNumFaces = iFaces;
-				pcMesh->mNumVertices = iVertices;
-				pcMesh->mFaces = new aiFace[iFaces];
-				pcMesh->mVertices = new aiVector3D[iVertices];
-				pcMesh->mMaterialIndex = i;
-				if ((*j) & 0x2)pcMesh->mNormals = new aiVector3D[iVertices];
-				if ((*j) & 0x4)
-				{
-					pcMesh->mTangents    = new aiVector3D[iVertices];
-					pcMesh->mBitangents  = new aiVector3D[iVertices];
-				}
-				iFaces = 0;
-				while ((*j) & (0x100 << iFaces))
+
+	// Keep scene hierarchy? It's an easy job in this case ...
+	// we go on and transform all meshes, if one is referenced by nodes
+	// with different absolute transformations a depth copy of the mesh
+	// is required.
+	if( configKeepHierarchy ) {
+
+		// Hack: store the matrix we're transforming a mesh with in aiMesh::mBones
+		BuildWCSMeshes(apcOutMeshes,pScene->mMeshes,pScene->mNumMeshes, pScene->mRootNode);
+
+		// ... if new meshes have been generated, append them to the end of the scene
+		if (apcOutMeshes.size() > 0) {
+			aiMesh** npp = new aiMesh*[pScene->mNumMeshes + apcOutMeshes.size()];
+
+			::memcpy(npp,pScene->mMeshes,sizeof(aiMesh*)*pScene->mNumMeshes);
+			::memcpy(npp+pScene->mNumMeshes,&apcOutMeshes[0],sizeof(aiMesh*)*apcOutMeshes.size());
+
+			pScene->mNumMeshes  += apcOutMeshes.size();
+			delete[] pScene->mMeshes; pScene->mMeshes = npp;
+		}
+
+		// now iterate through all meshes and transform them to worldspace
+		for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) {
+			ApplyTransform(pScene->mMeshes[i],*reinterpret_cast<aiMatrix4x4*>( pScene->mMeshes[i]->mBones ));
+
+			// prevent improper destruction
+			pScene->mMeshes[i]->mBones    = NULL;
+			pScene->mMeshes[i]->mNumBones = 0;
+		}
+	}
+	else {
+
+		apcOutMeshes.reserve(pScene->mNumMaterials<<1u);
+		std::list<unsigned int> aiVFormats;
+
+		std::vector<unsigned int> s(pScene->mNumMeshes,0);
+		BuildMeshRefCountArray(pScene->mRootNode,&s[0]);
+
+		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)	{
+				unsigned int iVertices = 0;
+				unsigned int iFaces = 0; 
+				CountVerticesAndFaces(pScene,pScene->mRootNode,i,*j,&iFaces,&iVertices);
+				if (0 != iFaces && 0 != iVertices)
 				{
-					pcMesh->mTextureCoords[iFaces] = new aiVector3D[iVertices];
-					if ((*j) & (0x10000 << iFaces))pcMesh->mNumUVComponents[iFaces] = 3;
-					else pcMesh->mNumUVComponents[iFaces] = 2;
-					iFaces++;
+					apcOutMeshes.push_back(new aiMesh());
+					aiMesh* pcMesh = apcOutMeshes.back();
+					pcMesh->mNumFaces = iFaces;
+					pcMesh->mNumVertices = iVertices;
+					pcMesh->mFaces = new aiFace[iFaces];
+					pcMesh->mVertices = new aiVector3D[iVertices];
+					pcMesh->mMaterialIndex = i;
+					if ((*j) & 0x2)pcMesh->mNormals = new aiVector3D[iVertices];
+					if ((*j) & 0x4)
+					{
+						pcMesh->mTangents    = new aiVector3D[iVertices];
+						pcMesh->mBitangents  = new aiVector3D[iVertices];
+					}
+					iFaces = 0;
+					while ((*j) & (0x100 << iFaces))
+					{
+						pcMesh->mTextureCoords[iFaces] = new aiVector3D[iVertices];
+						if ((*j) & (0x10000 << iFaces))pcMesh->mNumUVComponents[iFaces] = 3;
+						else pcMesh->mNumUVComponents[iFaces] = 2;
+						iFaces++;
+					}
+					iFaces = 0;
+					while ((*j) & (0x1000000 << iFaces))
+						pcMesh->mColors[iFaces++] = new aiColor4D[iVertices];
+
+					// fill the mesh ...
+					unsigned int aiTemp[2] = {0,0};
+					CollectData(pScene,pScene->mRootNode,i,*j,pcMesh,aiTemp,&s[0]);
 				}
-				iFaces = 0;
-				while ((*j) & (0x1000000 << iFaces))
-					pcMesh->mColors[iFaces++] = new aiColor4D[iVertices];
+			}
+		}
 
-				// fill the mesh ...
-				unsigned int aiTemp[2] = {0,0};
-				CollectData(pScene,pScene->mRootNode,i,*j,pcMesh,aiTemp);
+		// now delete all meshes in the scene and build a new mesh list
+		for (unsigned int i = 0; i < pScene->mNumMeshes;++i)
+		{
+			aiMesh* mesh = pScene->mMeshes[i];
+			mesh->mNumBones = 0;
+			mesh->mBones    = NULL;
+
+			// we're reusing the face index arrays. avoid destruction
+			for (unsigned int a = 0; a < mesh->mNumFaces; ++a) {
+				mesh->mFaces[a].mNumIndices = 0;
+				mesh->mFaces[a].mIndices = NULL;
 			}
+
+			delete mesh;
+
+			// Invalidate the contents of the old mesh array. We will most
+			// likely have less output meshes now, so the last entries of 
+			// the mesh array are not overridden. We set them to NULL to 
+			// make sure the developer gets notified when his application
+			// attempts to access these fields ...
+			mesh = NULL;
+		}
+
+		// If no meshes are referenced in the node graph it is possible that we get no output meshes. 
+		if (apcOutMeshes.empty())	{		
+			throw new ImportErrorException("No output meshes: all meshes are orphaned and are not referenced by nodes");
+		}
+		else
+		{
+			// It is impossible that we have more output meshes than 
+			// input meshes, so we can easily reuse the old mesh array
+			pScene->mNumMeshes = (unsigned int)apcOutMeshes.size();
+			for (unsigned int i = 0; i < pScene->mNumMeshes;++i)
+				pScene->mMeshes[i] = apcOutMeshes[i];
 		}
 	}
 
@@ -341,40 +581,6 @@ void PretransformVertices::Execute( aiScene* pScene)
 	pScene->mAnimations    = NULL;
 	pScene->mNumAnimations = 0;
 
-	// now delete all meshes in the scene and build a new mesh list
-	for (unsigned int i = 0; i < pScene->mNumMeshes;++i)
-	{
-		pScene->mMeshes[i]->mBones = NULL;
-		delete pScene->mMeshes[i];
-
-		// invalidate the contents of the old mesh array. We will most
-		// likely have less output meshes now, so the last entries of 
-		// the mesh array are not overridden. We set them to NULL to 
-		// make sure the developer gets notified when his application
-		// attempts to access these fields ...
-		pScene->mMeshes[i] = NULL;
-	}
-
-	// If no meshes are referenced in the node graph it is
-	// possible that we get no output meshes. However, this 
-	// is OK if we had no input meshes, too
-	if (apcOutMeshes.empty())
-	{
-		if (pScene->mNumMeshes)
-		{
-			throw new ImportErrorException("No output meshes: all meshes are orphaned "
-				"and have no node references");
-		}
-	}
-	else
-	{
-		// It is impossible that we have more output meshes than 
-		// input meshes, so we can easily reuse the old mesh array
-		pScene->mNumMeshes = (unsigned int)apcOutMeshes.size();
-		for (unsigned int i = 0; i < pScene->mNumMeshes;++i)
-			pScene->mMeshes[i] = apcOutMeshes[i];
-	}
-
 	// --- we need to keep all cameras and lights 
 	for (unsigned int i = 0; i < pScene->mNumCameras;++i)
 	{
@@ -401,52 +607,59 @@ void PretransformVertices::Execute( aiScene* pScene)
 		l->mDirection  = aiMatrix3x3( nd->mTransformation ) * l->mDirection;
 	}
 
-	// now delete all nodes in the scene and build a new
-	// flat node graph with a root node and some level 1 children
-	delete pScene->mRootNode;
-	pScene->mRootNode = new aiNode();
-	pScene->mRootNode->mName.Set("<dummy_root>");
+	if( !configKeepHierarchy ) {
 
-	if (1 == pScene->mNumMeshes && !pScene->mNumLights && !pScene->mNumCameras)
-	{
-		pScene->mRootNode->mNumMeshes = 1;
-		pScene->mRootNode->mMeshes = new unsigned int[1];
-		pScene->mRootNode->mMeshes[0] = 0;
-	}
-	else
-	{
-		pScene->mRootNode->mNumChildren = pScene->mNumMeshes+pScene->mNumLights+pScene->mNumCameras;
-		aiNode** nodes = pScene->mRootNode->mChildren = new aiNode*[pScene->mRootNode->mNumChildren];
+		// now delete all nodes in the scene and build a new
+		// flat node graph with a root node and some level 1 children
+		delete pScene->mRootNode;
+		pScene->mRootNode = new aiNode();
+		pScene->mRootNode->mName.Set("<dummy_root>");
 
-		// generate mesh nodes
-		for (unsigned int i = 0; i < pScene->mNumMeshes;++i,++nodes)
+		if (1 == pScene->mNumMeshes && !pScene->mNumLights && !pScene->mNumCameras)
 		{
-			aiNode* pcNode = *nodes = new aiNode();
-			pcNode->mParent = pScene->mRootNode;
-			pcNode->mName.length = ::sprintf(pcNode->mName.data,"mesh_%i",i);
-
-			// setup mesh indices
-			pcNode->mNumMeshes = 1;
-			pcNode->mMeshes = new unsigned int[1];
-			pcNode->mMeshes[0] = i;
+			pScene->mRootNode->mNumMeshes = 1;
+			pScene->mRootNode->mMeshes = new unsigned int[1];
+			pScene->mRootNode->mMeshes[0] = 0;
 		}
-		// generate light nodes
-		for (unsigned int i = 0; i < pScene->mNumLights;++i,++nodes)
+		else
 		{
-			aiNode* pcNode = *nodes = new aiNode();
-			pcNode->mParent = pScene->mRootNode;
-			pcNode->mName.length = ::sprintf(pcNode->mName.data,"light_%i",i);
-			pScene->mLights[i]->mName = pcNode->mName;
-		}
-		// generate camera nodes
-		for (unsigned int i = 0; i < pScene->mNumCameras;++i,++nodes)
-		{
-			aiNode* pcNode = *nodes = new aiNode();
-			pcNode->mParent = pScene->mRootNode;
-			pcNode->mName.length = ::sprintf(pcNode->mName.data,"cam_%i",i);
-			pScene->mCameras[i]->mName = pcNode->mName;
+			pScene->mRootNode->mNumChildren = pScene->mNumMeshes+pScene->mNumLights+pScene->mNumCameras;
+			aiNode** nodes = pScene->mRootNode->mChildren = new aiNode*[pScene->mRootNode->mNumChildren];
+
+			// generate mesh nodes
+			for (unsigned int i = 0; i < pScene->mNumMeshes;++i,++nodes)
+			{
+				aiNode* pcNode = *nodes = new aiNode();
+				pcNode->mParent = pScene->mRootNode;
+				pcNode->mName.length = ::sprintf(pcNode->mName.data,"mesh_%i",i);
+
+				// setup mesh indices
+				pcNode->mNumMeshes = 1;
+				pcNode->mMeshes = new unsigned int[1];
+				pcNode->mMeshes[0] = i;
+			}
+			// generate light nodes
+			for (unsigned int i = 0; i < pScene->mNumLights;++i,++nodes)
+			{
+				aiNode* pcNode = *nodes = new aiNode();
+				pcNode->mParent = pScene->mRootNode;
+				pcNode->mName.length = ::sprintf(pcNode->mName.data,"light_%i",i);
+				pScene->mLights[i]->mName = pcNode->mName;
+			}
+			// generate camera nodes
+			for (unsigned int i = 0; i < pScene->mNumCameras;++i,++nodes)
+			{
+				aiNode* pcNode = *nodes = new aiNode();
+				pcNode->mParent = pScene->mRootNode;
+				pcNode->mName.length = ::sprintf(pcNode->mName.data,"cam_%i",i);
+				pScene->mCameras[i]->mName = pcNode->mName;
+			}
 		}
 	}
+	else {
+		// ... and finally set the transformation matrix of all nodes to identity
+		MakeIdentityTransform(pScene->mRootNode);
+	}
 
 	// print statistics
 	if (!DefaultLogger::isNullLogger())
@@ -467,7 +680,5 @@ void PretransformVertices::Execute( aiScene* pScene)
 			iOldMeshes,pScene->mNumMeshes);
 		DefaultLogger::get()->info(buffer);
 	}
-
-	return;
 }
 

+ 91 - 13
code/PretransformVertices.h

@@ -38,16 +38,18 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ----------------------------------------------------------------------
 */
 
-/** @file Defines a post processing step to pretransform all 
- vertices in the scenegraph */
+/** @file PretransformVertices.h
+ *  @brief Defines a post processing step to pretransform all 
+ *    vertices in the scenegraph 
+ */
 #ifndef AI_PRETRANSFORMVERTICES_H_INC
 #define AI_PRETRANSFORMVERTICES_H_INC
 
 #include "BaseProcess.h"
 #include "../include/aiMesh.h"
 
-namespace Assimp
-{
+class PretransformVerticesTest;
+namespace Assimp	{
 
 // ---------------------------------------------------------------------------
 /** The PretransformVertices pretransforms all vertices in the nodegraph
@@ -57,6 +59,7 @@ namespace Assimp
 class ASSIMP_API PretransformVertices : public BaseProcess
 {
 	friend class Importer;
+	friend class ::PretransformVerticesTest;
 
 protected:
 	/** Constructor to be privately used by Importer */
@@ -66,21 +69,96 @@ protected:
 	~PretransformVertices ();
 
 public:
+
 	// -------------------------------------------------------------------
-	/** Returns whether the processing step is present in the given flag field.
-	* @param pFlags The processing flags the importer was called with. A bitwise
-	*   combination of #aiPostProcessSteps.
-	* @return true if the process is present in this flag fields, false if not.
-	*/
+	// Check whether step is active
 	bool IsActive( unsigned int pFlags) const;
 
 	// -------------------------------------------------------------------
-	/** Executes the post processing step on the given imported data.
-	* At the moment a process is not supposed to fail.
-	* @param pScene The imported data to work at.
-	*/
+	// Execute step on a given scene
 	void Execute( aiScene* pScene);
 
+	// -------------------------------------------------------------------
+	// Setup import settings
+	void SetupProperties(const Importer* pImp);
+
+
+	// -------------------------------------------------------------------
+	/** @brief Toggle the 'keep hierarchy' option
+	 *  @param d hm ... difficult to guess what this means, hu!?
+	 */
+	void KeepHierarchy(bool d) {
+		configKeepHierarchy = d;
+	}
+
+	// -------------------------------------------------------------------
+	/** @brief Check whether 'keep hierarchy' is currently enabled.
+	 *  @return ...
+	 */
+	bool IsHierarchyKept() const {
+		return configKeepHierarchy;
+	}
+
+private:
+
+	// -------------------------------------------------------------------
+	// Count the number of nodes
+	unsigned int CountNodes( aiNode* pcNode );
+
+	// -------------------------------------------------------------------
+	// Get a bitwise combination identifying the vertex format of a mesh
+	unsigned int GetMeshVFormat(aiMesh* pcMesh);
+
+	// -------------------------------------------------------------------
+	// Count the number of vertices in the whole scene and a given
+	// material index
+	void CountVerticesAndFaces( aiScene* pcScene, aiNode* pcNode, 
+		unsigned int iMat,
+		unsigned int iVFormat, 
+		unsigned int* piFaces,
+		unsigned int* piVertices);
+
+	// -------------------------------------------------------------------
+	// Collect vertex/face data
+	void CollectData( aiScene* pcScene, aiNode* pcNode,
+		unsigned int iMat,
+		unsigned int iVFormat, 
+		aiMesh* pcMeshOut, 
+		unsigned int aiCurrent[2],
+		unsigned int* num_refs);
+
+	// -------------------------------------------------------------------
+	// Get a list of all vertex formats that occur for a given material
+	// The output list contains duplicate elements
+	void GetVFormatList( aiScene* pcScene, unsigned int iMat,
+		std::list<unsigned int>& aiOut);
+
+	// -------------------------------------------------------------------
+	// Compute the absolute transformation matrices of each node
+	void ComputeAbsoluteTransform( aiNode* pcNode );
+
+	// -------------------------------------------------------------------
+	// Simple routine to build meshes in worldspace, no further optimization
+	void BuildWCSMeshes(std::vector<aiMesh*>& out, aiMesh** in,
+		unsigned int numIn, aiNode* node);
+
+	// -------------------------------------------------------------------
+	// Apply the node transformation to a mesh
+	void ApplyTransform(aiMesh* mesh, const aiMatrix4x4& mat);
+
+	// -------------------------------------------------------------------
+	// Reset transformation matrices to identity
+	void MakeIdentityTransform(aiNode* nd);
+
+	// -------------------------------------------------------------------
+	// Build reference counters for all meshes
+	void BuildMeshRefCountArray(aiNode* nd, unsigned int * refs);
+
+
+
+	//! Configuration option: keep scene hierarchy as long as possible
+	bool configKeepHierarchy;
+
 };
 
 } // end of namespace Assimp

+ 149 - 32
code/ProcessHelper.h

@@ -46,11 +46,144 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "SpatialSort.h"
 #include "BaseProcess.h"
 
+// -------------------------------------------------------------------------------
+// Some extensions to std namespace. Mainly std::min and std::max for all
+// flat data types in the aiScene. They're used to quickly determine the
+// min/max bounds of data arrays.
+#ifdef __cplusplus
+namespace std {
+
+	// std::min for aiVector3D
+	inline ::aiVector3D min (const ::aiVector3D& a, const ::aiVector3D& b)	{
+		return ::aiVector3D (min(a.x,b.x),min(a.y,b.y),min(a.z,b.z));
+	}
+
+	// std::max for aiVector3D
+	inline ::aiVector3D max (const ::aiVector3D& a, const ::aiVector3D& b)	{
+		return ::aiVector3D (max(a.x,b.x),max(a.y,b.y),max(a.z,b.z));
+	}
+
+	// std::min for aiColor4D
+	inline ::aiColor4D min (const ::aiColor4D& a, const ::aiColor4D& b)	{
+		return ::aiColor4D (min(a.r,b.r),min(a.g,b.g),min(a.b,b.b),min(a.a,b.a));
+	}
+
+	// std::max for aiColor4D
+	inline ::aiColor4D max (const ::aiColor4D& a, const ::aiColor4D& b)	{
+		return ::aiColor4D (max(a.r,b.r),max(a.g,b.g),max(a.b,b.b),max(a.a,b.a));
+	}
+
+	// std::min for aiQuaternion
+	inline ::aiQuaternion min (const ::aiQuaternion& a, const ::aiQuaternion& b)	{
+		return ::aiQuaternion (min(a.w,b.w),min(a.x,b.x),min(a.y,b.y),min(a.z,b.z));
+	}
+
+	// std::max for aiQuaternion
+	inline ::aiQuaternion max (const ::aiQuaternion& a, const ::aiQuaternion& b)	{
+		return ::aiQuaternion (max(a.w,b.w),max(a.x,b.x),max(a.y,b.y),max(a.z,b.z));
+	}
+
+	// std::min for aiVectorKey
+	inline ::aiVectorKey min (const ::aiVectorKey& a, const ::aiVectorKey& b)	{
+		return ::aiVectorKey (min(a.mTime,b.mTime),min(a.mValue,b.mValue));
+	}
+
+	// std::max for aiVectorKey
+	inline ::aiVectorKey max (const ::aiVectorKey& a, const ::aiVectorKey& b)	{
+		return ::aiVectorKey (max(a.mTime,b.mTime),max(a.mValue,b.mValue));
+	}
+
+	// std::min for aiQuatKey
+	inline ::aiQuatKey min (const ::aiQuatKey& a, const ::aiQuatKey& b)	{
+		return ::aiQuatKey (min(a.mTime,b.mTime),min(a.mValue,b.mValue));
+	}
+
+	// std::max for aiQuatKey
+	inline ::aiQuatKey max (const ::aiQuatKey& a, const ::aiQuatKey& b)	{
+		return ::aiQuatKey (max(a.mTime,b.mTime),max(a.mValue,b.mValue));
+	}
+
+	// std::min for aiVertexWeight
+	inline ::aiVertexWeight min (const ::aiVertexWeight& a, const ::aiVertexWeight& b)	{
+		return ::aiVertexWeight (min(a.mVertexId,b.mVertexId),min(a.mWeight,b.mWeight));
+	}
+
+	// std::max for aiVertexWeight
+	inline ::aiVertexWeight max (const ::aiVertexWeight& a, const ::aiVertexWeight& b)	{
+		return ::aiVertexWeight (max(a.mVertexId,b.mVertexId),max(a.mWeight,b.mWeight));
+	}
+
+} // end namespace std
+#endif // !! C++
+
 namespace Assimp {
 
-// some aliases to make the whole stuff easier to read
-typedef std::pair	< unsigned int,float > PerVertexWeight;
-typedef std::vector	< PerVertexWeight    > VertexWeightTable;
+// -------------------------------------------------------------------------------
+// Start points for ArrayBounds<T> for all supported Ts
+template <typename T>
+struct MinMaxChooser;
+
+template <> struct MinMaxChooser<float> {
+	void operator ()(float& min,float& max) {
+		max = -10e10f;
+		min =  10e10f;
+}};
+template <> struct MinMaxChooser<double> {
+	void operator ()(double& min,double& max) {
+		max = -10e10;
+		min =  10e10;
+}};
+template <> struct MinMaxChooser<unsigned int> {
+	void operator ()(unsigned int& min,unsigned int& max) {
+		max = 0;
+		min = (1u<<(sizeof(unsigned int)*8-1));
+}};
+
+template <> struct MinMaxChooser<aiVector3D> {
+	void operator ()(aiVector3D& min,aiVector3D& max) {
+		max = aiVector3D(-10e10f,-10e10f,-10e10f);
+		min = aiVector3D( 10e10f, 10e10f, 10e10f);
+}};
+template <> struct MinMaxChooser<aiColor4D> {
+	void operator ()(aiColor4D& min,aiColor4D& max) {
+		max = aiColor4D(-10e10f,-10e10f,-10e10f,-10e10f);
+		min = aiColor4D( 10e10f, 10e10f, 10e10f, 10e10f);
+}};
+
+template <> struct MinMaxChooser<aiQuaternion> {
+	void operator ()(aiQuaternion& min,aiQuaternion& max) {
+		max = aiQuaternion(-10e10f,-10e10f,-10e10f,-10e10f);
+		min = aiQuaternion( 10e10f, 10e10f, 10e10f, 10e10f);
+}};
+
+template <> struct MinMaxChooser<aiVectorKey> {
+	void operator ()(aiVectorKey& min,aiVectorKey& max) {
+		MinMaxChooser<double>()(min.mTime,max.mTime);
+		MinMaxChooser<aiVector3D>()(min.mValue,max.mValue);
+}};
+template <> struct MinMaxChooser<aiQuatKey> {
+	void operator ()(aiQuatKey& min,aiQuatKey& max) {
+		MinMaxChooser<double>()(min.mTime,max.mTime);
+		MinMaxChooser<aiQuaternion>()(min.mValue,max.mValue);
+}};
+
+template <> struct MinMaxChooser<aiVertexWeight> {
+	void operator ()(aiVertexWeight& min,aiVertexWeight& max) {
+		MinMaxChooser<unsigned int>()(min.mVertexId,max.mVertexId);
+		MinMaxChooser<float>()(min.mWeight,max.mWeight);
+}};
+
+// -------------------------------------------------------------------------------
+// Find the min/max values of an array of Ts
+template <typename T>
+inline void ArrayBounds(const T* in, unsigned int size, T& min, T& max) 
+{
+	MinMaxChooser<T> ()(min,max);
+	for (unsigned int i = 0; i < size;++i) {
+		min = std::min(in[i],min);
+		max = std::max(in[i],max);
+	}
+}
 
 // -------------------------------------------------------------------------------
 /** Little helper function to calculate the quadratic difference 
@@ -67,24 +200,6 @@ inline float GetColorDifference( const aiColor4D& pColor1, const aiColor4D& pCol
 	return c.r*c.r + c.g*c.g + c.b*c.b + c.a*c.a;
 }
 
-// -------------------------------------------------------------------------------
-// Compute the AABB of a mesh
-inline void FindAABB (const aiMesh* mesh, aiVector3D& min, aiVector3D& max)
-{
-	min = aiVector3D (10e10f,  10e10f, 10e10f);
-	max = aiVector3D (-10e10f,-10e10f,-10e10f);
-	for (unsigned int i = 0;i < mesh->mNumVertices;++i)
-	{
-		const aiVector3D& v = mesh->mVertices[i];
-		min.x = ::std::min(v.x,min.x);
-		min.y = ::std::min(v.y,min.y);
-		min.z = ::std::min(v.z,min.z);
-		max.x = ::std::max(v.x,max.x);
-		max.y = ::std::max(v.y,max.y);
-		max.z = ::std::max(v.z,max.z);
-	}
-}
-
 // -------------------------------------------------------------------------------
 // Compute the AABB of a mesh after applying a given transform
 inline void FindAABBTransformed (const aiMesh* mesh, aiVector3D& min, aiVector3D& max, 
@@ -95,12 +210,8 @@ inline void FindAABBTransformed (const aiMesh* mesh, aiVector3D& min, aiVector3D
 	for (unsigned int i = 0;i < mesh->mNumVertices;++i)
 	{
 		const aiVector3D v = m * mesh->mVertices[i];
-		min.x = ::std::min(v.x,min.x);
-		min.y = ::std::min(v.y,min.y);
-		min.z = ::std::min(v.z,min.z);
-		max.x = ::std::max(v.x,max.x);
-		max.y = ::std::max(v.y,max.y);
-		max.z = ::std::max(v.z,max.z);
+		min = std::min(v,min);
+		max = std::max(v,max);
 	}
 }
 
@@ -108,7 +219,7 @@ inline void FindAABBTransformed (const aiMesh* mesh, aiVector3D& min, aiVector3D
 // Helper function to determine the 'real' center of a mesh
 inline void FindMeshCenter (aiMesh* mesh, aiVector3D& out, aiVector3D& min, aiVector3D& max)
 {
-	FindAABB(mesh,min,max);
+	ArrayBounds(mesh->mVertices,mesh->mNumVertices, min,max);
 	out = min + (max-min)*0.5f;
 }
 
@@ -146,7 +257,7 @@ inline float ComputePositionEpsilon(const aiMesh* pMesh)
 
 	// calculate the position bounds so we have a reliable epsilon to check position differences against 
 	aiVector3D minVec, maxVec;
-	FindAABB(pMesh,minVec,maxVec);
+	ArrayBounds(pMesh->mVertices,pMesh->mNumVertices,minVec,maxVec);
 	return (maxVec - minVec).Length() * epsilon;
 }
 
@@ -165,8 +276,10 @@ inline unsigned int GetMeshVFormatUnique(aiMesh* pcMesh)
 	// tangents and bitangents
 	if (pcMesh->HasTangentsAndBitangents())iRet |= 0x4;
 
+#ifdef BOOST_STATIC_ASSERT
 	BOOST_STATIC_ASSERT(8 >= AI_MAX_NUMBER_OF_COLOR_SETS);
 	BOOST_STATIC_ASSERT(8 >= AI_MAX_NUMBER_OF_TEXTURECOORDS);
+#endif
 
 	// texture coordinates
 	unsigned int p = 0;
@@ -184,6 +297,9 @@ inline unsigned int GetMeshVFormatUnique(aiMesh* pcMesh)
 	return iRet;
 }
 
+typedef std::pair <unsigned int,float> PerVertexWeight;
+typedef std::vector	<PerVertexWeight> VertexWeightTable;
+
 // -------------------------------------------------------------------------------
 // Compute a per-vertex bone weight table
 // please .... delete result with operator delete[] ...
@@ -205,13 +321,14 @@ inline VertexWeightTable* ComputeVertexBoneWeightTable(aiMesh* pMesh)
 	return avPerVertexWeights;
 }
 
-
 // -------------------------------------------------------------------------------
 // Get a string for a given aiTextureType
 inline const char* TextureTypeToString(aiTextureType in)
 {
 	switch (in)
 	{
+	case aiTextureType_NONE:
+		return "n/a";
 	case aiTextureType_DIFFUSE:
 		return "Diffuse";
 	case aiTextureType_SPECULAR:
@@ -237,7 +354,7 @@ inline const char* TextureTypeToString(aiTextureType in)
 	case aiTextureType_UNKNOWN:
 		return "Unknown";
     default:
-        return "HUGE ERROR, please leave the room immediately and call the police";        
+        return  "HUGE ERROR. Expect BSOD (linux guys: kernel panic ...).";          
 	}
 }
 
@@ -260,7 +377,7 @@ inline const char* MappingTypeToString(aiTextureMapping in)
 	case aiTextureMapping_OTHER:
 		return "Other";
     default:
-        return "HUGE ERROR, please leave the room immediately and call the police";        
+        return  "HUGE ERROR. Expect BSOD (linux guys: kernel panic ...).";    
 	}
 }
 

+ 24 - 18
code/Q3DLoader.cpp

@@ -39,7 +39,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ---------------------------------------------------------------------------
 */
 
-/** @file Implementation of the Q3D importer class */
+/** @file  Q3DLoader.cpp
+ *  @brief Implementation of the Q3D importer class
+ */
 
 #include "AssimpPCH.h"
 #ifndef ASSIMP_BUILD_NO_Q3D_IMPORTER
@@ -54,30 +56,34 @@ using namespace Assimp;
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
 Q3DImporter::Q3DImporter()
-{
-}
+{}
 
 // ------------------------------------------------------------------------------------------------
 // Destructor, private as well 
 Q3DImporter::~Q3DImporter()
-{
-}
+{}
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file. 
-bool Q3DImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
+bool Q3DImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
 {
-	// simple check of file extension is enough for the moment
-	std::string::size_type pos = pFile.find_last_of('.');
-	// no file extension - can't read
-	if( pos == std::string::npos)return false;
-
-	std::string extension = pFile.substr( pos);
-	for (std::string::iterator it = extension.begin();
-		it != extension.end(); ++it)
-		*it = ::tolower(*it);
+	const std::string extension = GetExtension(pFile);
+
+	if (extension == "q3s" || extension == "q3o")
+		return true;
+	else if (!extension.length() || checkSig)	{
+		if (!pIOHandler)
+			return true;
+		const char* tokens[] = {"quick3Do","quick3Ds"};
+		return SearchFileHeaderForToken(pIOHandler,pFile,tokens,2);
+	}
+	return false;
+}
 
-	return (extension == ".q3o" || extension == ".q3s"); 
+// ------------------------------------------------------------------------------------------------
+void Q3DImporter::GetExtensionList(std::string& append)
+{
+	append.append("*.q3o;*.q3s");
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -91,11 +97,11 @@ void Q3DImporter::InternReadFile( const std::string& pFile,
 	if (stream.GetRemainingSize() < 22)
 		throw new ImportErrorException("File is either empty or corrupt: " + pFile);
 
-	// Check the file signature
+	// Check the file's signature
 	if (ASSIMP_strincmp( (const char*)stream.GetPtr(), "quick3Do", 8 ) &&
 		ASSIMP_strincmp( (const char*)stream.GetPtr(), "quick3Ds", 8 ))
 	{
-		throw new ImportErrorException("No Quick3D file. Signature is: " + 
+		throw new ImportErrorException("Not a Quick3D file. Signature string is: " + 
 			std::string((const char*)stream.GetPtr(),8));
 	}
 

+ 6 - 6
code/Q3DLoader.h

@@ -38,7 +38,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ----------------------------------------------------------------------
 */
 
-/** @file Declaration of the Q3D importer class. */
+/** @file  Q3DLoader.h
+ *  @brief Declaration of the Q3D importer class.
+ */
 #ifndef AI_Q3DLOADER_H_INCLUDED
 #define AI_Q3DLOADER_H_INCLUDED
 
@@ -67,7 +69,8 @@ public:
 	// -------------------------------------------------------------------
 	/** Returns whether the class can handle the format of the given file. 
 	* See BaseImporter::CanRead() for details.	*/
-	bool CanRead( const std::string& pFile, IOSystem* pIOHandler) const;
+	bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
+		bool checkSig) const;
 
 protected:
 
@@ -75,10 +78,7 @@ protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 */
-	void GetExtensionList(std::string& append)
-	{
-		append.append("*.q3o;*.q3s");
-	}
+	void GetExtensionList(std::string& append);
 
 	// -------------------------------------------------------------------
 	/** Imports the given file into the given scene structure. 

+ 12 - 15
code/RawLoader.cpp

@@ -39,7 +39,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ---------------------------------------------------------------------------
 */
 
-/** @file Implementation of the RAW importer class */
+/** @file  RawLoader.cpp
+ *  @brief Implementation of the RAW importer class 
+ */
 
 #include "AssimpPCH.h"
 #ifndef ASSIMP_BUILD_NO_RAW_IMPORTER
@@ -63,21 +65,16 @@ RAWImporter::~RAWImporter()
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file. 
-bool RAWImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
+bool RAWImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
 {
-	// simple check of file extension is enough for the moment
-	std::string::size_type pos = pFile.find_last_of('.');
-	// no file extension - can't read
-	if( pos == std::string::npos)return false;
-	std::string extension = pFile.substr( pos);
-
-	if (extension.length() < 4)return false;
-	if (extension[0] != '.')return false;
-
-	return !(extension.length() != 4 || extension[0] != '.' ||
-			 extension[1] != 'r' && extension[1] != 'R' ||
-			 extension[2] != 'a' && extension[2] != 'A' ||
-			 extension[3] != 'w' && extension[3] != 'W');
+	return SimpleExtensionCheck(pFile,"raw");
+}
+
+// ------------------------------------------------------------------------------------------------
+// Get the list of all supported file extensions
+void RAWImporter::GetExtensionList(std::string& append)
+{
+	append.append("*.raw");
 }
 
 // ------------------------------------------------------------------------------------------------

+ 8 - 7
code/RawLoader.h

@@ -38,7 +38,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ----------------------------------------------------------------------
 */
 
-/** @file Declaration of the RAW importer class. */
+/** @file  RAWLoader.h
+ *  @brief Declaration of the RAW importer class.
+ */
 #ifndef AI_RAWLOADER_H_INCLUDED
 #define AI_RAWLOADER_H_INCLUDED
 
@@ -66,8 +68,10 @@ public:
 
 	// -------------------------------------------------------------------
 	/** Returns whether the class can handle the format of the given file. 
-	* See BaseImporter::CanRead() for details.	*/
-	bool CanRead( const std::string& pFile, IOSystem* pIOHandler) const;
+	 * See BaseImporter::CanRead() for details.
+ 	 */
+	bool CanRead( const std::string& pFile, IOSystem* pIOHandler, 
+		bool checkSig) const;
 
 protected:
 
@@ -75,10 +79,7 @@ protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 */
-	void GetExtensionList(std::string& append)
-	{
-		append.append("*.raw");
-	}
+	void GetExtensionList(std::string& append);
 
 	// -------------------------------------------------------------------
 	/** Imports the given file into the given scene structure. 

+ 75 - 8
code/RemoveRedundantMaterials.cpp

@@ -38,12 +38,14 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ---------------------------------------------------------------------------
 */
-/** @file Implementation of the "RemoveRedundantMaterials" post processing step 
+/** @file RemoveRedundantMaterials.cpp
+ *  @brief Implementation of the "RemoveRedundantMaterials" post processing step 
 */
 
 // internal headers
 #include "AssimpPCH.h"
 #include "RemoveRedundantMaterials.h"
+#include "ParsingUtils.h"
 
 using namespace Assimp;
 
@@ -68,6 +70,39 @@ bool RemoveRedundantMatsProcess::IsActive( unsigned int pFlags) const
 	return (pFlags & aiProcess_RemoveRedundantMaterials) != 0;
 }
 
+// ------------------------------------------------------------------------------------------------
+// Setup import properties
+void RemoveRedundantMatsProcess::SetupProperties(const Importer* pImp)
+{
+	// Get value of AI_CONFIG_PP_RRM_EXCLUDE_LIST
+	configFixedMaterials = pImp->GetPropertyString(AI_CONFIG_PP_RRM_EXCLUDE_LIST,"");
+}
+
+// ------------------------------------------------------------------------------------------------
+// Extract single strings from a list of identifiers
+void ConvertListToStrings(const std::string& in, std::list<std::string>& out)
+{
+	const char* s = in.c_str();
+	while (*s) {
+		SkipSpacesAndLineEnd(&s);
+		if (*s == '\'') {
+			const char* base = ++s;
+			while (*s != '\'') {
+				++s;
+				if (*s == '\0') {
+					DefaultLogger::get()->error("RemoveRedundantMaterials: String list is ill-formatted");
+					return;
+				}
+			}
+			out.push_back(std::string(base,(size_t)(s-base)));
+			++s;
+		}
+		else {
+			out.push_back(GetNextToken(s));
+		}
+	}
+}
+
 // ------------------------------------------------------------------------------------------------
 // Executes the post processing step on the given imported data.
 void RemoveRedundantMatsProcess::Execute( aiScene* pScene)
@@ -77,16 +112,49 @@ void RemoveRedundantMatsProcess::Execute( aiScene* pScene)
 	unsigned int iCnt = 0, unreferenced = 0;
 	if (pScene->mNumMaterials)
 	{
+		// Find out which materials are referenced by meshes
+		std::vector<bool> abReferenced(pScene->mNumMaterials,false);
+		for (unsigned int i = 0;i < pScene->mNumMeshes;++i)
+			abReferenced[pScene->mMeshes[i]->mMaterialIndex] = true;
+
+		// If a list of materials to be excluded was given, match the list with 
+		// our imported materials and 'salt' all positive matches to ensure that
+		// we get unique hashes later.
+		if (configFixedMaterials.length()) {
+
+			std::list<std::string> strings;
+			ConvertListToStrings(configFixedMaterials,strings);
+			
+			for (unsigned int i = 0; i < pScene->mNumMaterials;++i) {
+				aiMaterial* mat = pScene->mMaterials[i];
+
+				aiString name;
+				mat->Get(AI_MATKEY_NAME,name);
+
+				if (name.length) {
+					std::list<std::string>::const_iterator it = std::find(strings.begin(), strings.end(), name.data);
+					if (it != strings.end()) {
+						
+						// Our brilliant 'salt': A single material property with ~ as first
+						// character to mark it as internal and temporary.
+						const int dummy = 1;
+						((MaterialHelper*)mat)->AddProperty(&dummy,1,"~RRM.UniqueMaterial",0,0);
+
+						// Keep this material even if no mesh references it
+						abReferenced[i] = true;
+						DefaultLogger::get()->debug(std::string("Found positive match in exclusion list: \'") + name.data + "\'");
+					}
+				}
+			}
+		}
+
+
 		// TODO: reimplement this algorithm to work in-place
 
 		unsigned int* aiMappingTable = new unsigned int[pScene->mNumMaterials];
 		unsigned int iNewNum = 0;
 
-		std::vector<bool> abReferenced(pScene->mNumMaterials,false);
-		for (unsigned int i = 0;i < pScene->mNumMeshes;++i)
-			abReferenced[pScene->mMeshes[i]->mMaterialIndex] = true;
-
-		// iterate through all materials and calculate a hash for them
+		// Iterate through all materials and calculate a hash for them
 		// store all hashes in a list and so a quick search whether
 		// we do already have a specific hash. This allows us to
 		// determine which materials are identical.
@@ -136,8 +204,7 @@ void RemoveRedundantMatsProcess::Execute( aiScene* pScene)
 				else ppcMaterials[idx] = pScene->mMaterials[p];
 			}
 			// update all material indices
-			for (unsigned int p = 0; p < pScene->mNumMeshes;++p)
-			{
+			for (unsigned int p = 0; p < pScene->mNumMeshes;++p)	{
 				aiMesh* mesh = pScene->mMeshes[p];
 				mesh->mMaterialIndex = aiMappingTable[mesh->mMaterialIndex];
 			}

+ 35 - 14
code/RemoveRedundantMaterials.h

@@ -38,7 +38,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ----------------------------------------------------------------------
 */
 
-/** @file Defines a post processing step to remove redundant materials */
+/** @file RemoveRedundantMaterials.h
+ *  @brief Defines a post processing step to remove redundant materials 
+ */
 #ifndef AI_REMOVEREDUNDANTMATERIALS_H_INC
 #define AI_REMOVEREDUNDANTMATERIALS_H_INC
 
@@ -46,12 +48,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "../include/aiMesh.h"
 
 class RemoveRedundantMatsTest;
-namespace Assimp
-	{
+namespace Assimp	{
 
 // ---------------------------------------------------------------------------
-/** RemoveRedundantMatsProcess: Class to remove redundant materials
-*/
+/** RemoveRedundantMatsProcess: Postprocessing steo to remove redundant 
+ *  materials from the imported scene.
+ */
 class ASSIMP_API RemoveRedundantMatsProcess : public BaseProcess
 {
 	friend class Importer;
@@ -66,19 +68,38 @@ protected:
 
 public:
 	// -------------------------------------------------------------------
-	/** Returns whether the processing step is present in the given flag field.
-	* @param pFlags The processing flags the importer was called with. A bitwise
-	*   combination of #aiPostProcessSteps.
-	* @return true if the process is present in this flag fields, false if not.
-	*/
+	// Check whether step is active
 	bool IsActive( unsigned int pFlags) const;
 
 	// -------------------------------------------------------------------
-	/** Executes the post processing step on the given imported data.
-	* At the moment a process is not supposed to fail.
-	* @param pScene The imported data to work at.
-	*/
+	// Execute step on a given scene
 	void Execute( aiScene* pScene);
+
+	// -------------------------------------------------------------------
+	// Setup import settings
+	void SetupProperties(const Importer* pImp);
+
+
+	// -------------------------------------------------------------------
+	/** @brief Set list of fixed (unmutable) materials
+	 *  @param fixed See #AI_CONFIG_PP_RRM_EXCLUDE_LIST
+	 */
+	void SetFixedMaterialsString(const std::string& fixed = "") {
+		configFixedMaterials = fixed;
+	}
+
+	// -------------------------------------------------------------------
+	/** @brief Get list of fixed (unmutable) materials
+	 *  @return See #AI_CONFIG_PP_RRM_EXCLUDE_LIST
+	 */
+	const std::string& GetFixedMaterialsString() const {
+		return configFixedMaterials;
+	}
+
+private:
+
+	//! Configuration option: list of all fixed materials
+	std::string configFixedMaterials;
 };
 
 } // end of namespace Assimp

+ 2 - 5
code/RemoveVCProcess.cpp

@@ -51,15 +51,12 @@ using namespace Assimp;
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
 RemoveVCProcess::RemoveVCProcess()
-{
-}
+{}
 
 // ------------------------------------------------------------------------------------------------
 // Destructor, private as well
 RemoveVCProcess::~RemoveVCProcess()
-{
-	// nothing to do here
-}
+{}
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the processing step is present in the given flag field.

+ 18 - 35
code/SMDLoader.cpp

@@ -39,7 +39,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ---------------------------------------------------------------------------
 */
 
-/** @file Implementation of the SMD importer class */
+/** @file  SMDLoader.cpp 
+ *  @brief Implementation of the SMD importer class 
+ */
 
 #include "AssimpPCH.h"
 #ifndef ASSIMP_BUILD_NO_SMD_IMPORTER
@@ -54,56 +56,37 @@ using namespace Assimp;
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
 SMDImporter::SMDImporter()
-{
-	// nothing to do here
-}
+{}
 
 // ------------------------------------------------------------------------------------------------
 // Destructor, private as well 
 SMDImporter::~SMDImporter()
-{
-	// nothing to do here
-}
+{}
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file. 
-bool SMDImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
+bool SMDImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool) const
 {
-	// simple check of file extension is enough for the moment
-	std::string::size_type pos = pFile.find_last_of('.');
-	// no file extension - can't read
-	if( pos == std::string::npos)
-		return false;
-	std::string extension = pFile.substr( pos);
-
-	if (extension.length() < 4)return false;
-	if (extension[0] != '.')return false;
+	// fixme: auto format detection
+	return SimpleExtensionCheck(pFile,"smd","vta");
+}
 
-	// VTA is not really supported as it contains vertex animations.
-	// However, at least the first keyframe can be loaded
-	if ((extension[1] != 's' && extension[1] != 'S') ||
-	    (extension[2] != 'm' && extension[2] != 'M') ||
-	    (extension[3] != 'd' && extension[3] != 'D'))
-	{
-		if ((extension[1] != 'v' && extension[1] != 'V') ||
-			(extension[2] != 't' && extension[2] != 'T') ||
-			(extension[3] != 'a' && extension[3] != 'A'))
-		{
-			return false;
-		}
-	}
-	return true;
+// ------------------------------------------------------------------------------------------------
+// Get a list of all supported file extensions
+void SMDImporter::GetExtensionList(std::string& append)
+{
+	append.append("*.smd;*.vta");
 }
 
 // ------------------------------------------------------------------------------------------------
 // Setup configuration properties
 void SMDImporter::SetupProperties(const Importer* pImp)
 {
-	// The AI_CONFIG_IMPORT_SMD_KEYFRAME option overrides the
+	// The 
+	// AI_CONFIG_IMPORT_SMD_KEYFRAME option overrides the
 	// AI_CONFIG_IMPORT_GLOBAL_KEYFRAME option.
-	if(0xffffffff == (configFrameID = pImp->GetPropertyInteger(
-		AI_CONFIG_IMPORT_SMD_KEYFRAME,0xffffffff)))
-	{
+	configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_SMD_KEYFRAME,0xffffffff);
+	if(0xffffffff == configFrameID)	{
 		configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_GLOBAL_KEYFRAME,0);
 	}
 }

+ 11 - 13
code/SMDLoader.h

@@ -38,10 +38,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ----------------------------------------------------------------------
 */
 
-
-//!
-//! @file Definition of SMD importer class
-//!
+/** @file  SMDLoader.h
+ *  @brief Defintion of the Valve SMD file format
+ */
 
 #ifndef AI_SMDLOADER_H_INCLUDED
 #define AI_SMDLOADER_H_INCLUDED
@@ -186,14 +185,16 @@ public:
 
 	// -------------------------------------------------------------------
 	/** Returns whether the class can handle the format of the given file. 
-	* See BaseImporter::CanRead() for details.	*/
-	bool CanRead( const std::string& pFile, IOSystem* pIOHandler) const;
+	 * See BaseImporter::CanRead() for details.
+	 */
+	bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
+		bool checkSig) const;
 
 	// -------------------------------------------------------------------
 	/** Called prior to ReadFile().
-	* The function is a request to the importer to update its configuration
-	* basing on the Importer's configuration property list.
-	*/
+	 * The function is a request to the importer to update its configuration
+	 * basing on the Importer's configuration property list.
+	 */
 	void SetupProperties(const Importer* pImp);
 
 protected:
@@ -203,10 +204,7 @@ protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 */
-	void GetExtensionList(std::string& append)
-	{
-		append.append("*.smd;*.vta");
-	}
+	void GetExtensionList(std::string& append);
 
 	// -------------------------------------------------------------------
 	/** Imports the given file into the given scene structure. 

+ 19 - 15
code/STLLoader.cpp

@@ -64,27 +64,31 @@ STLImporter::~STLImporter()
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file. 
-bool STLImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
+bool STLImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
 {
-	// simple check of file extension is enough for the moment
-	std::string::size_type pos = pFile.find_last_of('.');
-	// no file extension - can't read
-	if( pos == std::string::npos)return false;
-	std::string extension = pFile.substr( pos);
+	const std::string extension = GetExtension(pFile);
 
-	if (extension.length() < 4)return false;
-	if (extension[0] != '.')return false;
-
-	if (extension[1] != 's' && extension[1] != 'S')return false;
-	if (extension[2] != 't' && extension[2] != 'T')return false;
-	if (extension[3] != 'l' && extension[3] != 'L')return false;
+	if (extension == "stl")
+		return true;
+	else if (!extension.length() || checkSig)	{
+		if (!pIOHandler)
+			return true;
+		const char* tokens[] = {"STL","solid"};
+		return SearchFileHeaderForToken(pIOHandler,pFile,tokens,2);
+	}
+	return false;
+}
 
-	return true;
+// ------------------------------------------------------------------------------------------------
+void STLImporter::GetExtensionList(std::string& append)
+{
+	append.append("*.stl");
 }
+
 // ------------------------------------------------------------------------------------------------
 // Imports the given file into the given scene structure. 
 void STLImporter::InternReadFile( 
-	const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler)
+								 const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler)
 {
 	boost::scoped_ptr<IOStream> file( pIOHandler->Open( pFile, "rb"));
 
@@ -183,7 +187,7 @@ void STLImporter::LoadASCIIFile()
 	pMesh->mVertices = new aiVector3D[pMesh->mNumVertices];
 	pMesh->mNormals = new aiVector3D[pMesh->mNumVertices];
 	
-	unsigned int curFace = 0, curVertex = 0;
+	unsigned int curFace = 0, curVertex = 3;
 	while (true)
 	{
 		// go to the next token

+ 9 - 8
code/STLLoader.h

@@ -38,7 +38,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ----------------------------------------------------------------------
 */
 
-/** @file Declaration of the STL importer class. */
+/** @file STLLoader.h
+ *  Declaration of the STL importer class. 
+ */
 #ifndef AI_STLLOADER_H_INCLUDED
 #define AI_STLLOADER_H_INCLUDED
 
@@ -48,7 +50,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 namespace Assimp	{
 
 // ---------------------------------------------------------------------------
-/** Clas to load STL files
+/** Importer class for the sterolithography STL file format
 */
 class STLImporter : public BaseImporter
 {
@@ -65,8 +67,10 @@ public:
 
 	// -------------------------------------------------------------------
 	/** Returns whether the class can handle the format of the given file. 
-	* See BaseImporter::CanRead() for details.	*/
-	bool CanRead( const std::string& pFile, IOSystem* pIOHandler) const;
+	 * See BaseImporter::CanRead() for details.	
+	 */
+	bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
+		bool checkSig) const;
 
 protected:
 
@@ -74,10 +78,7 @@ protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 */
-	void GetExtensionList(std::string& append)
-	{
-		append.append("*.stl");
-	}
+	void GetExtensionList(std::string& append);
 
 	// -------------------------------------------------------------------
 	/** Imports the given file into the given scene structure. 

+ 148 - 71
code/SceneCombiner.cpp

@@ -74,40 +74,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 namespace Assimp	{
 
-// ------------------------------------------------------------------------------------------------
-/** This is a small helper data structure simplifying our work
- */
-struct SceneHelper
-{
-	SceneHelper ()
-		: scene		(NULL)
-		, idlen		(0)
-	{
-		id[0] = 0;
-	}
-
-	SceneHelper (aiScene* _scene)
-		: scene		(_scene)
-		, idlen		(0)
-	{
-		id[0] = 0;
-	}
-
-	AI_FORCE_INLINE aiScene* operator-> () const
-	{
-		return scene;
-	}
-
-	// scene we're working on
-	aiScene* scene;
-
-	// prefix to be added to all identifiers in the scene ...
-	char id [32];
-
-	// and its strlen() 
-	unsigned int idlen;
-};
-
 // ------------------------------------------------------------------------------------------------
 // Add a prefix to a string
 inline void PrefixString(aiString& string,const char* prefix, unsigned int len)
@@ -124,6 +90,21 @@ inline void PrefixString(aiString& string,const char* prefix, unsigned int len)
 	string.length += len;
 }
 
+// ------------------------------------------------------------------------------------------------
+// Add node identifiers to a hashing set
+void SceneCombiner::AddNodeHashes(aiNode* node, std::set<unsigned int>& hashes)
+{
+	// Add node name to hashing set if it is non-empty - empty nodes are allowed 
+	// and they can't have any anims assigned so its absolutely safe to duplicate them.
+	if (node->mName.length) {
+		hashes.insert( SuperFastHash(node->mName.data,node->mName.length) );
+	}
+
+	// Process all children recursively
+	for (unsigned int i = 0; i < node->mNumChildren;++i)
+		AddNodeHashes(node->mChildren[i],hashes);
+}
+
 // ------------------------------------------------------------------------------------------------
 // Add a name prefix to all nodes in a hierarchy
 void SceneCombiner::AddNodePrefixes(aiNode* node, const char* prefix, unsigned int len)
@@ -136,6 +117,44 @@ void SceneCombiner::AddNodePrefixes(aiNode* node, const char* prefix, unsigned i
 		AddNodePrefixes(node->mChildren[i],prefix,len);
 }
 
+// ------------------------------------------------------------------------------------------------
+// Search for matching names
+bool SceneCombiner::FindNameMatch(const aiString& name, std::vector<SceneHelper>& input, unsigned int cur)
+{
+	const unsigned int hash = SuperFastHash(name.data, name.length);
+
+	// Check whether we find a positive match in one of the given sets
+	for (unsigned int i = 0; i < input.size(); ++i) {
+
+		if (cur != i && input[i].hashes.find(hash) != input[i].hashes.end()) {
+			return true;
+		}
+	}
+	return false;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Add a name prefix to all nodes in a hierarchy if a hash match is found
+void SceneCombiner::AddNodePrefixesChecked(aiNode* node, const char* prefix, unsigned int len,
+	std::vector<SceneHelper>& input, unsigned int cur)
+{
+	ai_assert(NULL != prefix);
+	const unsigned int hash = SuperFastHash(node->mName.data,node->mName.length);
+
+	// Check whether we find a positive match in one of the given sets
+	for (unsigned int i = 0; i < input.size(); ++i) {
+
+		if (cur != i && input[i].hashes.find(hash) != input[i].hashes.end()) {
+			PrefixString(node->mName,prefix,len);
+			break;
+		}
+	}
+
+	// Process all children recursively
+	for (unsigned int i = 0; i < node->mNumChildren;++i)
+		AddNodePrefixesChecked(node->mChildren[i],prefix,len,input,cur);
+}
+
 // ------------------------------------------------------------------------------------------------
 // Add an offset to all mesh indices in a node graph
 void SceneCombiner::OffsetNodeMeshIndices (aiNode* node, unsigned int offset)
@@ -300,6 +319,20 @@ void SceneCombiner::MergeScenes(aiScene** _dest, aiScene* master,
 
 			const unsigned int random = rndGen();
 			src[i].idlen = ::sprintf(src[i].id,"$%.6X$_",random);
+
+			if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY) {
+				
+				// Compute hashes for all identifiers in this scene and store them
+				// in a sorted table (for convenience I'm using std::set). We hash
+				// just the node and animation channel names, all identifiers except
+				// the material names should be caught by doing this.
+				AddNodeHashes(src[i]->mRootNode,src[i].hashes);
+
+				for (unsigned int a = 0; a < src[i]->mNumAnimations;++a) {
+					aiAnimation* anim = src[i]->mAnimations[a];
+					src[i].hashes.insert(SuperFastHash(anim->mName.data,anim->mName.length));
+				}
+			}
 		}
 	}
 	
@@ -321,7 +354,11 @@ void SceneCombiner::MergeScenes(aiScene** _dest, aiScene* master,
 		dest->mNumAnimations += (*cur)->mNumAnimations;
 
 		// Combine the flags of all scenes
-		dest->mFlags |= (*cur)->mFlags;
+		// We need to process them flag-by-flag here to get correct results
+		// dest->mFlags ; //|= (*cur)->mFlags;
+		if ((*cur)->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT) {
+			dest->mFlags |= AI_SCENE_FLAGS_NON_VERBOSE_FORMAT;
+		}
 	}
 
 	// generate the output texture list + an offset table for all texture indices
@@ -352,11 +389,10 @@ void SceneCombiner::MergeScenes(aiScene** _dest, aiScene* master,
 
 	// generate the output material list + an offset table for all material indices
 	if (dest->mNumMaterials)
-	{
+	{ 
 		aiMaterial** pip = dest->mMaterials = new aiMaterial*[dest->mNumMaterials];
 		cnt = 0;
-		for ( unsigned int n = 0; n < src.size();++n )
-		{
+		for ( unsigned int n = 0; n < src.size();++n )	{
 			SceneHelper* cur = &src[n];
 			for (unsigned int i = 0; i < (*cur)->mNumMaterials;++i)
 			{
@@ -369,8 +405,7 @@ void SceneCombiner::MergeScenes(aiScene** _dest, aiScene* master,
 				}
 				else *pip = (*cur)->mMaterials[i];
 
-				if ((*cur)->mNumTextures != dest->mNumTextures)
-				{
+				if ((*cur)->mNumTextures != dest->mNumTextures)		{
 					// We need to update all texture indices of the mesh. So we need to search for
 					// a material property called '$tex.file'
 
@@ -391,8 +426,7 @@ void SceneCombiner::MergeScenes(aiScene** _dest, aiScene* master,
 						}
 
 						// Need to generate new, unique material names?
-						else if (!::strcmp( prop->mKey.data,"$mat.name" ) &&
-							flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_MATNAMES)
+						else if (!::strcmp( prop->mKey.data,"$mat.name" ) && flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_MATNAMES)
 						{
 							aiString* pcSrc = (aiString*) prop->mData; 
 							PrefixString(*pcSrc, (*cur).id, (*cur).idlen);
@@ -458,7 +492,7 @@ void SceneCombiner::MergeScenes(aiScene** _dest, aiScene* master,
 	aiAnimation** ppAnims = dest->mAnimations = (dest->mNumAnimations 
 		? new aiAnimation*[dest->mNumAnimations] : NULL);
 
-	for ( unsigned int n = 0; n < src.size();++n )
+	for ( int n = src.size()-1; n >= 0 ;--n ) /* !!! important !!! */
 	{
 		SceneHelper* cur = &src[n];
 		aiNode* node;
@@ -466,7 +500,9 @@ void SceneCombiner::MergeScenes(aiScene** _dest, aiScene* master,
 		// To offset or not to offset, this is the question
 		if (n != duplicates[n])
 		{
+			// Get full scenegraph copy
 			Copy( &node, (*cur)->mRootNode );
+			OffsetNodeMeshIndices(node,offset[duplicates[n]]);
 
 			if (flags & AI_INT_MERGE_SCENE_DUPLICATES_DEEP_CPY)	{
 				// (note:) they are already 'offseted' by offset[duplicates[n]] 
@@ -481,6 +517,30 @@ void SceneCombiner::MergeScenes(aiScene** _dest, aiScene* master,
 		if (n) // src[0] is the master node
 			nodes.push_back(NodeAttachmentInfo( node,srcList[n-1].attachToNode,n ));
 
+		// add name prefixes?
+		if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES) {
+
+			// or the whole scenegraph
+			if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY) {
+				AddNodePrefixesChecked(node,(*cur).id,(*cur).idlen,src,n);
+			}
+			else AddNodePrefixes(node,(*cur).id,(*cur).idlen);
+
+			// meshes
+			for (unsigned int i = 0; i < (*cur)->mNumMeshes;++i)	{
+				aiMesh* mesh = (*cur)->mMeshes[i]; 
+
+				// rename all bones
+				for (unsigned int a = 0; a < mesh->mNumBones;++a)	{
+					if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY) {
+						if (!FindNameMatch(mesh->mBones[a]->mName,src,n))
+							continue;
+					}
+					PrefixString(mesh->mBones[a]->mName,(*cur).id,(*cur).idlen);
+				}
+			}
+		}
+
 		// --------------------------------------------------------------------
 		// Copy light sources
 		for (unsigned int i = 0; i < (*cur)->mNumLights;++i,++ppLights)
@@ -490,6 +550,17 @@ void SceneCombiner::MergeScenes(aiScene** _dest, aiScene* master,
 				Copy(ppLights, (*cur)->mLights[i]);
 			}
 			else *ppLights = (*cur)->mLights[i];
+
+
+			// Add name prefixes?
+			if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES) {
+				if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY) {
+					if (!FindNameMatch((*ppLights)->mName,src,n))
+						continue;
+				}
+
+				PrefixString((*ppLights)->mName,(*cur).id,(*cur).idlen);
+			}
 		}
 
 		// --------------------------------------------------------------------
@@ -500,6 +571,16 @@ void SceneCombiner::MergeScenes(aiScene** _dest, aiScene* master,
 				Copy(ppCameras, (*cur)->mCameras[i]);
 			}
 			else *ppCameras = (*cur)->mCameras[i];
+
+			// Add name prefixes?
+			if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES) {
+				if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY) {
+					if (!FindNameMatch((*ppCameras)->mName,src,n))
+						continue;
+				}
+
+				PrefixString((*ppCameras)->mName,(*cur).id,(*cur).idlen);
+			}
 		}
 
 		// --------------------------------------------------------------------
@@ -510,30 +591,26 @@ void SceneCombiner::MergeScenes(aiScene** _dest, aiScene* master,
 				Copy(ppAnims, (*cur)->mAnimations[i]);
 			}
 			else *ppAnims = (*cur)->mAnimations[i];
-		}
-	}
 
-	for ( unsigned int n = 1; n < src.size();++n )	{
-		SceneHelper* cur = &src[n];
-		// --------------------------------------------------------------------
-		// Add prefixes
-		if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES)
-		{
-			for (unsigned int i = 0; i < (*cur)->mNumLights;++i)
-				PrefixString(dest->mLights[i]->mName,(*cur).id,(*cur).idlen);
-
-			for (unsigned int i = 0; i < (*cur)->mNumCameras;++i)
-				PrefixString(dest->mCameras[i]->mName,(*cur).id,(*cur).idlen);
+			// Add name prefixes?
+			if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES) {
+				if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY) {
+					if (!FindNameMatch((*ppAnims)->mName,src,n))
+						continue;
+				}
 
-			for (unsigned int i = 0; i < (*cur)->mNumAnimations;++i)	{
-				aiAnimation* anim = dest->mAnimations[i]; 
-				PrefixString(anim->mName,(*cur).id,(*cur).idlen);
+				PrefixString((*ppAnims)->mName,(*cur).id,(*cur).idlen);
 
 				// don't forget to update all node animation channels
-				for (unsigned int a = 0; a < anim->mNumChannels;++a)
-					PrefixString(anim->mChannels[a]->mNodeName,(*cur).id,(*cur).idlen);
+				for (unsigned int a = 0; a < (*ppAnims)->mNumChannels;++a) {
+					if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY) {
+						if (!FindNameMatch((*ppAnims)->mChannels[a]->mNodeName,src,n))
+							continue;
+					}
+
+					PrefixString((*ppAnims)->mChannels[a]->mNodeName,(*cur).id,(*cur).idlen);
+				}
 			}
-			AddNodePrefixes(nodes[n-1].node,(*cur).id,(*cur).idlen);
 		}
 	}
 
@@ -585,6 +662,11 @@ void SceneCombiner::MergeScenes(aiScene** _dest, aiScene* master,
 		delete deleteMe;
 	}
 
+	// Check flags
+	if (!dest->mNumMeshes || !dest->mNumMaterials) {
+		dest->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
+	}
+
 	// We're finished
 }
 
@@ -605,10 +687,8 @@ void SceneCombiner::BuildUniqueBoneList(std::list<BoneWithHash>& asBones,
 			std::list<BoneWithHash>::iterator it2  = asBones.begin();
 			std::list<BoneWithHash>::iterator end2 = asBones.end();
 
-			for (;it2 != end2;++it2)
-			{
-				if ((*it2).first == itml)
-				{
+			for (;it2 != end2;++it2)	{
+				if ((*it2).first == itml)	{
 					(*it2).pSrcBones.push_back(BoneSrcIndex(p,iOffset));
 					break;
 				}
@@ -666,10 +746,7 @@ void SceneCombiner::MergeBones(aiMesh* out,std::vector<aiMesh*>::const_iterator
 			if (wmit != (*it).pSrcBones.begin() &&
 				pc->mOffsetMatrix != (*wmit).first->mOffsetMatrix)
 			{
-				DefaultLogger::get()->warn("Bones with equal names but different "
-					"offset matrices can't be joined at the moment. If this causes "
-					"problems, deactivate the OptimizeGraph-Step");
-
+				DefaultLogger::get()->warn("Bones with equal names but different offset matrices can't be joined at the moment");
 				continue;
 			}
 			pc->mOffsetMatrix = (*wmit).first->mOffsetMatrix;
@@ -1026,7 +1103,7 @@ void SceneCombiner::Copy     (aiAnimation** _dest, const aiAnimation* src)
 	::memcpy(dest,src,sizeof(aiAnimation));
 
 	// and reallocate all arrays
-	GetArrayCopy( dest->mChannels, dest->mNumChannels );
+	CopyPtrArray( dest->mChannels, src->mChannels, dest->mNumChannels );
 }
 
 // ------------------------------------------------------------------------------------------------

+ 58 - 1
code/SceneCombiner.h

@@ -118,7 +118,7 @@ struct NodeAttachmentInfo
 /** @def AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY
  * Can be combined with AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES.
  * Unique names are generated, but only if this is absolutely
- * required (if there would be conflicts otherwuse.)
+ * required to avoid name conflicts.
  */
 #define AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY 0x10
 
@@ -133,6 +133,43 @@ struct BoneWithHash : public std::pair<uint32_t,aiString*>	{
 };
 
 
+// ---------------------------------------------------------------------------
+/** @brief Utility for SceneCombiner
+ */
+struct SceneHelper
+{
+	SceneHelper ()
+		: scene		(NULL)
+		, idlen		(0)
+	{
+		id[0] = 0;
+	}
+
+	SceneHelper (aiScene* _scene)
+		: scene		(_scene)
+		, idlen		(0)
+	{
+		id[0] = 0;
+	}
+
+	AI_FORCE_INLINE aiScene* operator-> () const
+	{
+		return scene;
+	}
+
+	// scene we're working on
+	aiScene* scene;
+
+	// prefix to be added to all identifiers in the scene ...
+	char id [32];
+
+	// and its strlen() 
+	unsigned int idlen;
+
+	// hash table to quickly check whether a name is contained in the scene
+	std::set<unsigned int> hashes;
+};
+
 // ---------------------------------------------------------------------------
 /** \brief Static helper class providing various utilities to merge two
  *    scenes. It is intended as internal utility and NOT for use by 
@@ -301,6 +338,26 @@ public:
 
 	// recursive, of course
 	static void Copy     (aiNode** dest, const aiNode* src);
+
+
+private:
+
+	// -------------------------------------------------------------------
+	// Same as AddNodePrefixes, but with an additional check
+	static void AddNodePrefixesChecked(aiNode* node, const char* prefix, 
+		unsigned int len,
+		std::vector<SceneHelper>& input, 
+		unsigned int cur);
+
+	// -------------------------------------------------------------------
+	// Add node identifiers to a hashing set
+	static void AddNodeHashes(aiNode* node, std::set<unsigned int>& hashes);
+
+
+	// -------------------------------------------------------------------
+	// Search for duplicate names
+	static bool FindNameMatch(const aiString& name, 
+		std::vector<SceneHelper>& input, unsigned int cur);
 };
 
 }

+ 70 - 17
code/ScenePreprocessor.cpp

@@ -64,26 +64,56 @@ void ScenePreprocessor::ProcessScene ()
 	// Generate a default material if none was specified
 	if (!scene->mNumMaterials && scene->mNumMeshes)
 	{
-		scene->mMaterials      = new aiMaterial*[scene->mNumMaterials = 1];
-		MaterialHelper* helper = new MaterialHelper();
-		scene->mMaterials[0]   = helper;
+		scene->mMaterials      = new aiMaterial*[2];
+		MaterialHelper* helper;
 
-		// gray
-		aiColor3D clr(0.6f,0.6f,0.6f);
-		helper->AddProperty(&clr,1,AI_MATKEY_COLOR_DIFFUSE);
+		aiString name;
 
-		// add a small ambient color value
-		clr = aiColor3D(0.05f,0.05f,0.05f);
-		helper->AddProperty(&clr,1,AI_MATKEY_COLOR_AMBIENT);
+		// Check whether there are meshes with at least one set of uv coordinates ... add a dummy texture for them
+		// meshes without texture coordinates receive a boring gray default material.
+		unsigned int mat0 = 0xffffffff, mat1 = 0xffffffff;
+		for (unsigned int i = 0; i < scene->mNumMeshes;++i) {
+			if (scene->mMeshes[i]->mTextureCoords[0]) {
 
-		// setup the default name
-		aiString name(AI_DEFAULT_MATERIAL_NAME);
-		helper->AddProperty(&name,AI_MATKEY_NAME);
+				if (mat0 == 0xffffffff) {
+					scene->mMaterials[scene->mNumMaterials] = helper = new MaterialHelper();
 
-		for (unsigned int i = 0; i < scene->mNumMeshes;++i)
-			scene->mMeshes[i]->mMaterialIndex = 0;
+					// dummy texture
+					name.Set("texture.png");
+					helper->AddProperty(&name,AI_MATKEY_TEXTURE_DIFFUSE(0));
 
-		DefaultLogger::get()->debug("ScenePreprocessor: Added default material \'" AI_DEFAULT_MATERIAL_NAME  "\'");
+					// setup default name
+					name.Set(AI_DEFAULT_TEXTURED_MATERIAL_NAME);
+					helper->AddProperty(&name,AI_MATKEY_NAME);
+
+					mat0 = scene->mNumMaterials++;
+					DefaultLogger::get()->debug("ScenePreprocessor: Adding textured material \'" AI_DEFAULT_TEXTURED_MATERIAL_NAME  "\'");
+				}
+				scene->mMeshes[i]->mMaterialIndex = mat0;
+			}
+			else
+			{
+				if (mat1 == 0xffffffff) {
+					scene->mMaterials[scene->mNumMaterials] = helper = new MaterialHelper();
+
+					// gray
+					aiColor3D clr(0.6f,0.6f,0.6f);
+					helper->AddProperty(&clr,1,AI_MATKEY_COLOR_DIFFUSE);
+
+					// add a small ambient color value
+					clr = aiColor3D(0.05f,0.05f,0.05f);
+					helper->AddProperty(&clr,1,AI_MATKEY_COLOR_AMBIENT);
+
+					// setup the default name
+					name.Set(AI_DEFAULT_MATERIAL_NAME);
+					helper->AddProperty(&name,AI_MATKEY_NAME);
+
+					mat1 = scene->mNumMaterials++;
+					DefaultLogger::get()->debug("ScenePreprocessor: Adding grey material \'" AI_DEFAULT_MATERIAL_NAME  "\'");
+				}
+				scene->mMeshes[i]->mMaterialIndex = mat1;
+			}
+		}
 	}
 }
 
@@ -96,8 +126,22 @@ void ScenePreprocessor::ProcessMesh (aiMesh* mesh)
 		if (!mesh->mTextureCoords[i])
 			mesh->mNumUVComponents[i] = 0;
 
-		else if( !mesh->mNumUVComponents[i])
-			mesh->mNumUVComponents[i] = 2;
+		else {
+			if( !mesh->mNumUVComponents[i])
+				mesh->mNumUVComponents[i] = 2;
+
+			// Ensure unsued components are zeroed. This will make 1D texture channels work
+			// as if they were 2D channels .. just in case an application doesn't handle
+			// this case
+			if (2 == mesh->mNumUVComponents[i]) {
+				for (aiVector3D* p = mesh->mTextureCoords[i], *end = p+mesh->mNumVertices; p != end; ++p)
+					p->z = 0.f;
+			}
+			else if (1 == mesh->mNumUVComponents[i]) {
+				for (aiVector3D* p = mesh->mTextureCoords[i], *end = p+mesh->mNumVertices; p != end; ++p)
+					p->z = p->y = 0.f;
+			}
+		}
 	}
 
 	// If the information which primitive types are there in the
@@ -127,6 +171,15 @@ void ScenePreprocessor::ProcessMesh (aiMesh* mesh)
 			}
 		}
 	}
+
+	// If tangents and normals are given but no bitangents compute them
+	if (mesh->mTangents && mesh->mNormals && !mesh->mBitangents)
+	{
+		mesh->mBitangents = new aiVector3D[mesh->mNumVertices];
+		for (unsigned int i = 0; i < mesh->mNumVertices;++i)	{
+			mesh->mBitangents[i] = mesh->mNormals[i] ^ mesh->mTangents[i];
+		}
+	}
 }
 
 // ---------------------------------------------------------------------------------------------

+ 3 - 2
code/SkeletonMeshBuilder.cpp

@@ -120,11 +120,12 @@ void SkeletonMeshBuilder::CreateGeometry( const aiNode* pNode)
 			mFaces.push_back( Face( localVertexStart + 6, localVertexStart + 7, localVertexStart + 8));
 			mFaces.push_back( Face( localVertexStart + 9, localVertexStart + 10, localVertexStart + 11));
 		}
-	} else
+	} 
+	else
 	{
 		// if the node has no children, it's an end node. Put a little knob there instead
 		aiVector3D ownpos( pNode->mTransformation.a4, pNode->mTransformation.b4, pNode->mTransformation.c4);
-		float sizeEstimate = ownpos.Length() * 0.2f;
+		float sizeEstimate = ownpos.Length() * 0.18f;
 
 		mVertices.push_back( aiVector3D( -sizeEstimate, 0.0f, 0.0f));
 		mVertices.push_back( aiVector3D( 0.0f, sizeEstimate, 0.0f));

+ 1 - 1
code/SortByPTypeProcess.cpp

@@ -303,7 +303,7 @@ void SortByPTypeProcess::Execute( aiScene* pScene)
 					if (vert)
 					{
 						*vert++ = mesh->mVertices[idx];
-						//mesh->mVertices[idx].x = std::numeric_limits<float>::quiet_NaN();
+						//mesh->mVertices[idx].x = get_qnan();
 					}
 					if (nor )*nor++  = mesh->mNormals[idx];
 					if (tan )

Some files were not shown because too many files changed in this diff