Pārlūkot izejas kodu

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 gadi atpakaļ
vecāks
revīzija
4bbc03332b
100 mainītis faili ar 4869 papildinājumiem un 1608 dzēšanām
  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
 // Setup final material indices, generae a default material if necessary
 void Discreet3DSImporter::ReplaceDefaultMaterial()
 void Discreet3DSImporter::ReplaceDefaultMaterial()
 {
 {
-	//////////////////////////////////////////////////////////////////////////
+	
 	// Try to find an existing material that matches the
 	// Try to find an existing material that matches the
 	// typical default material setting:
 	// typical default material setting:
 	// - no textures
 	// - no textures
 	// - diffuse color (in grey!)
 	// - diffuse color (in grey!)
 	// NOTE: This is here to workaround the fact that some
 	// NOTE: This is here to workaround the fact that some
 	// exporters are writing a default material, too.
 	// exporters are writing a default material, too.
-	//////////////////////////////////////////////////////////////////////////
 	unsigned int idx = 0xcdcdcdcd;
 	unsigned int idx = 0xcdcdcdcd;
 	for (unsigned int i = 0; i < mScene->mMaterials.size();++i)
 	for (unsigned int i = 0; i < mScene->mMaterials.size();++i)
 	{
 	{
@@ -438,57 +437,65 @@ void Discreet3DSImporter::AddNodeToGraph(aiScene* pcSOut,aiNode* pcOut,
 	iArray.reserve(3);
 	iArray.reserve(3);
 
 
 	aiMatrix4x4 abs;
 	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
 	// 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->aScalingKeys.size()   > 1  || pcIn->aCameraRollKeys.size() > 1 ||
 		pcIn->aTargetPositionKeys.size() > 1)
 		pcIn->aTargetPositionKeys.size() > 1)
 	{
 	{

+ 3 - 5
code/3DSHelper.h

@@ -124,11 +124,10 @@ public:
 		CHUNK_PERCENTF	= 0x0031,		// float4  percentage
 		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,
 		CHUNK_PRJ       = 0xC23D,
 
 
-		// Unknown. Possibly a reference to an external .mli file?
+		// MDLI master chunk
 		CHUNK_MLI       = 0x3DAA,
 		CHUNK_MLI       = 0x3DAA,
 
 
 		// Primary main chunk of the .3ds file
 		// Primary main chunk of the .3ds file
@@ -178,7 +177,6 @@ public:
 		CHUNK_MESHCOLOR = 0x4165,
 		CHUNK_MESHCOLOR = 0x4165,
 		CHUNK_TXTINFO   = 0x4170,
 		CHUNK_TXTINFO   = 0x4170,
 		CHUNK_LIGHT     = 0x4600,
 		CHUNK_LIGHT     = 0x4600,
-		CHUNK_SPOTLIGHT = 0x4610,
 		CHUNK_CAMERA    = 0x4700,
 		CHUNK_CAMERA    = 0x4700,
 		CHUNK_HIERARCHY = 0x4F00,
 		CHUNK_HIERARCHY = 0x4F00,
 
 
@@ -330,7 +328,7 @@ struct Texture
 		, mMapMode	(aiTextureMapMode_Wrap)
 		, mMapMode	(aiTextureMapMode_Wrap)
 		, iUVSrc	(0)
 		, iUVSrc	(0)
 	{
 	{
-		mTextureBlend = std::numeric_limits<float>::quiet_NaN();
+		mTextureBlend = get_qnan();
 	}
 	}
 
 
 	//! Specifies the blend factor for the texture
 	//! 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"
 #include "AssimpPCH.h"
 #ifndef ASSIMP_BUILD_NO_3DS_IMPORTER
 #ifndef ASSIMP_BUILD_NO_3DS_IMPORTER
@@ -53,12 +57,14 @@ using namespace Assimp;
 // Begins a new parsing block
 // Begins a new parsing block
 // - Reads the current chunk and validates it
 // - Reads the current chunk and validates it
 // - computes its length
 // - 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. 
 // 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;
 	mMasterScale               = 1.0f;
 	mBackgroundImage           = "";
 	mBackgroundImage           = "";
 	bHasBG                     = false;
 	bHasBG                     = false;
+	bIsPrj                     = false;
 
 
 	// Parse the file
 	// Parse the file
 	ParseMainChunk();
 	ParseMainChunk();
@@ -138,8 +154,7 @@ void Discreet3DSImporter::InternReadFile( const std::string& pFile,
 	// vectors from the smoothing groups we read from the
 	// vectors from the smoothing groups we read from the
 	// file.
 	// file.
 	for (std::vector<D3DS::Mesh>::iterator i = mScene->mMeshes.begin(),
 	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);
 		CheckIndices(*i);
 		MakeUnique  (*i);
 		MakeUnique  (*i);
 		ComputeNormalsWithSmoothingsGroups<D3DS::Face>(*i);
 		ComputeNormalsWithSmoothingsGroups<D3DS::Face>(*i);
@@ -226,6 +241,9 @@ void Discreet3DSImporter::ParseMainChunk()
 	// get chunk type
 	// get chunk type
 	switch (chunk.Flag)
 	switch (chunk.Flag)
 	{
 	{
+	
+	case Discreet3DS::CHUNK_PRJ:
+		bIsPrj = true;
 	case Discreet3DS::CHUNK_MAIN:
 	case Discreet3DS::CHUNK_MAIN:
 		ParseEditorChunk();
 		ParseEditorChunk();
 		break;
 		break;
@@ -371,9 +389,9 @@ void Discreet3DSImporter::ParseChunk(const char* name, unsigned int num)
 
 
 		light->mColorDiffuse = aiColor3D(1.f,1.f,1.f);
 		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
 		// The specular light color is identical the the diffuse light
 		// color. The ambient light color is equal to the ambient base 
 		// 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)
 		if (camera->mHorizontalFOV < 0.001f)
 			camera->mHorizontalFOV = AI_DEG_TO_RAD(45.f);
 			camera->mHorizontalFOV = AI_DEG_TO_RAD(45.f);
 		}
 		}
+
+		// Now check for further subchunks 
+		if (!bIsPrj) /* fixme */
+			ParseCameraChunk();
 		break;
 		break;
 	};
 	};
 	ASSIMP_3DS_END_CHUNK();
 	ASSIMP_3DS_END_CHUNK();
@@ -440,7 +462,7 @@ void Discreet3DSImporter::ParseLightChunk()
 	// get chunk type
 	// get chunk type
 	switch (chunk.Flag)
 	switch (chunk.Flag)
 	{
 	{
-	case Discreet3DS::CHUNK_SPOTLIGHT:
+	case Discreet3DS::CHUNK_DL_SPOTLIGHT:
 		// Now we can be sure that the light is a spot light
 		// Now we can be sure that the light is a spot light
 		light->mType = aiLightSource_SPOT;
 		light->mType = aiLightSource_SPOT;
 
 
@@ -511,7 +533,7 @@ void Discreet3DSImporter::ParseKeyframeChunk()
 	switch (chunk.Flag)
 	switch (chunk.Flag)
 	{
 	{
 	case Discreet3DS::CHUNK_TRACKCAMTGT:
 	case Discreet3DS::CHUNK_TRACKCAMTGT:
-	case Discreet3DS::CHUNK_SPOTLIGHT:
+	case Discreet3DS::CHUNK_TRACKSPOTL:
 	case Discreet3DS::CHUNK_TRACKCAMERA:
 	case Discreet3DS::CHUNK_TRACKCAMERA:
 	case Discreet3DS::CHUNK_TRACKINFO:
 	case Discreet3DS::CHUNK_TRACKINFO:
 	case Discreet3DS::CHUNK_TRACKLIGHT:
 	case Discreet3DS::CHUNK_TRACKLIGHT:
@@ -574,14 +596,11 @@ void Discreet3DSImporter::SkipTCBInfo()
 {
 {
 	unsigned int flags = stream->GetI2();
 	unsigned int flags = stream->GetI2();
 
 
-	if (!flags)
-	{
-		//////////////////////////////////////////////////////////////////////////
+	if (!flags)	{
 		// Currently we can't do anything with these values. They occur
 		// Currently we can't do anything with these values. They occur
 		// quite rare, so it wouldn't be worth the effort implementing
 		// quite rare, so it wouldn't be worth the effort implementing
 		// them. 3DS ist not really suitable for complex animations,
 		// them. 3DS ist not really suitable for complex animations,
 		// so full support is not required.
 		// so full support is not required.
-		//////////////////////////////////////////////////////////////////////////
 		DefaultLogger::get()->warn("3DS: Skipping TCB animation info");
 		DefaultLogger::get()->warn("3DS: Skipping TCB animation info");
 	}
 	}
 
 
@@ -1015,35 +1034,7 @@ void Discreet3DSImporter::ParseMeshChunk()
 		mMesh.mMat.a4 = stream->GetF4();
 		mMesh.mMat.a4 = stream->GetF4();
 		mMesh.mMat.b4 = stream->GetF4();
 		mMesh.mMat.b4 = stream->GetF4();
 		mMesh.mMat.c4 = 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;
 		break;
 
 
 	case Discreet3DS::CHUNK_MAPLIST:
 	case Discreet3DS::CHUNK_MAPLIST:
@@ -1353,7 +1344,7 @@ float Discreet3DSImporter::ParsePercentageChunk()
 		return stream->GetF4();
 		return stream->GetF4();
 	else if (Discreet3DS::CHUNK_PERCENTW == chunk.Flag)
 	else if (Discreet3DS::CHUNK_PERCENTW == chunk.Flag)
 		return (float)((uint16_t)stream->GetI2()) / (float)0xFFFF;
 		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);
 	ai_assert(out != NULL);
 
 
 	// error return value
 	// 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);
 	static const aiColor3D clrError = aiColor3D(qnan,qnan,qnan);
 
 
 	Discreet3DS::Chunk chunk;
 	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
 #ifndef AI_3DSIMPORTER_H_INC
 #define 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;
 struct aiNode;
 #include "3DSHelper.h"
 #include "3DSHelper.h"
 
 
-namespace Assimp
-{
+namespace Assimp	{
 class MaterialHelper;
 class MaterialHelper;
 
 
 using namespace D3DS;
 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
 class Discreet3DSImporter : public BaseImporter
 {
 {
 	friend class Importer;
 	friend class Importer;
@@ -74,14 +74,16 @@ public:
 
 
 	// -------------------------------------------------------------------
 	// -------------------------------------------------------------------
 	/** Returns whether the class can handle the format of the given file. 
 	/** 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().
 	/** 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);
 	void SetupProperties(const Importer* pImp);
 
 
 protected:
 protected:
@@ -90,21 +92,18 @@ protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 * 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. 
 	/** 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, 
 	void InternReadFile( const std::string& pFile, aiScene* pScene, 
 		IOSystem* pIOHandler);
 		IOSystem* pIOHandler);
 
 
 	// -------------------------------------------------------------------
 	// -------------------------------------------------------------------
 	/** Converts a temporary material to the outer representation 
 	/** Converts a temporary material to the outer representation 
-	*/
+	 */
 	void ConvertMaterial(D3DS::Material& p_cMat,
 	void ConvertMaterial(D3DS::Material& p_cMat,
 		MaterialHelper& p_pcOut);
 		MaterialHelper& p_pcOut);
 
 
@@ -112,7 +111,7 @@ protected:
 	/** Read a chunk
 	/** Read a chunk
 	 *
 	 *
 	 *  @param pcOut Receives the current chunk
 	 *  @param pcOut Receives the current chunk
-	*/
+	 */
 	void ReadChunk(Discreet3DS::Chunk* pcOut);
 	void ReadChunk(Discreet3DS::Chunk* pcOut);
 
 
 	// -------------------------------------------------------------------
 	// -------------------------------------------------------------------
@@ -271,6 +270,9 @@ protected:
 	/** Path to the background image of the scene */
 	/** Path to the background image of the scene */
 	std::string mBackgroundImage;
 	std::string mBackgroundImage;
 	bool bHasBG;
 	bool bHasBG;
+
+	/** true if PRJ file */
+	bool bIsPrj;
 };
 };
 
 
 } // end of namespace Assimp
 } // end of namespace Assimp

+ 19 - 10
code/ACLoader.cpp

@@ -108,28 +108,38 @@ using namespace Assimp;
 // Constructor to be privately used by Importer
 // Constructor to be privately used by Importer
 AC3DImporter::AC3DImporter()
 AC3DImporter::AC3DImporter()
 {
 {
+	// nothing to be done here
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Destructor, private as well 
 // Destructor, private as well 
 AC3DImporter::~AC3DImporter()
 AC3DImporter::~AC3DImporter()
 {
 {
+	// nothing to be done here
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file. 
 // 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);
 	return SkipSpaces(&buffer);
 }
 }
 
 
-
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Parse an object section in an AC file
 // Parse an object section in an AC file
 void AC3DImporter::LoadObjectSection(std::vector<Object>& objects)
 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
 #ifndef AI_AC3DLOADER_H_INCLUDED
 #define 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. 
 	/** 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:
 protected:
 
 
@@ -175,10 +179,7 @@ protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 * 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. 
 	/** 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"
 #include "AssimpPCH.h"
 #ifndef ASSIMP_BUILD_NO_ASE_IMPORTER
 #ifndef ASSIMP_BUILD_NO_ASE_IMPORTER
@@ -69,23 +71,25 @@ ASEImporter::~ASEImporter()
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file. 
 // 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 + ".");
 		throw new ImportErrorException( "Failed to open ASE file " + pFile + ".");
 
 
 	size_t fileSize = file->FileSize();
 	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
 	// Allocate storage and copy the contents of the file to a memory buffer
 	// (terminate it with zero)
 	// (terminate it with zero)
@@ -778,8 +784,7 @@ void ASEImporter::BuildNodes()
 		delete pc;
 		delete pc;
 	}
 	}
 	// The root node should not have at least one child or the file is invalid
 	// 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");
 		throw new ImportErrorException("No nodes loaded. The ASE/ASK file is either empty or corrupt");
 	}
 	}
 	return;
 	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
 #ifndef AI_ASELOADER_H_INCLUDED
 #define 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;
 struct aiNode;
 #include "ASEParser.h"
 #include "ASEParser.h"
 
 
-namespace Assimp
-{
+namespace Assimp {
 class MaterialHelper;
 class MaterialHelper;
 
 
 using namespace ASE;
 using namespace ASE;
 
 
-// ---------------------------------------------------------------------------
-/** Used to load ASE files
-*/
+// --------------------------------------------------------------------------------
+/** Importer class for the 3DS ASE ASCII format
+ *
+ * fixme: consider code cleanup
+ */
 class ASEImporter : public BaseImporter
 class ASEImporter : public BaseImporter
 {
 {
 	friend class Importer;
 	friend class Importer;
@@ -72,8 +75,10 @@ public:
 
 
 	// -------------------------------------------------------------------
 	// -------------------------------------------------------------------
 	/** Returns whether the class can handle the format of the given file. 
 	/** 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:
 protected:
 
 
@@ -81,10 +86,7 @@ protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 * 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. 
 	/** 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"
 #include "AssimpPCH.h"
 
 
@@ -120,8 +122,8 @@ Parser::Parser (const char* szFile, unsigned int fileFormatDefault)
 	iFileFormat = fileFormatDefault;
 	iFileFormat = fileFormatDefault;
 
 
 	// make sure that the color values are invalid
 	// 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
 	// setup some default values
 	iLineNumber = 0;
 	iLineNumber = 0;

+ 1 - 1
code/ASEParser.h

@@ -227,7 +227,7 @@ struct BaseNode
 		mName = szTemp;
 		mName = szTemp;
 
 
 		// Set mTargetPosition to qnan
 		// Set mTargetPosition to qnan
-		const float qnan = std::numeric_limits<float>::quiet_NaN();
+		const float qnan = get_qnan();
 		mTargetPosition.x = 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 "AssimpPCH.h"
 #include "./../include/aiVersion.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"
 "A free C/C++ library to import various 3D file formats into applications\n\n"
 
 
 "(c) ASSIMP Development Team, 2008-2009\n"
 "(c) ASSIMP Development Team, 2008-2009\n"
-"License: BSD\n"
+"License: 3-clause BSD license\n"
 "Website: http://assimp.sourceforge.net\n"
 "Website: http://assimp.sourceforge.net\n"
 ;
 ;
 
 
@@ -59,10 +59,12 @@ ASSIMP_API unsigned int aiGetCompileFlags ()	{
 	return flags;
 	return flags;
 }
 }
 
 
+// include current build revision
+#include "../mkutil/revision.h"
+
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 ASSIMP_API unsigned int aiGetVersionRevision ()
 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
 #	define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1
 #endif
 #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 
 // Actually that's not required for MSVC (it is included somewhere in 
 // the STL ..) but it is necessary for build with STLport.
 // the STL ..) but it is necessary for build with STLport.
 #include <ctype.h>
 #include <ctype.h>
@@ -75,6 +82,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <vector>
 #include <vector>
 #include <list>
 #include <list>
 #include <map>
 #include <map>
+#include <set>
 #include <string>
 #include <string>
 #include <sstream>
 #include <sstream>
 #include <iomanip>
 #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"
 #include "AssimpPCH.h"
 #ifndef ASSIMP_BUILD_NO_B3D_IMPORTER
 #ifndef ASSIMP_BUILD_NO_B3D_IMPORTER
@@ -52,7 +54,7 @@ using namespace Assimp;
 using namespace std;
 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( '.' );
 	int pos=pFile.find_last_of( '.' );
 	if( pos==string::npos ) return false;
 	if( pos==string::npos ) return false;

+ 1 - 1
code/B3DImporter.h

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

+ 11 - 10
code/BVHLoader.cpp

@@ -61,18 +61,19 @@ BVHLoader::~BVHLoader()
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file. 
 // 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 
 	// 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
 #ifndef AI_BVHLOADER_H_INC
 #define 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
 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
 class BVHLoader : public BaseImporter
 {
 {
@@ -88,7 +95,7 @@ protected:
 public:
 public:
 	/** Returns whether the class can handle the format of the given file. 
 	/** Returns whether the class can handle the format of the given file. 
 	 * See BaseImporter::CanRead() for details.	*/
 	 * 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:
 protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	/** 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 "AssimpPCH.h"
 #include "BaseImporter.h"
 #include "BaseImporter.h"
 
 
-
 using namespace Assimp;
 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 std::string&	pFile,
 	const char**		tokens, 
 	const char**		tokens, 
 	unsigned int		numTokens,
 	unsigned int		numTokens,
@@ -142,17 +143,105 @@ bool BaseImporter::SearchFileHeaderForToken(IOSystem* pIOHandler,
 	return false;
 	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
 // Represents an import request
 struct LoadRequest
 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)
 		:	file	(_file)
 		,	flags	(_flags)
 		,	flags	(_flags)
 		,	refCnt	(1)
 		,	refCnt	(1)
 		,	scene	(NULL)            
 		,	scene	(NULL)            
 		,	loaded	(false)
 		,	loaded	(false)
+		,	id		(_id)
 	{
 	{
 		if (_map)
 		if (_map)
 			map = *_map;
 			map = *_map;
@@ -164,6 +253,7 @@ struct LoadRequest
 	aiScene* scene;
 	aiScene* scene;
 	bool loaded;
 	bool loaded;
 	BatchLoader::PropertyMap map;
 	BatchLoader::PropertyMap map;
+	unsigned int id;
 
 
 	bool operator== (const std::string& f) {
 	bool operator== (const std::string& f) {
 		return file == f;
 		return file == f;
@@ -174,6 +264,10 @@ struct LoadRequest
 // BatchLoader::pimpl data structure
 // BatchLoader::pimpl data structure
 struct Assimp::BatchData
 struct Assimp::BatchData
 {
 {
+	BatchData()
+		:	next_id(0xffff)
+	{}
+
 	// IO system to be used for all imports
 	// IO system to be used for all imports
 	IOSystem* pIOSystem;
 	IOSystem* pIOSystem;
 
 
@@ -185,6 +279,9 @@ struct Assimp::BatchData
 
 
 	// Base path
 	// Base path
 	std::string pathBase;
 	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;
 	std::string::size_type ss,ss2;
 	if (std::string::npos != (ss = data->pathBase.find_first_of('.')))
 	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)
 			if (ss > ss2)
 				data->pathBase.erase(ss2,data->pathBase.length()-ss2);
 				data->pathBase.erase(ss2,data->pathBase.length()-ss2);
 		}
 		}
-		else return;
+		else {
+			data->pathBase = "";
+			return;
+		}
 	}
 	}
 
 
 	// make sure the directory is terminated properly
 	// 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*/)
 	unsigned int steps /*= 0*/, const PropertyMap* map /*= NULL*/)
 {
 {
 	ai_assert(!file.empty());
 	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 
 	// build a full path if this is a relative path and 
 	// we have a new base directory given
 	// 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;
 		real = data->pathBase + file;
 	}
 	}
 	else real = file;
 	else real = file;
@@ -258,32 +356,29 @@ void BatchLoader::AddLoadRequest	(const std::string& file,
 		// Call IOSystem's path comparison function here
 		// Call IOSystem's path comparison function here
 		if (data->pIOSystem->ComparePaths((*it).file,real))
 		if (data->pIOSystem->ComparePaths((*it).file,real))
 		{
 		{
+			if (map) {
+				if (!((*it).map == *map))
+					continue;
+			}
+			else if (!(*it).map.empty())
+				continue;
+
 			(*it).refCnt++;
 			(*it).refCnt++;
-			return;
+			return (*it).id;
 		}
 		}
 	}
 	}
 
 
 	// no, we don't have it. So add it to the queue ...
 	// 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)
 	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;
 			aiScene* sc = (*it).scene;
 			if (!(--(*it).refCnt))
 			if (!(--(*it).refCnt))

+ 137 - 57
code/BaseImporter.h

@@ -52,6 +52,10 @@ namespace Assimp	{
 class IOSystem;
 class IOSystem;
 class Importer;
 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. */
 /** Simple exception class to be thrown if an error occurs while importing. */
 class ASSIMP_API ImportErrorException 
 class ASSIMP_API ImportErrorException 
@@ -97,36 +101,48 @@ protected:
 public:
 public:
 	// -------------------------------------------------------------------
 	// -------------------------------------------------------------------
 	/** Returns whether the class can handle the format of the given file.
 	/** 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, 
 	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.
 	/** 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);
 	aiScene* ReadFile( const std::string& pFile, IOSystem* pIOHandler);
 
 
 
 
@@ -135,19 +151,20 @@ public:
 	 * @return A description of the last error that occured. An empty
 	 * @return A description of the last error that occured. An empty
 	 * string if there was no error.
 	 * 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().
 	/** 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 /*,
 	virtual void SetupProperties(const Importer* pImp /*,
 		unsigned int ppFlags*/);
 		unsigned int ppFlags*/);
 
 
@@ -157,7 +174,7 @@ protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 *  Importer implementations should append all file extensions
 	 *  Importer implementations should append all file extensions
 	 *  which they supported to the passed string.
 	 *  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
 	 * @param append Output string
 	 */
 	 */
 	virtual void GetExtensionList(std::string& append) = 0;
 	virtual void GetExtensionList(std::string& append) = 0;
@@ -169,23 +186,36 @@ protected:
 	 * expected to be correct. Override this function to implement the 
 	 * expected to be correct. Override this function to implement the 
 	 * actual importing.
 	 * actual importing.
 	 * <br>
 	 * <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
 	 *   In fact this means that every vertex that is referenced by
 	 *   a face is unique. Or the other way round: a vertex index may
 	 *   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
 	 *   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!
 	 * crash if one of the conditions is not met!
 	 *
 	 *
 	 * @param pFile Path of the file to be imported.
 	 * @param pFile Path of the file to be imported.
@@ -218,12 +248,53 @@ protected:
 		unsigned int		numTokens,
 		unsigned int		numTokens,
 		unsigned int		searchBytes = 200);
 		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 **/
 #if 0 /** TODO **/
 	// -------------------------------------------------------------------
 	// -------------------------------------------------------------------
 	/** An utility for all text file loaders. It converts a file to our
 	/** 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
 	 *  @param length Length of the input buffer, in bytes. Receives the
 	 *    number of output characters, excluding the terminal char.
 	 *    number of output characters, excluding the terminal char.
 	 *  @return true if the source format did not match our internal
 	 *  @return true if the source format did not match our internal
@@ -266,10 +337,18 @@ public:
 		Importer::IntPropertyMap     ints;
 		Importer::IntPropertyMap     ints;
 		Importer::FloatPropertyMap   floats;
 		Importer::FloatPropertyMap   floats;
 		Importer::StringPropertyMap  strings;
 		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:
 public:
+	
 
 
 	/** Construct a batch loader from a given IO system
 	/** Construct a batch loader from a given IO system
 	 */
 	 */
@@ -293,8 +372,10 @@ public:
 	 *  @param file File to be loaded
 	 *  @param file File to be loaded
 	 *  @param steps Steps to be executed on the file
 	 *  @param steps Steps to be executed on the file
 	 *  @param map Optional configuration properties
 	 *  @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);
 		unsigned int steps = 0, const PropertyMap* map = NULL);
 
 
 
 
@@ -304,11 +385,11 @@ public:
 	 *  If an import is requested several times, this function
 	 *  If an import is requested several times, this function
 	 *  can be called several times, too.
 	 *  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
 	 *  @return NULL if there is no scene with this file name
 	 *  in the queue of the scene hasn't been loaded yet.
 	 *  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.
 	/** Waits until all scenes have been loaded.
@@ -321,7 +402,6 @@ private:
 	BatchData* data;
 	BatchData* data;
 };
 };
 
 
-
 } // end of namespace Assimp
 } // end of namespace Assimp
 
 
 #endif // AI_BASEIMPORTER_H_INC
 #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
 	// catch exceptions thrown inside the PostProcess-Step
 	try
 	try
 	{
 	{
-		this->Execute(pImp->mScene);
+		Execute(pImp->mScene);
 
 
 	} catch( ImportErrorException* exception)
 	} 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;
 	const float angleEpsilon = 0.9999f;
 
 
 	std::vector<bool> vertexDone( pMesh->mNumVertices, false);
 	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
 	// create space for the tangents and bitangents
 	pMesh->mTangents = new aiVector3D[pMesh->mNumVertices];
 	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.
 			// their tangent vectors are set to qnan.
 			for (unsigned int i = 0; i < face.mNumIndices;++i)
 			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;
 			continue;
@@ -218,10 +220,10 @@ bool CalcTangentsProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex)
 		vertexFinder = &_vertexFinder;
 		vertexFinder = &_vertexFinder;
 		posEpsilon = ComputePositionEpsilon(pMesh);
 		posEpsilon = ComputePositionEpsilon(pMesh);
 	}
 	}
-
 	std::vector<unsigned int> verticesFound;
 	std::vector<unsigned int> verticesFound;
 
 
 	const float fLimit = cosf(this->configMaxAngle); 
 	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 
 	// in the second pass we now smooth out all tangents and bitangents at the same local position 
 	// if they are not too far off.
 	// 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& origNorm = pMesh->mNormals[a];
 		const aiVector3D& origTang = pMesh->mTangents[a];
 		const aiVector3D& origTang = pMesh->mTangents[a];
 		const aiVector3D& origBitang = pMesh->mBitangents[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
 		// find all vertices close to that position
 		vertexFinder->FindPositions( origPos, posEpsilon, verticesFound);
 		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
 		// 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++)
 		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. 
 // 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 
 	// 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;
 		return true;
 
 
 	// XML - too generic, we need to open the file and search for typical keywords
 	// 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
 		/*  If CanRead() is called in order to check whether we
 		 *  support a specific file extension in general pIOHandler
 		 *  support a specific file extension in general pIOHandler
 		 *  might be NULL and it's our duty to return true here.
 		 *  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;
 	return false;
 }
 }
 
 
+// ------------------------------------------------------------------------------------------------
+// Get file extension list
+void ColladaLoader::GetExtensionList( std::string& append)
+{
+	append.append("*.dae");
+}
+
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Imports the given file into the given scene structure. 
 // Imports the given file into the given scene structure. 
 void ColladaLoader::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler)
 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();
 	aiNode* node = new aiNode();
 
 
 	// now setup the name of the node. We take the name if not empty, otherwise the collada ID
 	// 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);
 		node->mName.Set(pNode->mName);
 	else if (!pNode->mID.empty())
 	else if (!pNode->mID.empty())
 		node->mName.Set(pNode->mID);
 		node->mName.Set(pNode->mID);
@@ -632,7 +634,7 @@ void ColladaLoader::AddTexture ( Assimp::MaterialHelper& mat, const ColladaParse
 		_AI_MATKEY_TEXTURE_BASE,type,idx);
 		_AI_MATKEY_TEXTURE_BASE,type,idx);
 
 
 	// mapping mode
 	// mapping mode
-	int map = map = aiTextureMapMode_Clamp;
+	int map = aiTextureMapMode_Clamp;
 	if (sampler.mWrapU)
 	if (sampler.mWrapU)
 		map = aiTextureMapMode_Wrap;
 		map = aiTextureMapMode_Wrap;
 	if (sampler.mWrapU && sampler.mMirrorU)
 	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:
 public:
 	/** Returns whether the class can handle the format of the given file. 
 	/** Returns whether the class can handle the format of the given file. 
 	 * See BaseImporter::CanRead() for details.	*/
 	 * 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:
 protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 * 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. 
 	/** Imports the given file into the given scene structure. 
 	 * See BaseImporter::InternReadFile() for details
 	 * 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)
 		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;
 			break;
 		}
 		}
@@ -2106,4 +2106,4 @@ Collada::InputType ColladaParser::GetTypeForSemantic( const std::string& pSemant
 	return IT_Invalid;
 	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.
 	/** Reads the text contents of an element, returns NULL if not given.
 	    Skips leading whitespace. */
 	    Skips leading whitespace. */
-	const char* ColladaParser::TestTextContent();
+	const char* TestTextContent();
 
 
 	/** Reads a single bool from current text content */
 	/** Reads a single bool from current text content */
 	bool ReadBoolFromTextContent();
 	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"
 #include "AssimpPCH.h"
 #ifndef ASSIMP_BUILD_NO_DXF_IMPORTER
 #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]))
 #define AI_DXF_NUM_INDEX_COLORS (sizeof(g_aclrDxfIndexColors)/sizeof(g_aclrDxfIndexColors[0]))
 
 
 // invalid/unassigned color value
 // 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
 // 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. 
 // 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
 #ifndef AI_DXFLOADER_H_INCLUDED
 #define 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. 
 	/** Returns whether the class can handle the format of the given file. 
 	* See BaseImporter::CanRead() for details.	*/
 	* 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:
 protected:
 
 
@@ -93,10 +96,7 @@ protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 * 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. 
 	/** 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 "StdOStreamLogStream.h"
 #include "FileLogStream.h"
 #include "FileLogStream.h"
 
 
+#ifndef ASSIMP_BUILD_SINGLETHREADED
+#	include <boost/thread/thread.hpp>
+#	include <boost/thread/mutex.hpp>
+
+boost::mutex loggerMutex;
+#endif
+
 namespace Assimp	{
 namespace Assimp	{
 
 
 // ----------------------------------------------------------------------------------
 // ----------------------------------------------------------------------------------
@@ -119,6 +126,11 @@ Logger *DefaultLogger::create(const char* name /*= "AssimpLog.txt"*/,
 	unsigned int defStreams                    /*= DLS_DEBUGGER | DLS_FILE*/,
 	unsigned int defStreams                    /*= DLS_DEBUGGER | DLS_FILE*/,
 	IOSystem* io		                       /*= NULL*/)
 	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() )
 	if (m_pLogger && !isNullLogger() )
 		delete m_pLogger;
 		delete m_pLogger;
 
 
@@ -146,31 +158,56 @@ Logger *DefaultLogger::create(const char* name /*= "AssimpLog.txt"*/,
 
 
 // ----------------------------------------------------------------------------------
 // ----------------------------------------------------------------------------------
 void Logger::debug(const std::string &message)	{
 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());
 	return OnDebug(message.c_str());
 }
 }
 
 
 // ----------------------------------------------------------------------------------
 // ----------------------------------------------------------------------------------
 void Logger::info(const std::string &message)	{
 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());
 	return OnInfo(message.c_str());
 }
 }
 	
 	
 // ----------------------------------------------------------------------------------
 // ----------------------------------------------------------------------------------
 void Logger::warn(const std::string &message)	{
 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());
 	return OnWarn(message.c_str());
 }
 }
 
 
 // ----------------------------------------------------------------------------------
 // ----------------------------------------------------------------------------------
 void Logger::error(const std::string &message)	{
 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());
 	return OnError(message.c_str());
 }
 }
 
 
 // ----------------------------------------------------------------------------------
 // ----------------------------------------------------------------------------------
 void DefaultLogger::set( Logger *logger )
 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 (!logger)logger = &s_pNullLogger;
 	if (m_pLogger && !isNullLogger() )
 	if (m_pLogger && !isNullLogger() )
 		delete m_pLogger;
 		delete m_pLogger;
@@ -195,6 +232,11 @@ Logger *DefaultLogger::get()
 //	Kills the only instance
 //	Kills the only instance
 void DefaultLogger::kill()
 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;
 	if (m_pLogger != &s_pNullLogger)return;
 	delete m_pLogger;
 	delete m_pLogger;
 	m_pLogger = &s_pNullLogger;
 	m_pLogger = &s_pNullLogger;
@@ -243,23 +285,14 @@ void DefaultLogger::OnError( const char* message )
 	WriteToStreams( msg, Logger::ERR );
 	WriteToStreams( msg, Logger::ERR );
 }
 }
 
 
-// ----------------------------------------------------------------------------------
-//	Severity setter
-void DefaultLogger::setLogSeverity( LogSeverity log_severity )
-{
-	m_Severity = log_severity;
-}
-
 // ----------------------------------------------------------------------------------
 // ----------------------------------------------------------------------------------
 //	Attachs a new stream
 //	Attachs a new stream
-void DefaultLogger::attachStream( LogStream *pStream, unsigned int severity )
+bool DefaultLogger::attachStream( LogStream *pStream, unsigned int severity )
 {
 {
 	if (!pStream)
 	if (!pStream)
-		return;
+		return false;
 
 
-	// fix (Aramis)
-	if (0 == severity)
-	{
+	if (0 == severity)	{
 		severity = Logger::INFO | Logger::ERR | Logger::WARN | Logger::DEBUGGING;
 		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 )
 		if ( (*it)->m_pStream == pStream )
 		{
 		{
 			(*it)->m_uiErrorSeverity |= severity;
 			(*it)->m_uiErrorSeverity |= severity;
-			return;
+			return true;
 		}
 		}
 	}
 	}
 	
 	
 	LogStreamInfo *pInfo = new LogStreamInfo( severity, pStream );
 	LogStreamInfo *pInfo = new LogStreamInfo( severity, pStream );
 	m_StreamArray.push_back( pInfo );
 	m_StreamArray.push_back( pInfo );
+	return true;
 }
 }
 
 
 // ----------------------------------------------------------------------------------
 // ----------------------------------------------------------------------------------
 //	Detatch a stream
 //	Detatch a stream
-void DefaultLogger::detatchStream( LogStream *pStream, unsigned int severity )
+bool DefaultLogger::detatchStream( LogStream *pStream, unsigned int severity )
 {
 {
 	if (!pStream)
 	if (!pStream)
-		return;
+		return false;
 
 
-	// fix (Aramis)
-	if (0 == severity)
-	{
+	if (0 == severity)	{
 		severity = Logger::INFO | Logger::ERR | Logger::WARN | Logger::DEBUGGING;
 		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 );
 				m_StreamArray.erase( it );
 				break;
 				break;
 			}
 			}
+			return true;
 		}
 		}
 	}
 	}
+	return false;
 }
 }
 
 
 // ----------------------------------------------------------------------------------
 // ----------------------------------------------------------------------------------
 //	Constructor
 //	Constructor
 DefaultLogger::DefaultLogger(LogSeverity severity) 
 DefaultLogger::DefaultLogger(LogSeverity severity) 
 
 
-	:	m_Severity	( severity )
+	:	Logger	( severity )
 	,	noRepeatMsg	(false)
 	,	noRepeatMsg	(false)
 	,	lastLen( 0 )
 	,	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"
 #include "AssimpPCH.h"
@@ -54,8 +54,8 @@ using namespace Assimp;
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
 // Constructor to be privately used by Importer
 FindDegeneratesProcess::FindDegeneratesProcess()
 FindDegeneratesProcess::FindDegeneratesProcess()
-{
-}
+: configRemoveDegenerates (false)
+{}
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Destructor, private as well
 // Destructor, private as well
@@ -71,83 +71,144 @@ bool FindDegeneratesProcess::IsActive( unsigned int pFlags) const
 	return 0 != (pFlags & aiProcess_FindDegenerates);
 	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.
 // Executes the post processing step on the given imported data.
 void FindDegeneratesProcess::Execute( aiScene* pScene)
 void FindDegeneratesProcess::Execute( aiScene* pScene)
 {
 {
 	DefaultLogger::get()->debug("FindDegeneratesProcess begin");
 	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();
 	~FindDegeneratesProcess();
 
 
 public:
 public:
+	
 	// -------------------------------------------------------------------
 	// -------------------------------------------------------------------
+	// Check whether step is active
 	bool IsActive( unsigned int pFlags) const;
 	bool IsActive( unsigned int pFlags) const;
 
 
 	// -------------------------------------------------------------------
 	// -------------------------------------------------------------------
+	// Execute step on a given scene
 	void Execute( aiScene* pScene);
 	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:
 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
 	// allocate an array to hold the output normals
 	pMesh->mNormals = new aiVector3D[pMesh->mNumVertices];
 	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
 	// iterate through all faces and compute per-face normals but store
 	// them per-vertex. 
 	// them per-vertex. 

+ 7 - 9
code/HMPFileData.h

@@ -46,17 +46,15 @@ namespace HMP	{
 
 
 #include "./../include/Compiler/pushpack1.h"
 #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.
 /** 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. 
 // 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. 
 // Imports the given file into the given scene structure. 
 void HMPImporter::InternReadFile( const std::string& pFile, 
 void HMPImporter::InternReadFile( const std::string& pFile, 
-	aiScene* _pScene, IOSystem* _pIOHandler)
+								 aiScene* _pScene, IOSystem* _pIOHandler)
 {
 {
 	pScene     = _pScene;
 	pScene     = _pScene;
 	pIOHandler = _pIOHandler;
 	pIOHandler = _pIOHandler;
@@ -225,7 +234,8 @@ void HMPImporter::InternReadFile_HMP5( )
 	}
 	}
 
 
 	// generate texture coordinates if necessary
 	// generate texture coordinates if necessary
-	if (pcHeader->numskins)GenerateTextureCoords(width,height);
+	if (pcHeader->numskins)
+		GenerateTextureCoords(width,height);
 
 
 	// now build a list of faces
 	// now build a list of faces
 	CreateOutputFaceList(width,height);	
 	CreateOutputFaceList(width,height);	
@@ -477,7 +487,7 @@ void HMPImporter::GenerateTextureCoords(
 
 
 	for (unsigned int y = 0; y < height;++y)	{
 	for (unsigned int y = 0; y < height;++y)	{
 		for (unsigned int x = 0; x < width;++x,++uv)	{
 		for (unsigned int x = 0; x < width;++x,++uv)	{
-			uv->y = 1.0f-fY*y;
+			uv->y = fY*y;
 			uv->x = fX*x;
 			uv->x = fX*x;
 			uv->z = 0.0f;
 			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
 #ifndef AI_HMPLOADER_H_INCLUDED
 #define 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. 
 	/** 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:
 protected:
 
 
@@ -85,10 +87,7 @@ protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 * 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. 
 	/** Imports the given file into the given scene structure. 

+ 4 - 4
code/HalfLifeFileData.h

@@ -57,10 +57,10 @@ namespace Assimp	{
 namespace MDL	{
 namespace MDL	{
 
 
 // magic bytes used in Half Life 2 MDL models
 // 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
 /** \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"
 #include "AssimpPCH.h"
 
 
@@ -81,26 +83,17 @@ IRRImporter::~IRRImporter()
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file. 
 // 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
 	/* NOTE: A simple check for the file extension is not enough
 	 * here. Irrmesh and irr are easy, but xml is too generic
 	 * here. Irrmesh and irr are easy, but xml is too generic
 	 * and could be collada, too. So we need to open the file and
 	 * and could be collada, too. So we need to open the file and
 	 * search for typical tokens.
 	 * 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
 		/*  If CanRead() is called in order to check whether we
 		 *  support a specific file extension in general pIOHandler
 		 *  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
 	// read the output frame rate of all node animation channels
 	fps = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_IRR_ANIM_FPS,100);
 	fps = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_IRR_ANIM_FPS,100);
-	if (!fps)
-	{
+	if (fps < 10.)	{
 		DefaultLogger::get()->error("IRR: Invalid FPS configuration");
 		DefaultLogger::get()->error("IRR: Invalid FPS configuration");
 		fps = 100;
 		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
 // 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
 	// Check whether there are texture properties defined - setup
 	// the desired texture mapping mode for all of them and ignore
 	// 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
 	// rebuild the output array
 	if (p.size() > mat->mNumAllocated)	{
 	if (p.size() > mat->mNumAllocated)	{
 		delete[] mat->mProperties;
 		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();
 	mat->mNumProperties = (unsigned int)p.size();
 	::memcpy(mat->mProperties,&p[0],sizeof(void*)*mat->mNumProperties);
 	::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
 			// Get the loaded mesh from the scene and add it to
 			// the list of all scenes to be attached to the 
 			// the list of all scenes to be attached to the 
 			// graph we're currently building
 			// graph we're currently building
-			aiScene* scene = batch.GetImport(root->meshPath);
+			aiScene* scene = batch.GetImport(root->id);
 			if (!scene)
 			if (!scene)
 			{
 			{
 				DefaultLogger::get()->error("IRR: Unable to load external file: " 
 				DefaultLogger::get()->error("IRR: Unable to load external file: " 
@@ -688,6 +685,8 @@ void IRRImporter::GenerateGraph(Node* root,aiNode* rootOut ,aiScene* scene,
 				break;
 				break;
 			}
 			}
 			attach.push_back(AttachmentInfo(scene,rootOut));
 			attach.push_back(AttachmentInfo(scene,rootOut));
+
+#if 0
 			meshTrafoAssign = 1;
 			meshTrafoAssign = 1;
 
 
 			// If the root node of the scene is animated - and *this* node
 			// 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
 			// 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,
 			// NOTE: Each mesh should have exactly one material assigned,
 			// but we do it in a separate loop if this behaviour changes
 			// 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)
 			for (unsigned int i = 0; i < scene->mNumMeshes;++i)
 			{
 			{
 				// Process material flags 
 				// Process material flags 
@@ -901,8 +901,7 @@ void IRRImporter::GenerateGraph(Node* root,aiNode* rootOut ,aiScene* scene,
 		rootOut->mNumMeshes = (unsigned int)meshes.size() - oldMeshSize;
 		rootOut->mNumMeshes = (unsigned int)meshes.size() - oldMeshSize;
 		rootOut->mMeshes    = new unsigned int[rootOut->mNumMeshes];
 		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;
 			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
 	// Now compute the final local transformation matrix of the
 	// node from the given translation, rotation and scaling values.
 	// node from the given translation, rotation and scaling values.
 	// (the rotation is given in Euler angles, XYZ order)
 	// (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) );
 	rootOut->mTransformation.FromEulerAnglesXYZ(AI_DEG_TO_RAD(root->rotation) );
 
 
 	// apply scaling
 	// apply scaling
@@ -920,20 +920,20 @@ void IRRImporter::GenerateGraph(Node* root,aiNode* rootOut ,aiScene* scene,
 	mat.a1 *= root->scaling.x;
 	mat.a1 *= root->scaling.x;
 	mat.b1 *= root->scaling.x; 
 	mat.b1 *= root->scaling.x; 
 	mat.c1 *= 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
 	// apply translation
 	mat.a4 += root->position.x; 
 	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
 	// now compute animations for the node
 	ComputeAnimations(root,rootOut, anims);
 	ComputeAnimations(root,rootOut, anims);
@@ -1446,7 +1446,7 @@ void IRRImporter::InternReadFile( const std::string& pFile,
 										}
 										}
 										else
 										else
 										{
 										{
-											batch.AddLoadRequest(prop.value,pp,&map);
+											curNode->id = batch.AddLoadRequest(prop.value,pp,&map);
 											curNode->meshPath = prop.value;
 											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
 	/*  Now iterate through all cameras and compute their final (horizontal) FOV
 	 */
 	 */
 	for (std::vector<aiCamera*>::iterator it = cameras.begin(), end = cameras.end();
 	for (std::vector<aiCamera*>::iterator it = cameras.begin(), end = cameras.end();
-		 it != end; ++it)
-	{
+		 it != end; ++it)	{
 		aiCamera* cam = *it;
 		aiCamera* cam = *it;
 		if (cam->mAspect) // screen aspect could be missing
 		if (cam->mAspect) // screen aspect could be missing
 		{
 		{
@@ -1625,8 +1624,8 @@ void IRRImporter::InternReadFile( const std::string& pFile,
 	 *  attachment points in the scenegraph.
 	 *  attachment points in the scenegraph.
 	 */
 	 */
 	SceneCombiner::MergeScenes(&pScene,tempScene,attach,
 	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
 	/*  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;
 		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 
 	/* Finished ... everything destructs automatically and all 
 	 * temporary scenes have already been deleted by MergeScenes()
 	 * 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
 #ifndef AI_IRRLOADER_H_INCLUDED
 #define 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. 
 	/** Returns whether the class can handle the format of the given file. 
 	 *  See BaseImporter::CanRead() for details.	
 	 *  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:
 protected:
 
 
@@ -203,6 +205,7 @@ private:
 
 
 		// Meshes: path to the mesh to be loaded
 		// Meshes: path to the mesh to be loaded
 		std::string meshPath;
 		std::string meshPath;
+		unsigned int id;
 
 
 		// Meshes: List of materials to be assigned
 		// Meshes: List of materials to be assigned
 		// along with their corresponding material flags
 		// along with their corresponding material flags
@@ -297,8 +300,11 @@ private:
 
 
 private:
 private:
 
 
+	/** Configuration option: desired output FPS */
 	double fps;
 	double fps;
 
 
+	/** Configuration option: speed flag was set? */
+	bool configSpeedFlag;
 };
 };
 
 
 } // end of namespace Assimp
 } // 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. 
 // 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
 	/* NOTE: A simple check for the file extension is not enough
 	 * here. Irrmesh and irr are easy, but xml is too generic
 	 * here. Irrmesh and irr are easy, but xml is too generic
 	 * and could be collada, too. So we need to open the file and
 	 * and could be collada, too. So we need to open the file and
 	 * search for typical tokens.
 	 * 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
 		/*  If CanRead() is called to check whether the loader
 		 *  supports a specific file extension in general we
 		 *  supports a specific file extension in general we
@@ -99,6 +90,14 @@ bool IRRMeshImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) c
 	return false;
 	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. 
 // Imports the given file into the given scene structure. 
 void IRRMeshImporter::InternReadFile( const std::string& pFile, 
 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
 #ifndef AI_IRRMESHLOADER_H_INCLUDED
 #define 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. 
 	/** Returns whether the class can handle the format of the given file. 
 	 *  See BaseImporter::CanRead() for details.	
 	 *  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:
 protected:
 
 
@@ -80,16 +83,7 @@ protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 * 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. 
 	/** 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)
 					// material type (shader)
 					if (prop.name == "Type")
 					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;
 							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"
 #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
 // Internal headers
 // =======================================================================================
 // =======================================================================================
@@ -135,6 +143,20 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #ifndef AI_BUILD_NO_TERRAGEN_IMPORTER
 #ifndef AI_BUILD_NO_TERRAGEN_IMPORTER
 #	include "TerragenLoader.h"
 #	include "TerragenLoader.h"
 #endif
 #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
 // PostProcess-Steps
@@ -325,6 +347,18 @@ Importer::Importer()
 #if (!defined AI_BUILD_NO_TERRAGEN_IMPORTER)
 #if (!defined AI_BUILD_NO_TERRAGEN_IMPORTER)
 	mImporter.push_back( new TerragenImporter());
 	mImporter.push_back( new TerragenImporter());
 #endif
 #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 
 	// Add an instance of each post processing step here in the order 
@@ -333,10 +367,6 @@ Importer::Importer()
 	// ======================================================================
 	// ======================================================================
 	mPostProcessingSteps.reserve(25);
 	mPostProcessingSteps.reserve(25);
 
 
-#if (!defined AI_BUILD_NO_VALIDATEDS_PROCESS)
-	mPostProcessingSteps.push_back( new ValidateDSProcess()); 
-#endif
-
 #if (!defined AI_BUILD_NO_REMOVEVC_PROCESS)
 #if (!defined AI_BUILD_NO_REMOVEVC_PROCESS)
 	mPostProcessingSteps.push_back( new RemoveVCProcess());
 	mPostProcessingSteps.push_back( new RemoveVCProcess());
 #endif
 #endif
@@ -548,10 +578,9 @@ bool Importer::IsDefaultIOHandler()
 	return mIsDefaultHandler;
 	return mIsDefaultHandler;
 }
 }
 
 
-#ifdef _DEBUG
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Validate post process step flags 
 // Validate post process step flags 
-bool ValidateFlags(unsigned int pFlags)
+bool _ValidateFlags(unsigned int pFlags)
 {
 {
 	if (pFlags & aiProcess_GenSmoothNormals &&
 	if (pFlags & aiProcess_GenSmoothNormals &&
 		pFlags & aiProcess_GenNormals)
 		pFlags & aiProcess_GenNormals)
@@ -562,7 +591,6 @@ bool ValidateFlags(unsigned int pFlags)
 	}
 	}
 	return true;
 	return true;
 }
 }
-#endif // ! DEBUG
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Free the current scene
 // Free the current scene
@@ -602,12 +630,50 @@ aiScene* Importer::GetOrphanedScene()
 	return s;
 	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. 
 // Reads the given file and returns its contents if successful. 
 const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags)
 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);
 	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(). 
 	// that might be thrown by STL containers or by new(). 
 	// ImportErrorException's are throw by ourselves and caught elsewhere.
 	// ImportErrorException's are throw by ourselves and caught elsewhere.
 	// ======================================================================
 	// ======================================================================
+#ifdef ASSIMP_CATCH_GLOBAL_EXCEPTIONS
 	try
 	try
+#endif // ! ASSIMP_CATCH_GLOBAL_EXCEPTIONS
 	{
 	{
 		// Check whether this Importer instance has already loaded
 		// Check whether this Importer instance has already loaded
 		// a scene. In this case we need to delete the old one
 		// 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
 		// Find an worker class which can handle the file
 		BaseImporter* imp = NULL;
 		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];
 				imp = mImporter[a];
 				break;
 				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
 		// 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 successful, apply all active post processing steps to the imported data
 		if( mScene)
 		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);
 			ScenePreprocessor pre(mScene);
 			pre.ProcessScene();
 			pre.ProcessScene();
 
 
@@ -669,11 +763,11 @@ const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags)
 #ifdef _DEBUG
 #ifdef _DEBUG
 			if (bExtraVerbose)
 			if (bExtraVerbose)
 			{
 			{
-#if (!defined AI_BUILD_NO_VALIDATEDS_PROCESS)
+#ifndef AI_BUILD_NO_VALIDATEDS_PROCESS
 
 
 				DefaultLogger::get()->error("Extra verbose mode not available, library"
 				DefaultLogger::get()->error("Extra verbose mode not available, library"
 					" wasn't build with the ValidateDS-Step");
 					" wasn't build with the ValidateDS-Step");
-#endif
+#endif  // no validation
 
 
 
 
 				pFlags |= aiProcess_ValidateDataStructure;
 				pFlags |= aiProcess_ValidateDataStructure;
@@ -694,14 +788,16 @@ const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags)
 
 
 #ifndef AI_BUILD_NO_VALIDATEDS_PROCESS
 #ifndef AI_BUILD_NO_VALIDATEDS_PROCESS
 				continue;
 				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
 				// VaidateDataStructureStep again after each step
-				if (bExtraVerbose && a)
+				if (bExtraVerbose)
 				{
 				{
 					DefaultLogger::get()->debug("Extra verbose: revalidating data structures");
 					DefaultLogger::get()->debug("Extra verbose: revalidating data structures");
-					((ValidateDSProcess*)mPostProcessingSteps[0])->ExecuteOnScene (this);
+					
+					ValidateDSProcess ds; 
+					ds.ExecuteOnScene (this);
 					if( !mScene)
 					if( !mScene)
 					{
 					{
 						DefaultLogger::get()->error("Extra verbose: failed to revalidate data structures");
 						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
 		// 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
 		// clear any data allocated by post-process steps
 		mPPShared->Clean();
 		mPPShared->Clean();
 	}
 	}
+#ifdef ASSIMP_CATCH_GLOBAL_EXCEPTIONS
 	catch (std::exception &e)
 	catch (std::exception &e)
 	{
 	{
 #if (defined _MSC_VER) &&	(defined _CPPRTTI) 
 #if (defined _MSC_VER) &&	(defined _CPPRTTI) 
@@ -729,6 +827,7 @@ const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags)
 		DefaultLogger::get()->error(mErrorString);
 		DefaultLogger::get()->error(mErrorString);
 		delete mScene;mScene = NULL;
 		delete mScene;mScene = NULL;
 	}
 	}
+#endif // ! ASSIMP_CATCH_GLOBAL_EXCEPTIONS
 
 
 	// either successful or failure - the pointer expresses it anyways
 	// either successful or failure - the pointer expresses it anyways
 	return mScene;
 	return mScene;
@@ -750,7 +849,7 @@ BaseImporter* Importer::FindLoader (const char* _szExtension)
 		i != mImporter.end();++i)
 		i != mImporter.end();++i)
 	{
 	{
 		// pass the file extension to the CanRead(..,NULL)-method
 		// 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;
 	return NULL;
 }
 }
@@ -765,8 +864,10 @@ void Importer::GetExtensionList(aiString& szOut)
 		i =  mImporter.begin();
 		i =  mImporter.begin();
 		i != mImporter.end();++i,++iNum)
 		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(";");
 			tmp.append(";");
 
 
 		(*i)->GetExtensionList(tmp);
 		(*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>
  * <br>
  * The algorithm is roughly basing on this paper:
  * The algorithm is roughly basing on this paper:
  * http://www.cs.princeton.edu/gfx/pubs/Sander_2007_%3ETR/tipsy.pdf
  * http://www.cs.princeton.edu/gfx/pubs/Sander_2007_%3ETR/tipsy.pdf
+ *   ... TODO: implement overdraw reduction
  */
  */
 
 
 #include "AssimpPCH.h"
 #include "AssimpPCH.h"
@@ -54,16 +55,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 using namespace Assimp;
 using namespace Assimp;
 
 
-#if _MSC_VER >= 1400
-#	define sprintf sprintf_s
-#endif
-
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
 // 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;
 	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.
 // Executes the post processing step on the given imported data.
 void ImproveCacheLocalityProcess::Execute( aiScene* pScene)
 void ImproveCacheLocalityProcess::Execute( aiScene* pScene)
 {
 {
-	if (!pScene->mNumMeshes)
-	{
+	if (!pScene->mNumMeshes) {
 		DefaultLogger::get()->debug("ImproveCacheLocalityProcess skipped; there are no meshes");
 		DefaultLogger::get()->debug("ImproveCacheLocalityProcess skipped; there are no meshes");
 		return;
 		return;
 	}
 	}
 
 
 	DefaultLogger::get()->debug("ImproveCacheLocalityProcess begin");
 	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
 // 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);
 	ai_assert(NULL != pMesh);
 
 
@@ -109,52 +124,50 @@ void ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshN
 	// - there must be vertices and faces (haha)
 	// - there must be vertices and faces (haha)
 	// - all faces must be triangulated
 	// - all faces must be triangulated
 	if (!pMesh->HasFaces() || !pMesh->HasPositions())
 	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");
 		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;
 	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
 			char szBuff[128]; // should be sufficiently large in every case
 
 
 			// the JoinIdenticalVertices process has not been executed on this
 			// 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 ...
 			// smaller than 3.0 ...
 			::sprintf(szBuff,"Mesh %i: Not suitable for vcache optimization",meshNum);
 			::sprintf(szBuff,"Mesh %i: Not suitable for vcache optimization",meshNum);
 			DefaultLogger::get()->warn(szBuff);
 			DefaultLogger::get()->warn(szBuff);
+			return 0.f;
 		}
 		}
-		return;
 	}
 	}
 
 
 	// first we need to build a vertex-triangle adjacency list
 	// 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);
 			iMaxRefTris = std::max(iMaxRefTris,*piCur);
 	}
 	}
 	unsigned int* piCandidates = new unsigned int[iMaxRefTris*3];
 	unsigned int* piCandidates = new unsigned int[iMaxRefTris*3];
-	iCacheMisses = 0;
+	unsigned int iCacheMisses = 0;
 
 
 	/** PSEUDOCODE for the algorithm
 	/** PSEUDOCODE for the algorithm
 
 
@@ -236,7 +249,7 @@ void ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshN
 
 
 	int ivdx = 0;
 	int ivdx = 0;
 	int ics = 1;
 	int ics = 1;
-	int iStampCnt = this->configCacheDepth+1;
+	int iStampCnt = configCacheDepth+1;
 	while (ivdx >= 0)
 	while (ivdx >= 0)
 	{
 	{
 		unsigned int icnt = piNumTriPtrNoModify[ivdx]; 
 		unsigned int icnt = piNumTriPtrNoModify[ivdx]; 
@@ -274,7 +287,7 @@ void ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshN
 					*piCSIter++ = dp;
 					*piCSIter++ = dp;
 
 
 					// if the vertex is not yet in cache, set its cache count
 					// 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++;
 						piCachingStamps[dp] = iStampCnt++;
 						++iCacheMisses;
 						++iCacheMisses;
@@ -302,7 +315,7 @@ void ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshN
 
 
 				// will the vertex be in cache, even after fanning occurs?
 				// will the vertex be in cache, even after fanning occurs?
 				unsigned int tmp;
 				unsigned int tmp;
-				if ((tmp = iStampCnt-piCachingStamps[dp]) + 2*piNumTriPtr[dp] <= this->configCacheDepth)
+				if ((tmp = iStampCnt-piCachingStamps[dp]) + 2*piNumTriPtr[dp] <= configCacheDepth)
 					priority = tmp;
 					priority = tmp;
 				// keep best candidate
 				// keep best candidate
 				if (priority > max_priority)
 				if (priority > max_priority)
@@ -344,18 +357,24 @@ void ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshN
 			}
 			}
 		}
 		}
 	}
 	}
+	float fACMR2 = 0.0f;
 	if (!DefaultLogger::isNullLogger())
 	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
 	// sort the output index buffer back to the input array
 	piCSIter = piIBOutput;
 	piCSIter = piIBOutput;
-
 	for (aiFace* pcFace = pMesh->mFaces; pcFace != pcEnd;++pcFace)
 	for (aiFace* pcFace = pMesh->mFaces; pcFace != pcEnd;++pcFace)
 	{
 	{
 		pcFace->mIndices[0] = *piCSIter++;
 		pcFace->mIndices[0] = *piCSIter++;
@@ -368,4 +387,5 @@ void ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshN
 	delete[] piIBOutput;
 	delete[] piIBOutput;
 	delete[] piCandidates;
 	delete[] piCandidates;
 	delete[] piNumTriPtrNoModify;
 	delete[] piNumTriPtrNoModify;
+	return fACMR2;
 }
 }

+ 9 - 11
code/ImproveCacheLocality.h

@@ -70,30 +70,28 @@ protected:
 	~ImproveCacheLocalityProcess();
 	~ImproveCacheLocalityProcess();
 
 
 public:
 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;
 	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);
 	void Execute( aiScene* pScene);
 
 
+	// -------------------------------------------------------------------
+	// Configures the pp step
+	void SetupProperties(const Importer* pImp);
+
 protected:
 protected:
 	// -------------------------------------------------------------------
 	// -------------------------------------------------------------------
 	/** Executes the postprocessing step on the given mesh
 	/** Executes the postprocessing step on the given mesh
 	 * @param pMesh The mesh to process.
 	 * @param pMesh The mesh to process.
 	 * @param meshNum Index of 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
 	//! Configuration parameter: specifies the size of the cache to
 	//! optimize the vertex data for.
 	//! optimize the vertex data for.
 	unsigned int configCacheDepth;
 	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;
 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
 // Constructor to be privately used by Importer
@@ -75,7 +81,6 @@ bool JoinVerticesProcess::IsActive( unsigned int pFlags) const
 {
 {
 	return (pFlags & aiProcess_JoinIdenticalVertices) != 0;
 	return (pFlags & aiProcess_JoinIdenticalVertices) != 0;
 }
 }
-
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Executes the post processing step on the given imported data.
 // Executes the post processing step on the given imported data.
 void JoinVerticesProcess::Execute( aiScene* pScene)
 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
 	// get the total number of vertices BEFORE the step is executed
 	int iNumOldVertices = 0;
 	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
 	// execute the step
 	int iNumVertices = 0;
 	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 (!DefaultLogger::isNullLogger())
 	{
 	{
 		if (iNumOldVertices == iNumVertices)DefaultLogger::get()->debug("JoinVerticesProcess finished ");
 		if (iNumOldVertices == iNumVertices)DefaultLogger::get()->debug("JoinVerticesProcess finished ");
@@ -117,81 +123,103 @@ void JoinVerticesProcess::Execute( aiScene* pScene)
 // Unites identical vertices in the given mesh
 // Unites identical vertices in the given mesh
 int JoinVerticesProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex)
 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())
 	if (!pMesh->HasPositions() || !pMesh->HasFaces())
 		return 0;
 		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;
 	std::vector<Vertex> uniqueVertices;
 	uniqueVertices.reserve( pMesh->mNumVertices);
 	uniqueVertices.reserve( pMesh->mNumVertices);
 
 
-	//unsigned int iOldVerts = pMesh->mNumVertices;
-
 	// For each vertex the index of the vertex it was replaced by. 
 	// For each vertex the index of the vertex it was replaced by. 
 	std::vector<unsigned int> replaceIndex( pMesh->mNumVertices, 0xffffffff);
 	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)
 	// 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);
 	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
 	// 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;
 	SpatialSort  _vertexFinder;
 	if (shared)
 	if (shared)
 	{
 	{
 		std::vector<std::pair<SpatialSort,float> >* avf;
 		std::vector<std::pair<SpatialSort,float> >* avf;
 		shared->GetProperty(AI_SPP_SPATIAL_SORT,avf);
 		shared->GetProperty(AI_SPP_SPATIAL_SORT,avf);
-		if (avf)
-		{
+		if (avf)	{
 			std::pair<SpatialSort,float>& blubb = avf->operator [] (meshIndex);
 			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.Fill(pMesh->mVertices, pMesh->mNumVertices, sizeof( aiVector3D));
 		vertexFinder = &_vertexFinder;
 		vertexFinder = &_vertexFinder;
-		posEpsilon = ComputePositionEpsilon(pMesh);
+		posEpsilonSqr = ComputePositionEpsilon(pMesh);
 	}
 	}
 
 
 	// squared because we check against squared length of the vector difference
 	// 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++)
 	for( unsigned int a = 0; a < pMesh->mNumVertices; a++)
 	{
 	{
 		// collect the vertex data
 		// collect the vertex data
 		Vertex v;
 		Vertex v;
 		v.mPosition = pMesh->mVertices[a];
 		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
 		// 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;
 		unsigned int matchIndex = 0xffffffff;
 		// check all unique vertices close to the position if this vertex is already present among them
 		// 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++)
 		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])
 			if( uidx == 0xffffffff || !isVertexUnique[ vidx])
 				continue;
 				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.
 			// 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. 
 			// 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.
 			// By this method the non-present attributes are effectively ignored in the comparision.
-			
 			if( (uv.mNormal - v.mNormal).SquareLength() > squareEpsilon)
 			if( (uv.mNormal - v.mNormal).SquareLength() > squareEpsilon)
 				continue;
 				continue;
 			if( (uv.mTangent - v.mTangent).SquareLength() > squareEpsilon)
 			if( (uv.mTangent - v.mTangent).SquareLength() > squareEpsilon)
 				continue;
 				continue;
 			if( (uv.mBitangent - v.mBitangent).SquareLength() > squareEpsilon)
 			if( (uv.mBitangent - v.mBitangent).SquareLength() > squareEpsilon)
 				continue;
 				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)
 			if( (uv.mTexCoords[0] - v.mTexCoords[0]).SquareLength() > squareEpsilon)
 				continue;
 				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
 			// we're still here -> this vertex perfectly matches our given vertex
 			matchIndex = uidx;
 			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
 		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,
 			meshIndex,
 			pMesh->mNumVertices,
 			pMesh->mNumVertices,
 			(int)uniqueVertices.size(),
 			(int)uniqueVertices.size(),
 			((pMesh->mNumVertices - uniqueVertices.size()) / (float)pMesh->mNumVertices) * 100.f);
 			((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
 	// replace vertex data with the unique data sets
 	pMesh->mNumVertices = (unsigned int)uniqueVertices.size();
 	pMesh->mNumVertices = (unsigned int)uniqueVertices.size();
+
 	// Position
 	// Position
 	delete [] pMesh->mVertices;
 	delete [] pMesh->mVertices;
 	pMesh->mVertices = new aiVector3D[pMesh->mNumVertices];
 	pMesh->mVertices = new aiVector3D[pMesh->mNumVertices];
 	for( unsigned int a = 0; a < pMesh->mNumVertices; a++)
 	for( unsigned int a = 0; a < pMesh->mNumVertices; a++)
 		pMesh->mVertices[a] = uniqueVertices[a].mPosition;
 		pMesh->mVertices[a] = uniqueVertices[a].mPosition;
+
 	// Normals, if present
 	// Normals, if present
 	if( pMesh->mNormals)
 	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++)
 	for( unsigned int a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; a++)
 	{
 	{
 		if( !pMesh->mColors[a])
 		if( !pMesh->mColors[a])
-			continue;
+			break;
 
 
 		delete [] pMesh->mColors[a];
 		delete [] pMesh->mColors[a];
 		pMesh->mColors[a] = new aiColor4D[pMesh->mNumVertices];
 		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++)
 	for( unsigned int a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; a++)
 	{
 	{
 		if( !pMesh->mTextureCoords[a])
 		if( !pMesh->mTextureCoords[a])
-			continue;
+			break;
 
 
 		delete [] pMesh->mTextureCoords[a];
 		delete [] pMesh->mTextureCoords[a];
 		pMesh->mTextureCoords[a] = new aiVector3D[pMesh->mNumVertices];
 		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++)
 	for( unsigned int a = 0; a < pMesh->mNumFaces; a++)
 	{
 	{
 		aiFace& face = pMesh->mFaces[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
 	 * @param meshIndex Index of the mesh to process
 	 */
 	 */
 	int ProcessMesh( aiMesh* pMesh, unsigned int meshIndex);
 	int ProcessMesh( aiMesh* pMesh, unsigned int meshIndex);
+
+private:
 };
 };
 
 
 } // end of namespace Assimp
 } // 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;
 			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);
 			if (cursor < end)CopyFaceIndicesLWOB(it,cursor,end,numPolygons);
 		}
 		}
 		face.surfaceIndex = surface-1;
 		face.surfaceIndex = surface-1;
@@ -194,6 +194,27 @@ LWO::Texture* LWOImporter::SetupNewTextureLWOB(LWO::TextureList& list,unsigned i
 
 
 	std::string type;
 	std::string type;
 	GetS0(type,size);
 	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;
 	return tex;
 }
 }
@@ -271,7 +292,7 @@ void LWOImporter::LoadLWOBSurface(unsigned int size)
 		case AI_LWO_SMAN:
 		case AI_LWO_SMAN:
 			{
 			{
 				AI_LWO_VALIDATE_CHUNK_LENGTH(head->length,SMAN,4);
 				AI_LWO_VALIDATE_CHUNK_LENGTH(head->length,SMAN,4);
-				surf.mMaximumSmoothAngle = GetF4();
+				surf.mMaximumSmoothAngle = fabs( GetF4() );
 				break;
 				break;
 			}
 			}
 		// glossiness
 		// glossiness
@@ -323,8 +344,7 @@ void LWOImporter::LoadLWOBSurface(unsigned int size)
 				{
 				{
 					GetS0(pTex->mFileName,head->length);	
 					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;
 				break;
 			}
 			}
 		// texture strength
 		// texture strength
@@ -332,8 +352,28 @@ void LWOImporter::LoadLWOBSurface(unsigned int size)
 			{
 			{
 				AI_LWO_VALIDATE_CHUNK_LENGTH(head->length,TVAL,1);
 				AI_LWO_VALIDATE_CHUNK_LENGTH(head->length,TVAL,1);
 				if (pTex)pTex->mStrength = (float)GetU1()/ 255.f;
 				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;
 				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.
 The chunks are taken from the official LightWave SDK headers.
-Original copyright notice: "Ernie Wright  17 Sep 00" 
  
  
 */
 */
 #ifndef AI_LWO_FILEDATA_INCLUDED
 #ifndef AI_LWO_FILEDATA_INCLUDED
@@ -56,6 +56,7 @@ Original copyright notice: "Ernie Wright  17 Sep 00"
 
 
 // internal headers
 // internal headers
 #include "IFF.h"
 #include "IFF.h"
+#include "LWOAnimation.h"
 
 
 namespace Assimp {
 namespace Assimp {
 namespace LWO {
 namespace LWO {
@@ -254,29 +255,45 @@ namespace LWO {
  */
  */
 struct Face : public aiFace
 struct Face : public aiFace
 {
 {
+	//! Default construction
 	Face() 
 	Face() 
 		: surfaceIndex	(0)
 		: surfaceIndex	(0)
 		, smoothGroup	(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;
 		*this = f;
 	}
 	}
 
 
+	//! Zero-based index into tags chunk
 	unsigned int surfaceIndex;
 	unsigned int surfaceIndex;
+
+	//! Smooth group this face is assigned to
 	unsigned int smoothGroup;
 	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);
 		aiFace::operator =(f);
 		surfaceIndex	= f.surfaceIndex;
 		surfaceIndex	= f.surfaceIndex;
 		smoothGroup		= f.smoothGroup;
 		smoothGroup		= f.smoothGroup;
+		type            = f.type;
 		return *this;
 		return *this;
 	}
 	}
 };
 };
 
 
-
 // ---------------------------------------------------------------------------
 // ---------------------------------------------------------------------------
 /** \brief Base structure for all vertex map representations
 /** \brief Base structure for all vertex map representations
  */
  */
@@ -473,8 +490,9 @@ struct Clip
 	} type;
 	} type;
 
 
 	Clip()
 	Clip()
-		: type	(UNSUPPORTED)
-		, idx	(0)
+		: type	 (UNSUPPORTED)
+		, idx	 (0)
+		, negate (false)
 	{}
 	{}
 
 
 	//! path to the base texture -
 	//! path to the base texture -
@@ -485,6 +503,9 @@ struct Clip
 
 
 	//! index of the clip
 	//! index of the clip
 	unsigned int idx;
 	unsigned int idx;
+
+	//! Negate the clip?
+	bool negate;
 };
 };
 
 
 
 
@@ -529,6 +550,7 @@ struct Surface
 		, mIOR					(1.f) // vakuum
 		, mIOR					(1.f) // vakuum
 		, mBumpIntensity		(1.f)
 		, mBumpIntensity		(1.f)
 		, mWireframe			(false)
 		, mWireframe			(false)
+		, mAdditiveTransparency (10e10f)
 	{}
 	{}
 
 
 	//! Name of the surface
 	//! Name of the surface
@@ -571,6 +593,9 @@ struct Surface
 
 
 	//! Wireframe flag
 	//! Wireframe flag
 	bool mWireframe;
 	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	<	VColorChannel	>	VColorChannelList;
 typedef std::vector	<	UVChannel		>	UVChannelList;
 typedef std::vector	<	UVChannel		>	UVChannelList;
 typedef std::vector	<	Clip			>	ClipList;
 typedef std::vector	<	Clip			>	ClipList;
-
+typedef std::vector	<	Envelope		>	EnvelopeList;
 
 
 // ---------------------------------------------------------------------------
 // ---------------------------------------------------------------------------
 /** \brief Represents a layer in the file
 /** \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. 
 // 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)
 	if((this->fileSize = (unsigned int)file->FileSize()) < 12)
 		throw new ImportErrorException("LWO: The file is too small to contain the IFF header");
 		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);
 	std::vector< uint8_t > mBuffer(fileSize);
 	file->Read( &mBuffer[0], 1, fileSize);
 	file->Read( &mBuffer[0], 1, fileSize);
 	this->pScene = pScene;
 	this->pScene = pScene;
 
 
-	// determine the type of the file
+	// Determine the type of the file
 	uint32_t fileType;
 	uint32_t fileType;
 	const char* sz = IFF::ReadHeader(&mBuffer[0],fileType);
 	const char* sz = IFF::ReadHeader(&mBuffer[0],fileType);
 	if (sz)throw new ImportErrorException(sz);
 	if (sz)throw new ImportErrorException(sz);
 
 
 	mFileBuffer = &mBuffer[0] + 12;
 	mFileBuffer = &mBuffer[0] + 12;
 	fileSize -= 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 
 	// instance. Therefore everything will be destructed properly if an exception 
 	// is thrown and we needn't take care of that.
 	// is thrown and we needn't take care of that.
 	LayerList		_mLayers;
 	LayerList		_mLayers;
@@ -218,6 +220,11 @@ void LWOImporter::InternReadFile( const std::string& pFile,
 			for (FaceList::iterator it = layer.mFaces.begin(), end = layer.mFaces.end();
 			for (FaceList::iterator it = layer.mFaces.begin(), end = layer.mFaces.end();
 				it != end;++it,++i)
 				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;
 				unsigned int idx = (*it).surfaceIndex;
 				if (idx >= mTags->size())
 				if (idx >= mTags->size())
 				{
 				{
@@ -282,7 +289,7 @@ void LWOImporter::InternReadFile( const std::string& pFile,
 					if (0xffffffff == vUVChannelIndices[mui])break;
 					if (0xffffffff == vUVChannelIndices[mui])break;
 					pvUV[mui] = mesh->mTextureCoords[mui] = new aiVector3D[mesh->mNumVertices];
 					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
 					// so we can directly setup this value
 					mesh->mNumUVComponents[0] = 2;
 					mesh->mNumUVComponents[0] = 2;
 				}
 				}
@@ -318,6 +325,7 @@ void LWOImporter::InternReadFile( const std::string& pFile,
 						register unsigned int idx = face.mIndices[q];
 						register unsigned int idx = face.mIndices[q];
 						*pv = layer.mTempPoints[idx] + layer.mPivot;
 						*pv = layer.mTempPoints[idx] + layer.mPivot;
 						pv->z *= -1.0f; // DX to OGL
 						pv->z *= -1.0f; // DX to OGL
+						//std::swap(pv->z,pv->y);
 						pv++;
 						pv++;
 
 
 						// process UV coordinates
 						// process UV coordinates
@@ -585,7 +593,8 @@ void LWOImporter::GenerateNodeGraph(std::vector<aiNode*>& apcNodes)
 		pScene->mRootNode->mChildren	= apcNewNodes;
 		pScene->mRootNode->mChildren	= apcNewNodes;
 		pScene->mRootNode->mNumChildren	= newSize;
 		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
 	// remove a single root node
 	// TODO: implement directly in the above loop, no need to deallocate here
 	// 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)
 void LWOImporter::LoadLWO2Polygons(unsigned int length)
 {
 {
 	LE_NCONST uint16_t* const end	= (LE_NCONST uint16_t*)(mFileBuffer+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
 	// Determine the type of the polygons
 	switch (type)
 	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_PTCH:
 	case  AI_LWO_FACE:
 	case  AI_LWO_FACE:
-
 		break;
 		break;
 	default:
 	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
 	// 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;
 	unsigned int iNumFaces = 0,iNumVertices = 0;
 	CountVertsAndFacesLWO2(iNumVertices,iNumFaces,cursor,end);
 	CountVertsAndFacesLWO2(iNumVertices,iNumFaces,cursor,end);
 
 
 	// allocate the output array and copy face indices
 	// allocate the output array and copy face indices
-	if (iNumFaces)
-	{
+	if (iNumFaces)	{
 		cursor = (uint16_t*)mFileBuffer;
 		cursor = (uint16_t*)mFileBuffer;
 
 
-		mCurLayer->mFaces.resize(iNumFaces);
+		mCurLayer->mFaces.resize(iNumFaces,LWO::Face(type));
 		FaceList::iterator it = mCurLayer->mFaces.begin();
 		FaceList::iterator it = mCurLayer->mFaces.begin();
 		CopyFaceIndicesLWO2(it,cursor,end);
 		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)
 void LWOImporter::LoadLWO2VertexMap(unsigned int length, bool perPoly)
 {
 {
 	LE_NCONST uint8_t* const end = mFileBuffer+length;
 	LE_NCONST uint8_t* const end = mFileBuffer+length;
@@ -915,24 +938,24 @@ void LWOImporter::LoadLWO2VertexMap(unsigned int length, bool perPoly)
 	switch (type)
 	switch (type)
 	{
 	{
 	case AI_LWO_TXUV:
 	case AI_LWO_TXUV:
-		if (dims != 2)
-		{
+		if (dims != 2)	{
 			DefaultLogger::get()->warn("LWO2: Found UV channel with != 2 components"); 
 			DefaultLogger::get()->warn("LWO2: Found UV channel with != 2 components"); 
+			return;
 		}
 		}
 		base = FindEntry(mCurLayer->mUVChannels,name,perPoly);
 		base = FindEntry(mCurLayer->mUVChannels,name,perPoly);
 		break;
 		break;
 	case AI_LWO_WGHT:
 	case AI_LWO_WGHT:
-		if (dims != 1)
-		{
+		if (dims != 1)	{
 			DefaultLogger::get()->warn("LWO2: found vertex weight map with != 1 components"); 
 			DefaultLogger::get()->warn("LWO2: found vertex weight map with != 1 components"); 
+			return;
 		}
 		}
 		base = FindEntry(mCurLayer->mWeightChannels,name,perPoly);
 		base = FindEntry(mCurLayer->mWeightChannels,name,perPoly);
 		break;
 		break;
 	case AI_LWO_RGB:
 	case AI_LWO_RGB:
 	case AI_LWO_RGBA:
 	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"); 
 			DefaultLogger::get()->warn("LWO2: found vertex color map with != 3&4 components"); 
+			return;
 		}
 		}
 		base = FindEntry(mCurLayer->mVColorChannels,name,perPoly);
 		base = FindEntry(mCurLayer->mVColorChannels,name,perPoly);
 		break;
 		break;
@@ -1028,6 +1051,7 @@ void LWOImporter::LoadLWO2VertexMap(unsigned int length, bool perPoly)
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
+// Load LWO2 clip
 void LWOImporter::LoadLWO2Clip(unsigned int length)
 void LWOImporter::LoadLWO2Clip(unsigned int length)
 {
 {
 	AI_LWO_VALIDATE_CHUNK_LENGTH(length,CLIP,10);
 	AI_LWO_VALIDATE_CHUNK_LENGTH(length,CLIP,10);
@@ -1042,6 +1066,7 @@ void LWOImporter::LoadLWO2Clip(unsigned int length)
 	switch (head->type)
 	switch (head->type)
 	{
 	{
 	case AI_LWO_STIL:
 	case AI_LWO_STIL:
+		AI_LWO_VALIDATE_CHUNK_LENGTH(head->length,STIL,1);
 
 
 		// "Normal" texture
 		// "Normal" texture
 		GetS0(clip.path,head->length);
 		GetS0(clip.path,head->length);
@@ -1049,7 +1074,7 @@ void LWOImporter::LoadLWO2Clip(unsigned int length)
 		break;
 		break;
 
 
 	case AI_LWO_ISEQ:
 	case AI_LWO_ISEQ:
-
+		AI_LWO_VALIDATE_CHUNK_LENGTH(head->length,ISEQ,16);
 		// Image sequence. We'll later take the first.
 		// Image sequence. We'll later take the first.
 		{
 		{
 			uint8_t digits = GetU1();  mFileBuffer++;
 			uint8_t digits = GetU1();  mFileBuffer++;
@@ -1078,18 +1103,124 @@ void LWOImporter::LoadLWO2Clip(unsigned int length)
 		break;
 		break;
 
 
 	case AI_LWO_XREF:
 	case AI_LWO_XREF:
+		AI_LWO_VALIDATE_CHUNK_LENGTH(head->length,XREF,4);
 
 
 		// Just a cross-reference to another CLIp
 		// Just a cross-reference to another CLIp
 		clip.type = Clip::REF;
 		clip.type = Clip::REF;
 		clip.clipRef = GetU4();
 		clip.clipRef = GetU4();
 		break;
 		break;
 
 
+	case AI_LWO_NEGA:
+		AI_LWO_VALIDATE_CHUNK_LENGTH(head->length,NEGA,2);
+		clip.negate = (0 != GetU2());
+		break;
+
 	default:
 	default:
 		DefaultLogger::get()->warn("LWO2: Encountered unknown CLIP subchunk");
 		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()
 void LWOImporter::LoadLWO2File()
 {
 {
 	bool skip = false;
 	bool skip = false;
@@ -1121,8 +1252,7 @@ void LWOImporter::LoadLWO2File()
 				// load this layer or ignore it? Check the layer index property
 				// load this layer or ignore it? Check the layer index property
 				// NOTE: The first layer is the default layer, so the layer
 				// NOTE: The first layer is the default layer, so the layer
 				// index is one-based now
 				// index is one-based now
-				if (0xffffffff != configLayerIndex && configLayerIndex != mLayers->size())
-				{
+				if (0xffffffff != configLayerIndex && configLayerIndex != mLayers->size()-1)	{
 					skip = true;
 					skip = true;
 				}
 				}
 				else skip = false;
 				else skip = false;
@@ -1138,16 +1268,14 @@ void LWOImporter::LoadLWO2File()
 				GetS0(layer.mName,head->length-16);
 				GetS0(layer.mName,head->length-16);
 
 
 				// if the name is empty, generate a default name
 				// if the name is empty, generate a default name
-				if (layer.mName.empty())
-				{
+				if (layer.mName.empty())	{
 					char buffer[128]; // should be sufficiently large
 					char buffer[128]; // should be sufficiently large
 					::sprintf(buffer,"Layer_%i", iUnnamed++);
 					::sprintf(buffer,"Layer_%i", iUnnamed++);
 					layer.mName = buffer;
 					layer.mName = buffer;
 				}
 				}
 
 
 				// load this layer or ignore it? Check the layer name property
 				// 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;
 					skip = true;
 				}
 				}
 				else hasNamedLayer = true;
 				else hasNamedLayer = true;
@@ -1161,7 +1289,8 @@ void LWOImporter::LoadLWO2File()
 			// vertex list
 			// vertex list
 		case AI_LWO_PNTS:
 		case AI_LWO_PNTS:
 			{
 			{
-				if (skip)break;
+				if (skip)
+					break;
 
 
 				unsigned int old = (unsigned int)mCurLayer->mTempPoints.size();
 				unsigned int old = (unsigned int)mCurLayer->mTempPoints.size();
 				LoadLWOPoints(head->length);
 				LoadLWOPoints(head->length);
@@ -1178,7 +1307,8 @@ void LWOImporter::LoadLWO2File()
 			// --- intentionally no break here
 			// --- intentionally no break here
 		case AI_LWO_VMAP:
 		case AI_LWO_VMAP:
 			{
 			{
-				if (skip)break;
+				if (skip)
+					break;
 
 
 				if (mCurLayer->mTempPoints.empty())
 				if (mCurLayer->mTempPoints.empty())
 					DefaultLogger::get()->warn("LWO2: Unexpected VMAP chunk");
 					DefaultLogger::get()->warn("LWO2: Unexpected VMAP chunk");
@@ -1188,7 +1318,8 @@ void LWOImporter::LoadLWO2File()
 			// face list
 			// face list
 		case AI_LWO_POLS:
 		case AI_LWO_POLS:
 			{
 			{
-				if (skip)break;
+				if (skip)
+					break;
 
 
 				unsigned int old = (unsigned int)mCurLayer->mFaces.size();
 				unsigned int old = (unsigned int)mCurLayer->mFaces.size();
 				LoadLWO2Polygons(head->length);
 				LoadLWO2Polygons(head->length);
@@ -1198,7 +1329,8 @@ void LWOImporter::LoadLWO2File()
 			// polygon tags 
 			// polygon tags 
 		case AI_LWO_PTAG:
 		case AI_LWO_PTAG:
 			{
 			{
-				if (skip)break;
+				if (skip)
+					break;
 
 
 				if (mCurLayer->mFaces.empty())
 				if (mCurLayer->mFaces.empty())
 					DefaultLogger::get()->warn("LWO2: Unexpected PTAG");
 					DefaultLogger::get()->warn("LWO2: Unexpected PTAG");
@@ -1227,9 +1359,16 @@ void LWOImporter::LoadLWO2File()
 				LoadLWO2Clip(head->length);
 				LoadLWO2Clip(head->length);
 				break;
 				break;
 			}
 			}
+
+			// envelope chunk
+		case AI_LWO_ENVL:
+			{
+				LoadLWO2Envelope(head->length);
+				break;
+			}
 		}
 		}
 		mFileBuffer = next;
 		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 "xxxLWOB[xxx]" are used with the older LWOB format.
  *         Methods named "xxxLWO[xxx]" are used with both formats.
  *         Methods named "xxxLWO[xxx]" are used with both formats.
  *         Methods named "xxx" are used to preprocess the loaded data -
  *         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
 class LWOImporter : public BaseImporter
@@ -81,8 +81,10 @@ public:
 
 
 	// -------------------------------------------------------------------
 	// -------------------------------------------------------------------
 	/** Returns whether the class can handle the format of the given file. 
 	/** 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);
 	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
 	/** Count vertices and faces in a LWOB/LWO2 file
@@ -372,6 +379,9 @@ protected:
 	/** Temporary clip list from the file */
 	/** Temporary clip list from the file */
 	ClipList mClips;
 	ClipList mClips;
 
 
+	/** Temporary envelope list from the file */
+	EnvelopeList mEnvelopes;
+
 	/** file buffer */
 	/** file buffer */
 	uint8_t* mFileBuffer;
 	uint8_t* mFileBuffer;
 
 
@@ -381,9 +391,16 @@ protected:
 	/** Output scene */
 	/** Output scene */
 	aiScene* pScene;
 	aiScene* pScene;
 
 
+	/** Configuration option: speed flag set? */
 	bool configSpeedFlag;
 	bool configSpeedFlag;
+
+	/** Configuration option: index of layer to be loaded */
 	unsigned int configLayerIndex;
 	unsigned int configLayerIndex;
+
+	/** Configuration option: name of layer to be loaded */
 	std::string  configLayerName;
 	std::string  configLayerName;
+
+	/** True if we have a named layer */
 	bool hasNamedLayer;
 	bool hasNamedLayer;
 };
 };
 
 

+ 64 - 32
code/LWOMaterial.cpp

@@ -183,13 +183,30 @@ bool LWOImporter::HandleTextures(MaterialHelper* pcMat, const TextureList& in, a
 			if (mClips.end() == clip)	{
 			if (mClips.end() == clip)	{
 				DefaultLogger::get()->error("LWO2: Clip index is out of bounds");
 				DefaultLogger::get()->error("LWO2: Clip index is out of bounds");
 				temp = 0;
 				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 
 		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");
 				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));
 		pcMat->AddProperty<int>((int*)&temp,1,AI_MATKEY_TEXOP(type,cur));
 
 
 		// setup the mapping mode
 		// setup the mapping mode
@@ -258,12 +276,12 @@ void LWOImporter::ConvertMaterial(const LWO::Surface& surf,MaterialHelper* pcMat
 	st.Set(surf.mName);
 	st.Set(surf.mName);
 	pcMat->AddProperty(&st,AI_MATKEY_NAME);
 	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
 	// 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;
 	aiShadingMode m;
 	if (surf.mSpecularValue && surf.mGlossiness)
 	if (surf.mSpecularValue && surf.mGlossiness)
@@ -281,16 +299,16 @@ void LWOImporter::ConvertMaterial(const LWO::Surface& surf,MaterialHelper* pcMat
 			else fGloss = 80.0f;
 			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;
 		m = aiShadingMode_Phong;
 	}
 	}
 	else m = aiShadingMode_Gouraud;
 	else m = aiShadingMode_Gouraud;
 
 
 	// specular color
 	// specular color
 	aiColor3D clr = lerp( aiColor3D(1.f,1.f,1.f), surf.mColor, surf.mColorHighlights );
 	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
 	// emissive color
 	// (luminosity is not really the same but it affects the surface in 
 	// (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;
 	clr.g = clr.b = clr.r = surf.mLuminosity*0.8f;
 	pcMat->AddProperty<aiColor3D>(&clr,1,AI_MATKEY_COLOR_EMISSIVE);
 	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
 	// ADD TEXTURES to the material
 	// TODO: find out how we can handle COLOR textures correctly...
 	// 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.mOpacityTextures,aiTextureType_OPACITY);
 	HandleTextures(pcMat,surf.mReflectionTextures,aiTextureType_REFLECTION);
 	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 
 	// 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();
 	for (ShaderList::const_iterator it = surf.mShaders.begin(), end = surf.mShaders.end();
 		 it != end;++it)
 		 it != end;++it)
 	{
 	{
 		//if (!(*it).enabled)continue;
 		//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 "
 			DefaultLogger::get()->info("LWO2: Mapping LW_SuperCelShader/AH_CelShader "
 				"to aiShadingMode_Toon");
 				"to aiShadingMode_Toon");
 
 
 			m = aiShadingMode_Toon;
 			m = aiShadingMode_Toon;
 			break;
 			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 "
 			DefaultLogger::get()->info("LWO2: Mapping LW_RealFresnel/LW_FastFresnel "
 				"to aiShadingMode_Fresnel");
 				"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);
 			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);
 	pcMat->AddProperty((int*)&m,1,AI_MATKEY_SHADING_MODEL);
 
 
 	// (the diffuse value is just a scaling factor)
 	// (the diffuse value is just a scaling factor)
 	// If a diffuse texture is set, we set this value to 1.0
 	// 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.r *= surf.mDiffuseValue;
 	clr.g *= surf.mDiffuseValue;
 	clr.g *= surf.mDiffuseValue;
 	clr.b *= surf.mDiffuseValue;
 	clr.b *= surf.mDiffuseValue;
@@ -365,9 +389,7 @@ void LWOImporter::FindUVChannels(LWO::TextureList& list, LWO::Layer& layer,
 		 it != end;++it)
 		 it != end;++it)
 	{
 	{
 		// Ignore textures with non-UV mappings for the moment.
 		// 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;
 			continue;
 		}
 		}
 		for (unsigned int i = 0; i < layer.mUVChannels.size();++i)
 		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])	{
 					if (i == out[m])	{
 						(*it).mRealUVIndex = m;
 						(*it).mRealUVIndex = m;
+						break;
 					}
 					}
 				}
 				}
 				if (0xffffffff == (*it).mRealUVIndex)
 				if (0xffffffff == (*it).mRealUVIndex)
@@ -710,7 +733,9 @@ void LWOImporter::LoadLWO2Surface(unsigned int size)
 			// transparency
 			// transparency
 		case AI_LWO_TRAN:
 		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);
 				AI_LWO_VALIDATE_CHUNK_LENGTH(head->length,TRAN,4);
 				surf.mTransparency = GetF4();
 				surf.mTransparency = GetF4();
@@ -741,6 +766,13 @@ void LWOImporter::LoadLWO2Surface(unsigned int size)
 				}
 				}
 				break;
 				break;
 			}
 			}
+			// additive transparency
+		case AI_LWO_ADTR:
+			{
+				AI_LWO_VALIDATE_CHUNK_LENGTH(head->length,ADTR,4);
+				surf.mAdditiveTransparency = GetF4();
+				break;
+			}
 			// wireframe mode
 			// wireframe mode
 		case AI_LWO_LINE:
 		case AI_LWO_LINE:
 			{
 			{
@@ -788,7 +820,7 @@ void LWOImporter::LoadLWO2Surface(unsigned int size)
 		case AI_LWO_SMAN:
 		case AI_LWO_SMAN:
 			{
 			{
 				AI_LWO_VALIDATE_CHUNK_LENGTH(head->length,SMAN,4);
 				AI_LWO_VALIDATE_CHUNK_LENGTH(head->length,SMAN,4);
-				surf.mMaximumSmoothAngle = GetF4();
+				surf.mMaximumSmoothAngle = fabs( GetF4() );
 				break;
 				break;
 			}
 			}
 			// vertex color channel to be applied to the surface
 			// 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"
 #include "AssimpPCH.h"
 
 
@@ -48,9 +50,60 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "fast_atof.h"
 #include "fast_atof.h"
 
 
 #include "SceneCombiner.h"
 #include "SceneCombiner.h"
+#include "GenericProperty.h"
+#include "SkeletonMeshBuilder.h"
 
 
 using namespace Assimp;
 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
 // 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. 
 // 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, 
 void LWSImporter::InternReadFile( const std::string& pFile, aiScene* pScene, 
 	IOSystem* pIOHandler)
 	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
 #ifndef AI_LWSLOADER_H_INCLUDED
 #define AI_LWSLOADER_H_INCLUDED
 #define AI_LWSLOADER_H_INCLUDED
 
 
+#include "LWOFileData.h"
+#include "SceneCombiner.h"
 
 
 namespace Assimp	{
 namespace Assimp	{
 	namespace LWS	{
 	namespace LWS	{
@@ -49,17 +53,107 @@ namespace Assimp	{
 // ---------------------------------------------------------------------------
 // ---------------------------------------------------------------------------
 /** Represents an element in a LWS file.
 /** 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
 class Element
 {
 {
-	std::string name, data;
+public:
+	Element()
+	{}
+
+	// first: name, second: rest
+	std::string tokens[2];
 	std::list<Element> children;
 	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
 } // end namespace LWS
 
 
@@ -84,28 +178,63 @@ protected:
 public:
 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:
 protected:
 
 
 	// -------------------------------------------------------------------
 	// -------------------------------------------------------------------
-	/** Called by Importer::GetExtensionList() for each loaded importer.
-	 * See BaseImporter::GetExtensionList() for details
-	 */
+	// Get list of supported extensions
 	void GetExtensionList(std::string& append);
 	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, 
 	void InternReadFile( const std::string& pFile, aiScene* pScene, 
 		IOSystem* pIOHandler);
 		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:
 private:
 
 
+	bool configSpeedFlag;
+	IOSystem* io;
 
 
+	double first,last,fps;
 };
 };
 
 
 } // end of namespace Assimp
 } // 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
 #ifndef AI_MD2FILEHELPER_H_INC
 #define AI_MD2FILEHELPER_H_INC
 #define AI_MD2FILEHELPER_H_INC
 
 
-#include <string>
-#include <vector>
-#include <sstream>
-
 #include "../include/aiTypes.h"
 #include "../include/aiTypes.h"
 #include "../include/aiMesh.h"
 #include "../include/aiMesh.h"
 #include "../include/aiAnim.h"
 #include "../include/aiAnim.h"
@@ -62,11 +54,9 @@ File format specification:
 namespace Assimp	{
 namespace Assimp	{
 namespace MD2	{
 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
 // common limitations
 #define AI_MD2_VERSION			15
 #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)
 void MD2::LookupNormalIndex(uint8_t iNormalIndex,aiVector3D& vOut)
 {
 {
 	// make sure the normal index has a valid value
 	// 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;
 		iNormalIndex = ARRAYSIZE(g_avNormals) - 1;
 	}
 	}
 	vOut = *((const aiVector3D*)(&g_avNormals[iNormalIndex]));
 	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
 // Constructor to be privately used by Importer
 MD2Importer::MD2Importer()
 MD2Importer::MD2Importer()
-{
-}
+{}
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Destructor, private as well 
 // Destructor, private as well 
 MD2Importer::~MD2Importer()
 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
 // Setup configuration properties
 void MD2Importer::SetupProperties(const Importer* pImp)
 void MD2Importer::SetupProperties(const Importer* pImp)
@@ -161,8 +164,8 @@ void MD2Importer::ValidateHeader( )
 
 
 	if (m_pcHeader->numFrames <= configFrameID )
 	if (m_pcHeader->numFrames <= configFrameID )
 		throw new ImportErrorException("The requested frame is not existing the file");
 		throw new ImportErrorException("The requested frame is not existing the file");
-
 }
 }
+
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Imports the given file into the given scene structure. 
 // Imports the given file into the given scene structure. 
 void MD2Importer::InternReadFile( const std::string& pFile, 
 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);
 		pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_AMBIENT);
 
 
 		aiString szName;
 		aiString szName;
-		szName.Set("MD2Default");
+		szName.Set(AI_DEFAULT_TEXTURED_MATERIAL_NAME);
 		pcHelper->AddProperty(&szName,AI_MATKEY_NAME);
 		pcHelper->AddProperty(&szName,AI_MATKEY_NAME);
 
 
 		aiString sz;
 		aiString sz;
 
 
 		// TODO: Try to guess the name of the texture file from the model file name
 		// 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));
 		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
 #ifndef AI_MD2LOADER_H_INCLUDED
 #define 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;
 struct aiNode;
 #include "MD2FileData.h"
 #include "MD2FileData.h"
 
 
-namespace Assimp
-{
+namespace Assimp	{
 class MaterialHelper;
 class MaterialHelper;
 
 
 using namespace MD2;
 using namespace MD2;
 
 
 // ---------------------------------------------------------------------------
 // ---------------------------------------------------------------------------
-/** Used to load MD2 files
+/** Importer class for MD2
 */
 */
 class MD2Importer : public BaseImporter
 class MD2Importer : public BaseImporter
 {
 {
@@ -73,7 +74,8 @@ public:
 	// -------------------------------------------------------------------
 	// -------------------------------------------------------------------
 	/** Returns whether the class can handle the format of the given file. 
 	/** Returns whether the class can handle the format of the given file. 
 	* See BaseImporter::CanRead() for details.	*/
 	* 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.
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 * 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. 
 	/** 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 Assimp	{
 namespace MD3	{
 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
 // common limitations
 #define AI_MD3_VERSION			15
 #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. 
 // 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
 	// AI_CONFIG_IMPORT_MD3_SHADER_SRC
 	configShaderFile = (pImp->GetPropertyString(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
 void MD3Importer::ReadShader(Q3Shader::ShaderData& fill) const
 {
 {
 	// Determine Q3 model name from given path
 	// 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));
 	const std::string model_file = path.substr(s+1,path.length()-(s+2));
 
 
 	// If no specific dir or file is given, use our default search behaviour
 	// If 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
 // Read a multi-part Q3 player model
 bool MD3Importer::ReadMultipartFile()
 bool MD3Importer::ReadMultipartFile()
@@ -520,32 +539,32 @@ bool MD3Importer::ReadMultipartFile()
 
 
 		// now read these three files
 		// now read these three files
 		BatchLoader batch(mIOHandler);
 		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();
 		batch.LoadAll();
 
 
 		// now construct a dummy scene to place these three parts in
 		// now construct a dummy scene to place these three parts in
 		aiScene* master   = new aiScene();
 		aiScene* master   = new aiScene();
 		aiNode* nd = master->mRootNode = new aiNode();
 		aiNode* nd = master->mRootNode = new aiNode();
-		nd->mName.Set("<M3D_Player>");
+		nd->mName.Set("<MD3_Player>");
 
 
 		// ... and get them. We need all of them.
 		// ... and get them. We need all of them.
-		scene_lower = batch.GetImport(lower);
+		scene_lower = batch.GetImport(_lower);
 		if (!scene_lower) {
 		if (!scene_lower) {
 			DefaultLogger::get()->error("M3D: Failed to read multipart model, lower.md3 fails to load");
 			DefaultLogger::get()->error("M3D: Failed to read multipart model, lower.md3 fails to load");
 			failure = "lower";
 			failure = "lower";
 			goto error_cleanup;
 			goto error_cleanup;
 		}
 		}
 
 
-		scene_upper = batch.GetImport(upper);
+		scene_upper = batch.GetImport(_upper);
 		if (!scene_upper) {
 		if (!scene_upper) {
 			DefaultLogger::get()->error("M3D: Failed to read multipart model, upper.md3 fails to load");
 			DefaultLogger::get()->error("M3D: Failed to read multipart model, upper.md3 fails to load");
 			failure = "upper";
 			failure = "upper";
 			goto error_cleanup;
 			goto error_cleanup;
 		}
 		}
 
 
-		scene_head  = batch.GetImport(head);
+		scene_head  = batch.GetImport(_head);
 		if (!scene_head) {
 		if (!scene_head) {
 			DefaultLogger::get()->error("M3D: Failed to read multipart model, head.md3 fails to load");
 			DefaultLogger::get()->error("M3D: Failed to read multipart model, head.md3 fails to load");
 			failure = "head";
 			failure = "head";
@@ -555,6 +574,7 @@ bool MD3Importer::ReadMultipartFile()
 		// build attachment infos. search for typical Q3 tags
 		// build attachment infos. search for typical Q3 tags
 
 
 		// original root
 		// original root
+		scene_lower->mRootNode->mName.Set("lower");
 		attach.push_back(AttachmentInfo(scene_lower, nd));
 		attach.push_back(AttachmentInfo(scene_lower, nd));
 
 
 		// tag_torso
 		// tag_torso
@@ -563,6 +583,7 @@ bool MD3Importer::ReadMultipartFile()
 			DefaultLogger::get()->error("M3D: Failed to find attachment tag for multipart model: tag_torso expected");
 			DefaultLogger::get()->error("M3D: Failed to find attachment tag for multipart model: tag_torso expected");
 			goto error_cleanup;
 			goto error_cleanup;
 		}
 		}
+		scene_upper->mRootNode->mName.Set("upper");
 		attach.push_back(AttachmentInfo(scene_upper,tag_torso));
 		attach.push_back(AttachmentInfo(scene_upper,tag_torso));
 
 
 		// tag_head
 		// tag_head
@@ -571,13 +592,21 @@ bool MD3Importer::ReadMultipartFile()
 			DefaultLogger::get()->error("M3D: Failed to find attachment tag for multipart model: tag_head expected");
 			DefaultLogger::get()->error("M3D: Failed to find attachment tag for multipart model: tag_head expected");
 			goto error_cleanup;
 			goto error_cleanup;
 		}
 		}
+		scene_head->mRootNode->mName.Set("head");
 		attach.push_back(AttachmentInfo(scene_head,tag_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
 		// and merge the scenes
 		SceneCombiner::MergeScenes(&mScene,master, attach,
 		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;
 		return true;
 
 

+ 5 - 1
code/MD3Loader.h

@@ -225,7 +225,8 @@ public:
 	// -------------------------------------------------------------------
 	// -------------------------------------------------------------------
 	/** Returns whether the class can handle the format of the given file. 
 	/** Returns whether the class can handle the format of the given file. 
 	* See BaseImporter::CanRead() for details.	*/
 	* 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 */
 	/** Configuration option: name or path of shader */
 	std::string configShaderFile;
 	std::string configShaderFile;
 
 
+	/** Configuration option: speed flag was set? */
+	bool configSpeedFlag;
+
 	/** Header of the MD3 file */
 	/** Header of the MD3 file */
 	BE_NCONST MD3::Header* pcHeader;
 	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"
 #include "AssimpPCH.h"
 #ifndef ASSIMP_BUILD_NO_MD5_IMPORTER
 #ifndef ASSIMP_BUILD_NO_MD5_IMPORTER
@@ -56,34 +58,35 @@ using namespace Assimp;
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
 // Constructor to be privately used by Importer
 MD5Importer::MD5Importer()
 MD5Importer::MD5Importer()
-{
-	// nothing to do here
-}
+{}
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Destructor, private as well 
 // Destructor, private as well 
 MD5Importer::~MD5Importer()
 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
 	// remove the file extension
 	std::string::size_type pos = pFile.find_last_of('.');
 	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->pIOHandler = pIOHandler;
 	this->pScene = pScene;
 	this->pScene = pScene;
 
 
 	bHadMD5Mesh = bHadMD5Anim = false;
 	bHadMD5Mesh = bHadMD5Anim = false;
 
 
 	// load the animation keyframes
 	// load the animation keyframes
-	this->LoadMD5AnimFile();
+	LoadMD5AnimFile();
 
 
 	// load the mesh vertices and bones
 	// load the mesh vertices and bones
-	this->LoadMD5MeshFile();
+	LoadMD5MeshFile();
 
 
 	// make sure we return no incomplete data
 	// make sure we return no incomplete data
 	if (!bHadMD5Mesh && !bHadMD5Anim)
 	if (!bHadMD5Mesh && !bHadMD5Anim)
@@ -260,7 +263,7 @@ void MD5Importer::LoadMD5MeshFile ()
 	bHadMD5Mesh = true;
 	bHadMD5Mesh = true;
 
 
 	// now load the file into memory
 	// now load the file into memory
-	this->LoadFileIntoMemory(file.get());
+	LoadFileIntoMemory(file.get());
 
 
 	// now construct a parser and parse the file
 	// now construct a parser and parse the file
 	MD5::MD5Parser parser(mBuffer,fileSize);
 	MD5::MD5Parser parser(mBuffer,fileSize);
@@ -341,10 +344,7 @@ void MD5Importer::LoadMD5MeshFile ()
 		unsigned int* piCount = new unsigned int[meshParser.mJoints.size()];
 		unsigned int* piCount = new unsigned int[meshParser.mJoints.size()];
 		::memset(piCount,0,sizeof(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)
 			for (unsigned int jub = (*iter).mFirstWeight, w = jub; w < jub + (*iter).mNumWeights;++w)
 			{
 			{
 				MD5::WeightDesc& desc = meshSrc.mWeights[w];
 				MD5::WeightDesc& desc = meshSrc.mWeights[w];
@@ -444,7 +444,7 @@ void MD5Importer::LoadMD5MeshFile ()
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void MD5Importer::LoadMD5AnimFile ()
 void MD5Importer::LoadMD5AnimFile ()
 {
 {
-	std::string pFile = this->mFile + "MD5ANIM";
+	std::string pFile = mFile + "MD5ANIM";
 	boost::scoped_ptr<IOStream> file( pIOHandler->Open( pFile, "rb"));
 	boost::scoped_ptr<IOStream> file( pIOHandler->Open( pFile, "rb"));
 
 
 	// Check whether we can read from the file
 	// 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
 #ifndef AI_MD5LOADER_H_INCLUDED
 #define 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. 
 	/** Returns whether the class can handle the format of the given file. 
 	* See BaseImporter::CanRead() for details.	*/
 	* 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:
 protected:
 
 
@@ -82,11 +84,8 @@ protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 * 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. 
 	/** Imports the given file into the given scene structure. 
 	* See BaseImporter::InternReadFile() for details
 	* See BaseImporter::InternReadFile() for details

+ 6 - 5
code/MD5Parser.cpp

@@ -112,21 +112,22 @@ void MD5Parser::ParseHeader()
 	if (0 != ASSIMP_strincmp(buffer,"MD5Version",10) ||
 	if (0 != ASSIMP_strincmp(buffer,"MD5Version",10) ||
 		!IsSpace(*(buffer+=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();
 	SkipSpaces();
 	unsigned int iVer = ::strtol10(buffer,(const char**)&buffer);
 	unsigned int iVer = ::strtol10(buffer,(const char**)&buffer);
 	if (10 != iVer)
 	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
 	// print the command line options to the console
+	// fix: can break the log length limit ...
 	char* sz = buffer;
 	char* sz = buffer;
 	while (!IsLineEnd( *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)
 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
 #ifndef AI_MD5PARSER_H_INCLUDED
 #define AI_MD5PARSER_H_INCLUDED
 #define AI_MD5PARSER_H_INCLUDED
 
 
 #include "../include/aiTypes.h"
 #include "../include/aiTypes.h"
 #include "ParsingUtils.h"
 #include "ParsingUtils.h"
-#include <vector>
 
 
 struct aiFace;
 struct aiFace;
 
 
 namespace Assimp	{
 namespace Assimp	{
-namespace MD5		{
-
+namespace MD5			{
 
 
 // ---------------------------------------------------------------------------
 // ---------------------------------------------------------------------------
 /** Represents a single element in a MD5 file
 /** Represents a single element in a MD5 file
@@ -335,12 +334,13 @@ public:
 	static void ReportWarning (const char* warn, unsigned int line);
 	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:
 public:
 
 
@@ -367,24 +367,22 @@ private:
 
 
 	// override these functions to make sure the line counter gets incremented
 	// 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;
 		++lineNumber;
 		return Assimp::SkipLine(in,out);
 		return Assimp::SkipLine(in,out);
 	}
 	}
 	// -------------------------------------------------------------------
 	// -------------------------------------------------------------------
-	inline bool SkipLine( )
+	bool SkipLine( )
 	{
 	{
 		return SkipLine(buffer,(const char**)&buffer);
 		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;
 		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 ...
 				if (!bHad) // we open files in binary mode, so there could be \r\n sequences ...
 				{
 				{
 					bHad = true;
 					bHad = true;
@@ -400,12 +398,12 @@ private:
 		return *in != '\0';
 		return *in != '\0';
 	}
 	}
 	// -------------------------------------------------------------------
 	// -------------------------------------------------------------------
-	inline bool SkipSpacesAndLineEnd( )
+	bool SkipSpacesAndLineEnd( )
 	{
 	{
 		return SkipSpacesAndLineEnd(buffer,(const char**)&buffer);
 		return SkipSpacesAndLineEnd(buffer,(const char**)&buffer);
 	}
 	}
 	// -------------------------------------------------------------------
 	// -------------------------------------------------------------------
-	inline bool SkipSpaces( )
+	bool SkipSpaces( )
 	{
 	{
 		return Assimp::SkipSpaces((const char**)&buffer);
 		return Assimp::SkipSpaces((const char**)&buffer);
 	}
 	}

+ 3 - 5
code/MDCFileData.h

@@ -60,11 +60,9 @@ namespace Assimp {
 namespace MDC {
 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
 // common limitations
 #define AI_MDC_VERSION			2
 #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. 
 // 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
 #ifndef AI_MDCLOADER_H_INCLUDED
 #define 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 "MDCFileData.h"
 #include "ByteSwap.h"
 #include "ByteSwap.h"
 
 
-namespace Assimp
-{
+namespace Assimp	{
 using namespace MDC;
 using namespace MDC;
 
 
 // ---------------------------------------------------------------------------
 // ---------------------------------------------------------------------------
-/** Used to load MDC files
+/** Importer class to load the RtCW MDC file format
 */
 */
 class MDCImporter : public BaseImporter
 class MDCImporter : public BaseImporter
 {
 {
@@ -71,7 +72,8 @@ public:
 	// -------------------------------------------------------------------
 	// -------------------------------------------------------------------
 	/** Returns whether the class can handle the format of the given file. 
 	/** Returns whether the class can handle the format of the given file. 
 	* See BaseImporter::CanRead() for details.	*/
 	* 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().
 	/** Called prior to ReadFile().
@@ -86,10 +88,7 @@ protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 * 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. 
 	/** Imports the given file into the given scene structure. 

+ 13 - 14
code/MDLFileData.h

@@ -58,30 +58,29 @@ namespace Assimp	{
 namespace MDL	{
 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
 // 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
 // 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
 // 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
 // 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
 // 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,
 // 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. 
 // 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
 // Setup configuration properties
 void MDLImporter::SetupProperties(const Importer* pImp)
 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.
 	// 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);
 		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");
 	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. 
 	/** Returns whether the class can handle the format of the given file. 
 	* See BaseImporter::CanRead() for details.	*/
 	* 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);
 	ai_assert(NULL != pcTexture);
 
 
 	aiColor4D clrOut;
 	aiColor4D clrOut;
-	clrOut.r = std::numeric_limits<float>::quiet_NaN();
+	clrOut.r = get_qnan();
 	if (!pcTexture->mHeight || !pcTexture->mWidth)
 	if (!pcTexture->mHeight || !pcTexture->mWidth)
 		return clrOut;
 		return clrOut;
 
 
@@ -572,7 +572,7 @@ void MDLImporter::ParseSkinLump_3DGS_MDL7(
 	// been converted to MDL7 from other formats, such as MDL5
 	// been converted to MDL7 from other formats, such as MDL5
 	aiColor4D clrTexture;
 	aiColor4D clrTexture;
 	if (pcNew)clrTexture = this->ReplaceTextureWithColor(pcNew);
 	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
 	// check whether a material definition is contained in the skin
 	if (iType & AI_MDL7_SKINTYPE_MATERIAL)
 	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. 
 // 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() \
 #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(center); \
 	AI_NFF_PARSE_TRIPLE(radius); \
 	AI_NFF_PARSE_TRIPLE(radius); \
 	if (is_qnan(radius.z))radius.z = radius.x; \
 	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
 	// check whether this is the NFF2 file format
 	if (TokenMatch(buffer,"nff",3))
 	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 aiColor4D  cQNAN = aiColor4D (qnan,0.f,0.f,1.f);
 		const aiVector3D vQNAN = aiVector3D(qnan,0.f,0.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
 #ifndef AI_NFFLOADER_H_INCLUDED
 #define 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	{
 namespace Assimp	{
 
 
-// ---------------------------------------------------------------------------
+// ----------------------------------------------------------------------------------
 /** NFF (Neutral File Format) Importer class.
 /** NFF (Neutral File Format) Importer class.
  *
  *
  * The class implements both Eric Haynes NFF format and Sense8's NFF (NFF2) format.
  * 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. 
 	/** 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:
 protected:
 
 
@@ -80,10 +84,7 @@ protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 * 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. 
 	/** 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"
 #include "AssimpPCH.h"
 #ifndef ASSIMP_BUILD_NO_OFF_IMPORTER
 #ifndef ASSIMP_BUILD_NO_OFF_IMPORTER
@@ -64,25 +66,31 @@ OFFImporter::~OFFImporter()
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file. 
 // 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. 
 // Imports the given file into the given scene structure. 
 void OFFImporter::InternReadFile( const std::string& pFile, 
 void OFFImporter::InternReadFile( const std::string& pFile, 
-	aiScene* pScene, IOSystem* pIOHandler)
+								 aiScene* pScene, IOSystem* pIOHandler)
 {
 {
 	boost::scoped_ptr<IOStream> file( pIOHandler->Open( pFile, "rb"));
 	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
 #ifndef AI_OFFLOADER_H_INCLUDED
 #define 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. 
 	/** Returns whether the class can handle the format of the given file. 
 	* See BaseImporter::CanRead() for details.	*/
 	* 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:
 protected:
 
 
@@ -75,10 +78,7 @@ protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 * 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. 
 	/** Imports the given file into the given scene structure. 

+ 3 - 16
code/ObjFileImporter.cpp

@@ -51,9 +51,6 @@ namespace Assimp	{
 
 
 using namespace std;
 using namespace std;
 
 
-//!	Obj-file-format extention
-const string ObjFileImporter::OBJ_EXT = "obj";
-
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 //	Default constructor
 //	Default constructor
 ObjFileImporter::ObjFileImporter() :
 ObjFileImporter::ObjFileImporter() :
@@ -76,20 +73,10 @@ ObjFileImporter::~ObjFileImporter()
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 //	Returns true, fi file is an obj file
 //	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;
 	friend class Importer;
 
 
-	//!	OB file extention
-	static const std::string OBJ_EXT;
-
 protected:
 protected:
 	///	\brief	Default constructor
 	///	\brief	Default constructor
 	ObjFileImporter();
 	ObjFileImporter();
@@ -79,7 +76,7 @@ protected:
 public:
 public:
 	/// \brief	Returns whether the class can handle the format of the given file. 
 	/// \brief	Returns whether the class can handle the format of the given file. 
 	/// \remark	See BaseImporter::CanRead() for details.
 	/// \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:
 private:
 
 

+ 29 - 28
code/PlyLoader.cpp

@@ -64,27 +64,31 @@ PLYImporter::~PLYImporter()
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file. 
 // 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. 
 // Imports the given file into the given scene structure. 
 void PLYImporter::InternReadFile( 
 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));
 	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
 	// the beginning of the file must be PLY - magic, magic
 	if (mBuffer[0] != 'P' && mBuffer[0] != 'p' ||
 	if (mBuffer[0] != 'P' && mBuffer[0] != 'p' ||
 		mBuffer[1] != 'L' && mBuffer[1] != 'l' ||
 		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");
 		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
 		else // triangle strips
 		{
 		{
-			// HUGE TODO: MAKE THAT FUCK WORK!
-
 			// normally we have only one triangle strip instance where
 			// normally we have only one triangle strip instance where
 			// a value of -1 indicates a restart of the strip
 			// a value of -1 indicates a restart of the strip
 			for (std::vector<ElementInstance>::const_iterator i =  pcList->alInstances.begin();
 			for (std::vector<ElementInstance>::const_iterator i =  pcList->alInstances.begin();
 				i != pcList->alInstances.end();++i)
 				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};
 				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)
 					if (-1 == p)
 					{
 					{
 						// restart the strip ...
 						// restart the strip ...
@@ -819,12 +820,12 @@ void PLYImporter::LoadFaces(std::vector<PLY::Face>* pvOut)
 						continue;
 						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[0]);
 					sFace.mIndices.push_back((unsigned int)aiTable[1]);
 					sFace.mIndices.push_back((unsigned int)aiTable[1]);
 					sFace.mIndices.push_back((unsigned int)p);
 					sFace.mIndices.push_back((unsigned int)p);
-					pvOut->push_back(sFace);
-
+		
 					aiTable[0] = aiTable[1];
 					aiTable[0] = aiTable[1];
 					aiTable[1] = p;
 					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
 #ifndef AI_PLYLOADER_H_INCLUDED
 #define AI_PLYLOADER_H_INCLUDED
 #define AI_PLYLOADER_H_INCLUDED
 
 
@@ -50,15 +51,13 @@ struct aiNode;
 
 
 #include "PlyParser.h"
 #include "PlyParser.h"
 
 
-namespace Assimp
-{
+namespace Assimp	{
 class MaterialHelper;
 class MaterialHelper;
 
 
-
 using namespace PLY;
 using namespace PLY;
 
 
 // ---------------------------------------------------------------------------
 // ---------------------------------------------------------------------------
-/** Used to load PLY files
+/** Importer class to load the stanford PLY file format
 */
 */
 class PLYImporter : public BaseImporter
 class PLYImporter : public BaseImporter
 {
 {
@@ -75,8 +74,10 @@ public:
 
 
 	// -------------------------------------------------------------------
 	// -------------------------------------------------------------------
 	/** Returns whether the class can handle the format of the given file. 
 	/** 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:
 protected:
 
 
@@ -84,10 +85,7 @@ protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 * 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. 
 	/** 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);
 		unsigned int iNum = PLY::PropertyInstance::ConvertTo<unsigned int>(v,prop->eFirstType);
 
 
 		// parse all list elements
 		// 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
 	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 "AssimpPCH.h"
 #include "PretransformVertices.h"
 #include "PretransformVertices.h"
 #include "ProcessHelper.h"
 #include "ProcessHelper.h"
+#include "SceneCombiner.h"
 
 
 using namespace Assimp;
 using namespace Assimp;
 
 
@@ -55,6 +57,7 @@ using namespace Assimp;
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
 // Constructor to be privately used by Importer
 PretransformVertices::PretransformVertices()
 PretransformVertices::PretransformVertices()
+:	configKeepHierarchy (false)
 {
 {
 }
 }
 
 
@@ -72,9 +75,17 @@ bool PretransformVertices::IsActive( unsigned int pFlags) const
 	return	(pFlags & aiProcess_PreTransformVertices) != 0;
 	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
 // Count the number of nodes
-unsigned int CountNodes( aiNode* pcNode )
+unsigned int PretransformVertices::CountNodes( aiNode* pcNode )
 {
 {
 	unsigned int iRet = 1;
 	unsigned int iRet = 1;
 	for (unsigned int i = 0;i < pcNode->mNumChildren;++i)
 	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
 // 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.
 	// the vertex format is stored in aiMesh::mBones for later retrieval.
 	// there isn't a good reason to compute it a few hundred times
 	// 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
 // Count the number of vertices in the whole scene and a given
 // material index
 // 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)
 	unsigned int iVFormat, unsigned int* piFaces, unsigned int* piVertices)
 {
 {
 	for (unsigned int i = 0; i < pcNode->mNumMeshes;++i)
 	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
 // 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 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)
 	for (unsigned int i = 0; i < pcNode->mNumMeshes;++i)
 	{
 	{
 		aiMesh* pcMesh = pcScene->mMeshes[ pcNode->mMeshes[i] ]; 
 		aiMesh* pcMesh = pcScene->mMeshes[ pcNode->mMeshes[i] ]; 
 		if (iMat == pcMesh->mMaterialIndex && iVFormat == GetMeshVFormat(pcMesh))
 		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)	{
 				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;
 			unsigned int p = 0;
@@ -180,26 +225,35 @@ void CollectData( aiScene* pcScene, aiNode* pcNode, unsigned int iMat,
 					pcMesh->mNumVertices * sizeof(aiColor4D));
 					pcMesh->mNumVertices * sizeof(aiColor4D));
 				++p;
 				++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
 				// Update the mPrimitiveTypes member of the mesh
 				switch (pcMesh->mFaces[planck].mNumIndices)
 				switch (pcMesh->mFaces[planck].mNumIndices)
@@ -222,24 +276,24 @@ void CollectData( aiScene* pcScene, aiNode* pcNode, unsigned int iMat,
 			aiCurrent[AI_PTVS_FACE]   += pcMesh->mNumFaces;
 			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,
 		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
 // Get a list of all vertex formats that occur for a given material index
 // The output list contains duplicate elements
 // 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)
 	std::list<unsigned int>& aiOut)
 {
 {
 	for (unsigned int i = 0; i < pcScene->mNumMeshes;++i)
 	for (unsigned int i = 0; i < pcScene->mNumMeshes;++i)
 	{
 	{
 		aiMesh* pcMesh = pcScene->mMeshes[ i ]; 
 		aiMesh* pcMesh = pcScene->mMeshes[ i ]; 
-		if (iMat == pcMesh->mMaterialIndex)
-		{
+		if (iMat == pcMesh->mMaterialIndex)	{
 			aiOut.push_back(GetMeshVFormat(pcMesh));
 			aiOut.push_back(GetMeshVFormat(pcMesh));
 		}
 		}
 	}
 	}
@@ -247,7 +301,7 @@ void GetVFormatList( aiScene* pcScene, unsigned int iMat,
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Compute the absolute transformation matrices of each node
 // Compute the absolute transformation matrices of each node
-void ComputeAbsoluteTransform( aiNode* pcNode )
+void PretransformVertices::ComputeAbsoluteTransform( aiNode* pcNode )
 {
 {
 	if (pcNode->mParent)	{
 	if (pcNode->mParent)	{
 		pcNode->mTransformation = pcNode->mParent->mTransformation*pcNode->mTransformation;
 		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.
 // Executes the post processing step on the given imported data.
 void PretransformVertices::Execute( aiScene* pScene)
 void PretransformVertices::Execute( aiScene* pScene)
 {
 {
 	DefaultLogger::get()->debug("PretransformVerticesProcess begin");
 	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 iOldMeshes = pScene->mNumMeshes;
 	const unsigned int iOldAnimationChannels = pScene->mNumAnimations;
 	const unsigned int iOldAnimationChannels = pScene->mNumAnimations;
 	const unsigned int iOldNodes = CountNodes(pScene->mRootNode);
 	const unsigned int iOldNodes = CountNodes(pScene->mRootNode);
@@ -271,11 +439,10 @@ void PretransformVertices::Execute( aiScene* pScene)
 	// first compute absolute transformation matrices for all nodes
 	// first compute absolute transformation matrices for all nodes
 	ComputeAbsoluteTransform(pScene->mRootNode);
 	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
 	// removed during this step and we need the pointer as
 	// temporary storage
 	// 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];
 		aiMesh* mesh = pScene->mMeshes[i];
 
 
 		for (unsigned int a = 0; a < mesh->mNumBones;++a)
 		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
 	// now build a list of output meshes
 	std::vector<aiMesh*> apcOutMeshes;
 	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->mAnimations    = NULL;
 	pScene->mNumAnimations = 0;
 	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 
 	// --- we need to keep all cameras and lights 
 	for (unsigned int i = 0; i < pScene->mNumCameras;++i)
 	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;
 		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
 	// print statistics
 	if (!DefaultLogger::isNullLogger())
 	if (!DefaultLogger::isNullLogger())
@@ -467,7 +680,5 @@ void PretransformVertices::Execute( aiScene* pScene)
 			iOldMeshes,pScene->mNumMeshes);
 			iOldMeshes,pScene->mNumMeshes);
 		DefaultLogger::get()->info(buffer);
 		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
 #ifndef AI_PRETRANSFORMVERTICES_H_INC
 #define AI_PRETRANSFORMVERTICES_H_INC
 #define AI_PRETRANSFORMVERTICES_H_INC
 
 
 #include "BaseProcess.h"
 #include "BaseProcess.h"
 #include "../include/aiMesh.h"
 #include "../include/aiMesh.h"
 
 
-namespace Assimp
-{
+class PretransformVerticesTest;
+namespace Assimp	{
 
 
 // ---------------------------------------------------------------------------
 // ---------------------------------------------------------------------------
 /** The PretransformVertices pretransforms all vertices in the nodegraph
 /** The PretransformVertices pretransforms all vertices in the nodegraph
@@ -57,6 +59,7 @@ namespace Assimp
 class ASSIMP_API PretransformVertices : public BaseProcess
 class ASSIMP_API PretransformVertices : public BaseProcess
 {
 {
 	friend class Importer;
 	friend class Importer;
+	friend class ::PretransformVerticesTest;
 
 
 protected:
 protected:
 	/** Constructor to be privately used by Importer */
 	/** Constructor to be privately used by Importer */
@@ -66,21 +69,96 @@ protected:
 	~PretransformVertices ();
 	~PretransformVertices ();
 
 
 public:
 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;
 	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);
 	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
 } // 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 "SpatialSort.h"
 #include "BaseProcess.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 {
 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 
 /** 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;
 	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
 // Compute the AABB of a mesh after applying a given transform
 inline void FindAABBTransformed (const aiMesh* mesh, aiVector3D& min, aiVector3D& max, 
 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)
 	for (unsigned int i = 0;i < mesh->mNumVertices;++i)
 	{
 	{
 		const aiVector3D v = m * mesh->mVertices[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
 // Helper function to determine the 'real' center of a mesh
 inline void FindMeshCenter (aiMesh* mesh, aiVector3D& out, aiVector3D& min, aiVector3D& max)
 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;
 	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 
 	// calculate the position bounds so we have a reliable epsilon to check position differences against 
 	aiVector3D minVec, maxVec;
 	aiVector3D minVec, maxVec;
-	FindAABB(pMesh,minVec,maxVec);
+	ArrayBounds(pMesh->mVertices,pMesh->mNumVertices,minVec,maxVec);
 	return (maxVec - minVec).Length() * epsilon;
 	return (maxVec - minVec).Length() * epsilon;
 }
 }
 
 
@@ -165,8 +276,10 @@ inline unsigned int GetMeshVFormatUnique(aiMesh* pcMesh)
 	// tangents and bitangents
 	// tangents and bitangents
 	if (pcMesh->HasTangentsAndBitangents())iRet |= 0x4;
 	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_COLOR_SETS);
 	BOOST_STATIC_ASSERT(8 >= AI_MAX_NUMBER_OF_TEXTURECOORDS);
 	BOOST_STATIC_ASSERT(8 >= AI_MAX_NUMBER_OF_TEXTURECOORDS);
+#endif
 
 
 	// texture coordinates
 	// texture coordinates
 	unsigned int p = 0;
 	unsigned int p = 0;
@@ -184,6 +297,9 @@ inline unsigned int GetMeshVFormatUnique(aiMesh* pcMesh)
 	return iRet;
 	return iRet;
 }
 }
 
 
+typedef std::pair <unsigned int,float> PerVertexWeight;
+typedef std::vector	<PerVertexWeight> VertexWeightTable;
+
 // -------------------------------------------------------------------------------
 // -------------------------------------------------------------------------------
 // Compute a per-vertex bone weight table
 // Compute a per-vertex bone weight table
 // please .... delete result with operator delete[] ...
 // please .... delete result with operator delete[] ...
@@ -205,13 +321,14 @@ inline VertexWeightTable* ComputeVertexBoneWeightTable(aiMesh* pMesh)
 	return avPerVertexWeights;
 	return avPerVertexWeights;
 }
 }
 
 
-
 // -------------------------------------------------------------------------------
 // -------------------------------------------------------------------------------
 // Get a string for a given aiTextureType
 // Get a string for a given aiTextureType
 inline const char* TextureTypeToString(aiTextureType in)
 inline const char* TextureTypeToString(aiTextureType in)
 {
 {
 	switch (in)
 	switch (in)
 	{
 	{
+	case aiTextureType_NONE:
+		return "n/a";
 	case aiTextureType_DIFFUSE:
 	case aiTextureType_DIFFUSE:
 		return "Diffuse";
 		return "Diffuse";
 	case aiTextureType_SPECULAR:
 	case aiTextureType_SPECULAR:
@@ -237,7 +354,7 @@ inline const char* TextureTypeToString(aiTextureType in)
 	case aiTextureType_UNKNOWN:
 	case aiTextureType_UNKNOWN:
 		return "Unknown";
 		return "Unknown";
     default:
     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:
 	case aiTextureMapping_OTHER:
 		return "Other";
 		return "Other";
     default:
     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"
 #include "AssimpPCH.h"
 #ifndef ASSIMP_BUILD_NO_Q3D_IMPORTER
 #ifndef ASSIMP_BUILD_NO_Q3D_IMPORTER
@@ -54,30 +56,34 @@ using namespace Assimp;
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
 // Constructor to be privately used by Importer
 Q3DImporter::Q3DImporter()
 Q3DImporter::Q3DImporter()
-{
-}
+{}
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Destructor, private as well 
 // Destructor, private as well 
 Q3DImporter::~Q3DImporter()
 Q3DImporter::~Q3DImporter()
-{
-}
+{}
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file. 
 // 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)
 	if (stream.GetRemainingSize() < 22)
 		throw new ImportErrorException("File is either empty or corrupt: " + pFile);
 		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 ) &&
 	if (ASSIMP_strincmp( (const char*)stream.GetPtr(), "quick3Do", 8 ) &&
 		ASSIMP_strincmp( (const char*)stream.GetPtr(), "quick3Ds", 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));
 			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
 #ifndef AI_Q3DLOADER_H_INCLUDED
 #define 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. 
 	/** Returns whether the class can handle the format of the given file. 
 	* See BaseImporter::CanRead() for details.	*/
 	* 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:
 protected:
 
 
@@ -75,10 +78,7 @@ protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 * 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. 
 	/** 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"
 #include "AssimpPCH.h"
 #ifndef ASSIMP_BUILD_NO_RAW_IMPORTER
 #ifndef ASSIMP_BUILD_NO_RAW_IMPORTER
@@ -63,21 +65,16 @@ RAWImporter::~RAWImporter()
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file. 
 // 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
 #ifndef AI_RAWLOADER_H_INCLUDED
 #define 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. 
 	/** 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:
 protected:
 
 
@@ -75,10 +79,7 @@ protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 * 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. 
 	/** 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.
 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
 // internal headers
 #include "AssimpPCH.h"
 #include "AssimpPCH.h"
 #include "RemoveRedundantMaterials.h"
 #include "RemoveRedundantMaterials.h"
+#include "ParsingUtils.h"
 
 
 using namespace Assimp;
 using namespace Assimp;
 
 
@@ -68,6 +70,39 @@ bool RemoveRedundantMatsProcess::IsActive( unsigned int pFlags) const
 	return (pFlags & aiProcess_RemoveRedundantMaterials) != 0;
 	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.
 // Executes the post processing step on the given imported data.
 void RemoveRedundantMatsProcess::Execute( aiScene* pScene)
 void RemoveRedundantMatsProcess::Execute( aiScene* pScene)
@@ -77,16 +112,49 @@ void RemoveRedundantMatsProcess::Execute( aiScene* pScene)
 	unsigned int iCnt = 0, unreferenced = 0;
 	unsigned int iCnt = 0, unreferenced = 0;
 	if (pScene->mNumMaterials)
 	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
 		// TODO: reimplement this algorithm to work in-place
 
 
 		unsigned int* aiMappingTable = new unsigned int[pScene->mNumMaterials];
 		unsigned int* aiMappingTable = new unsigned int[pScene->mNumMaterials];
 		unsigned int iNewNum = 0;
 		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
 		// store all hashes in a list and so a quick search whether
 		// we do already have a specific hash. This allows us to
 		// we do already have a specific hash. This allows us to
 		// determine which materials are identical.
 		// determine which materials are identical.
@@ -136,8 +204,7 @@ void RemoveRedundantMatsProcess::Execute( aiScene* pScene)
 				else ppcMaterials[idx] = pScene->mMaterials[p];
 				else ppcMaterials[idx] = pScene->mMaterials[p];
 			}
 			}
 			// update all material indices
 			// 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];
 				aiMesh* mesh = pScene->mMeshes[p];
 				mesh->mMaterialIndex = aiMappingTable[mesh->mMaterialIndex];
 				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
 #ifndef AI_REMOVEREDUNDANTMATERIALS_H_INC
 #define 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"
 #include "../include/aiMesh.h"
 
 
 class RemoveRedundantMatsTest;
 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
 class ASSIMP_API RemoveRedundantMatsProcess : public BaseProcess
 {
 {
 	friend class Importer;
 	friend class Importer;
@@ -66,19 +68,38 @@ protected:
 
 
 public:
 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;
 	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);
 	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
 } // end of namespace Assimp

+ 2 - 5
code/RemoveVCProcess.cpp

@@ -51,15 +51,12 @@ using namespace Assimp;
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
 // Constructor to be privately used by Importer
 RemoveVCProcess::RemoveVCProcess()
 RemoveVCProcess::RemoveVCProcess()
-{
-}
+{}
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Destructor, private as well
 // Destructor, private as well
 RemoveVCProcess::~RemoveVCProcess()
 RemoveVCProcess::~RemoveVCProcess()
-{
-	// nothing to do here
-}
+{}
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Returns whether the processing step is present in the given flag field.
 // 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"
 #include "AssimpPCH.h"
 #ifndef ASSIMP_BUILD_NO_SMD_IMPORTER
 #ifndef ASSIMP_BUILD_NO_SMD_IMPORTER
@@ -54,56 +56,37 @@ using namespace Assimp;
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
 // Constructor to be privately used by Importer
 SMDImporter::SMDImporter()
 SMDImporter::SMDImporter()
-{
-	// nothing to do here
-}
+{}
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Destructor, private as well 
 // Destructor, private as well 
 SMDImporter::~SMDImporter()
 SMDImporter::~SMDImporter()
-{
-	// nothing to do here
-}
+{}
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file. 
 // 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
 // Setup configuration properties
 void SMDImporter::SetupProperties(const Importer* pImp)
 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.
 	// 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);
 		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
 #ifndef AI_SMDLOADER_H_INCLUDED
 #define 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. 
 	/** 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().
 	/** 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);
 	void SetupProperties(const Importer* pImp);
 
 
 protected:
 protected:
@@ -203,10 +204,7 @@ protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 * 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. 
 	/** 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. 
 // 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. 
 // Imports the given file into the given scene structure. 
 void STLImporter::InternReadFile( 
 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"));
 	boost::scoped_ptr<IOStream> file( pIOHandler->Open( pFile, "rb"));
 
 
@@ -183,7 +187,7 @@ void STLImporter::LoadASCIIFile()
 	pMesh->mVertices = new aiVector3D[pMesh->mNumVertices];
 	pMesh->mVertices = new aiVector3D[pMesh->mNumVertices];
 	pMesh->mNormals = new aiVector3D[pMesh->mNumVertices];
 	pMesh->mNormals = new aiVector3D[pMesh->mNumVertices];
 	
 	
-	unsigned int curFace = 0, curVertex = 0;
+	unsigned int curFace = 0, curVertex = 3;
 	while (true)
 	while (true)
 	{
 	{
 		// go to the next token
 		// 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
 #ifndef AI_STLLOADER_H_INCLUDED
 #define 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	{
 namespace Assimp	{
 
 
 // ---------------------------------------------------------------------------
 // ---------------------------------------------------------------------------
-/** Clas to load STL files
+/** Importer class for the sterolithography STL file format
 */
 */
 class STLImporter : public BaseImporter
 class STLImporter : public BaseImporter
 {
 {
@@ -65,8 +67,10 @@ public:
 
 
 	// -------------------------------------------------------------------
 	// -------------------------------------------------------------------
 	/** Returns whether the class can handle the format of the given file. 
 	/** 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:
 protected:
 
 
@@ -74,10 +78,7 @@ protected:
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	/** Called by Importer::GetExtensionList() for each loaded importer.
 	 * See BaseImporter::GetExtensionList() for details
 	 * 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. 
 	/** 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	{
 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
 // Add a prefix to a string
 inline void PrefixString(aiString& string,const char* prefix, unsigned int len)
 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;
 	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
 // Add a name prefix to all nodes in a hierarchy
 void SceneCombiner::AddNodePrefixes(aiNode* node, const char* prefix, unsigned int len)
 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);
 		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
 // Add an offset to all mesh indices in a node graph
 void SceneCombiner::OffsetNodeMeshIndices (aiNode* node, unsigned int offset)
 void SceneCombiner::OffsetNodeMeshIndices (aiNode* node, unsigned int offset)
@@ -300,6 +319,20 @@ void SceneCombiner::MergeScenes(aiScene** _dest, aiScene* master,
 
 
 			const unsigned int random = rndGen();
 			const unsigned int random = rndGen();
 			src[i].idlen = ::sprintf(src[i].id,"$%.6X$_",random);
 			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;
 		dest->mNumAnimations += (*cur)->mNumAnimations;
 
 
 		// Combine the flags of all scenes
 		// 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
 	// 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
 	// generate the output material list + an offset table for all material indices
 	if (dest->mNumMaterials)
 	if (dest->mNumMaterials)
-	{
+	{ 
 		aiMaterial** pip = dest->mMaterials = new aiMaterial*[dest->mNumMaterials];
 		aiMaterial** pip = dest->mMaterials = new aiMaterial*[dest->mNumMaterials];
 		cnt = 0;
 		cnt = 0;
-		for ( unsigned int n = 0; n < src.size();++n )
-		{
+		for ( unsigned int n = 0; n < src.size();++n )	{
 			SceneHelper* cur = &src[n];
 			SceneHelper* cur = &src[n];
 			for (unsigned int i = 0; i < (*cur)->mNumMaterials;++i)
 			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];
 				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
 					// We need to update all texture indices of the mesh. So we need to search for
 					// a material property called '$tex.file'
 					// a material property called '$tex.file'
 
 
@@ -391,8 +426,7 @@ void SceneCombiner::MergeScenes(aiScene** _dest, aiScene* master,
 						}
 						}
 
 
 						// Need to generate new, unique material names?
 						// 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; 
 							aiString* pcSrc = (aiString*) prop->mData; 
 							PrefixString(*pcSrc, (*cur).id, (*cur).idlen);
 							PrefixString(*pcSrc, (*cur).id, (*cur).idlen);
@@ -458,7 +492,7 @@ void SceneCombiner::MergeScenes(aiScene** _dest, aiScene* master,
 	aiAnimation** ppAnims = dest->mAnimations = (dest->mNumAnimations 
 	aiAnimation** ppAnims = dest->mAnimations = (dest->mNumAnimations 
 		? new aiAnimation*[dest->mNumAnimations] : NULL);
 		? 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];
 		SceneHelper* cur = &src[n];
 		aiNode* node;
 		aiNode* node;
@@ -466,7 +500,9 @@ void SceneCombiner::MergeScenes(aiScene** _dest, aiScene* master,
 		// To offset or not to offset, this is the question
 		// To offset or not to offset, this is the question
 		if (n != duplicates[n])
 		if (n != duplicates[n])
 		{
 		{
+			// Get full scenegraph copy
 			Copy( &node, (*cur)->mRootNode );
 			Copy( &node, (*cur)->mRootNode );
+			OffsetNodeMeshIndices(node,offset[duplicates[n]]);
 
 
 			if (flags & AI_INT_MERGE_SCENE_DUPLICATES_DEEP_CPY)	{
 			if (flags & AI_INT_MERGE_SCENE_DUPLICATES_DEEP_CPY)	{
 				// (note:) they are already 'offseted' by offset[duplicates[n]] 
 				// (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
 		if (n) // src[0] is the master node
 			nodes.push_back(NodeAttachmentInfo( node,srcList[n-1].attachToNode,n ));
 			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
 		// Copy light sources
 		for (unsigned int i = 0; i < (*cur)->mNumLights;++i,++ppLights)
 		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]);
 				Copy(ppLights, (*cur)->mLights[i]);
 			}
 			}
 			else *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]);
 				Copy(ppCameras, (*cur)->mCameras[i]);
 			}
 			}
 			else *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]);
 				Copy(ppAnims, (*cur)->mAnimations[i]);
 			}
 			}
 			else *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
 				// 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;
 		delete deleteMe;
 	}
 	}
 
 
+	// Check flags
+	if (!dest->mNumMeshes || !dest->mNumMaterials) {
+		dest->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
+	}
+
 	// We're finished
 	// 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 it2  = asBones.begin();
 			std::list<BoneWithHash>::iterator end2 = asBones.end();
 			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));
 					(*it2).pSrcBones.push_back(BoneSrcIndex(p,iOffset));
 					break;
 					break;
 				}
 				}
@@ -666,10 +746,7 @@ void SceneCombiner::MergeBones(aiMesh* out,std::vector<aiMesh*>::const_iterator
 			if (wmit != (*it).pSrcBones.begin() &&
 			if (wmit != (*it).pSrcBones.begin() &&
 				pc->mOffsetMatrix != (*wmit).first->mOffsetMatrix)
 				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;
 				continue;
 			}
 			}
 			pc->mOffsetMatrix = (*wmit).first->mOffsetMatrix;
 			pc->mOffsetMatrix = (*wmit).first->mOffsetMatrix;
@@ -1026,7 +1103,7 @@ void SceneCombiner::Copy     (aiAnimation** _dest, const aiAnimation* src)
 	::memcpy(dest,src,sizeof(aiAnimation));
 	::memcpy(dest,src,sizeof(aiAnimation));
 
 
 	// and reallocate all arrays
 	// 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
 /** @def AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY
  * Can be combined with AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES.
  * Can be combined with AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES.
  * Unique names are generated, but only if this is absolutely
  * 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
 #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
 /** \brief Static helper class providing various utilities to merge two
  *    scenes. It is intended as internal utility and NOT for use by 
  *    scenes. It is intended as internal utility and NOT for use by 
@@ -301,6 +338,26 @@ public:
 
 
 	// recursive, of course
 	// recursive, of course
 	static void Copy     (aiNode** dest, const aiNode* src);
 	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
 	// Generate a default material if none was specified
 	if (!scene->mNumMaterials && scene->mNumMeshes)
 	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])
 		if (!mesh->mTextureCoords[i])
 			mesh->mNumUVComponents[i] = 0;
 			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
 	// 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 + 6, localVertexStart + 7, localVertexStart + 8));
 			mFaces.push_back( Face( localVertexStart + 9, localVertexStart + 10, localVertexStart + 11));
 			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
 		// 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);
 		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( -sizeEstimate, 0.0f, 0.0f));
 		mVertices.push_back( aiVector3D( 0.0f, sizeEstimate, 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)
 					if (vert)
 					{
 					{
 						*vert++ = mesh->mVertices[idx];
 						*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 (nor )*nor++  = mesh->mNormals[idx];
 					if (tan )
 					if (tan )

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels