ソースを参照

3DS bugfixes; ASE bugfix (newline), PLY revert to older version, SMD is working now (partially); homepage added to SVN

git-svn-id: https://assimp.svn.sourceforge.net/svnroot/assimp/trunk@63 67173fc5-114c-0410-ac8e-9d2fd5bffc1f
aramis_acg 17 年 前
コミット
fc8a8b54f9
67 ファイル変更8038 行追加696 行削除
  1. 70 87
      code/3DSConverter.cpp
  2. 28 14
      code/3DSHelper.h
  3. 194 205
      code/3DSLoader.cpp
  4. 10 10
      code/3DSLoader.h
  5. 2 2
      code/ASELoader.cpp
  6. 5 3
      code/ASEParser.cpp
  7. 5 8
      code/ParsingUtils.h
  8. 2 2
      code/PlyLoader.cpp
  9. 27 132
      code/PlyParser.cpp
  10. 2 2
      code/PlyParser.h
  11. 112 0
      code/RemoveComments.cpp
  12. 89 0
      code/RemoveComments.h
  13. 150 60
      code/SMDLoader.cpp
  14. 3 3
      code/SMDLoader.h
  15. 96 57
      code/ValidateDataStructure.cpp
  16. 1 1
      code/ValidateDataStructure.h
  17. 12 6
      include/aiAnim.h
  18. 24 14
      include/aiMesh.h
  19. 36 12
      include/aiScene.h
  20. BIN
      tools/assimp_view/assimp_view.aps
  21. 123 78
      tools/assimp_view/help.rtf
  22. BIN
      web/freecsstemplates.org_logistix_template.zip
  23. 55 0
      web/root/FAQ.html
  24. 243 0
      web/root/LightBoxAndCSSTemplateLicense.txt
  25. BIN
      web/root/avdoc/Reference.pdf
  26. 177 0
      web/root/css/faq_style.css
  27. 220 0
      web/root/css/style.css
  28. BIN
      web/root/images/boost.png
  29. BIN
      web/root/images/bullet.gif
  30. BIN
      web/root/images/close.gif
  31. BIN
      web/root/images/closelabel.gif
  32. BIN
      web/root/images/donate-button.gif
  33. BIN
      web/root/images/download-icon.gif
  34. BIN
      web/root/images/image-1.jpg
  35. BIN
      web/root/images/img01.jpg
  36. BIN
      web/root/images/img02.jpg
  37. BIN
      web/root/images/img02.png
  38. BIN
      web/root/images/img03.gif
  39. BIN
      web/root/images/img04.jpg
  40. BIN
      web/root/images/img05.gif
  41. BIN
      web/root/images/img06.gif
  42. BIN
      web/root/images/img07.gif
  43. BIN
      web/root/images/loading.gif
  44. BIN
      web/root/images/nextlabel.gif
  45. BIN
      web/root/images/prevlabel.gif
  46. BIN
      web/root/images/thumb-1.jpg
  47. 101 0
      web/root/index.html
  48. 136 0
      web/root/js/builder.js
  49. 1122 0
      web/root/js/effects.js
  50. 497 0
      web/root/js/lightbox.js
  51. 4221 0
      web/root/js/prototype.js
  52. 58 0
      web/root/js/scriptaculous.js
  53. 0 0
      web/root/main_contact.html
  54. 68 0
      web/root/main_doc.html
  55. 70 0
      web/root/main_downloads.html
  56. 0 0
      web/root/main_features.html
  57. 71 0
      web/root/main_license.html
  58. 0 0
      web/root/main_viewer.html
  59. BIN
      web/root/screenshots/sshot1.png
  60. BIN
      web/root/screenshots/sshot1s.png
  61. BIN
      web/root/screenshots/sshot2.png
  62. BIN
      web/root/screenshots/sshot2s.png
  63. BIN
      web/root/screenshots/sshot3.png
  64. BIN
      web/root/screenshots/sshot3s.png
  65. BIN
      web/root/screenshots/sshot4.png
  66. BIN
      web/root/screenshots/sshot4s.png
  67. 8 0
      workspaces/vc8/assimp.vcproj

+ 70 - 87
code/3DSConverter.cpp

@@ -429,10 +429,10 @@ void Dot3DSImporter::ConvertMeshes(aiScene* pcOut)
 			a =  (*i).mFaceMaterials.begin();
 			a =  (*i).mFaceMaterials.begin();
 			a != (*i).mFaceMaterials.end();++a,++iNum)
 			a != (*i).mFaceMaterials.end();++a,++iNum)
 		{
 		{
-		// check range
+			// check range
 			if ((*a) >= this->mScene->mMaterials.size())
 			if ((*a) >= this->mScene->mMaterials.size())
 			{
 			{
-				DefaultLogger::get()->error("Face material index is out of range");
+				DefaultLogger::get()->error("3DS face material index is out of range");
 
 
 				// use the last material instead
 				// use the last material instead
 				aiSplit[this->mScene->mMaterials.size()-1].push_back(iNum);
 				aiSplit[this->mScene->mMaterials.size()-1].push_back(iNum);
@@ -440,7 +440,6 @@ void Dot3DSImporter::ConvertMeshes(aiScene* pcOut)
 		else aiSplit[*a].push_back(iNum);
 		else aiSplit[*a].push_back(iNum);
 		}
 		}
 		// now generate submeshes
 		// now generate submeshes
-
 		bool bFirst = true;
 		bool bFirst = true;
 		for (unsigned int p = 0; p < this->mScene->mMaterials.size();++p)
 		for (unsigned int p = 0; p < this->mScene->mMaterials.size();++p)
 		{
 		{
@@ -452,19 +451,9 @@ void Dot3DSImporter::ConvertMeshes(aiScene* pcOut)
 				p_pcOut->mMaterialIndex = p;
 				p_pcOut->mMaterialIndex = p;
 
 
 				// use the color data as temporary storage
 				// use the color data as temporary storage
-				p_pcOut->mColors[0] = (aiColor4D*)new std::string((*i).mName);
+				p_pcOut->mColors[0] = (aiColor4D*)(&*i);
 				avOutMeshes.push_back(p_pcOut);
 				avOutMeshes.push_back(p_pcOut);
-
-// (code for keyframe animation. however, this is currently not supported by Assimp)
-#if 0
-				if (bFirst)
-				{
-					p_pcOut->mColors[1] = (aiColor4D*)new aiMatrix4x4();
-					*((aiMatrix4x4*)p_pcOut->mColors[1]) = (*i).mMat;
-					bFirst = false;
-				}
-#endif
-
+				
 				// convert vertices
 				// convert vertices
 				p_pcOut->mNumVertices = (unsigned int)aiSplit[p].size()*3;
 				p_pcOut->mNumVertices = (unsigned int)aiSplit[p].size()*3;
 				p_pcOut->mNumFaces = (unsigned int)aiSplit[p].size();
 				p_pcOut->mNumFaces = (unsigned int)aiSplit[p].size();
@@ -552,87 +541,72 @@ void Dot3DSImporter::ConvertMeshes(aiScene* pcOut)
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void Dot3DSImporter::AddNodeToGraph(aiScene* pcSOut,aiNode* pcOut,Dot3DS::Node* pcIn)
 void Dot3DSImporter::AddNodeToGraph(aiScene* pcSOut,aiNode* pcOut,Dot3DS::Node* pcIn)
 {
 {
-	// find the corresponding mesh indices
 	std::vector<unsigned int> iArray;
 	std::vector<unsigned int> iArray;
+	iArray.reserve(3);
 
 
 	if (pcIn->mName != "$$$DUMMY")
 	if (pcIn->mName != "$$$DUMMY")
 	{		
 	{		
 		for (unsigned int a = 0; a < pcSOut->mNumMeshes;++a)
 		for (unsigned int a = 0; a < pcSOut->mNumMeshes;++a)
 		{
 		{
-			if (0 == ASSIMP_stricmp(pcIn->mName.c_str(),
-				((std::string*)pcSOut->mMeshes[a]->mColors[0])->c_str()))
+			const Dot3DS::Mesh* pcMesh = (const Dot3DS::Mesh*)pcSOut->mMeshes[a]->mColors[0];
+			ai_assert(NULL != pcMesh);
+
+			if (0 == ASSIMP_stricmp(pcIn->mName.c_str(),pcMesh->mName.c_str()))
 			{
 			{
 				iArray.push_back(a);
 				iArray.push_back(a);
 			}
 			}
 		}
 		}
-	}
-	pcOut->mName.Set(pcIn->mName);
-	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];
-
-// (code for keyframe animation. however, this is currently not supported by Assimp)
-#if 0
-		if (NULL != pcSOut->mMeshes[iIndex]->mColors[1])
+		if (!iArray.empty())
 		{
 		{
-			pcOut->mTransformation = *((aiMatrix4x4*)
-				(pcSOut->mMeshes[iIndex]->mColors[1]));
-
-			delete (aiMatrix4x4*)pcSOut->mMeshes[iIndex]->mColors[1];
-			pcSOut->mMeshes[iIndex]->mColors[1] = NULL;
-		}
-#endif
-		pcOut->mMeshes[i] = iIndex;
-	}
-
-	// (code for keyframe animation. however, this is currently not supported by Assimp)
-#if 0
-	// build the scaling matrix. Toggle y and z axis
-	aiMatrix4x4 mS;
-	mS.a1 = pcIn->vScaling.x;
-	mS.b2 = pcIn->vScaling.z;
-	mS.c3 = pcIn->vScaling.y;
-
-	// build the translation matrix. Toggle y and z axis
-	aiMatrix4x4 mT;
-	mT.a4 = pcIn->vPosition.x;
-	mT.b4 = pcIn->vPosition.z;
-	mT.c4 = pcIn->vPosition.y;
-
-	// build the pivot matrix. Toggle y and z axis
-	aiMatrix4x4 mP;
-	mP.a4 = -pcIn->vPivot.x;
-	mP.b4 = -pcIn->vPivot.z;
-	mP.c4 = -pcIn->vPivot.y;
+			aiMatrix4x4& mTrafo = ((Dot3DS::Mesh*)pcSOut->mMeshes[iArray[0]]->mColors[0])->mMat;
+			aiMatrix4x4 mInv = mTrafo;
+			mInv.Inverse();
+
+			pcOut->mName.Set(pcIn->mName);
+			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];
 
 
+				// http://www.zfx.info/DisplayThread.php?MID=235690#235690
+				const aiVector3D& pivot = pcIn->vPivot;
+				const aiVector3D* const pvEnd = mesh->mVertices+mesh->mNumVertices;
+				aiVector3D* pvCurrent = mesh->mVertices;
 
 
-#endif
-	// build a matrix to flip the z coordinate of the vertices
-	aiMatrix4x4 mF;
-	mF.c3 = -1.0f;
-
-
-	// build the final matrix
-	// NOTE: This should be the identity. Theoretically. In reality
-	// there are many models with very funny local matrices and
-	// very different keyframe values ... this is the only reason
-	// why we extract the data from the first keyframe. 
-	pcOut->mTransformation = mF; /*   mF * mT * pcIn->mRotation * mS *  mP * 
-		pcOut->mTransformation.Inverse(); */
-
-	// (code for keyframe animation. however, this is currently not supported by Assimp)
-#if 0
-	if (pcOut->mTransformation != mF) 
-	{
-		DefaultLogger::get()->warn("The local transformation matrix of the "
-			"3ds file does not match the first keyframe. Using the "
-			"information from the keyframe.");
+				if(pivot.x || pivot.y || pivot.z)
+				{
+					while (pvCurrent != pvEnd)
+					{
+						*pvCurrent = mInv * (*pvCurrent);
+						pvCurrent->x -= pivot.x;
+						pvCurrent->y -= pivot.y;
+						pvCurrent->z -= pivot.z;
+						*pvCurrent = mTrafo * (*pvCurrent);
+						std::swap( pvCurrent->y, pvCurrent->z );
+						++pvCurrent;
+					}
+				}
+				else
+				{
+					while (pvCurrent != pvEnd)
+					{
+						std::swap( pvCurrent->y, pvCurrent->z );
+						pvCurrent->y *= -1.0f;
+						++pvCurrent;
+					}
+				}
+				pcOut->mMeshes[i] = iIndex;
+			}
+		}
+		/*else
+		{
+			DefaultLogger::get()->warn("A node that is not a dummy does not "
+				"reference a valid mesh.");
+		}*/
 	}
 	}
-#endif
-
+	pcOut->mTransformation = aiMatrix4x4(); 
 	pcOut->mNumChildren = (unsigned int)pcIn->mChildren.size();
 	pcOut->mNumChildren = (unsigned int)pcIn->mChildren.size();
 	pcOut->mChildren = new aiNode*[pcIn->mChildren.size()];
 	pcOut->mChildren = new aiNode*[pcIn->mChildren.size()];
 	for (unsigned int i = 0; i < pcIn->mChildren.size();++i)
 	for (unsigned int i = 0; i < pcIn->mChildren.size();++i)
@@ -697,13 +671,22 @@ void Dot3DSImporter::GenerateNodeGraph(aiScene* pcOut)
 	else this->AddNodeToGraph(pcOut,  pcOut->mRootNode, this->mRootNode);
 	else this->AddNodeToGraph(pcOut,  pcOut->mRootNode, this->mRootNode);
 
 
 	for (unsigned int a = 0; a < pcOut->mNumMeshes;++a)
 	for (unsigned int a = 0; a < pcOut->mNumMeshes;++a)
-	{
-		delete (std::string*)pcOut->mMeshes[a]->mColors[0];
 		pcOut->mMeshes[a]->mColors[0] = NULL;
 		pcOut->mMeshes[a]->mColors[0] = NULL;
 
 
-		// may be NULL
-		delete (aiMatrix4x4*)pcOut->mMeshes[a]->mColors[1];
-		pcOut->mMeshes[a]->mColors[1] = NULL;
+	// if the root node has only one child ... set the child as root node
+	if (1 == pcOut->mRootNode->mNumChildren)
+	{
+		aiNode* pcOld = pcOut->mRootNode;
+		pcOut->mRootNode = pcOut->mRootNode->mChildren[0];
+		pcOut->mRootNode->mParent = NULL;
+		pcOld->mChildren[0] = NULL;
+		delete pcOld;
+	}
+
+	// if the root node is a default node setup a name for it
+	if (pcOut->mRootNode->mName.data[0] == '$' && pcOut->mRootNode->mName.data[1] == '$')
+	{
+		pcOut->mRootNode->mName.Set("<root>");
 	}
 	}
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------

+ 28 - 14
code/3DSHelper.h

@@ -458,7 +458,11 @@ struct Mesh
 		static int iCnt = 0;
 		static int iCnt = 0;
 		
 		
 		char szTemp[128];
 		char szTemp[128];
-		sprintf(szTemp,"$$_UNNAMED_%i_$$",iCnt++);
+#if _MSC_VER >= 1400
+		::sprintf_s(szTemp,"$$_UNNAMED_%i_$$",iCnt++);
+#else
+		::sprintf(szTemp,"$$_UNNAMED_%i_$$",iCnt++);
+#endif
 		mName = szTemp;
 		mName = szTemp;
 	}
 	}
 
 
@@ -489,20 +493,24 @@ struct Node
 {
 {
 	Node()
 	Node()
 
 
-	// (code for keyframe animation. however, this is currently not supported by Assimp)
-#if 0
-		: vScaling(1.0f,1.0f,1.0f)
-#endif
+		: mHierarchyPos(0),mHierarchyIndex(0)
 
 
 	{
 	{
 		static int iCnt = 0;
 		static int iCnt = 0;
 		
 		
 		char szTemp[128];
 		char szTemp[128];
-		sprintf(szTemp,"$$_UNNAMED_%i_$$",iCnt++);
+#if _MSC_VER >= 1400
+		::sprintf_s(szTemp,"$$_UNNAMED_%i_$$",iCnt++);
+#else
+		::sprintf(szTemp,"$$_UNNAMED_%i_$$",iCnt++);
+#endif
 		mName = szTemp;
 		mName = szTemp;
 
 
-		mHierarchyPos = 0;
-		mHierarchyIndex = 0;
+#ifdef AI_3DS_KEYFRAME_ANIMATION
+		aRotationKeys.reserve(10);
+		aPositionKeys.reserve(10);
+		aScalingKeys.reserve(10);
+#endif
 	}
 	}
 
 
 	//! Pointer to the parent node
 	//! Pointer to the parent node
@@ -520,14 +528,20 @@ struct Node
 	//! Index of the node
 	//! Index of the node
 	int16_t mHierarchyIndex;
 	int16_t mHierarchyIndex;
 
 
-// (code for keyframe animation. however, this is currently not supported by Assimp)
-#if 0
-	aiVector3D vPivot;
-	aiVector3D vScaling;
-	aiMatrix4x4 mRotation;
-	aiVector3D vPosition;
+#ifdef AI_3DS_KEYFRAME_ANIMATION
+	//! Rotation keys loaded from the file
+	std::vector<aiQuatKey> aRotationKeys;
+
+	//! Position keys loaded from the file
+	std::vector<aiVectorKey> aPositionKeys;
+
+	//! Scaling keys loaded from the file
+	std::vector<aiVectorKey> aScalingKeys;
 #endif
 #endif
 
 
+	//! Pivot position loaded from the file
+	aiVector3D vPivot;
+
 	//! Add a child node, setup the right parent node for it
 	//! Add a child node, setup the right parent node for it
 	//! \param pc Node to be 'adopted'
 	//! \param pc Node to be 'adopted'
 	inline Node& push_back(Node* pc)
 	inline Node& push_back(Node* pc)

+ 194 - 205
code/3DSLoader.cpp

@@ -139,7 +139,17 @@ void Dot3DSImporter::InternReadFile(
 	this->bHasBG = false;
 	this->bHasBG = false;
 
 
 	int iRemaining = (unsigned int)fileSize;
 	int iRemaining = (unsigned int)fileSize;
-	this->ParseMainChunk(&iRemaining);
+
+	// parse the file
+	try
+	{
+		this->ParseMainChunk(iRemaining);
+	}
+	catch ( ImportErrorException* ex)
+	{
+		delete[] this->mBuffer;
+		throw ex;
+	};
 
 
 	// Generate an unique set of vertices/indices for
 	// Generate an unique set of vertices/indices for
 	// all meshes contained in the file
 	// all meshes contained in the file
@@ -202,8 +212,7 @@ void Dot3DSImporter::ReadChunk(const Dot3DSFile::Chunk** p_ppcOut)
 	// read chunk
 	// read chunk
 	if (this->mCurrent >= this->mLast)
 	if (this->mCurrent >= this->mLast)
 	{
 	{
-		*p_ppcOut = NULL;
-		return;
+		throw new ImportErrorException("Unexpected end of file, can't read chunk header");
 	}
 	}
 	const uintptr_t iDiff = this->mLast - this->mCurrent;
 	const uintptr_t iDiff = this->mLast - this->mCurrent;
 	if (iDiff < sizeof(Dot3DSFile::Chunk)) 
 	if (iDiff < sizeof(Dot3DSFile::Chunk)) 
@@ -211,18 +220,21 @@ void Dot3DSImporter::ReadChunk(const Dot3DSFile::Chunk** p_ppcOut)
 		*p_ppcOut = NULL;
 		*p_ppcOut = NULL;
 		return;
 		return;
 	}
 	}
-
 	*p_ppcOut = (const Dot3DSFile::Chunk*) this->mCurrent;
 	*p_ppcOut = (const Dot3DSFile::Chunk*) this->mCurrent;
+	if ((**p_ppcOut).Size + this->mCurrent > this->mLast)
+	{
+		throw new ImportErrorException("Unexpected end of file, can't read chunk footer");
+	}
 	this->mCurrent += sizeof(Dot3DSFile::Chunk);
 	this->mCurrent += sizeof(Dot3DSFile::Chunk);
 	return;
 	return;
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void Dot3DSImporter::ParseMainChunk(int* piRemaining)
+void Dot3DSImporter::ParseMainChunk(int& piRemaining)
 {
 {
 	const Dot3DSFile::Chunk* psChunk;
 	const Dot3DSFile::Chunk* psChunk;
 
 
 	this->ReadChunk(&psChunk);
 	this->ReadChunk(&psChunk);
-	if (NULL == psChunk)return;
+	
 
 
 	const unsigned char* pcCur = this->mCurrent;
 	const unsigned char* pcCur = this->mCurrent;
 	const unsigned char* pcCurNext = pcCur + (psChunk->Size 
 	const unsigned char* pcCurNext = pcCur + (psChunk->Size 
@@ -235,7 +247,7 @@ void Dot3DSImporter::ParseMainChunk(int* piRemaining)
 	case Dot3DSFile::CHUNK_MAIN:
 	case Dot3DSFile::CHUNK_MAIN:
 	//case 0x444d: // bugfix
 	//case 0x444d: // bugfix
 
 
-		this->ParseEditorChunk(&iRemaining);
+		this->ParseEditorChunk(iRemaining);
 		break;
 		break;
 	};
 	};
 	if (pcCurNext < this->mCurrent)
 	if (pcCurNext < this->mCurrent)
@@ -247,17 +259,17 @@ void Dot3DSImporter::ParseMainChunk(int* piRemaining)
 	}
 	}
 	// Go to the starting position of the next top-level chunk
 	// Go to the starting position of the next top-level chunk
 	this->mCurrent = pcCurNext;
 	this->mCurrent = pcCurNext;
-	*piRemaining -= psChunk->Size;
-	if (0 >= *piRemaining)return;
+	piRemaining -= psChunk->Size;
+	if (0 >= piRemaining)return;
 	return this->ParseMainChunk(piRemaining);
 	return this->ParseMainChunk(piRemaining);
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void Dot3DSImporter::ParseEditorChunk(int* piRemaining)
+void Dot3DSImporter::ParseEditorChunk(int& piRemaining)
 {
 {
 	const Dot3DSFile::Chunk* psChunk;
 	const Dot3DSFile::Chunk* psChunk;
 
 
 	this->ReadChunk(&psChunk);
 	this->ReadChunk(&psChunk);
-	if (NULL == psChunk)return;
+	
 
 
 	const unsigned char* pcCur = this->mCurrent;
 	const unsigned char* pcCur = this->mCurrent;
 	const unsigned char* pcCurNext = pcCur + (psChunk->Size 
 	const unsigned char* pcCurNext = pcCur + (psChunk->Size 
@@ -269,14 +281,35 @@ void Dot3DSImporter::ParseEditorChunk(int* piRemaining)
 	{
 	{
 	case Dot3DSFile::CHUNK_OBJMESH:
 	case Dot3DSFile::CHUNK_OBJMESH:
 
 
-		this->ParseObjectChunk(&iRemaining);
+		this->ParseObjectChunk(iRemaining);
 		break;
 		break;
 
 
 	// NOTE: In several documentations in the internet this
 	// NOTE: In several documentations in the internet this
 	// chunk appears at different locations
 	// chunk appears at different locations
 	case Dot3DSFile::CHUNK_KEYFRAMER:
 	case Dot3DSFile::CHUNK_KEYFRAMER:
 
 
-		this->ParseKeyframeChunk(&iRemaining);
+		this->ParseKeyframeChunk(iRemaining);
+		break;
+
+	case Dot3DSFile::CHUNK_VERSION:
+
+		if (psChunk->Size >= 2+sizeof(Dot3DSFile::Chunk))
+		{
+			// print the version number
+			char szBuffer[128];
+#if _MSC_VER >= 1400
+			::sprintf_s(szBuffer,"3DS file version chunk: %i",
+				(int) *((uint16_t*)this->mCurrent));
+#else
+			::sprintf(szBuffer,"3DS file version chunk: %i",
+				(int) *((uint16_t*)this->mCurrent));
+#endif
+			DefaultLogger::get()->info(szBuffer);
+		}
+		else
+		{
+			DefaultLogger::get()->warn("Invalid version chunk in 3DS file");
+		}
 		break;
 		break;
 	};
 	};
 	if (pcCurNext < this->mCurrent)
 	if (pcCurNext < this->mCurrent)
@@ -288,17 +321,17 @@ void Dot3DSImporter::ParseEditorChunk(int* piRemaining)
 	}
 	}
 	// Go to the starting position of the next top-level chunk
 	// Go to the starting position of the next top-level chunk
 	this->mCurrent = pcCurNext;
 	this->mCurrent = pcCurNext;
-	*piRemaining -= psChunk->Size;
-	if (0 >= *piRemaining)return;
+	piRemaining -= psChunk->Size;
+	if (0 >= piRemaining)return;
 	return this->ParseEditorChunk(piRemaining);
 	return this->ParseEditorChunk(piRemaining);
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void Dot3DSImporter::ParseObjectChunk(int* piRemaining)
+void Dot3DSImporter::ParseObjectChunk(int& piRemaining)
 {
 {
 	const Dot3DSFile::Chunk* psChunk;
 	const Dot3DSFile::Chunk* psChunk;
 
 
 	this->ReadChunk(&psChunk);
 	this->ReadChunk(&psChunk);
-	if (NULL == psChunk)return;
+	
 
 
 	const unsigned char* pcCur = this->mCurrent;
 	const unsigned char* pcCur = this->mCurrent;
 	const unsigned char* pcCurNext = pcCur + (psChunk->Size 
 	const unsigned char* pcCurNext = pcCur + (psChunk->Size 
@@ -329,13 +362,13 @@ void Dot3DSImporter::ParseObjectChunk(int* piRemaining)
 
 
 		this->mCurrent += iCnt;
 		this->mCurrent += iCnt;
 		iRemaining -= iCnt;
 		iRemaining -= iCnt;
-		this->ParseChunk(&iRemaining);
+		this->ParseChunk(iRemaining);
 		break;
 		break;
 
 
 	case Dot3DSFile::CHUNK_MAT_MATERIAL:
 	case Dot3DSFile::CHUNK_MAT_MATERIAL:
 
 
 		this->mScene->mMaterials.push_back(Dot3DS::Material());
 		this->mScene->mMaterials.push_back(Dot3DS::Material());
-		this->ParseMaterialChunk(&iRemaining);
+		this->ParseMaterialChunk(iRemaining);
 		break;
 		break;
 
 
 	case Dot3DSFile::CHUNK_AMBCOLOR:
 	case Dot3DSFile::CHUNK_AMBCOLOR:
@@ -373,7 +406,7 @@ void Dot3DSImporter::ParseObjectChunk(int* piRemaining)
 	// chunk appears at different locations
 	// chunk appears at different locations
 	case Dot3DSFile::CHUNK_KEYFRAMER:
 	case Dot3DSFile::CHUNK_KEYFRAMER:
 
 
-		this->ParseKeyframeChunk(&iRemaining);
+		this->ParseKeyframeChunk(iRemaining);
 		break;
 		break;
 
 
 	};
 	};
@@ -386,8 +419,8 @@ void Dot3DSImporter::ParseObjectChunk(int* piRemaining)
 	}
 	}
 	// Go to the starting position of the next top-level chunk
 	// Go to the starting position of the next top-level chunk
 	this->mCurrent = pcCurNext;
 	this->mCurrent = pcCurNext;
-	*piRemaining -= psChunk->Size;
-	if (0 >= *piRemaining)return;
+	piRemaining -= psChunk->Size;
+	if (0 >= piRemaining)return;
 	return this->ParseObjectChunk(piRemaining);
 	return this->ParseObjectChunk(piRemaining);
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
@@ -395,17 +428,17 @@ void Dot3DSImporter::SkipChunk()
 {
 {
 	const Dot3DSFile::Chunk* psChunk;
 	const Dot3DSFile::Chunk* psChunk;
 	this->ReadChunk(&psChunk);
 	this->ReadChunk(&psChunk);
-	if (NULL == psChunk)return;
+	
 	this->mCurrent += psChunk->Size - sizeof(Dot3DSFile::Chunk);
 	this->mCurrent += psChunk->Size - sizeof(Dot3DSFile::Chunk);
 	return;
 	return;
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void Dot3DSImporter::ParseChunk(int* piRemaining)
+void Dot3DSImporter::ParseChunk(int& piRemaining)
 {
 {
 	const Dot3DSFile::Chunk* psChunk;
 	const Dot3DSFile::Chunk* psChunk;
 
 
 	this->ReadChunk(&psChunk);
 	this->ReadChunk(&psChunk);
-	if (NULL == psChunk)return;
+	
 
 
 	const unsigned char* pcCur = this->mCurrent;
 	const unsigned char* pcCur = this->mCurrent;
 	const unsigned char* pcCurNext = pcCur + (psChunk->Size 
 	const unsigned char* pcCurNext = pcCur + (psChunk->Size 
@@ -417,7 +450,7 @@ void Dot3DSImporter::ParseChunk(int* piRemaining)
 	{
 	{
 	case Dot3DSFile::CHUNK_TRIMESH:
 	case Dot3DSFile::CHUNK_TRIMESH:
 		// this starts a new mesh
 		// this starts a new mesh
-		this->ParseMeshChunk(&iRemaining);
+		this->ParseMeshChunk(iRemaining);
 		break;
 		break;
 	};
 	};
 	if (pcCurNext < this->mCurrent)
 	if (pcCurNext < this->mCurrent)
@@ -430,17 +463,17 @@ void Dot3DSImporter::ParseChunk(int* piRemaining)
 	// Go to the starting position of the next top-level chunk
 	// Go to the starting position of the next top-level chunk
 	this->mCurrent = pcCurNext;
 	this->mCurrent = pcCurNext;
 
 
-	*piRemaining -= psChunk->Size;
-	if (0 >= *piRemaining)return;
+	piRemaining -= psChunk->Size;
+	if (0 >= piRemaining)return;
 	return this->ParseChunk(piRemaining);
 	return this->ParseChunk(piRemaining);
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void Dot3DSImporter::ParseKeyframeChunk(int* piRemaining)
+void Dot3DSImporter::ParseKeyframeChunk(int& piRemaining)
 {
 {
 	const Dot3DSFile::Chunk* psChunk;
 	const Dot3DSFile::Chunk* psChunk;
 
 
 	this->ReadChunk(&psChunk);
 	this->ReadChunk(&psChunk);
-	if (NULL == psChunk)return;
+	
 
 
 	const unsigned char* pcCur = this->mCurrent;
 	const unsigned char* pcCur = this->mCurrent;
 	const unsigned char* pcCurNext = pcCur + (psChunk->Size 
 	const unsigned char* pcCurNext = pcCur + (psChunk->Size 
@@ -452,7 +485,7 @@ void Dot3DSImporter::ParseKeyframeChunk(int* piRemaining)
 	{
 	{
 	case Dot3DSFile::CHUNK_TRACKINFO:
 	case Dot3DSFile::CHUNK_TRACKINFO:
 		// this starts a new mesh
 		// this starts a new mesh
-		this->ParseHierarchyChunk(&iRemaining);
+		this->ParseHierarchyChunk(iRemaining);
 		break;
 		break;
 	};
 	};
 	if (pcCurNext < this->mCurrent)
 	if (pcCurNext < this->mCurrent)
@@ -465,8 +498,8 @@ void Dot3DSImporter::ParseKeyframeChunk(int* piRemaining)
 	// Go to the starting position of the next top-level chunk
 	// Go to the starting position of the next top-level chunk
 	this->mCurrent = pcCurNext;
 	this->mCurrent = pcCurNext;
 
 
-	*piRemaining -= psChunk->Size;
-	if (0 >= *piRemaining)return;
+	piRemaining -= psChunk->Size;
+	if (0 >= piRemaining)return;
 	return this->ParseKeyframeChunk(piRemaining);
 	return this->ParseKeyframeChunk(piRemaining);
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
@@ -487,12 +520,12 @@ void Dot3DSImporter::InverseNodeSearch(Dot3DS::Node* pcNode,Dot3DS::Node* pcCurr
 	return this->InverseNodeSearch(pcNode,pcCurrent->mParent);
 	return this->InverseNodeSearch(pcNode,pcCurrent->mParent);
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void Dot3DSImporter::ParseHierarchyChunk(int* piRemaining)
+void Dot3DSImporter::ParseHierarchyChunk(int& piRemaining)
 {
 {
 	const Dot3DSFile::Chunk* psChunk;
 	const Dot3DSFile::Chunk* psChunk;
 
 
 	this->ReadChunk(&psChunk);
 	this->ReadChunk(&psChunk);
-	if (NULL == psChunk)return;
+	
 
 
 	const unsigned char* pcCur = this->mCurrent;
 	const unsigned char* pcCur = this->mCurrent;
 	const unsigned char* pcCurNext = pcCur + (psChunk->Size 
 	const unsigned char* pcCurNext = pcCur + (psChunk->Size 
@@ -545,15 +578,14 @@ void Dot3DSImporter::ParseHierarchyChunk(int* piRemaining)
 		this->mCurrentNode = pcNode;
 		this->mCurrentNode = pcNode;
 		break;
 		break;
 
 
-	// (code for keyframe animation. however, this is currently not supported by Assimp)
-#if 0
-
 	case Dot3DSFile::CHUNK_TRACKPIVOT:
 	case Dot3DSFile::CHUNK_TRACKPIVOT:
 
 
-		this->mCurrentNode->vPivot = *((aiVector3D*)this->mCurrent);
+		// pivot = origin of rotation and scaling
+		this->mCurrentNode->vPivot = *((const aiVector3D*)this->mCurrent);
 		this->mCurrent += sizeof(aiVector3D);
 		this->mCurrent += sizeof(aiVector3D);
 		break;
 		break;
 
 
+#ifdef AI_3DS_KEYFRAME_ANIMATION
 
 
 	case Dot3DSFile::CHUNK_TRACKPOS:
 	case Dot3DSFile::CHUNK_TRACKPOS:
 
 
@@ -569,26 +601,32 @@ void Dot3DSImporter::ParseHierarchyChunk(int* piRemaining)
 		}  pos[keys]; 	
 		}  pos[keys]; 	
 		*/
 		*/
 		this->mCurrent += 10;
 		this->mCurrent += 10;
-		iTemp = *((uint16_t*)mCurrent);
+		iTemp = *((const uint16_t*)mCurrent);
 
 
 		this->mCurrent += sizeof(uint16_t) * 2;
 		this->mCurrent += sizeof(uint16_t) * 2;
 
 
-		if (0 != iTemp)
+		for (unsigned int i = 0; i < (unsigned int)iTemp;++i)
+		{
+			uint16_t sNum = *((const uint16_t*)mCurrent);
+			this->mCurrent += sizeof(uint16_t);
+
+			aiVectorKey v;
+			v.mTime = (double)sNum;
+
+			this->mCurrent += sizeof(uint32_t);
+			v.mValue =  *((const aiVector3D*)this->mCurrent);
+			this->mCurrent += sizeof(aiVector3D);
+
+			// check whether we do already have this keyframe
+			for (std::vector<aiVectorKey>::const_iterator
+				i =  this->mCurrentNode->aPositionKeys.begin();
+				i != this->mCurrentNode->aPositionKeys.end();++i)
 			{
 			{
-			for (unsigned int i = 0; i < (unsigned int)iTemp;++i)
-				{
-				uint16_t sNum = *((uint16_t*)mCurrent);
-				this->mCurrent += sizeof(uint16_t);
-
-				if (0 == sNum)
-					{
-					this->mCurrent += sizeof(uint32_t);
-					this->mCurrentNode->vPosition = *((aiVector3D*)this->mCurrent);
-					this->mCurrent += sizeof(aiVector3D);
-					}
-				else this->mCurrent += sizeof(uint32_t) + sizeof(aiVector3D);
-				}
+				if ((*i).mTime == v.mTime){v.mTime = -10e10f;break;}
 			}
 			}
+			// add the new keyframe
+			if (v.mTime != -10e10f)this->mCurrentNode->aPositionKeys.push_back(v);
+		}
 		break;
 		break;
 
 
 	case Dot3DSFile::CHUNK_TRACKROTATE:
 	case Dot3DSFile::CHUNK_TRACKROTATE:
@@ -605,72 +643,39 @@ void Dot3DSImporter::ParseHierarchyChunk(int* piRemaining)
 		}  pos[keys]; 	
 		}  pos[keys]; 	
 		*/
 		*/
 		this->mCurrent += 10;
 		this->mCurrent += 10;
-		iTemp = *((uint16_t*)mCurrent);
+		iTemp = *((const uint16_t*)mCurrent);
 
 
 		this->mCurrent += sizeof(uint16_t) * 2;
 		this->mCurrent += sizeof(uint16_t) * 2;
-		if (0 != iTemp)
+
+		for (unsigned int i = 0; i < (unsigned int)iTemp;++i)
+		{
+			uint16_t sNum = *((const uint16_t*)mCurrent);
+			this->mCurrent += sizeof(uint16_t);
+
+			aiQuatKey v;
+			v.mTime = (double)sNum;
+
+			this->mCurrent += sizeof(uint32_t);
+
+			float fRadians = *((const float*)this->mCurrent);
+			this->mCurrent += sizeof(float);
+
+			aiVector3D vAxis = *((const aiVector3D*)this->mCurrent);
+			this->mCurrent += sizeof(aiVector3D);
+
+			// construct a rotation quaternion from the axis-angle pair
+			v.mValue = aiQuaternion(vAxis,fRadians);
+
+			// check whether we do already have this keyframe
+			for (std::vector<aiQuatKey>::const_iterator
+				i =  this->mCurrentNode->aRotationKeys.begin();
+				i != this->mCurrentNode->aRotationKeys.end();++i)
 			{
 			{
-			bool neg = false;
-			unsigned int iNum0 = 0;
-			for (unsigned int i = 0; i < (unsigned int)iTemp;++i)
-				{
-				uint16_t sNum = *((uint16_t*)mCurrent);
-				this->mCurrent += sizeof(uint16_t);
-
-				if (0 == sNum)
-					{
-					this->mCurrent		+= sizeof(uint32_t);
-					float fRadians		= *((float*)this->mCurrent);
-					this->mCurrent		+= sizeof(float);
-					aiVector3D vAxis	= *((aiVector3D*)this->mCurrent);
-					this->mCurrent		+= sizeof(aiVector3D);
-
-					// some idiotic files have rotations with fRadians = 0 ...
-					if (0.0f != fRadians)
-						{
-
-						// get the rotation matrix around the axis
-						const float fSin = sinf(-fRadians);
-						const float fCos = cosf(-fRadians);
-						const float fOneMinusCos = 1.0f - fCos;
-
-						std::swap(vAxis.z,vAxis.y);
-						//vAxis.z *= -1.0f;
-						//vAxis.Normalize();
-
-						aiMatrix4x4 mRot = aiMatrix4x4(
-							(vAxis.x * vAxis.x) * fOneMinusCos + fCos,
-							(vAxis.x * vAxis.y) * fOneMinusCos /*-*/- (vAxis.z * fSin),
-							(vAxis.x * vAxis.z) * fOneMinusCos /*+*/+ (vAxis.y * fSin),
-							0.0f,
-							(vAxis.y * vAxis.x) * fOneMinusCos /*+*/+ (vAxis.z * fSin),
-							(vAxis.y * vAxis.y) * fOneMinusCos + fCos,
-							(vAxis.y * vAxis.z) * fOneMinusCos /*-*/- (vAxis.x * fSin),
-							0.0f,
-							(vAxis.z * vAxis.x) * fOneMinusCos /*-*/- (vAxis.y * fSin),
-							(vAxis.z * vAxis.y) * fOneMinusCos /*+*/+ (vAxis.x * fSin),
-							(vAxis.z * vAxis.z) * fOneMinusCos + fCos,
-							0.0f,0.0f,0.0f,0.0f,1.0f);
-						mRot.Transpose();
-
-						// build a chain of concatenated rotation matrix'
-						// if there are multiple track chunks for the same frame
-						// (there are some silly files usinf this ...)
-						if (0 != iNum0)
-							{
-							this->mCurrentNode->mRotation = this->mCurrentNode->mRotation * mRot;
-							}
-						else
-							{
-							// for the first time simply set the rotation matrix
-							this->mCurrentNode->mRotation = mRot;
-							}
-						iNum0++;
-						}
-					}
-				else this->mCurrent += sizeof(uint32_t) + sizeof(aiVector3D) + sizeof(float);
-				}
+				if ((*i).mTime == v.mTime){v.mTime = -10e10f;break;}
 			}
 			}
+			// add the new keyframe
+			if (v.mTime != -10e10f)this->mCurrentNode->aRotationKeys.push_back(v);
+		}
 		break;
 		break;
 
 
 	case Dot3DSFile::CHUNK_TRACKSCALE:
 	case Dot3DSFile::CHUNK_TRACKSCALE:
@@ -687,40 +692,45 @@ void Dot3DSImporter::ParseHierarchyChunk(int* piRemaining)
 		}  pos[keys]; 	
 		}  pos[keys]; 	
 		*/
 		*/
 		this->mCurrent += 10;
 		this->mCurrent += 10;
-		iTemp = *((uint16_t*)mCurrent);
+		iTemp = *((const uint16_t*)mCurrent);
 
 
 		this->mCurrent += sizeof(uint16_t) * 2;
 		this->mCurrent += sizeof(uint16_t) * 2;
+		for (unsigned int i = 0; i < (unsigned int)iTemp;++i)
+		{
+			uint16_t sNum = *((const uint16_t*)mCurrent);
+			this->mCurrent += sizeof(uint16_t);
 
 
-		if (0 != iTemp)
+			aiVectorKey v;
+			v.mTime = (double)sNum;
+
+			this->mCurrent += sizeof(uint32_t);
+			v.mValue =  *((const aiVector3D*)this->mCurrent);
+			this->mCurrent += sizeof(aiVector3D);
+
+			// check whether we do already have this keyframe
+			for (std::vector<aiVectorKey>::const_iterator
+				i =  this->mCurrentNode->aScalingKeys.begin();
+				i != this->mCurrentNode->aScalingKeys.end();++i)
 			{
 			{
-			for (unsigned int i = 0; i < (unsigned int)iTemp;++i)
-				{
-				uint16_t sNum = *((uint16_t*)mCurrent);
-				this->mCurrent += sizeof(uint16_t);
-				if (0 == sNum)
-					{
-					this->mCurrent += sizeof(uint32_t);
-					aiVector3D vMe = *((aiVector3D*)this->mCurrent);
-					// ignore zero scalings
-					if (0.0f != vMe.x && 0.0f != vMe.y && 0.0f != vMe.z)
-						{
-						this->mCurrentNode->vScaling.x *= vMe.x;
-						this->mCurrentNode->vScaling.y *= vMe.y;
-						this->mCurrentNode->vScaling.z *= vMe.z;
-						}
-					else
-					{
-						DefaultLogger::get()->warn("Found zero scaling factors. "
-							"This will be ignored.");
-					}
-					this->mCurrent += sizeof(aiVector3D);
-					}
-				else this->mCurrent += sizeof(uint32_t) + sizeof(aiVector3D);
-				}
+				if ((*i).mTime == v.mTime){v.mTime = -10e10f;break;}
 			}
 			}
-		break;
-#endif // end keyframe animation code
+			// add the new keyframe
+			if (v.mTime != -10e10f)this->mCurrentNode->aScalingKeys.push_back(v);
 
 
+			if (v.mValue.x && v.mValue.y && v.mValue.z)
+			{
+				DefaultLogger::get()->warn("Found zero scaled axis in scaling keyframe");
+				++iCnt;
+			}
+		}
+		// there are 3DS files that have only zero scalings
+		if (iTemp == iCnt)
+		{
+			DefaultLogger::get()->warn("All scaling keys are zero. They will be removed");
+			this->mCurrentNode->aScalingKeys.clear();
+		}
+		break;
+#endif
 	};
 	};
 	if (pcCurNext < this->mCurrent)
 	if (pcCurNext < this->mCurrent)
 	{
 	{
@@ -732,19 +742,19 @@ void Dot3DSImporter::ParseHierarchyChunk(int* piRemaining)
 	// Go to the starting position of the next top-level chunk
 	// Go to the starting position of the next top-level chunk
 	this->mCurrent = pcCurNext;
 	this->mCurrent = pcCurNext;
 
 
-	*piRemaining -= psChunk->Size;
-	if (0 >= *piRemaining)return;
+	piRemaining -= psChunk->Size;
+	if (0 >= piRemaining)return;
 	return this->ParseHierarchyChunk(piRemaining);
 	return this->ParseHierarchyChunk(piRemaining);
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void Dot3DSImporter::ParseFaceChunk(int* piRemaining)
+void Dot3DSImporter::ParseFaceChunk(int& piRemaining)
 {
 {
 	const Dot3DSFile::Chunk* psChunk;
 	const Dot3DSFile::Chunk* psChunk;
 
 
 	Dot3DS::Mesh& mMesh = this->mScene->mMeshes.back();
 	Dot3DS::Mesh& mMesh = this->mScene->mMeshes.back();
 
 
 	this->ReadChunk(&psChunk);
 	this->ReadChunk(&psChunk);
-	if (NULL == psChunk)return;
+	
 
 
 	const unsigned char* pcCur = this->mCurrent;
 	const unsigned char* pcCur = this->mCurrent;
 	const unsigned char* pcCurNext = pcCur + (psChunk->Size 
 	const unsigned char* pcCurNext = pcCur + (psChunk->Size 
@@ -833,19 +843,19 @@ void Dot3DSImporter::ParseFaceChunk(int* piRemaining)
 	// Go to the starting position of the next chunk on this level
 	// Go to the starting position of the next chunk on this level
 	this->mCurrent = pcCurNext;
 	this->mCurrent = pcCurNext;
 
 
-	*piRemaining -= psChunk->Size;
-	if (0 >= *piRemaining)return;
+	piRemaining -= psChunk->Size;
+	if (0 >= piRemaining)return;
 	return ParseFaceChunk(piRemaining);
 	return ParseFaceChunk(piRemaining);
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void Dot3DSImporter::ParseMeshChunk(int* piRemaining)
+void Dot3DSImporter::ParseMeshChunk(int& piRemaining)
 {
 {
 	const Dot3DSFile::Chunk* psChunk;
 	const Dot3DSFile::Chunk* psChunk;
 
 
 	Dot3DS::Mesh& mMesh = this->mScene->mMeshes.back();
 	Dot3DS::Mesh& mMesh = this->mScene->mMeshes.back();
 
 
 	this->ReadChunk(&psChunk);
 	this->ReadChunk(&psChunk);
-	if (NULL == psChunk)return;
+	
 
 
 	const unsigned char* pcCur = this->mCurrent;
 	const unsigned char* pcCur = this->mCurrent;
 	const unsigned char* pcCurNext = pcCur + (psChunk->Size 
 	const unsigned char* pcCurNext = pcCur + (psChunk->Size 
@@ -866,11 +876,12 @@ void Dot3DSImporter::ParseMeshChunk(int* piRemaining)
 		while (iNum-- > 0)
 		while (iNum-- > 0)
 		{
 		{
 			mMesh.mPositions.push_back(*((aiVector3D*)this->mCurrent));
 			mMesh.mPositions.push_back(*((aiVector3D*)this->mCurrent));
-			mMesh.mPositions.back().z *= -1.0f;
+			aiVector3D& v = mMesh.mPositions.back();
+			//std::swap( v.y, v.z);
+			//v.y *= -1.0f;
 			this->mCurrent += sizeof(aiVector3D);
 			this->mCurrent += sizeof(aiVector3D);
 		}
 		}
 		break;
 		break;
-
 	case Dot3DSFile::CHUNK_TRMATRIX:
 	case Dot3DSFile::CHUNK_TRMATRIX:
 		{
 		{
 		// http://www.gamedev.net/community/forums/topic.asp?topic_id=263063
 		// http://www.gamedev.net/community/forums/topic.asp?topic_id=263063
@@ -879,43 +890,21 @@ void Dot3DSImporter::ParseMeshChunk(int* piRemaining)
 		this->mCurrent += 12 * sizeof(float);
 		this->mCurrent += 12 * sizeof(float);
 
 
 		mMesh.mMat.a1 = pf[0];
 		mMesh.mMat.a1 = pf[0];
-		mMesh.mMat.a2 = pf[1];
-		mMesh.mMat.a3 = pf[2];
-		mMesh.mMat.b1 = pf[3];
+		mMesh.mMat.b1 = pf[1];
+		mMesh.mMat.c1 = pf[2];
+		mMesh.mMat.a2 = pf[3];
 		mMesh.mMat.b2 = pf[4];
 		mMesh.mMat.b2 = pf[4];
-		mMesh.mMat.b3 = pf[5];
-		mMesh.mMat.c1 = pf[6];
-		mMesh.mMat.c2 = pf[7];
+		mMesh.mMat.c2 = pf[5];
+		mMesh.mMat.a3 = pf[6];
+		mMesh.mMat.b3 = pf[7];
 		mMesh.mMat.c3 = pf[8];
 		mMesh.mMat.c3 = pf[8];
-		mMesh.mMat.d1 = pf[9];
-		mMesh.mMat.d2 = pf[10];
-		mMesh.mMat.d3 = pf[11];
-
-		std::swap((float&)mMesh.mMat.d2, (float&)mMesh.mMat.d3);
-		std::swap((float&)mMesh.mMat.a2, (float&)mMesh.mMat.a3);
-		std::swap((float&)mMesh.mMat.b1, (float&)mMesh.mMat.c1);
-		std::swap((float&)mMesh.mMat.c2, (float&)mMesh.mMat.b3);
-		std::swap((float&)mMesh.mMat.b2, (float&)mMesh.mMat.c3);
-
-		mMesh.mMat.Transpose();
-
-		//aiMatrix4x4 mInv = mMesh.mMat;
-		//mInv.Inverse();
-
-		//// invert the matrix and transform all vertices with it
-		//// (the origin of all vertices is 0|0|0 now)
-		//for (register unsigned int i = 0; i < 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;
-		//	}
+		mMesh.mMat.a4 = pf[9];
+		mMesh.mMat.b4 = pf[10];
+		mMesh.mMat.c4 = pf[11];
+		//mMesh.mMat.Transpose(); // todo ----
 
 
 		// now check whether the matrix has got a negative determinant
 		// now check whether the matrix has got a negative determinant
-		// If yes, we need to flip all vertices x axis ....
+		// If yes, we need to flip all vertices' x axis ....
 		// From lib3ds, mesh.c
 		// From lib3ds, mesh.c
 		if (mMesh.mMat.Determinant() < 0.0f)
 		if (mMesh.mMat.Determinant() < 0.0f)
 			{
 			{
@@ -938,9 +927,9 @@ void Dot3DSImporter::ParseMeshChunk(int* piRemaining)
 				mMesh.mPositions[i] = c;
 				mMesh.mPositions[i] = c;
 				}
 				}
 			}
 			}
+			
 		}
 		}
 		break;
 		break;
-
 	case Dot3DSFile::CHUNK_MAPLIST:
 	case Dot3DSFile::CHUNK_MAPLIST:
 
 
 		iNum = *((uint16_t*)this->mCurrent);
 		iNum = *((uint16_t*)this->mCurrent);
@@ -983,7 +972,7 @@ void Dot3DSImporter::ParseMeshChunk(int* piRemaining)
 		mMesh.mFaceMaterials.resize(mMesh.mFaces.size(),0xcdcdcdcd);
 		mMesh.mFaceMaterials.resize(mMesh.mFaces.size(),0xcdcdcdcd);
 
 
 		iRemaining = (int)(pcCurNext - this->mCurrent);
 		iRemaining = (int)(pcCurNext - this->mCurrent);
-		if (iRemaining > 0)this->ParseFaceChunk(&iRemaining);
+		if (iRemaining > 0)this->ParseFaceChunk(iRemaining);
 		break;
 		break;
 
 
 	};
 	};
@@ -997,17 +986,17 @@ void Dot3DSImporter::ParseMeshChunk(int* piRemaining)
 	// Go to the starting position of the next chunk on this level
 	// Go to the starting position of the next chunk on this level
 	this->mCurrent = pcCurNext;
 	this->mCurrent = pcCurNext;
 
 
-	*piRemaining -= psChunk->Size;
-	if (0 >= *piRemaining)return;
+	piRemaining -= psChunk->Size;
+	if (0 >= piRemaining)return;
 	return ParseMeshChunk(piRemaining);
 	return ParseMeshChunk(piRemaining);
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void Dot3DSImporter::ParseMaterialChunk(int* piRemaining)
+void Dot3DSImporter::ParseMaterialChunk(int& piRemaining)
 {
 {
 	const Dot3DSFile::Chunk* psChunk;
 	const Dot3DSFile::Chunk* psChunk;
 
 
 	this->ReadChunk(&psChunk);
 	this->ReadChunk(&psChunk);
-	if (NULL == psChunk)return;
+	
 
 
 	const unsigned char* pcCur = this->mCurrent;
 	const unsigned char* pcCur = this->mCurrent;
 	const unsigned char* pcCurNext = pcCur + (psChunk->Size 
 	const unsigned char* pcCurNext = pcCur + (psChunk->Size 
@@ -1121,27 +1110,27 @@ void Dot3DSImporter::ParseMaterialChunk(int* piRemaining)
 	// parse texture chunks
 	// parse texture chunks
 	case Dot3DSFile::CHUNK_MAT_TEXTURE:
 	case Dot3DSFile::CHUNK_MAT_TEXTURE:
 		iRemaining = (psChunk->Size - sizeof(Dot3DSFile::Chunk));
 		iRemaining = (psChunk->Size - sizeof(Dot3DSFile::Chunk));
-		this->ParseTextureChunk(&iRemaining,&this->mScene->mMaterials.back().sTexDiffuse);
+		this->ParseTextureChunk(iRemaining,&this->mScene->mMaterials.back().sTexDiffuse);
 		break;
 		break;
 	case Dot3DSFile::CHUNK_MAT_BUMPMAP:
 	case Dot3DSFile::CHUNK_MAT_BUMPMAP:
 		iRemaining = (psChunk->Size - sizeof(Dot3DSFile::Chunk));
 		iRemaining = (psChunk->Size - sizeof(Dot3DSFile::Chunk));
-		this->ParseTextureChunk(&iRemaining,&this->mScene->mMaterials.back().sTexBump);
+		this->ParseTextureChunk(iRemaining,&this->mScene->mMaterials.back().sTexBump);
 		break;
 		break;
 	case Dot3DSFile::CHUNK_MAT_OPACMAP:
 	case Dot3DSFile::CHUNK_MAT_OPACMAP:
 		iRemaining = (psChunk->Size - sizeof(Dot3DSFile::Chunk));
 		iRemaining = (psChunk->Size - sizeof(Dot3DSFile::Chunk));
-		this->ParseTextureChunk(&iRemaining,&this->mScene->mMaterials.back().sTexOpacity);
+		this->ParseTextureChunk(iRemaining,&this->mScene->mMaterials.back().sTexOpacity);
 		break;
 		break;
 	case Dot3DSFile::CHUNK_MAT_MAT_SHINMAP:
 	case Dot3DSFile::CHUNK_MAT_MAT_SHINMAP:
 		iRemaining = (psChunk->Size - sizeof(Dot3DSFile::Chunk));
 		iRemaining = (psChunk->Size - sizeof(Dot3DSFile::Chunk));
-		this->ParseTextureChunk(&iRemaining,&this->mScene->mMaterials.back().sTexShininess);
+		this->ParseTextureChunk(iRemaining,&this->mScene->mMaterials.back().sTexShininess);
 		break;
 		break;
 	case Dot3DSFile::CHUNK_MAT_SPECMAP:
 	case Dot3DSFile::CHUNK_MAT_SPECMAP:
 		iRemaining = (psChunk->Size - sizeof(Dot3DSFile::Chunk));
 		iRemaining = (psChunk->Size - sizeof(Dot3DSFile::Chunk));
-		this->ParseTextureChunk(&iRemaining,&this->mScene->mMaterials.back().sTexSpecular);
+		this->ParseTextureChunk(iRemaining,&this->mScene->mMaterials.back().sTexSpecular);
 		break;
 		break;
 	case Dot3DSFile::CHUNK_MAT_SELFIMAP:
 	case Dot3DSFile::CHUNK_MAT_SELFIMAP:
 		iRemaining = (psChunk->Size - sizeof(Dot3DSFile::Chunk));
 		iRemaining = (psChunk->Size - sizeof(Dot3DSFile::Chunk));
-		this->ParseTextureChunk(&iRemaining,&this->mScene->mMaterials.back().sTexEmissive);
+		this->ParseTextureChunk(iRemaining,&this->mScene->mMaterials.back().sTexEmissive);
 		break;
 		break;
 	};
 	};
 	if (pcCurNext < this->mCurrent)
 	if (pcCurNext < this->mCurrent)
@@ -1154,17 +1143,17 @@ void Dot3DSImporter::ParseMaterialChunk(int* piRemaining)
 	// Go to the starting position of the next chunk on this level
 	// Go to the starting position of the next chunk on this level
 	this->mCurrent = pcCurNext;
 	this->mCurrent = pcCurNext;
 
 
-	*piRemaining -= psChunk->Size;
-	if (0 >= *piRemaining)return;
+	piRemaining -= psChunk->Size;
+	if (0 >= piRemaining)return;
 	return ParseMaterialChunk(piRemaining);
 	return ParseMaterialChunk(piRemaining);
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void Dot3DSImporter::ParseTextureChunk(int* piRemaining,Dot3DS::Texture* pcOut)
+void Dot3DSImporter::ParseTextureChunk(int& piRemaining,Dot3DS::Texture* pcOut)
 {
 {
 	const Dot3DSFile::Chunk* psChunk;
 	const Dot3DSFile::Chunk* psChunk;
 
 
 	this->ReadChunk(&psChunk);
 	this->ReadChunk(&psChunk);
-	if (NULL == psChunk)return;
+	
 
 
 	const unsigned char* pcCur = this->mCurrent;
 	const unsigned char* pcCur = this->mCurrent;
 	const unsigned char* pcCurNext = pcCur + (psChunk->Size 
 	const unsigned char* pcCurNext = pcCur + (psChunk->Size 
@@ -1246,8 +1235,8 @@ void Dot3DSImporter::ParseTextureChunk(int* piRemaining,Dot3DS::Texture* pcOut)
 	// Go to the starting position of the next chunk on this level
 	// Go to the starting position of the next chunk on this level
 	this->mCurrent = pcCurNext;
 	this->mCurrent = pcCurNext;
 
 
-	*piRemaining -= psChunk->Size;
-	if (0 >= *piRemaining)return;
+	piRemaining -= psChunk->Size;
+	if (0 >= piRemaining)return;
 	return ParseTextureChunk(piRemaining,pcOut);
 	return ParseTextureChunk(piRemaining,pcOut);
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------

+ 10 - 10
code/3DSLoader.h

@@ -138,52 +138,52 @@ protected:
 	// -------------------------------------------------------------------
 	// -------------------------------------------------------------------
 	/** Parse a main top-level chunk in the file
 	/** Parse a main top-level chunk in the file
 	*/
 	*/
-	void ParseMainChunk(int* piRemaining);
+	void ParseMainChunk(int& piRemaining);
 
 
 	// -------------------------------------------------------------------
 	// -------------------------------------------------------------------
 	/** Parse a top-level chunk in the file
 	/** Parse a top-level chunk in the file
 	*/
 	*/
-	void ParseChunk(int* piRemaining);
+	void ParseChunk(int& piRemaining);
 
 
 	// -------------------------------------------------------------------
 	// -------------------------------------------------------------------
 	/** Parse a top-level editor chunk in the file
 	/** Parse a top-level editor chunk in the file
 	*/
 	*/
-	void ParseEditorChunk(int* piRemaining);
+	void ParseEditorChunk(int& piRemaining);
 
 
 	// -------------------------------------------------------------------
 	// -------------------------------------------------------------------
 	/** Parse a top-level object chunk in the file
 	/** Parse a top-level object chunk in the file
 	*/
 	*/
-	void ParseObjectChunk(int* piRemaining);
+	void ParseObjectChunk(int& piRemaining);
 
 
 	// -------------------------------------------------------------------
 	// -------------------------------------------------------------------
 	/** Parse a material chunk in the file
 	/** Parse a material chunk in the file
 	*/
 	*/
-	void ParseMaterialChunk(int* piRemaining);
+	void ParseMaterialChunk(int& piRemaining);
 
 
 	// -------------------------------------------------------------------
 	// -------------------------------------------------------------------
 	/** Parse a mesh chunk in the file
 	/** Parse a mesh chunk in the file
 	*/
 	*/
-	void ParseMeshChunk(int* piRemaining);
+	void ParseMeshChunk(int& piRemaining);
 
 
 	// -------------------------------------------------------------------
 	// -------------------------------------------------------------------
 	/** Parse a face list chunk in the file
 	/** Parse a face list chunk in the file
 	*/
 	*/
-	void ParseFaceChunk(int* piRemaining);
+	void ParseFaceChunk(int& piRemaining);
 
 
 	// -------------------------------------------------------------------
 	// -------------------------------------------------------------------
 	/** Parse a keyframe chunk in the file
 	/** Parse a keyframe chunk in the file
 	*/
 	*/
-	void ParseKeyframeChunk(int* piRemaining);
+	void ParseKeyframeChunk(int& piRemaining);
 
 
 	// -------------------------------------------------------------------
 	// -------------------------------------------------------------------
 	/** Parse a hierarchy chunk in the file
 	/** Parse a hierarchy chunk in the file
 	*/
 	*/
-	void ParseHierarchyChunk(int* piRemaining);
+	void ParseHierarchyChunk(int& piRemaining);
 
 
 	// -------------------------------------------------------------------
 	// -------------------------------------------------------------------
 	/** Parse a texture chunk in the file
 	/** Parse a texture chunk in the file
 	*/
 	*/
-	void ParseTextureChunk(int* piRemaining,Dot3DS::Texture* pcOut);
+	void ParseTextureChunk(int& piRemaining,Dot3DS::Texture* pcOut);
 
 
 	// -------------------------------------------------------------------
 	// -------------------------------------------------------------------
 	/** Convert the meshes in the file
 	/** Convert the meshes in the file

+ 2 - 2
code/ASELoader.cpp

@@ -105,7 +105,7 @@ bool ASEImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
 void ASEImporter::InternReadFile( 
 void ASEImporter::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, "rt"));
 
 
 	// Check whether we can read from the file
 	// Check whether we can read from the file
 	if( file.get() == NULL)
 	if( file.get() == NULL)
@@ -195,7 +195,7 @@ void ASEImporter::GenerateDefaultMaterial()
 	mat.mSpecular = aiColor3D(1.0f,1.0f,1.0f);
 	mat.mSpecular = aiColor3D(1.0f,1.0f,1.0f);
 	mat.mAmbient = aiColor3D(0.05f,0.05f,0.05f);
 	mat.mAmbient = aiColor3D(0.05f,0.05f,0.05f);
 	mat.mShading = Dot3DSFile::Gouraud;
 	mat.mShading = Dot3DSFile::Gouraud;
-	mat.mName = "$$$ASE_DEFAULT";
+	mat.mName = AI_DEFAULT_MATERIAL_NAME;
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void ASEImporter::AddNodes(aiScene* pcScene,aiNode* pcParent,
 void ASEImporter::AddNodes(aiScene* pcScene,aiNode* pcParent,

+ 5 - 3
code/ASEParser.cpp

@@ -41,19 +41,21 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 /** @file Implementation of the ASE parser class */
 /** @file Implementation of the ASE parser class */
 
 
+// internal headers
 #include "TextureTransform.h"
 #include "TextureTransform.h"
 #include "ASELoader.h"
 #include "ASELoader.h"
 #include "MaterialSystem.h"
 #include "MaterialSystem.h"
-
-#include "../include/DefaultLogger.h"
 #include "fast_atof.h"
 #include "fast_atof.h"
 
 
+// public ASSIMP headers
+#include "../include/DefaultLogger.h"
 #include "../include/IOStream.h"
 #include "../include/IOStream.h"
 #include "../include/IOSystem.h"
 #include "../include/IOSystem.h"
 #include "../include/aiMesh.h"
 #include "../include/aiMesh.h"
 #include "../include/aiScene.h"
 #include "../include/aiScene.h"
 #include "../include/aiAssert.h"
 #include "../include/aiAssert.h"
 
 
+// boost headers
 #include <boost/scoped_ptr.hpp>
 #include <boost/scoped_ptr.hpp>
 
 
 using namespace Assimp;
 using namespace Assimp;
@@ -134,7 +136,7 @@ bool Parser::SkipToNextToken()
 
 
 		// increase the line number counter if necessary
 		// increase the line number counter if necessary
 		if (IsLineEnd(me))++this->iLineNumber;
 		if (IsLineEnd(me))++this->iLineNumber;
-		else if ('*' == me || '}' == me || '{' == me)return true;
+		if ('*' == me || '}' == me || '{' == me)return true;
 		else if ('\0' == me)return false;
 		else if ('\0' == me)return false;
 
 
 		++this->m_szFile;
 		++this->m_szFile;

+ 5 - 8
code/ParsingUtils.h

@@ -81,14 +81,10 @@ inline bool SkipLine( const char_t* in, const char_t** out)
 {
 {
 	while (*in != (char_t)'\r' && *in != (char_t)'\n' && *in != (char_t)'\0')in++;
 	while (*in != (char_t)'\r' && *in != (char_t)'\n' && *in != (char_t)'\0')in++;
 
 
-	if (*in == (char_t)'\0')
-	{
-		*out = in;
-		return false;
-	}
-	in++;
+	// files are opened in binary mode. Ergo there are both NL and CR
+	while (*in == (char_t)'\r' || *in == (char_t)'\n')in++;
 	*out = in;
 	*out = in;
-	return true;
+	return *in != (char_t)'\0';
 }
 }
 // ---------------------------------------------------------------------------------
 // ---------------------------------------------------------------------------------
 template <class char_t>
 template <class char_t>
@@ -98,11 +94,12 @@ inline bool SkipLine( const char_t** inout)
 }
 }
 // ---------------------------------------------------------------------------------
 // ---------------------------------------------------------------------------------
 template <class char_t>
 template <class char_t>
-inline void SkipSpacesAndLineEnd( const char_t* in, const char_t** out)
+inline bool SkipSpacesAndLineEnd( const char_t* in, const char_t** out)
 {
 {
 	while (*in == (char_t)' ' || *in == (char_t)'\t' ||
 	while (*in == (char_t)' ' || *in == (char_t)'\t' ||
 		*in == (char_t)'\r' || *in == (char_t)'\n')in++;
 		*in == (char_t)'\r' || *in == (char_t)'\n')in++;
 	*out = in;
 	*out = in;
+	return *in != '\0';
 }
 }
 // ---------------------------------------------------------------------------------
 // ---------------------------------------------------------------------------------
 template <class char_t>
 template <class char_t>

+ 2 - 2
code/PlyLoader.cpp

@@ -135,7 +135,7 @@ void PLYImporter::InternReadFile(
 		{
 		{
 			szMe += 6;
 			szMe += 6;
 			SkipLine(szMe,(const char**)&szMe);
 			SkipLine(szMe,(const char**)&szMe);
-			if(!PLY::DOM::ParseInstance(szMe,&sPlyDom, (unsigned int)fileSize))
+			if(!PLY::DOM::ParseInstance(szMe,&sPlyDom))
 			{
 			{
 				delete[] this->mBuffer;
 				delete[] this->mBuffer;
 				throw new ImportErrorException( "Invalid .ply file: Unable to build DOM (#1)");
 				throw new ImportErrorException( "Invalid .ply file: Unable to build DOM (#1)");
@@ -156,7 +156,7 @@ void PLYImporter::InternReadFile(
 
 
 			// skip the line, parse the rest of the header and build the DOM
 			// skip the line, parse the rest of the header and build the DOM
 			SkipLine(szMe,(const char**)&szMe);
 			SkipLine(szMe,(const char**)&szMe);
-			if(!PLY::DOM::ParseInstanceBinary(szMe,&sPlyDom,bIsBE, (unsigned int)fileSize))
+			if(!PLY::DOM::ParseInstanceBinary(szMe,&sPlyDom,bIsBE))
 			{
 			{
 				delete[] this->mBuffer;
 				delete[] this->mBuffer;
 				throw new ImportErrorException( "Invalid .ply file: Unable to build DOM (#2)");
 				throw new ImportErrorException( "Invalid .ply file: Unable to build DOM (#2)");

+ 27 - 132
code/PlyParser.cpp

@@ -57,23 +57,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <boost/scoped_ptr.hpp>
 #include <boost/scoped_ptr.hpp>
 
 
 using namespace Assimp;
 using namespace Assimp;
-
-// ------------------------------------------------------------------------------------------------
-void ValidateOut(const char* szCur, const char* szMax)
-{
-	if (szCur > szMax)
-	{
-		throw new ImportErrorException("Buffer overflow. PLY file contains invalid indices");
-	}
-	return;
-}
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 PLY::EDataType PLY::Property::ParseDataType(const char* p_szIn,const char** p_szOut)
 PLY::EDataType PLY::Property::ParseDataType(const char* p_szIn,const char** p_szOut)
 {
 {
 	ai_assert(NULL != p_szIn);
 	ai_assert(NULL != p_szIn);
 	ai_assert(NULL != p_szOut);
 	ai_assert(NULL != p_szOut);
 
 
-	const char* szMax = *p_szOut;
 
 
 	PLY::EDataType eOut = PLY::EDT_INVALID;
 	PLY::EDataType eOut = PLY::EDT_INVALID;
 
 
@@ -156,7 +145,6 @@ PLY::EDataType PLY::Property::ParseDataType(const char* p_szIn,const char** p_sz
 		DefaultLogger::get()->info("Found unknown data type in PLY file. This is OK");
 		DefaultLogger::get()->info("Found unknown data type in PLY file. This is OK");
 	}
 	}
 	*p_szOut = p_szIn;
 	*p_szOut = p_szIn;
-	ValidateOut(p_szIn,szMax);
 	return eOut;
 	return eOut;
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
@@ -165,8 +153,6 @@ PLY::ESemantic PLY::Property::ParseSemantic(const char* p_szIn,const char** p_sz
 	ai_assert(NULL != p_szIn);
 	ai_assert(NULL != p_szIn);
 	ai_assert(NULL != p_szOut);
 	ai_assert(NULL != p_szOut);
 
 
-	const char* szMax = *p_szOut;
-
 	PLY::ESemantic eOut = PLY::EST_INVALID;
 	PLY::ESemantic eOut = PLY::EST_INVALID;
 	if (0 == ASSIMP_strincmp(p_szIn,"red",3))
 	if (0 == ASSIMP_strincmp(p_szIn,"red",3))
 	{
 	{
@@ -336,7 +322,6 @@ PLY::ESemantic PLY::Property::ParseSemantic(const char* p_szIn,const char** p_sz
 	{
 	{
 		eOut = PLY::EST_INVALID;
 		eOut = PLY::EST_INVALID;
 	}
 	}
-	ValidateOut(p_szIn,szMax);
 	*p_szOut = p_szIn;
 	*p_szOut = p_szIn;
 	return eOut;
 	return eOut;
 }
 }
@@ -351,7 +336,6 @@ bool PLY::Property::ParseProperty (const char* p_szIn,
 	// Forms supported:
 	// Forms supported:
 	// "property float x"
 	// "property float x"
 	// "property list uchar int vertex_index"
 	// "property list uchar int vertex_index"
-	const char* szMax = *p_szOut;
 	*p_szOut = p_szIn;
 	*p_szOut = p_szIn;
 
 
 	// skip leading spaces
 	// skip leading spaces
@@ -373,44 +357,36 @@ bool PLY::Property::ParseProperty (const char* p_szIn,
 
 
 		// seems to be a list.
 		// seems to be a list.
 		p_szIn += 5;
 		p_szIn += 5;
-		const char* szPass = szMax;
-		if(EDT_INVALID == (pOut->eFirstType = PLY::Property::ParseDataType(p_szIn, &szPass)))
+		if(EDT_INVALID == (pOut->eFirstType = PLY::Property::ParseDataType(p_szIn, &p_szIn)))
 		{
 		{
 			// unable to parse list size data type
 			// unable to parse list size data type
 			SkipLine(p_szIn,&p_szIn);
 			SkipLine(p_szIn,&p_szIn);
 			*p_szOut = p_szIn;
 			*p_szOut = p_szIn;
 			return false;
 			return false;
 		}
 		}
-		p_szIn = szPass;
 		if (!SkipSpaces(p_szIn,&p_szIn))return false;
 		if (!SkipSpaces(p_szIn,&p_szIn))return false;
-		szPass = szMax;
-		if(EDT_INVALID == (pOut->eType = PLY::Property::ParseDataType(p_szIn, &szPass)))
+		if(EDT_INVALID == (pOut->eType = PLY::Property::ParseDataType(p_szIn, &p_szIn)))
 		{
 		{
 			// unable to parse list data type
 			// unable to parse list data type
 			SkipLine(p_szIn,&p_szIn);
 			SkipLine(p_szIn,&p_szIn);
 			*p_szOut = p_szIn;
 			*p_szOut = p_szIn;
 			return false;
 			return false;
 		}
 		}
-		p_szIn = szPass;
 	}
 	}
 	else
 	else
 	{
 	{
-		const char* szPass = szMax;
-		if(EDT_INVALID == (pOut->eType = PLY::Property::ParseDataType(p_szIn, &szPass)))
+		if(EDT_INVALID == (pOut->eType = PLY::Property::ParseDataType(p_szIn, &p_szIn)))
 		{
 		{
 			// unable to parse data type. Skip the property
 			// unable to parse data type. Skip the property
 			SkipLine(p_szIn,&p_szIn);
 			SkipLine(p_szIn,&p_szIn);
 			*p_szOut = p_szIn;
 			*p_szOut = p_szIn;
 			return false;
 			return false;
 		}
 		}
-		p_szIn = szPass;
 	}
 	}
 	
 	
 	if (!SkipSpaces(p_szIn,&p_szIn))return false;
 	if (!SkipSpaces(p_szIn,&p_szIn))return false;
 	const char* szCur = p_szIn;
 	const char* szCur = p_szIn;
-	const char* szPass = szMax;
-	pOut->Semantic = PLY::Property::ParseSemantic(p_szIn, &szPass);
-	p_szIn = szPass;
+	pOut->Semantic = PLY::Property::ParseSemantic(p_szIn, &p_szIn);
 
 
 	if (PLY::EST_INVALID == pOut->Semantic)
 	if (PLY::EST_INVALID == pOut->Semantic)
 	{
 	{
@@ -422,7 +398,6 @@ bool PLY::Property::ParseProperty (const char* p_szIn,
 	}
 	}
 
 
 	SkipSpacesAndLineEnd(p_szIn,&p_szIn);
 	SkipSpacesAndLineEnd(p_szIn,&p_szIn);
-	ValidateOut(p_szIn,szMax);
 	*p_szOut = p_szIn;
 	*p_szOut = p_szIn;
 	return true;
 	return true;
 }
 }
@@ -433,8 +408,6 @@ PLY::EElementSemantic PLY::Element::ParseSemantic(const char* p_szIn,
 	ai_assert(NULL != p_szIn);
 	ai_assert(NULL != p_szIn);
 	ai_assert(NULL != p_szOut);
 	ai_assert(NULL != p_szOut);
 
 
-	const char* szMax = *p_szOut;
-
 	PLY::EElementSemantic eOut = PLY::EEST_INVALID;
 	PLY::EElementSemantic eOut = PLY::EEST_INVALID;
 	if (0 == ASSIMP_strincmp(p_szIn,"vertex",6))
 	if (0 == ASSIMP_strincmp(p_szIn,"vertex",6))
 	{
 	{
@@ -474,7 +447,6 @@ PLY::EElementSemantic PLY::Element::ParseSemantic(const char* p_szIn,
 	{
 	{
 		eOut = PLY::EEST_INVALID;
 		eOut = PLY::EEST_INVALID;
 	}
 	}
-	ValidateOut(p_szIn,szMax);
 	*p_szOut = p_szIn;
 	*p_szOut = p_szIn;
 	return eOut;
 	return eOut;
 }
 }
@@ -488,7 +460,6 @@ bool PLY::Element::ParseElement (const char* p_szIn,
 	ai_assert(NULL != pOut);
 	ai_assert(NULL != pOut);
 
 
 	// Example format: "element vertex 8"
 	// Example format: "element vertex 8"
-	const char* szMax = *p_szOut;
 	*p_szOut = p_szIn;
 	*p_szOut = p_szIn;
 
 
 	// skip leading spaces
 	// skip leading spaces
@@ -506,10 +477,7 @@ bool PLY::Element::ParseElement (const char* p_szIn,
 
 
 	// parse the semantic of the element
 	// parse the semantic of the element
 	const char* szCur = p_szIn;
 	const char* szCur = p_szIn;
-	const char* szPass = szMax;
-	pOut->eSemantic = PLY::Element::ParseSemantic(p_szIn,&szPass);
-	p_szIn = szPass;
-
+	pOut->eSemantic = PLY::Element::ParseSemantic(p_szIn,&p_szIn);
 	if (PLY::EEST_INVALID == pOut->eSemantic)
 	if (PLY::EEST_INVALID == pOut->eSemantic)
 	{
 	{
 		// store the name of the semantic
 		// store the name of the semantic
@@ -534,14 +502,11 @@ bool PLY::Element::ParseElement (const char* p_szIn,
 
 
 		PLY::Property* prop = new PLY::Property();
 		PLY::Property* prop = new PLY::Property();
 
 
-		const char* szPass = szMax;
-		if(!PLY::Property::ParseProperty(p_szIn,&szPass,prop))break;
-		p_szIn = szPass;
+		if(!PLY::Property::ParseProperty(p_szIn,&p_szIn,prop))break;
 
 
 		// add the property to the property list
 		// add the property to the property list
 		pOut->alProperties.push_back(prop);
 		pOut->alProperties.push_back(prop);
 	}
 	}
-	ValidateOut(p_szIn,szMax);
 	*p_szOut = p_szIn;
 	*p_szOut = p_szIn;
 	return true;
 	return true;
 }
 }
@@ -552,7 +517,6 @@ bool PLY::DOM::SkipComments (const char* p_szIn,
 	ai_assert(NULL != p_szIn);
 	ai_assert(NULL != p_szIn);
 	ai_assert(NULL != p_szOut);
 	ai_assert(NULL != p_szOut);
 
 
-	const char* szMax = *p_szOut;
 	*p_szOut = p_szIn;
 	*p_szOut = p_szIn;
 
 
 	// skip spaces
 	// skip spaces
@@ -568,7 +532,6 @@ bool PLY::DOM::SkipComments (const char* p_szIn,
 		*p_szOut = p_szIn;
 		*p_szOut = p_szIn;
 		return true;
 		return true;
 	}
 	}
-	ValidateOut(p_szIn,szMax);
 	*p_szOut = p_szIn;
 	*p_szOut = p_szIn;
 	return false;
 	return false;
 }
 }
@@ -591,11 +554,8 @@ bool PLY::DOM::ParseHeader (const char* p_szIn,const char** p_szOut)
 		PLY::DOM::SkipComments(p_szIn,&p_szIn);
 		PLY::DOM::SkipComments(p_szIn,&p_szIn);
 
 
 		PLY::Element* out = new PLY::Element();
 		PLY::Element* out = new PLY::Element();
-		const char* szPass = szMax;
-		if(PLY::Element::ParseElement(p_szIn,&szPass,out))
+		if(PLY::Element::ParseElement(p_szIn,&p_szIn,out))
 		{
 		{
-			p_szIn = szPass;
-
 			// add the element to the list of elements
 			// add the element to the list of elements
 			this->alElements.push_back(out);
 			this->alElements.push_back(out);
 		}
 		}
@@ -608,7 +568,6 @@ bool PLY::DOM::ParseHeader (const char* p_szIn,const char** p_szOut)
 		// ignore unknown header elements
 		// ignore unknown header elements
 	}
 	}
 	SkipSpacesAndLineEnd(p_szIn,&p_szIn);
 	SkipSpacesAndLineEnd(p_szIn,&p_szIn);
-	ValidateOut(p_szIn,szMax);
 	*p_szOut = p_szIn;
 	*p_szOut = p_szIn;
 
 
 	DefaultLogger::get()->debug("PLY::DOM::ParseHeader() succeeded");
 	DefaultLogger::get()->debug("PLY::DOM::ParseHeader() succeeded");
@@ -636,14 +595,10 @@ bool PLY::DOM::ParseElementInstanceLists (
 	for (;i != this->alElements.end();++i,++a)
 	for (;i != this->alElements.end();++i,++a)
 	{
 	{
 		*a = new PLY::ElementInstanceList((*i)); // reserve enough storage
 		*a = new PLY::ElementInstanceList((*i)); // reserve enough storage
-
-		const char* szPass = szMax;
-		PLY::ElementInstanceList::ParseInstanceList(p_szIn,&szPass,(*i),(*a));
-		p_szIn = szPass;
+		PLY::ElementInstanceList::ParseInstanceList(p_szIn,&p_szIn,(*i),(*a));
 	}
 	}
 
 
 	DefaultLogger::get()->debug("PLY::DOM::ParseElementInstanceLists() succeeded");
 	DefaultLogger::get()->debug("PLY::DOM::ParseElementInstanceLists() succeeded");
-	ValidateOut(p_szIn,szMax);
 	*p_szOut = p_szIn;
 	*p_szOut = p_szIn;
 	return true;
 	return true;
 }
 }
@@ -657,8 +612,6 @@ bool PLY::DOM::ParseElementInstanceListsBinary (
 	ai_assert(NULL != p_szOut);
 	ai_assert(NULL != p_szOut);
 
 
 	DefaultLogger::get()->debug("PLY::DOM::ParseElementInstanceListsBinary() begin");
 	DefaultLogger::get()->debug("PLY::DOM::ParseElementInstanceListsBinary() begin");
-
-	const char* szMax = *p_szOut;
 	*p_szOut = p_szIn;
 	*p_szOut = p_szIn;
 	
 	
 	this->alElementData.resize(this->alElements.size());
 	this->alElementData.resize(this->alElements.size());
@@ -670,34 +623,27 @@ bool PLY::DOM::ParseElementInstanceListsBinary (
 	for (;i != this->alElements.end();++i,++a)
 	for (;i != this->alElements.end();++i,++a)
 	{
 	{
 		*a = new PLY::ElementInstanceList((*i)); // reserve enough storage
 		*a = new PLY::ElementInstanceList((*i)); // reserve enough storage
-		const char* szPass = szMax;
-		PLY::ElementInstanceList::ParseInstanceListBinary(p_szIn,&szPass,(*i),(*a),p_bBE);
-		p_szIn = szPass;
+		PLY::ElementInstanceList::ParseInstanceListBinary(p_szIn,&p_szIn,(*i),(*a),p_bBE);
 	}
 	}
 
 
 	DefaultLogger::get()->debug("PLY::DOM::ParseElementInstanceListsBinary() succeeded");
 	DefaultLogger::get()->debug("PLY::DOM::ParseElementInstanceListsBinary() succeeded");
-	ValidateOut(p_szIn,szMax);
 	*p_szOut = p_szIn;
 	*p_szOut = p_szIn;
 	return true;
 	return true;
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-bool PLY::DOM::ParseInstanceBinary (const char* p_szIn,DOM* p_pcOut,bool p_bBE,unsigned int iSize)
+bool PLY::DOM::ParseInstanceBinary (const char* p_szIn,DOM* p_pcOut,bool p_bBE)
 {
 {
 	ai_assert(NULL != p_szIn);
 	ai_assert(NULL != p_szIn);
 	ai_assert(NULL != p_pcOut);
 	ai_assert(NULL != p_pcOut);
 
 
 	DefaultLogger::get()->debug("PLY::DOM::ParseInstanceBinary() begin");
 	DefaultLogger::get()->debug("PLY::DOM::ParseInstanceBinary() begin");
 
 
-	const char* szMax = p_szIn + iSize;
-	const char* szPass = szMax;
-	if(!p_pcOut->ParseHeader(p_szIn,&szPass))
+	if(!p_pcOut->ParseHeader(p_szIn,&p_szIn))
 	{
 	{
 		DefaultLogger::get()->debug("PLY::DOM::ParseInstanceBinary() failure");
 		DefaultLogger::get()->debug("PLY::DOM::ParseInstanceBinary() failure");
 		return false;
 		return false;
 	}
 	}
-	p_szIn = szPass;
-	szPass = szMax;
-	if(!p_pcOut->ParseElementInstanceListsBinary(p_szIn,&szPass,p_bBE))
+	if(!p_pcOut->ParseElementInstanceListsBinary(p_szIn,&p_szIn,p_bBE))
 	{
 	{
 		DefaultLogger::get()->debug("PLY::DOM::ParseInstanceBinary() failure");
 		DefaultLogger::get()->debug("PLY::DOM::ParseInstanceBinary() failure");
 		return false;
 		return false;
@@ -706,23 +652,20 @@ bool PLY::DOM::ParseInstanceBinary (const char* p_szIn,DOM* p_pcOut,bool p_bBE,u
 	return true;
 	return true;
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-bool PLY::DOM::ParseInstance (const char* p_szIn,DOM* p_pcOut,unsigned int iSize)
+bool PLY::DOM::ParseInstance (const char* p_szIn,DOM* p_pcOut)
 {
 {
 	ai_assert(NULL != p_szIn);
 	ai_assert(NULL != p_szIn);
 	ai_assert(NULL != p_pcOut);
 	ai_assert(NULL != p_pcOut);
 
 
 	DefaultLogger::get()->debug("PLY::DOM::ParseInstance() begin");
 	DefaultLogger::get()->debug("PLY::DOM::ParseInstance() begin");
 
 
-	const char* szMax = p_szIn + iSize;
-	const char* szPass = szMax;
-	if(!p_pcOut->ParseHeader(p_szIn,&szPass))
+
+	if(!p_pcOut->ParseHeader(p_szIn,&p_szIn))
 	{
 	{
 		DefaultLogger::get()->debug("PLY::DOM::ParseInstance() failure");
 		DefaultLogger::get()->debug("PLY::DOM::ParseInstance() failure");
 		return false;
 		return false;
 	}
 	}
-	p_szIn = szPass;
-	szPass = szMax;
-	if(!p_pcOut->ParseElementInstanceLists(p_szIn,&szPass))
+	if(!p_pcOut->ParseElementInstanceLists(p_szIn,&p_szIn))
 	{
 	{
 		DefaultLogger::get()->debug("PLY::DOM::ParseInstance() failure");
 		DefaultLogger::get()->debug("PLY::DOM::ParseInstance() failure");
 		return false;
 		return false;
@@ -763,15 +706,11 @@ bool PLY::ElementInstanceList::ParseInstanceList (
 			PLY::DOM::SkipComments(p_szIn,&p_szIn);
 			PLY::DOM::SkipComments(p_szIn,&p_szIn);
 
 
 			PLY::ElementInstance* out = new PLY::ElementInstance();
 			PLY::ElementInstance* out = new PLY::ElementInstance();
-
-			const char* szPass = szMax;
-			PLY::ElementInstance::ParseInstance(p_szIn, &szPass,pcElement, out);
-			p_szIn = szPass;
+			PLY::ElementInstance::ParseInstance(p_szIn, &p_szIn,pcElement, out);
 			// add it to the list
 			// add it to the list
 			p_pcOut->alInstances[i] = out;
 			p_pcOut->alInstances[i] = out;
 		}
 		}
 	}
 	}
-	ValidateOut(p_szIn,szMax);
 	*p_szOut = p_szIn;
 	*p_szOut = p_szIn;
 	return true;
 	return true;
 }
 }
@@ -788,9 +727,6 @@ bool PLY::ElementInstanceList::ParseInstanceListBinary (
 	ai_assert(NULL != pcElement);
 	ai_assert(NULL != pcElement);
 	ai_assert(NULL != p_pcOut);
 	ai_assert(NULL != p_pcOut);
 
 
-	const char* szMax = *p_szOut;
-	*p_szOut = p_szIn;
-
 	// we can add special handling code for unknown element semantics since
 	// we can add special handling code for unknown element semantics since
 	// we can't skip it as a whole block (we don't know its exact size
 	// we can't skip it as a whole block (we don't know its exact size
 	// due to the fact that lists could be contained in the property list 
 	// due to the fact that lists could be contained in the property list 
@@ -798,13 +734,10 @@ bool PLY::ElementInstanceList::ParseInstanceListBinary (
 	for (unsigned int i = 0; i < pcElement->NumOccur;++i)
 	for (unsigned int i = 0; i < pcElement->NumOccur;++i)
 	{
 	{
 		PLY::ElementInstance* out = new PLY::ElementInstance();
 		PLY::ElementInstance* out = new PLY::ElementInstance();
-		const char* szPass = szMax;
 		PLY::ElementInstance::ParseInstanceBinary(p_szIn, &p_szIn,pcElement, out, p_bBE);
 		PLY::ElementInstance::ParseInstanceBinary(p_szIn, &p_szIn,pcElement, out, p_bBE);
-		p_szIn = szPass;
 		// add it to the list
 		// add it to the list
 		p_pcOut->alInstances[i] = out;
 		p_pcOut->alInstances[i] = out;
 	}
 	}
-	ValidateOut(p_szIn,szMax);
 	*p_szOut = p_szIn;
 	*p_szOut = p_szIn;
 	return true;
 	return true;
 }
 }
@@ -825,15 +758,11 @@ bool PLY::ElementInstance::ParseInstance (
 	// allocate enough storage
 	// allocate enough storage
 	p_pcOut->alProperties.resize(pcElement->alProperties.size());
 	p_pcOut->alProperties.resize(pcElement->alProperties.size());
 
 
-	const char* szMax = *p_szOut;
-	*p_szOut = p_szIn;
-
 	std::vector<PLY::PropertyInstance>::iterator i = p_pcOut->alProperties.begin();
 	std::vector<PLY::PropertyInstance>::iterator i = p_pcOut->alProperties.begin();
 	std::vector<PLY::Property*>::const_iterator a = pcElement->alProperties.begin();
 	std::vector<PLY::Property*>::const_iterator a = pcElement->alProperties.begin();
 	for (;i != p_pcOut->alProperties.end();++i,++a)
 	for (;i != p_pcOut->alProperties.end();++i,++a)
 	{
 	{
-		const char* szPass = szMax;
-		if(!(PLY::PropertyInstance::ParseInstance(p_szIn, &szPass,(*a),&(*i))))
+		if(!(PLY::PropertyInstance::ParseInstance(p_szIn, &p_szIn,(*a),&(*i))))
 		{
 		{
 			DefaultLogger::get()->warn("Unable to parse property instance. "
 			DefaultLogger::get()->warn("Unable to parse property instance. "
 				"Skipping this element instance");
 				"Skipping this element instance");
@@ -844,9 +773,7 @@ bool PLY::ElementInstance::ParseInstance (
 			PLY::PropertyInstance::ValueUnion v = PLY::PropertyInstance::DefaultValue((*a)->eType);
 			PLY::PropertyInstance::ValueUnion v = PLY::PropertyInstance::DefaultValue((*a)->eType);
 			(*i).avList.push_back(v);
 			(*i).avList.push_back(v);
 		}
 		}
-		p_szIn = szPass;
 	}
 	}
-	ValidateOut(p_szIn,szMax);
 	*p_szOut = p_szIn;
 	*p_szOut = p_szIn;
 	return true;
 	return true;
 }
 }
@@ -863,9 +790,6 @@ bool PLY::ElementInstance::ParseInstanceBinary (
 	ai_assert(NULL != pcElement);
 	ai_assert(NULL != pcElement);
 	ai_assert(NULL != p_pcOut);
 	ai_assert(NULL != p_pcOut);
 
 
-	const char* szMax = *p_szOut;
-	*p_szOut = p_szIn;
-
 	// allocate enough storage
 	// allocate enough storage
 	p_pcOut->alProperties.resize(pcElement->alProperties.size());
 	p_pcOut->alProperties.resize(pcElement->alProperties.size());
 
 
@@ -873,8 +797,7 @@ bool PLY::ElementInstance::ParseInstanceBinary (
 	std::vector<PLY::Property*>::const_iterator a =  pcElement->alProperties.begin();
 	std::vector<PLY::Property*>::const_iterator a =  pcElement->alProperties.begin();
 	for (;i != p_pcOut->alProperties.end();++i,++a)
 	for (;i != p_pcOut->alProperties.end();++i,++a)
 	{
 	{
-		const char* szPass = szMax;
-		if(!(PLY::PropertyInstance::ParseInstanceBinary(p_szIn, &szPass,(*a),&(*i),p_bBE)))
+		if(!(PLY::PropertyInstance::ParseInstanceBinary(p_szIn, &p_szIn,(*a),&(*i),p_bBE)))
 		{
 		{
 			DefaultLogger::get()->warn("Unable to parse binary property instance. "
 			DefaultLogger::get()->warn("Unable to parse binary property instance. "
 				"Skipping this element instance");
 				"Skipping this element instance");
@@ -882,9 +805,7 @@ bool PLY::ElementInstance::ParseInstanceBinary (
 			PLY::PropertyInstance::ValueUnion v = PLY::PropertyInstance::DefaultValue((*a)->eType);
 			PLY::PropertyInstance::ValueUnion v = PLY::PropertyInstance::DefaultValue((*a)->eType);
 			(*i).avList.push_back(v);
 			(*i).avList.push_back(v);
 		}
 		}
-		p_szIn = szPass;
 	}
 	}
-	ValidateOut(p_szIn,szMax);
 	*p_szOut = p_szIn;
 	*p_szOut = p_szIn;
 	return true;
 	return true;
 }
 }
@@ -897,7 +818,6 @@ bool PLY::PropertyInstance::ParseInstance (const char* p_szIn,const char** p_szO
 	ai_assert(NULL != prop);
 	ai_assert(NULL != prop);
 	ai_assert(NULL != p_pcOut);
 	ai_assert(NULL != p_pcOut);
 
 
-	const char* szMax = *p_szOut;
 	*p_szOut = p_szIn;
 	*p_szOut = p_szIn;
 
 
 	// skip spaces at the beginning
 	// skip spaces at the beginning
@@ -907,10 +827,7 @@ bool PLY::PropertyInstance::ParseInstance (const char* p_szIn,const char** p_szO
 	{
 	{
 		// parse the number of elements in the list
 		// parse the number of elements in the list
 		PLY::PropertyInstance::ValueUnion v;
 		PLY::PropertyInstance::ValueUnion v;
-
-		const char* szPass = szMax;
-		PLY::PropertyInstance::ParseValue(p_szIn, &szPass,prop->eFirstType,&v);
-		p_szIn = szPass;
+		PLY::PropertyInstance::ParseValue(p_szIn, &p_szIn,prop->eFirstType,&v);
 
 
 		// convert to unsigned int
 		// convert to unsigned int
 		unsigned int iNum = PLY::PropertyInstance::ConvertTo<unsigned int>(v,prop->eFirstType);
 		unsigned int iNum = PLY::PropertyInstance::ConvertTo<unsigned int>(v,prop->eFirstType);
@@ -919,10 +836,7 @@ bool PLY::PropertyInstance::ParseInstance (const char* p_szIn,const char** p_szO
 		for (unsigned int i = 0; i < iNum;++i)
 		for (unsigned int i = 0; i < iNum;++i)
 		{
 		{
 			if (!SkipSpaces(p_szIn, &p_szIn))return false;
 			if (!SkipSpaces(p_szIn, &p_szIn))return false;
-
-			const char* szPass = szMax;
-			PLY::PropertyInstance::ParseValue(p_szIn, &szPass,prop->eType,&v);
-			p_szIn = szPass;
+			PLY::PropertyInstance::ParseValue(p_szIn, &p_szIn,prop->eType,&v);
 			p_pcOut->avList.push_back(v);
 			p_pcOut->avList.push_back(v);
 		}
 		}
 	}
 	}
@@ -931,13 +845,10 @@ bool PLY::PropertyInstance::ParseInstance (const char* p_szIn,const char** p_szO
 		// parse the property
 		// parse the property
 		PLY::PropertyInstance::ValueUnion v;
 		PLY::PropertyInstance::ValueUnion v;
 
 
-		const char* szPass = szMax;
-		PLY::PropertyInstance::ParseValue(p_szIn, &szPass,prop->eType,&v);
-		p_szIn = szPass;
+		PLY::PropertyInstance::ParseValue(p_szIn, &p_szIn,prop->eType,&v);
 		p_pcOut->avList.push_back(v);
 		p_pcOut->avList.push_back(v);
 	}
 	}
 	SkipSpacesAndLineEnd(p_szIn, &p_szIn);
 	SkipSpacesAndLineEnd(p_szIn, &p_szIn);
-	ValidateOut(p_szIn,szMax);
 	*p_szOut = p_szIn;
 	*p_szOut = p_szIn;
 	return true;
 	return true;
 }
 }
@@ -950,16 +861,11 @@ bool PLY::PropertyInstance::ParseInstanceBinary (const char* p_szIn,const char**
 	ai_assert(NULL != prop);
 	ai_assert(NULL != prop);
 	ai_assert(NULL != p_pcOut);
 	ai_assert(NULL != p_pcOut);
 
 
-	const char* szMax = *p_szOut;
-	*p_szOut = p_szIn;
-
 	if (prop->bIsList)
 	if (prop->bIsList)
 	{
 	{
 		// parse the number of elements in the list
 		// parse the number of elements in the list
 		PLY::PropertyInstance::ValueUnion v;
 		PLY::PropertyInstance::ValueUnion v;
-		const char* szPass = szMax;
-		PLY::PropertyInstance::ParseValueBinary(p_szIn, &szPass,prop->eFirstType,&v,p_bBE);
-		p_szIn = szPass;
+		PLY::PropertyInstance::ParseValueBinary(p_szIn, &p_szIn,prop->eFirstType,&v,p_bBE);
 
 
 		// convert to unsigned int
 		// convert to unsigned int
 		unsigned int iNum = PLY::PropertyInstance::ConvertTo<unsigned int>(v,prop->eFirstType);
 		unsigned int iNum = PLY::PropertyInstance::ConvertTo<unsigned int>(v,prop->eFirstType);
@@ -967,9 +873,7 @@ bool PLY::PropertyInstance::ParseInstanceBinary (const char* p_szIn,const char**
 		// parse all list elements
 		// parse all list elements
 		for (unsigned int i = 0; i < iNum;++i)
 		for (unsigned int i = 0; i < iNum;++i)
 		{
 		{
-			const char* szPass = szMax;
-			PLY::PropertyInstance::ParseValueBinary(p_szIn, &szPass,prop->eType,&v,p_bBE);
-			p_szIn = szPass;
+			PLY::PropertyInstance::ParseValueBinary(p_szIn, &p_szIn,prop->eType,&v,p_bBE);
 			p_pcOut->avList.push_back(v);
 			p_pcOut->avList.push_back(v);
 		}
 		}
 	}
 	}
@@ -977,12 +881,9 @@ bool PLY::PropertyInstance::ParseInstanceBinary (const char* p_szIn,const char**
 	{
 	{
 		// parse the property
 		// parse the property
 		PLY::PropertyInstance::ValueUnion v;
 		PLY::PropertyInstance::ValueUnion v;
-		const char* szPass = szMax;
-		PLY::PropertyInstance::ParseValueBinary(p_szIn, &szPass,prop->eType,&v,p_bBE);
-		p_szIn = szPass;
+		PLY::PropertyInstance::ParseValueBinary(p_szIn, &p_szIn,prop->eType,&v,p_bBE);
 		p_pcOut->avList.push_back(v);
 		p_pcOut->avList.push_back(v);
 	}
 	}
-	ValidateOut(p_szIn,szMax);
 	*p_szOut = p_szIn;
 	*p_szOut = p_szIn;
 	return true;
 	return true;
 }
 }
@@ -1013,9 +914,6 @@ bool PLY::PropertyInstance::ParseValue(const char* p_szIn,const char** p_szOut,
 	ai_assert(NULL != p_szOut);
 	ai_assert(NULL != p_szOut);
 	ai_assert(NULL != out);
 	ai_assert(NULL != out);
 
 
-	const char* szMax = *p_szOut;
-	*p_szOut = p_szIn;
-
 	switch (eType)
 	switch (eType)
 	{
 	{
 	case EDT_UInt:
 	case EDT_UInt:
@@ -1060,9 +958,9 @@ bool PLY::PropertyInstance::ParseValue(const char* p_szIn,const char** p_szOut,
 		out->fDouble = (double)f;
 		out->fDouble = (double)f;
 
 
 	default:
 	default:
+		*p_szOut = p_szIn;
 		return false;
 		return false;
 	}
 	}
-	ValidateOut(p_szIn,szMax);
 	*p_szOut = p_szIn;
 	*p_szOut = p_szIn;
 	return true;
 	return true;
 }
 }
@@ -1078,9 +976,6 @@ bool PLY::PropertyInstance::ParseValueBinary(
 	ai_assert(NULL != p_szOut);
 	ai_assert(NULL != p_szOut);
 	ai_assert(NULL != out);
 	ai_assert(NULL != out);
 
 
-	const char* szMax = *p_szOut;
-	*p_szOut = p_szIn;
-
 	switch (eType)
 	switch (eType)
 	{
 	{
 	case EDT_UInt:
 	case EDT_UInt:
@@ -1163,9 +1058,9 @@ bool PLY::PropertyInstance::ParseValueBinary(
 		break;
 		break;
 		}
 		}
 	default:
 	default:
+		*p_szOut = p_szIn;
 		return false;
 		return false;
 	}
 	}
-	ValidateOut(p_szIn,szMax);
 	*p_szOut = p_szIn;
 	*p_szOut = p_szIn;
 	return true;
 	return true;
 }
 }

+ 2 - 2
code/PlyParser.h

@@ -481,9 +481,9 @@ public:
 
 
 	//! Parse the DOM for a PLY file. The input string is assumed
 	//! Parse the DOM for a PLY file. The input string is assumed
 	//! to be terminated with zero
 	//! to be terminated with zero
-	static bool ParseInstance (const char* p_szIn,DOM* p_pcOut, unsigned int iLen);
+	static bool ParseInstance (const char* p_szIn,DOM* p_pcOut);
 	static bool ParseInstanceBinary (const char* p_szIn,
 	static bool ParseInstanceBinary (const char* p_szIn,
-		DOM* p_pcOut,bool p_bBE,  unsigned int iLen);
+		DOM* p_pcOut,bool p_bBE);
 
 
 	//! Skip all comment lines after this
 	//! Skip all comment lines after this
 	static bool SkipComments (const char* p_szIn,const char** p_szOut);
 	static bool SkipComments (const char* p_szIn,const char** p_szOut);

+ 112 - 0
code/RemoveComments.cpp

@@ -0,0 +1,112 @@
+/*
+Open Asset Import Library (ASSIMP)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2008, ASSIMP Development Team
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms, 
+with or without modification, are permitted provided that the 
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the ASSIMP team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the ASSIMP Development Team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file Defines a helper class, "CommentRemover", which can be
+ *  used to remove comments (single and multi line) from a text file.
+ */
+#include "../include/aiTypes.h"
+#include "../include/DefaultLogger.h"
+#include "../include/aiAssert.h"
+
+#include "RemoveComments.h"
+
+namespace Assimp
+{
+
+// ------------------------------------------------------------------------------------------------
+void CommentRemover::RemoveLineComments(const char* szComment,
+	char* szBuffer, char chReplacement /* = ' ' */)
+{
+	// validate parameters
+	ai_assert(NULL != szComment && NULL != szBuffer && *szComment);
+
+	while (*szBuffer)
+	{
+		if (*szBuffer == *szComment)
+		{
+			if (0 == ::strcmp(szBuffer+1,szComment+1))
+			{
+				while (*szBuffer != '\r' && *szBuffer != '\n' && *szBuffer)
+					*szBuffer++ = chReplacement;
+			}
+		}
+		++szBuffer;
+	}
+}
+// ------------------------------------------------------------------------------------------------
+void CommentRemover::RemoveMultiLineComments(const char* szCommentStart,
+	const char* szCommentEnd,char* szBuffer,
+	char chReplacement)
+{
+	// validate parameters
+	ai_assert(NULL != szCommentStart && NULL != szCommentEnd &&
+		NULL != szBuffer && '\0' != *szCommentStart && '\0' != *szCommentEnd);
+
+	const size_t len = ::strlen(szCommentEnd);
+
+	while (*szBuffer)
+	{
+		if (*szBuffer == *szCommentStart)
+		{
+			if (0 == ::strcmp(szBuffer+1,szCommentStart+1))
+			{
+				while (*szBuffer)
+				{
+					if (*szBuffer == *szCommentEnd)
+					{
+						if (0 == ::strcmp(szBuffer+1,szCommentEnd+1))
+						{
+							for (unsigned int i = 0; i < len;++i)
+								*szBuffer++ = chReplacement;
+							goto __continue_outer; // WUHHHAAAAHHAA!
+						}
+					}
+					*szBuffer++ = chReplacement;
+				}
+				return;
+			}
+		}
+		++szBuffer;
+__continue_outer:
+		int i = 4; // NOP dummy
+	}
+}
+
+}; // !! Assimp

+ 89 - 0
code/RemoveComments.h

@@ -0,0 +1,89 @@
+/*
+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 Declares a helper class, "CommentRemover", which can be
+ *  used to remove comments (single and multi line) from a text file.
+ */
+#ifndef AI_REMOVE_COMMENTS_H_INC
+#define AI_REMOVE_COMMENTS_H_INC
+
+#include "../include/aiAssert.h"
+
+namespace Assimp
+{
+
+// ---------------------------------------------------------------------------
+/** \brief Helper class to remove single and multi line comments from a file
+ * 
+ *  Some mesh formats like MD5 have comments that are quite similar
+ *  to those in C or C++ so this code has been moved to a separate
+ *  module.
+ */
+class CommentRemover
+{
+	// class cannot be instanced
+	CommentRemover() {}
+
+public:
+
+	//! Remove single-line comments. The end of a line is
+	//! expected to be either NL or CR or NLCR.
+	//! \param szComment The start sequence of the comment, e.g. "//"
+	//! \param szBuffer Buffer to work with
+	//! \param chReplacement Character to be used as replacement
+	//! for commented lines. By default this is ' '
+	static void RemoveLineComments(const char* szComment,
+		char* szBuffer, char chReplacement = ' ');
+
+	//! Remove multi-line comments. The end of a line is
+	//! expected to be either NL or CR or NLCR. Multi-line comments
+	//! may not be nested (as in C).
+	//! \param szCommentStart The start sequence of the comment, e.g. "/*"
+	//! \param szCommentEnd The end sequence of the comment, e.g. "*/"
+	//! \param szBuffer Buffer to work with
+	//! \param chReplacement Character to be used as replacement
+	//! for commented lines. By default this is ' '
+	static void RemoveMultiLineComments(const char* szCommentStart,
+		const char* szCommentEnd,char* szBuffer,
+		char chReplacement = ' ');
+};
+} // ! Assimp
+
+#endif // !! AI_REMOVE_COMMENTS_H_INC

+ 150 - 60
code/SMDLoader.cpp

@@ -41,11 +41,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 /** @file Implementation of the SMD importer class */
 /** @file Implementation of the SMD importer class */
 
 
+// internal headers
 #include "MaterialSystem.h"
 #include "MaterialSystem.h"
 #include "SMDLoader.h"
 #include "SMDLoader.h"
 #include "StringComparison.h"
 #include "StringComparison.h"
 #include "fast_atof.h"
 #include "fast_atof.h"
 
 
+// public headers
 #include "../include/DefaultLogger.h"
 #include "../include/DefaultLogger.h"
 #include "../include/IOStream.h"
 #include "../include/IOStream.h"
 #include "../include/IOSystem.h"
 #include "../include/IOSystem.h"
@@ -53,6 +55,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "../include/aiScene.h"
 #include "../include/aiScene.h"
 #include "../include/aiAssert.h"
 #include "../include/aiAssert.h"
 
 
+// boost headers
 #include <boost/scoped_ptr.hpp>
 #include <boost/scoped_ptr.hpp>
 
 
 using namespace Assimp;
 using namespace Assimp;
@@ -105,7 +108,7 @@ bool SMDImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
 void SMDImporter::InternReadFile( 
 void SMDImporter::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, "rt"));
 
 
 	// Check whether we can read from the file
 	// Check whether we can read from the file
 	if( file.get() == NULL)
 	if( file.get() == NULL)
@@ -123,6 +126,9 @@ void SMDImporter::InternReadFile(
 	this->bHasUVs = true;
 	this->bHasUVs = true;
 	this->iLineNumber = 1;
 	this->iLineNumber = 1;
 
 
+	// append a terminal 0
+	this->mBuffer[this->iFileSize] = '\0';
+
 	// reserve enough space for ... hm ... 10 textures
 	// reserve enough space for ... hm ... 10 textures
 	this->aszTextures.reserve(10);
 	this->aszTextures.reserve(10);
 
 
@@ -137,14 +143,48 @@ void SMDImporter::InternReadFile(
 		// parse the file ...
 		// parse the file ...
 		this->ParseFile();
 		this->ParseFile();
 
 
-		// now fix invalid time values and make sure the animation starts at frame 0
-		this->FixTimeValues();
+		// if there are no triangles it seems to be an animation SMD,
+		// containing only the animation skeleton.
+		if (this->asTriangles.empty())
+		{
+			if (this->asBones.empty())
+			{
+				throw new ImportErrorException("No triangles and no bones have "
+					"been found in the file. This file seems to be invalid.");
+			}
+			// set the flag in the scene structure which indicates
+			// that there is nothing than an animation skeleton
+			pScene->mFlags |= AI_SCENE_FLAGS_ANIM_SKELETON_ONLY;
+		}
+		
+		if (!this->asBones.empty())
+		{
+			// check whether all bones have been initialized
+			for (std::vector<SMD::Bone>::const_iterator
+				i =  this->asBones.begin();
+				i != this->asBones.end();++i)
+			{
+				if (!(*i).mName.length())
+				{
+					DefaultLogger::get()->warn("Not all bones have been initialized");
+					break;
+				}
+			}
+
+			// now fix invalid time values and make sure the animation starts at frame 0
+			this->FixTimeValues();
 
 
-		// compute absolute bone transformation matrices
-		this->ComputeAbsoluteBoneTransformations();
+			// compute absolute bone transformation matrices
+			this->ComputeAbsoluteBoneTransformations();
+		}
+		if (!(pScene->mFlags & AI_SCENE_FLAGS_ANIM_SKELETON_ONLY))
+		{
+			// create output meshes
+			this->CreateOutputMeshes();
 
 
-		// create output meshes
-		this->CreateOutputMeshes();
+			// build an output material list
+			this->CreateOutputMaterials();
+		}
 
 
 		// build the output animation
 		// build the output animation
 		this->CreateOutputAnimations();
 		this->CreateOutputAnimations();
@@ -292,7 +332,7 @@ void SMDImporter::CreateOutputMeshes()
 
 
 			*pcVerts++ = face.avVertices[0].pos;
 			*pcVerts++ = face.avVertices[0].pos;
 			*pcVerts++ = face.avVertices[1].pos;
 			*pcVerts++ = face.avVertices[1].pos;
-			*pcVerts++ = face.avVertices[2].pos;
+			*pcVerts++ = face.avVertices[2].pos; 
 
 
 			// fill the normals
 			// fill the normals
 			*pcNormals++ = face.avVertices[0].nor;
 			*pcNormals++ = face.avVertices[0].nor;
@@ -310,10 +350,9 @@ void SMDImporter::CreateOutputMeshes()
 			for (unsigned int iVert = 0; iVert < 3;++iVert)
 			for (unsigned int iVert = 0; iVert < 3;++iVert)
 			{
 			{
 				float fSum = 0.0f;
 				float fSum = 0.0f;
-				for (unsigned int iBone = 0;iBone < face.avVertices[0].aiBoneLinks.size();++iBone)
+				for (unsigned int iBone = 0;iBone < face.avVertices[iVert].aiBoneLinks.size();++iBone)
 				{
 				{
 					TempWeightListEntry& pairval = face.avVertices[iVert].aiBoneLinks[iBone];
 					TempWeightListEntry& pairval = face.avVertices[iVert].aiBoneLinks[iBone];
-					
 					if (pairval.first >= this->asBones.size())
 					if (pairval.first >= this->asBones.size())
 					{
 					{
 						DefaultLogger::get()->error("[SMD/VTA] Bone index overflow. "
 						DefaultLogger::get()->error("[SMD/VTA] Bone index overflow. "
@@ -322,16 +361,35 @@ void SMDImporter::CreateOutputMeshes()
 						continue;
 						continue;
 					}
 					}
 					aaiBones[pairval.first].push_back(TempWeightListEntry(iNum,pairval.second));
 					aaiBones[pairval.first].push_back(TempWeightListEntry(iNum,pairval.second));
-
 					fSum += pairval.second;
 					fSum += pairval.second;
 				}
 				}
 				// if the sum of all vertex weights is not 1.0 we must assign 
 				// if the sum of all vertex weights is not 1.0 we must assign 
 				// the rest to the vertex' parent node. Well, at least the doc says 
 				// the rest to the vertex' parent node. Well, at least the doc says 
 				// we should ...
 				// we should ...
-				if (fSum <= 0.995f)
+				if (fSum <= 1.0f)
 				{
 				{
-					aaiBones[face.avVertices[iVert].iParentNode].push_back(
-						TempWeightListEntry(iNum,1.0f-fSum));
+					if (face.avVertices[iVert].iParentNode >= this->asBones.size())
+					{
+						DefaultLogger::get()->error("[SMD/VTA] Bone index overflow. "
+							"The index of the vertex parent bone is invalid. "
+							"The remaining weights will be normalized to 1.0");
+
+						if (fSum)
+						{
+							fSum = 1 / fSum;
+							for (unsigned int iBone = 0;iBone < face.avVertices[iVert].aiBoneLinks.size();++iBone)
+							{
+								TempWeightListEntry& pairval = face.avVertices[iVert].aiBoneLinks[iBone];
+								if (pairval.first >= this->asBones.size())continue;
+								aaiBones[pairval.first].back().second *= fSum;
+							}
+						}
+					}
+					else
+					{
+						aaiBones[face.avVertices[iVert].iParentNode].push_back(
+							TempWeightListEntry(iNum,1.0f-fSum));
+					}
 				}
 				}
 
 
 				pcMesh->mFaces[iFace].mIndices[iVert] = iNum++;
 				pcMesh->mFaces[iFace].mIndices[iVert] = iNum++;
@@ -375,7 +433,7 @@ void SMDImporter::CreateOutputMeshes()
 // add bone child nodes
 // add bone child nodes
 void SMDImporter::AddBoneChildren(aiNode* pcNode, uint32_t iParent)
 void SMDImporter::AddBoneChildren(aiNode* pcNode, uint32_t iParent)
 {
 {
-	ai_assert(NULL != pcNode && 0 != pcNode->mNumChildren && NULL != pcNode->mChildren);
+	ai_assert(NULL != pcNode && 0 == pcNode->mNumChildren && NULL == pcNode->mChildren);
 
 
 	// first count ...
 	// first count ...
 	for (unsigned int i = 0; i < this->asBones.size();++i)
 	for (unsigned int i = 0; i < this->asBones.size();++i)
@@ -399,42 +457,72 @@ void SMDImporter::AddBoneChildren(aiNode* pcNode, uint32_t iParent)
 
 
 		// store the local transformation matrix of the bind pose
 		// store the local transformation matrix of the bind pose
 		pc->mTransformation = bone.sAnim.asKeys[bone.sAnim.iFirstTimeKey].matrix;
 		pc->mTransformation = bone.sAnim.asKeys[bone.sAnim.iFirstTimeKey].matrix;
+		pc->mParent = pcNode;
+
+		// add children to this node, too
+		AddBoneChildren(pc,i);
 	}
 	}
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // create output nodes
 // create output nodes
 void SMDImporter::CreateOutputNodes()
 void SMDImporter::CreateOutputNodes()
 {
 {
-	// create one root node that renders all meshes
 	this->pScene->mRootNode = new aiNode();
 	this->pScene->mRootNode = new aiNode();
-	::strcpy(this->pScene->mRootNode->mName.data, "SMD_root");
-	this->pScene->mRootNode->mName.length = 8;
-	this->pScene->mRootNode->mNumMeshes = this->pScene->mNumMeshes;
-	this->pScene->mRootNode->mMeshes = new unsigned int[this->pScene->mNumMeshes];
-	for (unsigned int i = 0; i < this->pScene->mNumMeshes;++i)
-		this->pScene->mRootNode->mMeshes[i] = i;
+	if (!(this->pScene->mFlags & AI_SCENE_FLAGS_ANIM_SKELETON_ONLY))
+	{
+		// create one root node that renders all meshes
+		this->pScene->mRootNode->mNumMeshes = this->pScene->mNumMeshes;
+		this->pScene->mRootNode->mMeshes = new unsigned int[this->pScene->mNumMeshes];
+		for (unsigned int i = 0; i < this->pScene->mNumMeshes;++i)
+			this->pScene->mRootNode->mMeshes[i] = i;
+	}
 
 
 	// now add all bones as dummy sub nodes to the graph
 	// now add all bones as dummy sub nodes to the graph
 	this->AddBoneChildren(this->pScene->mRootNode,(uint32_t)-1);
 	this->AddBoneChildren(this->pScene->mRootNode,(uint32_t)-1);
+
+	// if we have only one bone we can even remove the root node
+	if (this->pScene->mFlags & AI_SCENE_FLAGS_ANIM_SKELETON_ONLY && 
+		1 == this->pScene->mRootNode->mNumChildren)
+	{
+		aiNode* pcOldRoot = this->pScene->mRootNode;
+		this->pScene->mRootNode = pcOldRoot->mChildren[0];
+		pcOldRoot->mChildren[0] = NULL;
+		delete pcOldRoot;
+
+		this->pScene->mRootNode->mParent = NULL;
+	}
+	else
+	{
+		::strcpy(this->pScene->mRootNode->mName.data, "<SMD_root>");
+		this->pScene->mRootNode->mName.length = 10;
+	}
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // create output animations
 // create output animations
 void SMDImporter::CreateOutputAnimations()
 void SMDImporter::CreateOutputAnimations()
 {
 {
+	unsigned int iNumBones = 0;
+	for (std::vector<SMD::Bone>::const_iterator
+		i =  this->asBones.begin();
+		i != this->asBones.end();++i)
+	{
+		if ((*i).bIsUsed)++iNumBones;
+	}
+	if (!iNumBones)
+	{
+		// just make sure this case doesn't occur ... (it could occur
+		// if the file was invalid)
+		return;
+	}
+
 	this->pScene->mNumAnimations = 1;
 	this->pScene->mNumAnimations = 1;
 	this->pScene->mAnimations = new aiAnimation*[1];
 	this->pScene->mAnimations = new aiAnimation*[1];
 	aiAnimation*& anim = this->pScene->mAnimations[0] = new aiAnimation();
 	aiAnimation*& anim = this->pScene->mAnimations[0] = new aiAnimation();
 
 
 	anim->mDuration = this->dLengthOfAnim;
 	anim->mDuration = this->dLengthOfAnim;
+	anim->mNumBones = iNumBones;
 	anim->mTicksPerSecond = 25.0; // FIXME: is this correct?
 	anim->mTicksPerSecond = 25.0; // FIXME: is this correct?
 
 
-	// this->pScene->mAnimations[0]->mNumBones = 0;
-	for (std::vector<SMD::Bone>::const_iterator
-		i =  this->asBones.begin();
-		i != this->asBones.end();++i)
-	{
-		if ((*i).bIsUsed)++anim->mNumBones;
-	}
 	aiBoneAnim** pp = anim->mBones = new aiBoneAnim*[anim->mNumBones];
 	aiBoneAnim** pp = anim->mBones = new aiBoneAnim*[anim->mNumBones];
 	
 	
 	// now build valid keys
 	// now build valid keys
@@ -492,8 +580,10 @@ void SMDImporter::ComputeAbsoluteBoneTransformations()
 		for (unsigned int i = 0; i < bone.sAnim.asKeys.size();++i)
 		for (unsigned int i = 0; i < bone.sAnim.asKeys.size();++i)
 		{
 		{
 			double d = std::min(bone.sAnim.asKeys[i].dTime,dMin);
 			double d = std::min(bone.sAnim.asKeys[i].dTime,dMin);
-			if (d < dMin)	{
-				dMin = d; iIndex = i;
+			if (d < dMin)	
+			{
+				dMin = d;
+				iIndex = i;
 			}
 			}
 		}
 		}
 		bone.sAnim.iFirstTimeKey = iIndex;
 		bone.sAnim.iFirstTimeKey = iIndex;
@@ -541,7 +631,7 @@ void SMDImporter::ComputeAbsoluteBoneTransformations()
 void SMDImporter::CreateOutputMaterials()
 void SMDImporter::CreateOutputMaterials()
 {
 {
 	this->pScene->mNumMaterials = (unsigned int)this->aszTextures.size();
 	this->pScene->mNumMaterials = (unsigned int)this->aszTextures.size();
-	this->pScene->mMaterials = new aiMaterial*[std::min(1u, this->pScene->mNumMaterials)];
+	this->pScene->mMaterials = new aiMaterial*[std::max(1u, this->pScene->mNumMaterials)];
 
 
 	for (unsigned int iMat = 0; iMat < this->pScene->mNumMaterials;++iMat)
 	for (unsigned int iMat = 0; iMat < this->pScene->mNumMaterials;++iMat)
 	{
 	{
@@ -556,7 +646,7 @@ void SMDImporter::CreateOutputMaterials()
 #endif
 #endif
 		pcMat->AddProperty(&szName,AI_MATKEY_NAME);
 		pcMat->AddProperty(&szName,AI_MATKEY_NAME);
 
 
-		strcpy(szName.data, this->aszTextures[iMat].c_str() );
+		::strcpy(szName.data, this->aszTextures[iMat].c_str() );
 		szName.length = this->aszTextures[iMat].length();
 		szName.length = this->aszTextures[iMat].length();
 		pcMat->AddProperty(&szName,AI_MATKEY_TEXTURE_DIFFUSE(0));
 		pcMat->AddProperty(&szName,AI_MATKEY_TEXTURE_DIFFUSE(0));
 	}
 	}
@@ -594,7 +684,7 @@ void SMDImporter::ParseFile()
 	// read line per line ...
 	// read line per line ...
 	while (true)
 	while (true)
 	{
 	{
-		if(!SkipSpaces(szCurrent,&szCurrent)) break;
+		if(!SkipSpacesAndLineEnd(szCurrent,&szCurrent)) break;
 
 
 		// "version <n> \n", <n> should be 1 for hl and hl² SMD files
 		// "version <n> \n", <n> should be 1 for hl and hl² SMD files
 		if (0 == ASSIMP_strincmp(szCurrent,"version",7) &&
 		if (0 == ASSIMP_strincmp(szCurrent,"version",7) &&
@@ -607,55 +697,43 @@ void SMDImporter::ParseFile()
 				DefaultLogger::get()->warn("SMD.version is not 1. This "
 				DefaultLogger::get()->warn("SMD.version is not 1. This "
 					"file format is not known. Continuing happily ...");
 					"file format is not known. Continuing happily ...");
 			}
 			}
+			//continue;
 		}
 		}
 		// "nodes\n" - Starts the node section
 		// "nodes\n" - Starts the node section
-		else if (0 == ASSIMP_strincmp(szCurrent,"nodes",5) &&
+		if (0 == ASSIMP_strincmp(szCurrent,"nodes",5) &&
 			IsSpaceOrNewLine(*(szCurrent+5)))
 			IsSpaceOrNewLine(*(szCurrent+5)))
 		{
 		{
 			szCurrent += 6;
 			szCurrent += 6;
 			this->ParseNodesSection(szCurrent,&szCurrent);
 			this->ParseNodesSection(szCurrent,&szCurrent);
+			//continue;
 		}
 		}
 		// "triangles\n" - Starts the triangle section
 		// "triangles\n" - Starts the triangle section
-		else if (0 == ASSIMP_strincmp(szCurrent,"triangles",9) &&
+		if (0 == ASSIMP_strincmp(szCurrent,"triangles",9) &&
 			IsSpaceOrNewLine(*(szCurrent+9)))
 			IsSpaceOrNewLine(*(szCurrent+9)))
 		{
 		{
 			szCurrent += 10;
 			szCurrent += 10;
 			this->ParseTrianglesSection(szCurrent,&szCurrent);
 			this->ParseTrianglesSection(szCurrent,&szCurrent);
+			//continue;
 		}
 		}
 		// "vertexanimation\n" - Starts the vertex animation section
 		// "vertexanimation\n" - Starts the vertex animation section
-		else if (0 == ASSIMP_strincmp(szCurrent,"vertexanimation",15) &&
+		if (0 == ASSIMP_strincmp(szCurrent,"vertexanimation",15) &&
 			IsSpaceOrNewLine(*(szCurrent+15)))
 			IsSpaceOrNewLine(*(szCurrent+15)))
 		{
 		{
 			this->bHasUVs = false;
 			this->bHasUVs = false;
 			szCurrent += 16;
 			szCurrent += 16;
 			this->ParseVASection(szCurrent,&szCurrent);
 			this->ParseVASection(szCurrent,&szCurrent);
+			//continue;
 		}
 		}
 		// "skeleton\n" - Starts the skeleton section
 		// "skeleton\n" - Starts the skeleton section
-		else if (0 == ASSIMP_strincmp(szCurrent,"skeleton",8) &&
+		if (0 == ASSIMP_strincmp(szCurrent,"skeleton",8) &&
 			IsSpaceOrNewLine(*(szCurrent+8)))
 			IsSpaceOrNewLine(*(szCurrent+8)))
 		{
 		{
 			szCurrent += 9;
 			szCurrent += 9;
 			this->ParseSkeletonSection(szCurrent,&szCurrent);
 			this->ParseSkeletonSection(szCurrent,&szCurrent);
+			//continue;
 		}
 		}
 		else SkipLine(szCurrent,&szCurrent);
 		else SkipLine(szCurrent,&szCurrent);
 	}
 	}
-
-	// if there are no triangles, we can't load the model
-	if (this->asTriangles.empty())
-	{
-		throw new ImportErrorException("No triangles have been found in the file");
-	}
-	// check whether all bones have been initialized
-	for (std::vector<SMD::Bone>::const_iterator
-		i =  this->asBones.begin();
-		i != this->asBones.end();++i)
-	{
-		if (!(*i).mName.length())
-		{
-			DefaultLogger::get()->warn("Not all bones have been initialized");
-			break;
-		}
-	}
 	return;
 	return;
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
@@ -701,6 +779,8 @@ void SMDImporter::ParseTrianglesSection(const char* szCurrent,
 	// and so on until we reach a token that looks quite similar to "end"
 	// and so on until we reach a token that looks quite similar to "end"
 	while (true)
 	while (true)
 	{
 	{
+		if(!SkipSpacesAndLineEnd(szCurrent,&szCurrent)) break;
+
 		// "end\n" - Ends the triangles section
 		// "end\n" - Ends the triangles section
 		if (0 == ASSIMP_strincmp(szCurrent,"end",3) &&
 		if (0 == ASSIMP_strincmp(szCurrent,"end",3) &&
 			IsSpaceOrNewLine(*(szCurrent+3)))
 			IsSpaceOrNewLine(*(szCurrent+3)))
@@ -721,6 +801,8 @@ void SMDImporter::ParseVASection(const char* szCurrent,
 	unsigned int iCurIndex = 0;
 	unsigned int iCurIndex = 0;
 	while (true)
 	while (true)
 	{
 	{
+		if(!SkipSpacesAndLineEnd(szCurrent,&szCurrent)) break;
+
 		// "end\n" - Ends the "vertexanimation" section
 		// "end\n" - Ends the "vertexanimation" section
 		if (0 == ASSIMP_strincmp(szCurrent,"end",3) &&
 		if (0 == ASSIMP_strincmp(szCurrent,"end",3) &&
 			IsSpaceOrNewLine(*(szCurrent+3)))
 			IsSpaceOrNewLine(*(szCurrent+3)))
@@ -730,7 +812,7 @@ void SMDImporter::ParseVASection(const char* szCurrent,
 			break;
 			break;
 		}
 		}
 		// "time <n>\n" 
 		// "time <n>\n" 
-		else if (0 == ASSIMP_strincmp(szCurrent,"time",4) &&
+		if (0 == ASSIMP_strincmp(szCurrent,"time",4) &&
 			IsSpaceOrNewLine(*(szCurrent+4)))
 			IsSpaceOrNewLine(*(szCurrent+4)))
 		{
 		{
 			szCurrent += 5;
 			szCurrent += 5;
@@ -767,6 +849,8 @@ void SMDImporter::ParseSkeletonSection(const char* szCurrent,
 	int iTime = 0;
 	int iTime = 0;
 	while (true)
 	while (true)
 	{
 	{
+		if(!SkipSpacesAndLineEnd(szCurrent,&szCurrent)) break;
+
 		// "end\n" - Ends the skeleton section
 		// "end\n" - Ends the skeleton section
 		if (0 == ASSIMP_strincmp(szCurrent,"end",3) &&
 		if (0 == ASSIMP_strincmp(szCurrent,"end",3) &&
 			IsSpaceOrNewLine(*(szCurrent+3)))
 			IsSpaceOrNewLine(*(szCurrent+3)))
@@ -796,6 +880,7 @@ void SMDImporter::ParseNodeInfo(const char* szCurrent,
 	const char** szCurrentOut)
 	const char** szCurrentOut)
 {
 {
 	unsigned int iBone  = 0;
 	unsigned int iBone  = 0;
+	SkipSpacesAndLineEnd(szCurrent,&szCurrent);
 	if(!this->ParseUnsignedInt(szCurrent,&szCurrent,iBone) || !SkipSpaces(szCurrent,&szCurrent))
 	if(!this->ParseUnsignedInt(szCurrent,&szCurrent,iBone) || !SkipSpaces(szCurrent,&szCurrent))
 	{
 	{
 		this->LogErrorNoThrow("Unexpected EOF/EOL while parsing bone index");
 		this->LogErrorNoThrow("Unexpected EOF/EOL while parsing bone index");
@@ -836,6 +921,8 @@ void SMDImporter::ParseNodeInfo(const char* szCurrent,
 		++szEnd;
 		++szEnd;
 	}
 	}
 	bone.mName = std::string(szCurrent,iBone);
 	bone.mName = std::string(szCurrent,iBone);
+	szCurrent = szEnd;
+
 	// the only negative bone parent index that could occur is -1 AFAIK
 	// the only negative bone parent index that could occur is -1 AFAIK
 	if(!this->ParseSignedInt(szCurrent,&szCurrent,(int&)bone.iParent))
 	if(!this->ParseSignedInt(szCurrent,&szCurrent,(int&)bone.iParent))
 	{
 	{
@@ -933,11 +1020,13 @@ void SMDImporter::ParseTriangle(const char* szCurrent,
 	}
 	}
 
 
 	// read the texture file name
 	// read the texture file name
-	const char* szEnd = szCurrent;
-	while (!IsSpaceOrNewLine(*szEnd++));
+	const char* szLast = szCurrent;
+	while (!IsSpaceOrNewLine(*szCurrent++));
 
 
-	face.iTexture = this->GetTextureIndex(std::string(szCurrent,
-		(uintptr_t)szEnd-(uintptr_t)szCurrent));
+	face.iTexture = this->GetTextureIndex(std::string(szLast,
+		(uintptr_t)szCurrent-(uintptr_t)szLast));
+
+	this->SkipLine(szCurrent,&szCurrent);
 
 
 	// load three vertices
 	// load three vertices
 	for (unsigned int iVert = 0; iVert < 3;++iVert)
 	for (unsigned int iVert = 0; iVert < 3;++iVert)
@@ -945,6 +1034,7 @@ void SMDImporter::ParseTriangle(const char* szCurrent,
 		this->ParseVertex(szCurrent,&szCurrent,
 		this->ParseVertex(szCurrent,&szCurrent,
 			face.avVertices[iVert]);
 			face.avVertices[iVert]);
 	}
 	}
+	*szCurrentOut = szCurrent;
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Parse a float
 // Parse a float

+ 3 - 3
code/SMDLoader.h

@@ -355,16 +355,16 @@ protected:
 		return true;
 		return true;
 	}
 	}
 	// -------------------------------------------------------------------
 	// -------------------------------------------------------------------
-	inline void SkipSpacesAndLineEnd( const char* in, const char** out)
+	inline bool SkipSpacesAndLineEnd( const char* in, const char** out)
 	{
 	{
-		::SkipSpacesAndLineEnd(in,out);
 		++iLineNumber;
 		++iLineNumber;
+		return ::SkipSpacesAndLineEnd(in,out);
 	}
 	}
 
 
 private:
 private:
 
 
 	/** Buffer to hold the loaded file */
 	/** Buffer to hold the loaded file */
-	const char* mBuffer;
+	char* mBuffer;
 
 
 	/** Output scene to be filled
 	/** Output scene to be filled
 	*/
 	*/

+ 96 - 57
code/ValidateDataStructure.cpp

@@ -155,7 +155,10 @@ void ValidateDSProcess::Execute( aiScene* pScene)
 			this->Validate(pScene->mMeshes[i]);
 			this->Validate(pScene->mMeshes[i]);
 		}
 		}
 	}
 	}
-	else this->ReportError("aiScene::mNumMeshes is 0. At least one mesh must be there");
+	else if (!(this->mScene->mFlags & AI_SCENE_FLAGS_ANIM_SKELETON_ONLY))
+	{
+		this->ReportError("aiScene::mNumMeshes is 0. At least one mesh must be there");
+	}
 
 
 	// validate all animations
 	// validate all animations
 	if (pScene->mNumAnimations)
 	if (pScene->mNumAnimations)
@@ -185,6 +188,11 @@ void ValidateDSProcess::Execute( aiScene* pScene)
 			}
 			}
 		}
 		}
 	}
 	}
+	else if (this->mScene->mFlags & AI_SCENE_FLAGS_ANIM_SKELETON_ONLY)
+	{
+		this->ReportError("aiScene::mNumAnimations is 0 and the "
+			"AI_SCENE_FLAGS_ANIM_SKELETON_ONLY flag is set.");
+	}
 
 
 	// validate all textures
 	// validate all textures
 	if (pScene->mNumTextures)
 	if (pScene->mNumTextures)
@@ -240,71 +248,83 @@ void ValidateDSProcess::Validate( const aiMesh* pMesh)
 			pMesh->mMaterialIndex,this->mScene->mNumMaterials-1);
 			pMesh->mMaterialIndex,this->mScene->mNumMaterials-1);
 	}
 	}
 
 
-	// positions must always be there ...
-	if (!pMesh->mNumVertices || !pMesh->mVertices)
+	if (this->mScene->mFlags & AI_SCENE_FLAGS_ANIM_SKELETON_ONLY)
 	{
 	{
-		this->ReportError("The mesh contains no vertices");
+		if (pMesh->mNumVertices || pMesh->mVertices ||
+			pMesh->mNumFaces || pMesh->mFaces)
+		{
+			this->ReportWarning("The mesh contains vertices and faces although "
+				"the AI_SCENE_FLAGS_ANIM_SKELETON_ONLY flag is set");
+		}
 	}
 	}
-
-	// faces, too
-	if (!pMesh->mNumFaces || !pMesh->mFaces)
+	else
 	{
 	{
-		this->ReportError("The mesh contains no faces");
-	}
+		// positions must always be there ...
+		if (!pMesh->mNumVertices || !pMesh->mVertices)
+		{
+			this->ReportError("The mesh contains no vertices");
+		}
 
 
-	// now check whether the face indexing layout is correct:
-	// unique vertices, pseudo-indexed.
-	std::vector<bool> abRefList;
-	abRefList.resize(pMesh->mNumVertices,false);
-	for (unsigned int i = 0; i < pMesh->mNumFaces;++i)
-	{
-		aiFace& face = pMesh->mFaces[i];
-		if (!face.mIndices)this->ReportError("aiMesh::mFaces[%i].mIndices is NULL",i);
-		if (face.mNumIndices < 3)this->ReportError(
-			"aiMesh::mFaces[%i].mIndices is not a triangle or polygon",i);
+		// faces, too
+		if (!pMesh->mNumFaces || !pMesh->mFaces)
+		{
+			this->ReportError("The mesh contains no faces");
+		}
 
 
-		for (unsigned int a = 0; a < face.mNumIndices;++a)
+		// now check whether the face indexing layout is correct:
+		// unique vertices, pseudo-indexed.
+		std::vector<bool> abRefList;
+		abRefList.resize(pMesh->mNumVertices,false);
+		for (unsigned int i = 0; i < pMesh->mNumFaces;++i)
 		{
 		{
-			if (face.mIndices[a] >= pMesh->mNumVertices)
-			{
-				this->ReportError("aiMesh::mFaces[%i]::mIndices[%a] is out of range",i,a);
-			}
-			if (abRefList[face.mIndices[a]])
+			aiFace& face = pMesh->mFaces[i];
+			if (!face.mIndices)this->ReportError("aiMesh::mFaces[%i].mIndices is NULL",i);
+			if (face.mNumIndices < 3)this->ReportError(
+				"aiMesh::mFaces[%i].mIndices is not a triangle or polygon",i);
+
+			for (unsigned int a = 0; a < face.mNumIndices;++a)
 			{
 			{
-				this->ReportError("aiMesh::mVertices[%i] is referenced twice - second "
-					"time by aiMesh::mFaces[%i]::mIndices[%i]",face.mIndices[a],i,a);
+				if (face.mIndices[a] >= pMesh->mNumVertices)
+				{
+					this->ReportError("aiMesh::mFaces[%i]::mIndices[%a] is out of range",i,a);
+				}
+				if (abRefList[face.mIndices[a]])
+				{
+					this->ReportError("aiMesh::mVertices[%i] is referenced twice - second "
+						"time by aiMesh::mFaces[%i]::mIndices[%i]",face.mIndices[a],i,a);
+				}
+				abRefList[face.mIndices[a]] = true;
 			}
 			}
-			abRefList[face.mIndices[a]] = true;
 		}
 		}
-	}
-	abRefList.clear();
+		abRefList.clear();
 
 
-	// texture channel 2 may not be set if channel 1 is zero ...
-	{
-	unsigned int i = 0;
-	for (;i < AI_MAX_NUMBER_OF_TEXTURECOORDS;++i)
-	{
-		if (!pMesh->HasTextureCoords(i))break;
-	}
-	for (;i < AI_MAX_NUMBER_OF_TEXTURECOORDS;++i)
-		if (pMesh->HasTextureCoords(i))
+		// texture channel 2 may not be set if channel 1 is zero ...
 		{
 		{
-			this->ReportError("Texture coordinate channel %i is existing, "
-				"although the previous channel was NULL.",i);
+			unsigned int i = 0;
+			for (;i < AI_MAX_NUMBER_OF_TEXTURECOORDS;++i)
+			{
+				if (!pMesh->HasTextureCoords(i))break;
+			}
+			for (;i < AI_MAX_NUMBER_OF_TEXTURECOORDS;++i)
+				if (pMesh->HasTextureCoords(i))
+				{
+					this->ReportError("Texture coordinate channel %i is existing, "
+						"although the previous channel was NULL.",i);
+				}
 		}
 		}
-	}
-	// the same for the vertex colors
-	{
-	unsigned int i = 0;
-	for (;i < AI_MAX_NUMBER_OF_COLOR_SETS;++i)
-	{
-		if (!pMesh->HasVertexColors(i))break;
-	}
-	for (;i < AI_MAX_NUMBER_OF_COLOR_SETS;++i)
-		if (pMesh->HasVertexColors(i))
+		// the same for the vertex colors
 		{
 		{
-			this->ReportError("Vertex color channel %i is existing, "
-				"although the previous channel was NULL.",i);
+			unsigned int i = 0;
+			for (;i < AI_MAX_NUMBER_OF_COLOR_SETS;++i)
+			{
+				if (!pMesh->HasVertexColors(i))break;
+			}
+			for (;i < AI_MAX_NUMBER_OF_COLOR_SETS;++i)
+				if (pMesh->HasVertexColors(i))
+				{
+					this->ReportError("Vertex color channel %i is existing, "
+						"although the previous channel was NULL.",i);
+				}
 		}
 		}
 	}
 	}
 
 
@@ -316,6 +336,13 @@ void ValidateDSProcess::Validate( const aiMesh* pMesh)
 			this->ReportError("aiMesh::mBones is NULL (aiMesh::mNumBones is %i)",
 			this->ReportError("aiMesh::mBones is NULL (aiMesh::mNumBones is %i)",
 				pMesh->mNumBones);
 				pMesh->mNumBones);
 		}
 		}
+		float* afSum = NULL;
+		if (pMesh->mNumVertices)
+		{
+			afSum = new float[pMesh->mNumVertices];
+			for (unsigned int i = 0; i < pMesh->mNumVertices;++i)
+				afSum[i] = 0.0f;
+		}
 
 
 		// check whether there are duplicate bone names
 		// check whether there are duplicate bone names
 		for (unsigned int i = 0; i < pMesh->mNumBones;++i)
 		for (unsigned int i = 0; i < pMesh->mNumBones;++i)
@@ -325,7 +352,7 @@ void ValidateDSProcess::Validate( const aiMesh* pMesh)
 				this->ReportError("aiMesh::mBones[%i] is NULL (aiMesh::mNumBones is %i)",
 				this->ReportError("aiMesh::mBones[%i] is NULL (aiMesh::mNumBones is %i)",
 					i,pMesh->mNumBones);
 					i,pMesh->mNumBones);
 			}
 			}
-			this->Validate(pMesh,pMesh->mBones[i]);
+			this->Validate(pMesh,pMesh->mBones[i],afSum);
 
 
 			for (unsigned int a = i+1; a < pMesh->mNumBones;++a)
 			for (unsigned int a = i+1; a < pMesh->mNumBones;++a)
 			{
 			{
@@ -336,11 +363,22 @@ void ValidateDSProcess::Validate( const aiMesh* pMesh)
 				}
 				}
 			}
 			}
 		}
 		}
+		// check whether all bone weights for a vertex sum to 1.0 ...
+		for (unsigned int i = 0; i < pMesh->mNumVertices;++i)
+		{
+			if (afSum[i] && (afSum[i] <= 0.995 || afSum[i] >= 1.005))
+			{
+				delete[] afSum;
+				this->ReportError("aiMesh::mVertices[%i]: The sum of all bone "
+					"weights isn't 1.0f (sum is %f)",i,afSum[i]);
+			}
+		}
+		delete[] afSum;
 	}
 	}
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void ValidateDSProcess::Validate( const aiMesh* pMesh,
 void ValidateDSProcess::Validate( const aiMesh* pMesh,
-	const aiBone* pBone)
+	const aiBone* pBone,float* afSum)
 {
 {
 	this->Validate(&pBone->mName);
 	this->Validate(&pBone->mName);
 
 
@@ -355,8 +393,8 @@ void ValidateDSProcess::Validate( const aiMesh* pMesh,
 		{
 		{
 			this->ReportWarning("aiBone::mWeights[%i].mWeight has an invalid value",i);
 			this->ReportWarning("aiBone::mWeights[%i].mWeight has an invalid value",i);
 		}
 		}
+		afSum[pBone->mWeights[i].mVertexId] += pBone->mWeights[i].mWeight;
 	}
 	}
-	// TODO: check whether all bone weights for a vertex sum to 1.0 ...
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void ValidateDSProcess::Validate( const aiAnimation* pAnimation)
 void ValidateDSProcess::Validate( const aiAnimation* pAnimation)
@@ -606,9 +644,10 @@ void ValidateDSProcess::Validate( const aiAnimation* pAnimation,
 		for (unsigned int a = 0; a < mesh->mNumBones;++a)
 		for (unsigned int a = 0; a < mesh->mNumBones;++a)
 		{
 		{
 			if (mesh->mBones[a]->mName == pBoneAnim->mBoneName)
 			if (mesh->mBones[a]->mName == pBoneAnim->mBoneName)
-				break;
+				goto __break_out;
 		}
 		}
 	}
 	}
+__break_out:
 	if (i == this->mScene->mNumMeshes)
 	if (i == this->mScene->mNumMeshes)
 	{
 	{
 		this->ReportWarning("aiBoneAnim::mBoneName is %s. However, no bone with this name was found",
 		this->ReportWarning("aiBoneAnim::mBoneName is %s. However, no bone with this name was found",

+ 1 - 1
code/ValidateDataStructure.h

@@ -115,7 +115,7 @@ protected:
 	 * @param pMesh Input mesh
 	 * @param pMesh Input mesh
 	 * @param pBone Input bone
 	 * @param pBone Input bone
 	 */
 	 */
-	void Validate( const aiMesh* pMesh,const aiBone* pBone);
+	void Validate( const aiMesh* pMesh,const aiBone* pBone,float* afSum);
 
 
 	// -------------------------------------------------------------------
 	// -------------------------------------------------------------------
 	/** Validates an animation
 	/** Validates an animation

+ 12 - 6
include/aiAnim.h

@@ -106,9 +106,12 @@ struct aiBoneAnim
 
 
 	~aiBoneAnim()
 	~aiBoneAnim()
 	{
 	{
-		delete [] mPositionKeys;
-		delete [] mRotationKeys;
-		delete [] mScalingKeys;
+		if (mNumPositionKeys)
+			delete [] mPositionKeys;
+		if (mNumRotationKeys)
+			delete [] mRotationKeys;
+		if (mNumScalingKeys)
+			delete [] mScalingKeys;
 	}
 	}
 #endif // __cplusplus
 #endif // __cplusplus
 };
 };
@@ -145,9 +148,12 @@ struct aiAnimation
 
 
 	~aiAnimation()
 	~aiAnimation()
 	{
 	{
-		for( unsigned int a = 0; a < mNumBones; a++)
-			delete mBones[a];
-		delete [] mBones;
+		if (mNumBones)
+		{
+			for( unsigned int a = 0; a < mNumBones; a++)
+				delete mBones[a];
+			delete [] mBones;
+		}
 	}
 	}
 #endif // __cplusplus
 #endif // __cplusplus
 };
 };

+ 24 - 14
include/aiMesh.h

@@ -78,7 +78,8 @@ struct aiFace
 	//! Default destructor. Delete the index array
 	//! Default destructor. Delete the index array
 	~aiFace()
 	~aiFace()
 	{
 	{
-		delete [] mIndices;
+		if (mNumIndices)
+			delete [] mIndices;
 	}
 	}
 
 
 	//! Copy constructor. Copy the index array
 	//! Copy constructor. Copy the index array
@@ -202,7 +203,7 @@ struct aiBone
 	//! Destructor to delete the array of vertex weights
 	//! Destructor to delete the array of vertex weights
 	~aiBone()
 	~aiBone()
 	{
 	{
-		delete [] mWeights;
+		if (mNumWeights)delete [] mWeights;
 	}
 	}
 #endif // __cplusplus
 #endif // __cplusplus
 };
 };
@@ -367,18 +368,27 @@ struct aiMesh
 
 
 	~aiMesh()
 	~aiMesh()
 	{
 	{
-		delete [] mVertices; 
-		delete [] mFaces;
-		delete [] mNormals;
-		delete [] mTangents;
-		delete [] mBitangents;
-		for( unsigned int a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; a++)
-			delete [] mTextureCoords[a];
-		for( unsigned int a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; a++)
-			delete [] mColors[a];
-		for( unsigned int a = 0; a < mNumBones; a++)
-			delete mBones[a];
-		delete [] mBones;
+		if ( mNumVertices) // fix to make this work for invalid scenes, too
+		{
+			delete [] mVertices; 
+			delete [] mNormals;
+			delete [] mTangents;
+			delete [] mBitangents;
+			for( unsigned int a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; a++)
+				delete [] mTextureCoords[a];
+			for( unsigned int a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; a++)
+				delete [] mColors[a];
+		}
+		if ( mNumBones) // fix to make this work for invalid scenes, too
+		{
+			for( unsigned int a = 0; a < mNumBones; a++)
+				delete mBones[a];
+			delete [] mBones;
+		}
+		if ( mNumFaces) // fix to make this work for invalid scenes, too
+		{
+			delete [] mFaces;
+		}
 	}
 	}
 
 
 	//! Check whether the mesh contains positions. Should always return true
 	//! Check whether the mesh contains positions. Should always return true

+ 36 - 12
include/aiScene.h

@@ -112,6 +112,12 @@ struct aiNode
 #endif // __cplusplus
 #endif // __cplusplus
 };
 };
 
 
+//! @def AI_SCENE_FLAGS_ANIM_SKELETON_ONLY
+//! Specifies that no model but only an animation skeleton has been
+//! imported. There are no materials in this case. There are no
+//! textures in this case. But there is a node graph, animation channels
+//! and propably meshes with bones.
+#define AI_SCENE_FLAGS_ANIM_SKELETON_ONLY	0x1
 
 
 // ---------------------------------------------------------------------------
 // ---------------------------------------------------------------------------
 /** The root structure of the imported data. 
 /** The root structure of the imported data. 
@@ -121,6 +127,11 @@ struct aiNode
 // ---------------------------------------------------------------------------
 // ---------------------------------------------------------------------------
 struct aiScene
 struct aiScene
 {
 {
+
+	/** Any combination of the AI_SCENE_FLAGS_XXX flags */
+	unsigned int mFlags;
+
+
 	/** The root node of the hierarchy. 
 	/** The root node of the hierarchy. 
 	* 
 	* 
 	* There will always be at least the root node if the import
 	* There will always be at least the root node if the import
@@ -189,6 +200,7 @@ struct aiScene
 		mNumMaterials = 0; mMaterials = NULL;
 		mNumMaterials = 0; mMaterials = NULL;
 		mNumAnimations = 0; mAnimations = NULL;
 		mNumAnimations = 0; mAnimations = NULL;
 		mNumTextures = 0; mTextures = NULL;
 		mNumTextures = 0; mTextures = NULL;
+		mFlags = 0;
 	}
 	}
 
 
 	//! Destructor
 	//! Destructor
@@ -196,18 +208,30 @@ struct aiScene
 	{
 	{
 		// delete all subobjects recursively
 		// delete all subobjects recursively
 		delete mRootNode;
 		delete mRootNode;
-		for( unsigned int a = 0; a < mNumMeshes; a++)
-			delete mMeshes[a];
-		delete [] mMeshes;
-		for( unsigned int a = 0; a < mNumMaterials; a++)
-			delete mMaterials[a];
-		delete [] mMaterials;
-		for( unsigned int a = 0; a < mNumAnimations; a++)
-			delete mAnimations[a];
-		delete [] mAnimations;
-		for( unsigned int a = 0; a < mNumTextures; a++)
-			delete mTextures[a];
-		delete [] mTextures;
+		if (mNumMeshes) // fix to make the d'tor work for invalid scenes, too
+		{
+			for( unsigned int a = 0; a < mNumMeshes; a++)
+				delete mMeshes[a];
+			delete [] mMeshes;
+		}
+		if (mNumMaterials) // fix to make the d'tor work for invalid scenes, too
+		{
+			for( unsigned int a = 0; a < mNumMaterials; a++)
+				delete mMaterials[a];
+			delete [] mMaterials;
+		}
+		if (mNumAnimations) // fix to make the d'tor work for invalid scenes, too
+		{
+			for( unsigned int a = 0; a < mNumAnimations; a++)
+				delete mAnimations[a];
+			delete [] mAnimations;
+		}
+		if (mNumTextures) // fix to make the d'tor work for invalid scenes, too
+		{
+			for( unsigned int a = 0; a < mNumTextures; a++)
+				delete mTextures[a];
+			delete [] mTextures;
+		}
 	}
 	}
 #endif // __cplusplus
 #endif // __cplusplus
 };
 };

BIN
tools/assimp_view/assimp_view.aps


+ 123 - 78
tools/assimp_view/help.rtf

@@ -1,13 +1,14 @@
-{\rtf1\adeflang1025\ansi\ansicpg1252\uc1\adeff31507\deff0\stshfdbch31505\stshfloch31506\stshfhich31506\stshfbi31507\deflang1031\deflangfe1031\themelang1031\themelangfe0\themelangcs0{\fonttbl{\f0\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f34\fbidi \froman\fcharset1\fprq2{\*\panose 02040503050406030204}Cambria Math;}
-{\f39\fbidi \fswiss\fcharset0\fprq2{\*\panose 020b0604030504040204}MS Reference Sans Serif;}{\flomajor\f31500\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}
+{\rtf1\adeflang1025\ansi\ansicpg1252\uc1\adeff31507\deff0\stshfdbch31505\stshfloch31506\stshfhich31506\stshfbi0\deflang1031\deflangfe1031\themelang1031\themelangfe0\themelangcs0{\fonttbl{\f0\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f34\fbidi \froman\fcharset0\fprq2{\*\panose 02040503050406030204}Cambria Math;}
+{\f39\fbidi \fswiss\fcharset0\fprq2{\*\panose 00000000000000000000}MS Reference Sans Serif;}{\flomajor\f31500\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}
 {\fdbmajor\f31501\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhimajor\f31502\fbidi \froman\fcharset0\fprq2{\*\panose 02040503050406030204}Cambria;}
 {\fdbmajor\f31501\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhimajor\f31502\fbidi \froman\fcharset0\fprq2{\*\panose 02040503050406030204}Cambria;}
 {\fbimajor\f31503\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\flominor\f31504\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}
 {\fbimajor\f31503\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\flominor\f31504\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}
 {\fdbminor\f31505\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhiminor\f31506\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;}
 {\fdbminor\f31505\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhiminor\f31506\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;}
-{\fbiminor\f31507\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f40\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\f41\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
-{\f43\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f44\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f45\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\f46\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}
-{\f47\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f48\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f430\fbidi \fswiss\fcharset238\fprq2 MS Reference Sans Serif CE;}
-{\f431\fbidi \fswiss\fcharset204\fprq2 MS Reference Sans Serif Cyr;}{\f433\fbidi \fswiss\fcharset161\fprq2 MS Reference Sans Serif Greek;}{\f434\fbidi \fswiss\fcharset162\fprq2 MS Reference Sans Serif Tur;}
-{\f437\fbidi \fswiss\fcharset186\fprq2 MS Reference Sans Serif Baltic;}{\f438\fbidi \fswiss\fcharset163\fprq2 MS Reference Sans Serif (Vietnamese);}{\flomajor\f31508\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}
+{\fbiminor\f31507\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f41\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\f42\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
+{\f44\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f45\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f46\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\f47\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}
+{\f48\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f49\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f381\fbidi \froman\fcharset238\fprq2 Cambria Math CE;}{\f382\fbidi \froman\fcharset204\fprq2 Cambria Math Cyr;}
+{\f384\fbidi \froman\fcharset161\fprq2 Cambria Math Greek;}{\f385\fbidi \froman\fcharset162\fprq2 Cambria Math Tur;}{\f388\fbidi \froman\fcharset186\fprq2 Cambria Math Baltic;}{\f431\fbidi \fswiss\fcharset238\fprq2 MS Reference Sans Serif CE;}
+{\f432\fbidi \fswiss\fcharset204\fprq2 MS Reference Sans Serif Cyr;}{\f434\fbidi \fswiss\fcharset161\fprq2 MS Reference Sans Serif Greek;}{\f435\fbidi \fswiss\fcharset162\fprq2 MS Reference Sans Serif Tur;}
+{\f438\fbidi \fswiss\fcharset186\fprq2 MS Reference Sans Serif Baltic;}{\f439\fbidi \fswiss\fcharset163\fprq2 MS Reference Sans Serif (Vietnamese);}{\flomajor\f31508\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}
 {\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}
 {\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}
 {\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}
 {\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}
 {\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbmajor\f31518\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fdbmajor\f31519\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
 {\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbmajor\f31518\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fdbmajor\f31519\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
@@ -32,10 +33,10 @@
 \ql \li0\ri0\sa200\sl276\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \fs22\lang1031\langfe1031\loch\f31506\hich\af31506\dbch\af31505\cgrid\langnp1031\langfenp1031 
 \ql \li0\ri0\sa200\sl276\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \fs22\lang1031\langfe1031\loch\f31506\hich\af31506\dbch\af31505\cgrid\langnp1031\langfenp1031 
 \snext0 \sqformat \spriority0 Normal;}{\*\cs10 \additive \ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\*
 \snext0 \sqformat \spriority0 Normal;}{\*\cs10 \additive \ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\*
 \ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tscellwidthfts0\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv \ql \li0\ri0\sa200\sl276\slmult1
 \ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tscellwidthfts0\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv \ql \li0\ri0\sa200\sl276\slmult1
-\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \fs22\lang1031\langfe1031\loch\f31506\hich\af31506\dbch\af31505\cgrid\langnp1031\langfenp1031 
-\snext11 \ssemihidden \sunhideused \sqformat Normal Table;}}{\*\rsidtbl \rsid3942873\rsid5053873\rsid5338181\rsid6912689\rsid7213193\rsid8068556\rsid9465848\rsid13463017}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0\mrMargin0
-\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}{\info{\operator Alexander Gessler}{\creatim\yr2008\mo5\dy13\hr12\min5}{\revtim\yr2008\mo5\dy13\hr12\min23}{\version8}{\edmins0}{\nofpages4}{\nofwords1365}{\nofchars6976}{\nofcharsws8325}{\vern32893}}
-{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/wordml}}\paperw12240\paperh15840\margl1417\margr1417\margt1417\margb1134\gutter0\ltrsect 
+\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs22\alang1025 \ltrch\fcs0 \fs22\lang1031\langfe1031\loch\f31506\hich\af31506\dbch\af31505\cgrid\langnp1031\langfenp1031 
+\snext11 \ssemihidden \sunhideused \sqformat Normal Table;}}{\*\rsidtbl \rsid2432159\rsid3942873\rsid5053873\rsid5113869\rsid5338181\rsid6912689\rsid6976329\rsid7213193\rsid8068556\rsid9465848\rsid13463017}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0
+\msmallFrac0\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}{\info{\operator Alexander Gessler}{\creatim\yr2008\mo5\dy13\hr12\min5}{\revtim\yr2008\mo7\dy8\hr13\min6}{\version11}{\edmins0}{\nofpages5}{\nofwords1505}{\nofchars7670}
+{\nofcharsws9157}{\vern32895}}{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/wordml}}\paperw12240\paperh15840\margl1417\margr1417\margt1417\margb1134\gutter0\ltrsect 
 \deftab708\widowctrl\ftnbj\aenddoc\hyphhotz425\trackmoves1\trackformatting1\donotembedsysfont0\relyonvml0\donotembedlingdata1\grfdocevents0\validatexml0\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors0\horzdoc\dghspace120\dgvspace120
 \deftab708\widowctrl\ftnbj\aenddoc\hyphhotz425\trackmoves1\trackformatting1\donotembedsysfont0\relyonvml0\donotembedlingdata1\grfdocevents0\validatexml0\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors0\horzdoc\dghspace120\dgvspace120
 \dghorigin1701\dgvorigin1984\dghshow0\dgvshow3\jcompress\viewkind1\viewscale91\viewzk1\rsidroot13463017 \fet0{\*\wgrffmtfilter 2450}\ilfomacatclnup0\ltrpar \sectd \ltrsect\linex0\sectdefaultcl\sftnbj {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang 
 \dghorigin1701\dgvorigin1984\dghshow0\dgvshow3\jcompress\viewkind1\viewscale91\viewzk1\rsidroot13463017 \fet0{\*\wgrffmtfilter 2450}\ilfomacatclnup0\ltrpar \sectd \ltrsect\linex0\sectdefaultcl\sftnbj {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang 
 {\pntxta .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}
 {\pntxta .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}
@@ -46,23 +47,24 @@
 \par }{\rtlch\fcs1 \ab\af39\afs16 \ltrch\fcs0 \b\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 Document version 1.0, April 2008
 \par }{\rtlch\fcs1 \ab\af39\afs16 \ltrch\fcs0 \b\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 Document version 1.0, April 2008
 \par }{\rtlch\fcs1 \ab\af39\afs24 \ltrch\fcs0 \f39\fs24\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 
 \par }{\rtlch\fcs1 \ab\af39\afs24 \ltrch\fcs0 \f39\fs24\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 
 \par }\pard \ltrpar\ql \fi-360\li340\ri0\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin0\lin340\itap0 {\rtlch\fcs1 \af39\afs24 \ltrch\fcs0 \f39\fs24\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 I.\tab 
 \par }\pard \ltrpar\ql \fi-360\li340\ri0\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin0\lin340\itap0 {\rtlch\fcs1 \af39\afs24 \ltrch\fcs0 \f39\fs24\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 I.\tab 
-\hich\af39\dbch\af31505\loch\f39 Usage\hich\af39\dbch\af31505\loch\f39  
+Usage 
 \par }\pard \ltrpar\qj \li340\ri850\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin850\lin340\itap0\pararsid5338181 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 
 \par }\pard \ltrpar\qj \li340\ri850\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin850\lin340\itap0\pararsid5338181 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 
 The ASSIMP Viewer Utility is a small and fast stand-alone viewer utility which is us\hich\af39\dbch\af31505\loch\f39 
 The ASSIMP Viewer Utility is a small and fast stand-alone viewer utility which is us\hich\af39\dbch\af31505\loch\f39 
 ing the ASSIMP library to import assets from files. It consists of a single executable file and has no external dependencies. It displays assets with even highly complex materials and bone animations correctly.
 ing the ASSIMP library to import assets from files. It consists of a single executable file and has no external dependencies. It displays assets with even highly complex materials and bone animations correctly.
-\par \hich\af39\dbch\af31505\loch\f39 AssimpView allows you to modify the textures \hich\af39\dbch\af31505\loch\f39 
-and material properties sets of your models at runtime, easy and fast per Drag&Drop. Furthermore it can visualize normals, UV sets, bounding boxes ... It allows artists and engine }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 
-\f39\fs20\lang1033\langfe1031\langnp1033\insrsid6912689 \hich\af39\dbch\af31505\loch\f39 programmers}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 
- to work together with maximal efficiency and find the ideal mod\hich\af39\dbch\af31505\loch\f39 el format for your internal workflow.
+\par \hich\af39\dbch\af31505\loch\f39 AssimpView allows you to modify the textures \hich\af39\dbch\af31505\loch\f39 and material properties sets of your models at runtime, }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 
+\f39\fs20\lang1033\langfe1031\langnp1033\insrsid6976329 \hich\af39\dbch\af31505\loch\f39 easily}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 
+ and fast per Drag&Drop. Furthermore it can visualize normals, UV sets, bounding boxes ... It allows artists and engine }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid6912689 \hich\af39\dbch\af31505\loch\f39 
+programmers}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39  to work together with maximal efficiency and find the ideal mode\hich\af39\dbch\af31505\loch\f39 
+l format for your internal workflow.
 \par }{\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 System requirements}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 
 \par }{\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 System requirements}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 
 \hich\af39\dbch\af31505\loch\f39 :
 \hich\af39\dbch\af31505\loch\f39 :
 \par }\pard \ltrpar\qj \fi-360\li700\ri850\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin850\lin700\itap0\pararsid5338181 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 -\tab 
 \par }\pard \ltrpar\qj \fi-360\li700\ri850\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin850\lin700\itap0\pararsid5338181 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 -\tab 
-\hich\af39\dbch\af31505\loch\f39 A Direct3D 9.0c compliant video card with at least support for Shader Model 2.0.}{\rtlch\fcs1 \af39\afs24 \ltrch\fcs0 \f39\fs24\lang1033\langfe1031\langnp1033\insrsid5338181 
-\par }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 -\tab \hich\af39\dbch\af31505\loch\f39 Shader Model 3.0 cards are recommended. Shader Model 2.0 cards always render the lighting in low-quality and hav
-\hich\af39\dbch\af31505\loch\f39 e some limitations concerning complex materials.}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39  
-\par }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 -\tab \hich\af39\dbch\af31505\loch\f39 
-Windows 2000 or higher. AssimpView should also run on older versions, with DirectX 9 installed, but this has never been tested. If you're a proud owner of such a version and AssimpView works for you, let u\hich\af39\dbch\af31505\loch\f39 s know.}{
-\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid8068556\charrsid8068556 
+\hich\af39\dbch\af31505\loch\f39 A Direct3D 9.0c compliant video card with }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \b\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid6976329 \hich\af39\dbch\af31505\loch\f39 at least}{\rtlch\fcs1 \af39\afs20 
+\ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39  support for Shader Model 2.0.}{\rtlch\fcs1 \af39\afs24 \ltrch\fcs0 \f39\fs24\lang1033\langfe1031\langnp1033\insrsid5338181 
+\par }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 -\tab \hich\af39\dbch\af31505\loch\f39 Shader Model 3.0 cards are recommended. Shader Model 2.0 cards always render the lighting in low-quality and have
+\hich\af39\dbch\af31505\loch\f39  some limitations concerning complex materials. 
+\par -\tab \hich\af39\dbch\af31505\loch\f39 Windows 2000 or higher. AssimpView should also run on older versions, with DirectX 9 installed, but this has never been tested. If you're a proud owner of such a version and AssimpView works for you, let u
+\hich\af39\dbch\af31505\loch\f39 s know.}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid8068556\charrsid8068556 
 \par }\pard \ltrpar\ql \li340\ri0\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin0\lin340\itap0 {\rtlch\fcs1 \af39\afs24 \ltrch\fcs0 \f39\fs24\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39  
 \par }\pard \ltrpar\ql \li340\ri0\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin0\lin340\itap0 {\rtlch\fcs1 \af39\afs24 \ltrch\fcs0 \f39\fs24\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39  
 \par }\pard \ltrpar\ql \fi-360\li340\ri0\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin0\lin340\itap0 {\rtlch\fcs1 \af39\afs24 \ltrch\fcs0 \f39\fs24\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 II.\tab 
 \par }\pard \ltrpar\ql \fi-360\li340\ri0\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin0\lin340\itap0 {\rtlch\fcs1 \af39\afs24 \ltrch\fcs0 \f39\fs24\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 II.\tab 
 User interface
 User interface
@@ -85,17 +87,17 @@ The menu bar provides access to the viewer\hich\f39 \rquote \loch\f39 s basic op
 ": Save a snapshot of the viewer to a file. The snapshot does only include the preview panel, the rest of the user interface is not visible. Saved screenshots have no watermarks. This is }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 ": Save a snapshot of the viewer to a file. The snapshot does only include the preview panel, the rest of the user interface is not visible. Saved screenshots have no watermarks. This is }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid6912689 \hich\af39\dbch\af31505\loch\f39 Open Source}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39  Software!
 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid6912689 \hich\af39\dbch\af31505\loch\f39 Open Source}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39  Software!
 \par \tab \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 Reset View}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \par \tab \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 Reset View}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
-\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 ": Reset the viewing position and direction to the default settings. This is: Camera position at 0|0|0, looking at 0|0|1.
+\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 "\hich\af39\dbch\af31505\loch\f39 : Reset the viewing position and direction to the default settings. This is: Camera position at 0|0|0, looking at 0|0|1.
 \par \tab \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 Memory consumption}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \par \tab \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 Memory consumption}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
-\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 ": Display a box with approximated memory statistics. Note that the total memory consumption needn'\hich\af39\dbch\af31505\loch\f39 
-t be equal to the physical memory allocated to the process (as displayed by Windows }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid6912689 \hich\af39\dbch\af31505\loch\f39 Task Manager}{\rtlch\fcs1 \af39\afs16 
-\ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 ). The memory consumption does only refer to the memory required for the asset itself, not for the rest of the viewer.
+\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 ": Display a box with approximated memory statistics. Note that the total memory consumption needn't be equal to \hich\af39\dbch\af31505\loch\f39 
+the physical memory allocated to the process (as displayed by Windows }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid6912689 \hich\af39\dbch\af31505\loch\f39 Task Manager}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
+\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 ). The memory consumption does only refer to the memory required for the asset itself, not for the rest of the viewer.
 \par \tab \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 Setup file associations}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \par \tab \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 Setup file associations}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
-\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 ": Associate al\hich\af39\dbch\af31505\loch\f39 l file formats which can be read by ASSIMP with the viewer. If you double }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
+\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 ": Associate all file format\hich\af39\dbch\af31505\loch\f39 s which can be read by ASSIMP with the viewer. If you double }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid6912689 \hich\af39\dbch\af31505\loch\f39 click}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 
 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid6912689 \hich\af39\dbch\af31505\loch\f39 click}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 
  on such a file in Windows Explorer, it will be automatically opened with AssimpView.
  on such a file in Windows Explorer, it will be automatically opened with AssimpView.
 \par \tab \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 Recent files}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \par \tab \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 Recent files}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
-\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 ": Displays a popup menu with a list of recently opened assets. Simply cli\hich\af39\dbch\af31505\loch\f39 ck on one to reload it.
+\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 ": Displays a popup menu with a list of recently opened assets. Simply click on one to r\hich\af39\dbch\af31505\loch\f39 eload it.
 \par \tab \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 Clear history}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \par \tab \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 Clear history}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 ": Clear the file history
 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 ": Clear the file history
 \par \tab \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 Quit}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \par \tab \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 Quit}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
@@ -114,13 +116,13 @@ low, errors in red, debug messages in blue and status }{\rtlch\fcs1 \af39\afs16
 \par \tab \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 Clear log}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \par \tab \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 Clear log}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 " Clear the contents of the log window
 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 " Clear the contents of the log window
 \par \tab \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 Original normals}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \par \tab \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 Original normals}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
-\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 ": Specifies that the original norma\hich\af39\dbch\af31505\loch\f39 
-l set from the model file is used in the preview. Normally this is the best choice, as smoothing groups etc. are handled correctly, but there are many models out there which have invalid or corrupt normal sets. However, if a model has no normal set, ASSIM
-\hich\af39\dbch\af31505\loch\f39 P\hich\af39\dbch\af31505\loch\f39  computes a smooth normal set for it. "Original normals" is in this case equivalent to "Smooth normals"
+\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 ": Specifies that the origin\hich\af39\dbch\af31505\loch\f39 
+al normal set from the model file is used in the preview. Normally this is the best choice, as smoothing groups etc. are handled correctly, but there are many models out there which have invalid or corrupt normal sets. However, if a model has no normal se
+\hich\af39\dbch\af31505\loch\f39 t\hich\af39\dbch\af31505\loch\f39 , ASSIMP computes a smooth normal set for it. "Original normals" is in this case equivalent to "Smooth normals"
 \par \tab \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 Smooth normals}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \par \tab \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 Smooth normals}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 ": Specifies that a smoothed, per-vertex, normal set is used in the preview window.
 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 ": Specifies that a smoothed, per-vertex, normal set is used in the preview window.
 \par \tab \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 Hard normals}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \par \tab \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 Hard normals}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
-\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 ": Specifies that a hard, per-face\hich\af39\dbch\af31505\loch\f39 , normal set is used
+\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 ": Specifies that a hard, \hich\af39\dbch\af31505\loch\f39 per-face, normal set is used
 \par \tab \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 Flip normals}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \par \tab \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 Flip normals}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 ": Flip all normal vectors
 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 ": Flip all normal vectors
 \par }\pard \ltrpar\qj \fi-360\li720\ri850\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin850\lin720\itap0\pararsid5338181 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 -}{\rtlch\fcs1 \af39\afs20 
 \par }\pard \ltrpar\qj \fi-360\li720\ri850\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin850\lin720\itap0\pararsid5338181 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 -}{\rtlch\fcs1 \af39\afs20 
@@ -130,22 +132,23 @@ l set from the model file is used in the preview. Normally this is the best choi
 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5338181 \hich\af39\dbch\af31505\loch\f39  are supported as skyboxes) as background image for the viewer.
 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5338181 \hich\af39\dbch\af31505\loch\f39  are supported as skyboxes) as background image for the viewer.
 \par }\pard \ltrpar\qj \fi-360\li720\ri1208\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin1208\lin720\itap0\pararsid3942873 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \tab }{\rtlch\fcs1 \af39\afs16 
 \par }\pard \ltrpar\qj \fi-360\li720\ri1208\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin1208\lin720\itap0\pararsid3942873 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \tab }{\rtlch\fcs1 \af39\afs16 
 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 
 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 
-\hich\af39\dbch\af31505\loch\f39 Set color}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 ": Displays a color picker where you can choose 
-\hich\af39\dbch\af31505\loch\f39 a new background color for the viewer.
+\hich\af39\dbch\af31505\loch\f39 Set color}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 
+": Displays a color picker where you can choose a new background color for the viewer.
 \par \tab \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 Load skybox}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \par \tab \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 Load skybox}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 ": Opens a file system dialog where you can select a skybox as background image. Accepted file formats are: *.dds, *.hdr.
 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 ": Opens a file system dialog where you can select a skybox as background image. Accepted file formats are: *.dds, *.hdr.
 \par \tab \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 Load texture}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \par \tab \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 Load texture}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
-\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 ": Opens a file system dialog where you can select a texture as \hich\af39\dbch\af31505\loch\f39 
-background image. If the format of the texture doesn't fit to the proportions of the preview window, the texture will be stretched. }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid3942873 
+\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 ": Opens a \hich\af39\dbch\af31505\loch\f39 
+file system dialog where you can select a texture as background image. If the format of the texture doesn't fit to the proportions of the preview window, the texture will be stretched. }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
+\f39\fs16\lang1033\langfe1031\langnp1033\insrsid3942873 
 \par }\pard \ltrpar\qj \fi-12\li720\ri1208\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin1208\lin720\itap0\pararsid3942873 {\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 
 \par }\pard \ltrpar\qj \fi-12\li720\ri1208\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin1208\lin720\itap0\pararsid3942873 {\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 
-\hich\af39\dbch\af31505\loch\f39 Accepted file formats are: *.jpg;*.png;*.tif;*.tga;*.dds;*.hdr;*.ppm;*.bmp
+\hich\af39\dbch\af31505\loch\f39 Accepted file formats are: *.jpg;*.png;*.tif;*.tga;*.dds;*.hdr;*.ppm;*\hich\af39\dbch\af31505\loch\f39 .bmp
 \par }\pard \ltrpar\ql \fi-360\li720\ri0\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin0\lin720\itap0 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 - "}{\rtlch\fcs1 
 \par }\pard \ltrpar\ql \fi-360\li720\ri0\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin0\lin720\itap0 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 - "}{\rtlch\fcs1 
 \ab\af39\afs20 \ltrch\fcs0 \b\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 ?}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 ": }{
 \ab\af39\afs20 \ltrch\fcs0 \b\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 ?}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 ": }{
 \rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \b\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 Help and feedback}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 
 \rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \b\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 Help and feedback}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 
 
 
 \par \tab }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \par \tab }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 Feedback}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 
 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 Feedback}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 
-\hich\af39\dbch\af31505\loch\f39 ": Opens a p\hich\af39\dbch\af31505\loch\f39 opup menu with two options:
+\hich\af39\dbch\af31505\loch\f39 ": Opens a popup menu with two options:
 \par }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid8068556 \tab }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 * "}{
 \par }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid8068556 \tab }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 * "}{
 \rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 Report bug}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 Report bug}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 ": Report a bug in AssimpView or in Assimp
 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid13463017 \hich\af39\dbch\af31505\loch\f39 ": Report a bug in AssimpView or in Assimp
@@ -156,7 +159,10 @@ background image. If the format of the texture doesn't fit to the proportions of
 \hich\af39\dbch\af31505\loch\f39 exotic file formats? 
 \hich\af39\dbch\af31505\loch\f39 exotic file formats? 
 \par }\pard \ltrpar\qj \li340\ri0\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin0\lin340\itap0\pararsid13463017 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 \hich\f39 
 \par }\pard \ltrpar\qj \li340\ri0\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin0\lin340\itap0\pararsid13463017 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 \hich\f39 
 The side panel \'93}{\rtlch\fcs1 \ai\af39\afs20 \ltrch\fcs0 \i\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 Statistics}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 
 The side panel \'93}{\rtlch\fcs1 \ai\af39\afs20 \ltrch\fcs0 \i\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 Statistics}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 
-\loch\af39\dbch\af31505\hich\f39 \'94\loch\f39  displays rendering statistics. The meanings of the fi\hich\af39\dbch\af31505\loch\f39 elds change if you change between normal and texture/material mode.
+\loch\af39\dbch\af31505\hich\f39 \'94\loch\f39  di\hich\af39\dbch\af31505\loch\f39 splays rendering statistics. The meanings of the fields change if you change between normal}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 
+\f39\fs20\lang1033\langfe1031\langnp1033\insrsid2432159 \hich\af39\dbch\af31505\loch\f39 , node a\hich\af39\dbch\af31505\loch\f39 nd texture or }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 
+\hich\af39\dbch\af31505\loch\f39 material}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid2432159 \hich\af39\dbch\af31505\loch\f39  view}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 
+\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39  mode.
 \par }\pard \ltrpar\qj \li340\ri1474\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin1474\lin340\itap0\pararsid13463017 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 
 \par }\pard \ltrpar\qj \li340\ri1474\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin1474\lin340\itap0\pararsid13463017 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 
 Normal view mode:
 Normal view mode:
 \par }\pard \ltrpar\qj \li340\ri1208\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin1208\lin340\itap0\pararsid5053873 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \tab }{\rtlch\fcs1 \af39\afs16 
 \par }\pard \ltrpar\qj \li340\ri1208\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin1208\lin340\itap0\pararsid5053873 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \tab }{\rtlch\fcs1 \af39\afs16 
@@ -169,46 +175,86 @@ er of faces (= triangles) in the asset
 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 ": Number of nodes in the scenegraph, including the root node
 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 ": Number of nodes in the scenegraph, including the root node
 \par }\pard \ltrpar\qj \fi-368\li708\ri1208\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin1208\lin708\itap0\pararsid5053873 {\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \tab 
 \par }\pard \ltrpar\qj \fi-368\li708\ri1208\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin1208\lin708\itap0\pararsid5053873 {\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \tab 
 \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 Mats:}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 Mats:}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
-\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 " Number of different materials found in the mesh. Most loaders \tab remove unreferenced materials.
+\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 " Number of different materials f}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid2432159 
+\hich\af39\dbch\af31505\loch\f39 ound in the mesh. Most loaders }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 remove unreferenced materials.
 \par }\pard \ltrpar\qj \li700\ri1208\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin1208\lin700\itap0\pararsid5053873 {\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 
 \par }\pard \ltrpar\qj \li700\ri1208\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin1208\lin700\itap0\pararsid5053873 {\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 
 \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 Mesh}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 Mesh}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 ": Number of }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid6912689\charrsid5053873 
 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 ": Number of }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid6912689\charrsid5053873 
-\hich\af39\dbch\af31505\loch\f39 sub meshes}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 . Each mesh has only one material \tab assigned, so thi
-\hich\af39\dbch\af31505\loch\f39 s number is in most cases equal to or higher than the \tab number of materials.
+\hich\af39\dbch\af31505\loch\f39 sub meshes}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 . Each mesh h}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
+\f39\fs16\lang1033\langfe1031\langnp1033\insrsid2432159 \hich\af39\dbch\af31505\loch\f39 as only one material \tab assigned, so }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 
+\hich\af39\dbch\af31505\loch\f39 this number\hich\af39\dbch\af31505\loch\f39  is in most cas}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid6976329 \hich\af39\dbch\af31505\loch\f39 es equal to or higher than the }{
+\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 number of materials.
 \par \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 Shd}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \par \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 Shd}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 ": Number of unique shaders created}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5053873 
 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 ": Number of unique shaders created}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5053873 
 \hich\af39\dbch\af31505\loch\f39  for this asset. If AssimpView }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 
 \hich\af39\dbch\af31505\loch\f39  for this asset. If AssimpView }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 
 detects that two materials can be rendered using }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5053873 \hich\af39\dbch\af31505\loch\f39 the same }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 detects that two materials can be rendered using }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5053873 \hich\af39\dbch\af31505\loch\f39 the same }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
-\f39\fs16\lang1033\langfe1031\langnp1033\insrsid6912689 \hich\af39\dbch\af31505\loch\f39 Shader}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5053873 \hich\af39\dbch\af31505\loch\f39  code, 
-\hich\af39\dbch\af31505\loch\f39 but \hich\af39\dbch\af31505\loch\f39 with }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid6912689\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 different}{\rtlch\fcs1 \af39\afs16 
-\ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39  }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid6912689\charrsid5053873 
-\hich\af39\dbch\af31505\loch\f39 parameterizations}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 , it do}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
-\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5053873 \hich\af39\dbch\af31505\loch\f39 esn't create the }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid6912689 \hich\af39\dbch\af31505\loch\f39 Shader}{\rtlch\fcs1 
-\af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5053873 \hich\af39\dbch\af31505\loch\f39  twice. }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 
-\hich\af39\dbch\af31505\loch\f39 Most engines do so, too. 
+\f39\fs16\lang1033\langfe1031\langnp1033\insrsid6912689 \hich\af39\dbch\af31505\loch\f39 Shader}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5053873 \hich\af39\dbch\af31505\loch\f39  code, but with }{\rtlch\fcs1 
+\af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid6912689\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 different}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 
+\hich\af39\dbch\af31505\loch\f39  }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid6912689\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 parameterizations}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
+\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 , it do}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5053873 \hich\af39\dbch\af31505\loch\f39 es
+\hich\af39\dbch\af31505\loch\f39 n't create the }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid6912689 \hich\af39\dbch\af31505\loch\f39 Shader}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
+\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5053873 \hich\af39\dbch\af31505\loch\f39  twice. }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 
+Most engines do so, too. }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid6976329 \hich\af39\dbch\af31505\loch\f39 This allows you to \hich\af39\dbch\af31505\loch\f39 approximate\hich\af39\dbch\af31505\loch\f39  the
+\hich\af39\dbch\af31505\loch\f39  rendering const for the asset.}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 
 \par \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 Time}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \par \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 Time}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 ": Time required for loading, in seco}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5053873 
 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 ": Time required for loading, in seco}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5053873 
 \hich\af39\dbch\af31505\loch\f39 nds. This time is the raw time }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 the process spent in Assimp loading the mod}{
 \hich\af39\dbch\af31505\loch\f39 nds. This time is the raw time }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 the process spent in Assimp loading the mod}{
 \rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5053873 \hich\af39\dbch\af31505\loch\f39 el itself, it does not include }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5053873 \hich\af39\dbch\af31505\loch\f39 el itself, it does not include }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 texture loading, }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid6912689\charrsid5053873 
 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 texture loading, }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid6912689\charrsid5053873 
-\hich\af39\dbch\af31505\loch\f39 Shader}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39  compilation ...
+\hich\af39\dbch\af31505\loch\f39 Shader}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39  }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
+\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5113869\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 compilation...}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181 
+\par }\pard \ltrpar\qj \li340\ri1474\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin1474\lin340\itap0\pararsid6976329 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid6976329 \hich\af39\dbch\af31505\loch\f39 
+Texture view mode\hich\af39\dbch\af31505\loch\f39 :
+\par }\pard \ltrpar\qj \li340\ri1208\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin1208\lin340\itap0\pararsid6976329 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid6976329 \tab }{\rtlch\fcs1 \af39\afs16 
+\ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid6976329\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid2432159 \hich\af39\dbch\af31505\loch\f39 
+Width}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid6976329\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 ": }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid2432159 
+\hich\af39\dbch\af31505\loch\f39 Width of the texture, in pixels}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid6976329\charrsid5053873 
+\par \tab \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid2432159 \hich\af39\dbch\af31505\loch\f39 Height}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
+\f39\fs16\lang1033\langfe1031\langnp1033\insrsid6976329 \hich\af39\dbch\af31505\loch\f39 ":}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid2432159 \hich\af39\dbch\af31505\loch\f39  \hich\af39\dbch\af31505\loch\f39 
+Height of the texture in pixels
+\par }\pard \ltrpar\qj \li708\ri1208\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin1208\lin708\itap0\pararsid2432159 {\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid6976329\charrsid5053873 
+\hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid2432159 \hich\af39\dbch\af31505\loch\f39 Format}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
+\f39\fs16\lang1033\langfe1031\langnp1033\insrsid6976329\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 ": }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid2432159 \hich\af39\dbch\af31505\loch\f39 
+Format of the texture. This will normally be ARGB8. Other possible \hich\af39\dbch\af31505\loch\f39 valu\hich\af39\dbch\af31505\loch\f39 e\hich\af39\dbch\af31505\loch\f39 s are:\hich\af39\dbch\af31505\loch\f39  \hich\af39\dbch\af31505\loch\f39 RRG565
+\hich\af39\dbch\af31505\loch\f39 , ARGB16f\hich\af39\dbch\af31505\loch\f39 , ARGB32F. Embedded textures are always converted \hich\af39\dbch\af31505\loch\f39 to ARGB8 format, even if they\loch\af39\dbch\af31505\hich\f39 \rquote 
+\hich\af39\dbch\af31505\loch\f39 re stored\hich\af39\dbch\af31505\loch\f39  in the file\hich\af39\dbch\af31505\loch\f39  with a higher color\hich\af39\dbch\af31505\loch\f39  \hich\af39\dbch\af31505\loch\f39 depth.}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
+\f39\fs16\lang1033\langfe1031\langnp1033\insrsid6976329 
+\par }\pard \ltrpar\qj \fi368\li340\ri1208\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin1208\lin340\itap0\pararsid2432159 {\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid2432159\charrsid5053873 
+\hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid2432159 \hich\af39\dbch\af31505\loch\f39 UV}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
+\f39\fs16\lang1033\langfe1031\langnp1033\insrsid2432159\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 ": }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid2432159 \hich\af39\dbch\af31505\loch\f39 
+UV channel used by the texture. Normally this is the first channel.}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid2432159\charrsid5053873 
+\par }\pard \ltrpar\qj \li340\ri1208\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin1208\lin340\itap0\pararsid2432159 {\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid2432159\charrsid5053873 \tab 
+\hich\af39\dbch\af31505\loch\f39 * }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid2432159 \loch\af39\dbch\af31505\hich\f39 \'93}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
+\b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid2432159 \hich\af39\dbch\af31505\loch\f39 MIPs}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid2432159 \hich\af39\dbch\af31505\loch\f39 ":
+\hich\af39\dbch\af31505\loch\f39  \hich\af39\dbch\af31505\loch\f39 Number of MIP map levels created for the textur\hich\af39\dbch\af31505\loch\f39 e
+\par }\pard \ltrpar\qj \li708\ri1208\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin1208\lin708\itap0\pararsid2432159 {\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid2432159\charrsid5053873 
+\hich\af39\dbch\af31505\loch\f39 * }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid2432159 \loch\af39\dbch\af31505\hich\f39 \'93}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
+\b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid2432159 \hich\af39\dbch\af31505\loch\f39 Blend}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid2432159 \hich\af39\dbch\af31505\loch\f39 ":
+\hich\af39\dbch\af31505\loch\f39  \hich\af39\dbch\af31505\loch\f39 Strength of the texture. The \hich\af39\dbch\af31505\loch\f39 color value obtained from\hich\af39\dbch\af31505\loch\f39  the texture is \hich\af39\dbch\af31505\loch\f39 multiplied
+\hich\af39\dbch\af31505\loch\f39  \hich\af39\dbch\af31505\loch\f39 with this factor to calculate the final color.
+\par }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid2432159\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid2432159 
+\hich\af39\dbch\af31505\loch\f39 Op}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid2432159\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 ": }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
+\f39\fs16\lang1033\langfe1031\langnp1033\insrsid2432159 \hich\af39\dbch\af31505\loch\f39 Blend operation used to combine this texture with the corresponding \hich\af39\dbch\af31505\loch\f39 material color \hich\af39\dbch\af31505\loch\f39 (
+\hich\af39\dbch\af31505\loch\f39 or the previous texture\hich\af39\dbch\af31505\loch\f39 ).
+\par }\pard \ltrpar\qj \li340\ri1474\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin1474\lin340\itap0\pararsid6976329 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid6976329 \hich\af39\dbch\af31505\loch\f39 
+Always visible\hich\af39\dbch\af31505\loch\f39 :}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid6976329\charrsid6976329 
 \par }\pard \ltrpar\qj \li700\ri1208\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin1208\lin700\itap0\pararsid9465848 {\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 
 \par }\pard \ltrpar\qj \li700\ri1208\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin1208\lin700\itap0\pararsid9465848 {\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 
 \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 FPS}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
 \hich\af39\dbch\af31505\loch\f39 * "}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \b\i\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 FPS}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
-\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 ": Current frame rate, in frames per se}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5053873 
-\hich\af39\dbch\af31505\loch\f39 cond. Note that this number }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid13463017\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 is }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
-\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 not really exact.}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5053873 \hich\af39\dbch\af31505\loch\f39 
- It is only an approximation to the real frame rate. The maximum frame rate \hich\af39\dbch\af31505\loch\f39 will\hich\af39\dbch\af31505\loch\f39  \hich\af39\dbch\af31505\loch\f39 never be reached since there is a 10 ms blocker included (
-\hich\af39\dbch\af31505\loch\f39 to work around good old AMD timing bug\hich\af39\dbch\af31505\loch\f39 )}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid9465848 
+\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 ": Cur\hich\af39\dbch\af31505\loch\f39 rent frame rate, in frames per se}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
+\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5053873 \hich\af39\dbch\af31505\loch\f39 cond. Note that this number }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid13463017\charrsid5053873 
+\hich\af39\dbch\af31505\loch\f39 is }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid5053873 \hich\af39\dbch\af31505\loch\f39 not really exact.}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 
+\f39\fs16\lang1033\langfe1031\langnp1033\insrsid5053873 \hich\af39\dbch\af31505\loch\f39  It is only an approximation to the real frame rate. The maximum frame rate will never be reached since there is a 10 ms blocker included (to work around good }{
+\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid6976329 \hich\af39\dbch\af31505\loch\f39 our }{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5053873 
+\hich\af39\dbch\af31505\loch\f39 old AMD timing b\hich\af39\dbch\af31505\loch\f39 ug)}{\rtlch\fcs1 \af39\afs16 \ltrch\fcs0 \f39\fs16\lang1033\langfe1031\langnp1033\insrsid5338181\charrsid9465848 
 \par }\pard \ltrpar\ql \li340\ri0\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin0\lin340\itap0 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 \hich\f39 The \'93}{
 \par }\pard \ltrpar\ql \li340\ri0\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin0\lin340\itap0 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 \hich\f39 The \'93}{
 \rtlch\fcs1 \ai\af39\afs20 \ltrch\fcs0 \i\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 Rendering}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 
 \rtlch\fcs1 \ai\af39\afs20 \ltrch\fcs0 \i\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 Rendering}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 
 \loch\af39\dbch\af31505\hich\f39 \'94\loch\f39  panel provides rendering-related options, such as wireframe/normal visualization.}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid9465848 
 \loch\af39\dbch\af31505\hich\f39 \'94\loch\f39  panel provides rendering-related options, such as wireframe/normal visualization.}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid9465848 
 \par }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 \hich\f39  \'93}{\rtlch\fcs1 \ai\af39\afs20 \ltrch\fcs0 \i\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 
 \par }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 \hich\f39  \'93}{\rtlch\fcs1 \ai\af39\afs20 \ltrch\fcs0 \i\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 
 \hich\af39\dbch\af31505\loch\f39 Interaction}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \loch\af39\dbch\af31505\hich\f39 \'94\loch\f39  bundles all input related options.
 \hich\af39\dbch\af31505\loch\f39 Interaction}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \loch\af39\dbch\af31505\hich\f39 \'94\loch\f39  bundles all input related options.
 \par \hich\af39\dbch\af31505\loch\f39 The main \hich\af39\dbch\af31505\loch\f39 component is the large viewer panel where the assets are displayed. Status messages (e.g. if a texture could not be loaded)}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 
 \par \hich\af39\dbch\af31505\loch\f39 The main \hich\af39\dbch\af31505\loch\f39 component is the large viewer panel where the assets are displayed. Status messages (e.g. if a texture could not be loaded)}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 
-\f39\fs20\lang1033\langfe1031\langnp1033\insrsid9465848 \hich\af39\dbch\af31505\loch\f39  \hich\af39\dbch\af31505\loch\f39 are displayed in the upper-right}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 
-\hich\af39\dbch\af31505\loch\f39  edge.}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid9465848 \hich\af39\dbch\af31505\loch\f39 
- Yellow messages are simple information messages, red messages are error messages. Green messages\hich\af39\dbch\af31505\loch\f39 , however, are coming from heart ;-)}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 
-\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 
+\f39\fs20\lang1033\langfe1031\langnp1033\insrsid9465848 \hich\af39\dbch\af31505\loch\f39  are displayed in the upper-right}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39  edge.
+}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid9465848 \hich\af39\dbch\af31505\loch\f39  Yellow messages are simple information messages, red messages are error messages. Green messa\hich\af39\dbch\af31505\loch\f39 
+ges, however, are coming from heart ;-)}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 
 \par }{\rtlch\fcs1 \af39\afs24 \ltrch\fcs0 \f39\fs24\lang1033\langfe1031\langnp1033\insrsid5338181 
 \par }{\rtlch\fcs1 \af39\afs24 \ltrch\fcs0 \f39\fs24\lang1033\langfe1031\langnp1033\insrsid5338181 
 \par }\pard \ltrpar\ql \fi-360\li340\ri0\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin0\lin340\itap0 {\rtlch\fcs1 \af39\afs24 \ltrch\fcs0 \f39\fs24\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 III.\tab Input
 \par }\pard \ltrpar\ql \fi-360\li340\ri0\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin0\lin340\itap0 {\rtlch\fcs1 \af39\afs24 \ltrch\fcs0 \f39\fs24\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 III.\tab Input
 \par }\pard \ltrpar\ql \li340\ri0\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin0\lin340\itap0 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 \hich\f39 
 \par }\pard \ltrpar\ql \li340\ri0\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin0\lin340\itap0 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 \hich\f39 
@@ -221,46 +267,45 @@ The input mode depends on the selected input behavior. If \'84}{\rtlch\fcs1 \ai\
 \par \loch\af39\dbch\af31505\hich\f39 \lquote }{\rtlch\fcs1 \ai\af39\afs20 \ltrch\fcs0 \i\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 Arrow left\hich\f39 \rquote }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 
 \par \loch\af39\dbch\af31505\hich\f39 \lquote }{\rtlch\fcs1 \ai\af39\afs20 \ltrch\fcs0 \i\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 Arrow left\hich\f39 \rquote }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 
 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39  \tab = Move to the left
 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39  \tab = Move to the left
 \par \loch\af39\dbch\af31505\hich\f39 \lquote }{\rtlch\fcs1 \ai\af39\afs20 \ltrch\fcs0 \i\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 Arrow down}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 
 \par \loch\af39\dbch\af31505\hich\f39 \lquote }{\rtlch\fcs1 \ai\af39\afs20 \ltrch\fcs0 \i\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 Arrow down}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 
-\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \loch\af39\dbch\af31505\hich\f39 \rquote \loch\f39  \tab = Move backwards
+\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \loch\af39\dbch\af31505\hich\f39 \rquote \loch\f39  \tab \hich\af39\dbch\af31505\loch\f39 = Move backwards
 \par \loch\af39\dbch\af31505\hich\f39 \lquote }{\rtlch\fcs1 \ai\af39\afs20 \ltrch\fcs0 \i\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 Arrow right}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 
 \par \loch\af39\dbch\af31505\hich\f39 \lquote }{\rtlch\fcs1 \ai\af39\afs20 \ltrch\fcs0 \i\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 Arrow right}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 
 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \loch\af39\dbch\af31505\hich\f39 \rquote \loch\f39  \tab = Move to the right
 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \loch\af39\dbch\af31505\hich\f39 \rquote \loch\f39  \tab = Move to the right
 \par }\pard \ltrpar\ql \fi340\li0\ri0\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin0\lin0\itap0 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 
 \par }\pard \ltrpar\ql \fi340\li0\ri0\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin0\lin0\itap0 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 
 The mouse specifies the view direction. This is the typical FPS input behavior.
 The mouse specifies the view direction. This is the typical FPS input behavior.
-\par }{\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 Otherwise\hich\af39\dbch\af31505\loch\f39 \hich\f39 , if \'84}{\rtlch\fcs1 \ab\ai\af39\afs20 \ltrch\fcs0 
+\par }{\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 \hich\f39 Otherwise, if \'84}{\rtlch\fcs1 \ab\ai\af39\afs20 \ltrch\fcs0 
 \b\i\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 Zoom/Rotate}{\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \loch\af39\dbch\af31505\hich\f39 \'94\loch\f39 
 \b\i\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 Zoom/Rotate}{\rtlch\fcs1 \ab\af39\afs20 \ltrch\fcs0 \b\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \loch\af39\dbch\af31505\hich\f39 \'94\loch\f39 
  is enabled:
  is enabled:
 \par }\pard \ltrpar\ql \li340\ri0\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin0\lin340\itap0 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \loch\af39\dbch\af31505\hich\f39 \'93}{\rtlch\fcs1 
 \par }\pard \ltrpar\ql \li340\ri0\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin0\lin340\itap0 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \loch\af39\dbch\af31505\hich\f39 \'93}{\rtlch\fcs1 
 \ai\af39\afs20 \ltrch\fcs0 \i\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 Left Mouse Button}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 
 \ai\af39\afs20 \ltrch\fcs0 \i\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 Left Mouse Button}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 
-\loch\af39\dbch\af31505\hich\f39 \'94\loch\f39  = Keep pressed and move the mouse to rotate the asset around its local axes. Inside the yellow circle: x/y-axis, outside: Z-axis. To rotate around one axis only use the axis snap-ins (yellow squares with a
-\hich\af39\dbch\af31505\loch\f39  cross inside).
+\loch\af39\dbch\af31505\hich\f39 \'94\loch\f39  = Keep pressed and move the mouse to rotate the asset around its\hich\af39\dbch\af31505\loch\f39 
+ local axes. Inside the yellow circle: x/y-axis, outside: Z-axis. To rotate around one axis only use the axis snap-ins (yellow squares with a cross inside).
 \par \loch\af39\dbch\af31505\hich\f39 \'93}{\rtlch\fcs1 \ai\af39\afs20 \ltrch\fcs0 \i\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 Right Mouse Button}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 
 \par \loch\af39\dbch\af31505\hich\f39 \'93}{\rtlch\fcs1 \ai\af39\afs20 \ltrch\fcs0 \i\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 Right Mouse Button}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 
-\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \loch\af39\dbch\af31505\hich\f39 \'94\loch\f39  = Keep pressed and move the mouse to rotate the light source(s) around their x and y axes.
+\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \loch\af39\dbch\af31505\hich\f39 \'94\loch\f39  = Keep pressed and move the mouse to rotate the light source(s) around their x\hich\af39\dbch\af31505\loch\f39  and y axes.
 \par \loch\af39\dbch\af31505\hich\f39 \'93}{\rtlch\fcs1 \ai\af39\afs20 \ltrch\fcs0 \i\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 Middle Mouse Button}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 
 \par \loch\af39\dbch\af31505\hich\f39 \'93}{\rtlch\fcs1 \ai\af39\afs20 \ltrch\fcs0 \i\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 Middle Mouse Button}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 
-\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \loch\af39\dbch\af31505\hich\f39 \'94\loch\f39  = Keep pressed and move the mouse from the left to the right or the other way round to increase/decrease t\hich\af39\dbch\af31505\loch\f39 
-he intensity of the light source(s)
+\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \loch\af39\dbch\af31505\hich\f39 \'94\loch\f39  = Keep pressed and move the mouse from the left to the right or the other way round to increase/decrease the intensity of the light source(s)
 \par \loch\af39\dbch\af31505\hich\f39 \'93}{\rtlch\fcs1 \ai\af39\afs20 \ltrch\fcs0 \i\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 Right }{\rtlch\fcs1 \ab\ai\af39\afs20 \ltrch\fcs0 
 \par \loch\af39\dbch\af31505\hich\f39 \'93}{\rtlch\fcs1 \ai\af39\afs20 \ltrch\fcs0 \i\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 Right }{\rtlch\fcs1 \ab\ai\af39\afs20 \ltrch\fcs0 
 \b\i\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 AND}{\rtlch\fcs1 \ai\af39\afs20 \ltrch\fcs0 \i\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39  Left Mouse Button}{
 \b\i\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 AND}{\rtlch\fcs1 \ai\af39\afs20 \ltrch\fcs0 \i\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39  Left Mouse Button}{
-\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \loch\af39\dbch\af31505\hich\f39 \'94\loch\f39  = Keep pressed and move the mouse to rotate skybox (if existing) around the global x/y axes.
+\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \loch\af39\dbch\af31505\hich\f39 \'94\loch\f39  = Keep pressed and move the mouse to rotate skybo\hich\af39\dbch\af31505\loch\f39 
+x (if existing) around the global x/y axes.
 \par \loch\af39\dbch\af31505\hich\f39 \'93}{\rtlch\fcs1 \ai\af39\afs20 \ltrch\fcs0 \i\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 Mouse wheel}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 
 \par \loch\af39\dbch\af31505\hich\f39 \'93}{\rtlch\fcs1 \ai\af39\afs20 \ltrch\fcs0 \i\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 Mouse wheel}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 
 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \loch\af39\dbch\af31505\hich\f39 \'94\loch\f39  = Zoom in/out
 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \loch\af39\dbch\af31505\hich\f39 \'94\loch\f39  = Zoom in/out
 \par }\pard \ltrpar\ql \fi-360\li340\ri0\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin0\lin340\itap0 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 IV.\tab }{\rtlch\fcs1 
 \par }\pard \ltrpar\ql \fi-360\li340\ri0\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin0\lin340\itap0 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 IV.\tab }{\rtlch\fcs1 
 \af39\afs24 \ltrch\fcs0 \f39\fs24\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 Rendering
 \af39\afs24 \ltrch\fcs0 \f39\fs24\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 Rendering
 \par }\pard \ltrpar\ql \li340\ri0\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin0\lin340\itap0 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 
 \par }\pard \ltrpar\ql \li340\ri0\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin0\lin340\itap0 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 
-Enabling MultiSampling improves rendering\hich\af39\dbch\af31505\loch\f39  quality. MultiSampling is activated by default. The highest quality MultiSampling mode supported by the video card is used.
-\par \hich\af39\dbch\af31505\loch\f39 MultiSampling is especially useful to remove line artifacts in wireframe/normals mode. Note that the transition between normal and \hich\af39\dbch\af31505\loch\f39 multisampled rendering may take a few seconds.
+Enabling MultiSampling improves rendering quality. MultiSampling is activated by default. The highest quality MultiSampling mode supported by the video card is used.
+\par \hich\af39\dbch\af31505\loch\f39 Mult\hich\af39\dbch\af31505\loch\f39 iSampling is especially useful to remove line artifacts in wireframe/normals mode. Note that the transition between normal and multisampled rendering may take a few seconds.
 \par \hich\af39\dbch\af31505\loch\f39 \hich\f39 Rendering is done via Direct3D\'99\loch\f39 \hich\f39  9.0c. Note that the \'93}{\rtlch\fcs1 \ai\af39\afs20 \ltrch\fcs0 \i\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 
 \par \hich\af39\dbch\af31505\loch\f39 \hich\f39 Rendering is done via Direct3D\'99\loch\f39 \hich\f39  9.0c. Note that the \'93}{\rtlch\fcs1 \ai\af39\afs20 \ltrch\fcs0 \i\f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 
-Low-quality lighting}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \loch\af39\dbch\af31505\hich\f39 \'94\loch\f39 -Option is not available for PS 2.0 cards. Lighting on PS 2.0 cards is always low quality.
-
+Low-quality lighting}{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \loch\af39\dbch\af31505\hich\f39 \'94\loch\f39 -Option \hich\af39\dbch\af31505\loch\f39 
+is not available for PS 2.0 cards. Lighting on PS 2.0 cards is always low quality.
 \par 
 \par 
 \par }\pard \ltrpar\ql \fi-360\li340\ri0\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin0\lin340\itap0 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 V.\tab }{\rtlch\fcs1 
 \par }\pard \ltrpar\ql \fi-360\li340\ri0\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin0\lin340\itap0 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 V.\tab }{\rtlch\fcs1 
 \af39\afs24 \ltrch\fcs0 \f39\fs24\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 Known issues 
 \af39\afs24 \ltrch\fcs0 \f39\fs24\lang1033\langfe1031\langnp1033\insrsid5338181 \hich\af39\dbch\af31505\loch\f39 Known issues 
 \par }\pard \ltrpar\ql \fi-360\li700\ri0\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin0\lin700\itap0 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 -\tab \hich\af39\dbch\af31505\loch\f39 
 \par }\pard \ltrpar\ql \fi-360\li700\ri0\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin0\lin700\itap0 {\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 -\tab \hich\af39\dbch\af31505\loch\f39 
-If a loading process is \hich\af39\dbch\af31505\loch\f39 canceled it is not guaranteed that further loading processes will succeed. ASSIMP Viewer might even crash in this case. }{\rtlch\fcs1 \af39\afs24 \ltrch\fcs0 
+If a loading process is canceled it is not guaranteed that further loading processes will succeed. ASSIMP Viewer might even crash in this case. }{\rtlch\fcs1 \af39\afs24 \ltrch\fcs0 \f39\fs24\lang1033\langfe1031\langnp1033\insrsid5338181 
+\par }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 -\tab \hich\af39\dbch\af31505\loch\f39 Some v\hich\af39\dbch\af31505\loch\f39 
+ery complex materials involving real time reflection/refraction are not displayed properly.}{\rtlch\fcs1 \af39\afs24 \ltrch\fcs0 \f39\fs24\lang1033\langfe1031\langnp1033\insrsid5338181 
+\par }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 -\tab \hich\af39\dbch\af31505\loch\f39 When rendering non-opaque objects some triangle artifacts might occur.}{\rtlch\fcs1 \af39\afs24 \ltrch\fcs0 
 \f39\fs24\lang1033\langfe1031\langnp1033\insrsid5338181 
 \f39\fs24\lang1033\langfe1031\langnp1033\insrsid5338181 
-\par }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 -\tab \hich\af39\dbch\af31505\loch\f39 Some very complex materials involving real time reflection/refraction are not displayed properly.}{\rtlch\fcs1 
-\af39\afs24 \ltrch\fcs0 \f39\fs24\lang1033\langfe1031\langnp1033\insrsid5338181 
-\par }{\rtlch\fcs1 \af39\afs20 \ltrch\fcs0 \f39\fs20\lang1033\langfe1031\langnp1033\insrsid5338181 -\tab \hich\af39\dbch\af31505\loch\f39 When rendering non-opaque objects\hich\af39\dbch\af31505\loch\f39  some triangle artifacts might occur.}{\rtlch\fcs1 
-\af39\afs24 \ltrch\fcs0 \f39\fs24\lang1033\langfe1031\langnp1033\insrsid5338181 
 \par }{\*\themedata 504b030414000600080000002100828abc13fa0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cb6ac3301045f785fe83d0b6d8
 \par }{\*\themedata 504b030414000600080000002100828abc13fa0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cb6ac3301045f785fe83d0b6d8
 72ba28a5d8cea249777d2cd20f18e4b12d6a8f843409c9df77ecb850ba082d74231062ce997b55ae8fe3a00e1893f354e9555e6885647de3a8abf4fbee29bbd7
 72ba28a5d8cea249777d2cd20f18e4b12d6a8f843409c9df77ecb850ba082d74231062ce997b55ae8fe3a00e1893f354e9555e6885647de3a8abf4fbee29bbd7
 2a3150038327acf409935ed7d757e5ee14302999a654e99e393c18936c8f23a4dc072479697d1c81e51a3b13c07e4087e6b628ee8cf5c4489cf1c4d075f92a0b
 2a3150038327acf409935ed7d757e5ee14302999a654e99e393c18936c8f23a4dc072479697d1c81e51a3b13c07e4087e6b628ee8cf5c4489cf1c4d075f92a0b
@@ -366,8 +411,8 @@ fffffffffffffffffdfffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffff
 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
-ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffffec69d9888b8b3d4c859eaf6cd158be0f000000000000000000000000a034
-b356e3b4c801feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000
+ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffffec69d9888b8b3d4c859eaf6cd158be0f000000000000000000000000b080
+05aceae0c801feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000
 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000
 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000
 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000
 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000
 0000000000000000000000000000000000000000000000000105000000000000}}
 0000000000000000000000000000000000000000000000000105000000000000}}

BIN
web/freecsstemplates.org_logistix_template.zip


+ 55 - 0
web/root/FAQ.html

@@ -0,0 +1,55 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8" />
+<title>Open Asset Import Library - Frequently Asked Questions (FAQ)</title>
+<meta name="keywords" content="ASSIMP Open Asset Import Library free Open Source 3d Model Import Library C C++ Java MDL X OBJ MD2 MD3 PLY " />
+<meta name="description" content="Open Asset Import Library" />
+<link href="css/faq_style.css" rel="stylesheet" type="text/css" media="screen"/>
+</head>
+<body>
+
+
+
+<div id="header">
+	
+
+</div>
+
+<div id="content">
+	<div id="posts_fl">
+		<div class="post_fl">
+
+<h1>ASSIMP - Frequently Asked Questions (FAQ)</h2>
+
+<h4>Can I use the library for a commercial product?</h4>
+Yes. ASSIMP is licensed under a modified BSD license. Its contents in just one sentence: you may use the library for free, in commercial or non-commercial applications, but you must include our license with your product and you may not advertise with us.
+
+<h4>Is ASSIMP able to export assets?</h4>
+No. ASSIMP is an <b>import</b> library. It is intended to be used to import assets exported from programs like 3ds max into game engines. Normally you won't need to export the assets again.
+
+
+<h4>For which languages is the API provided?</h4>
+The ASSIMP API is provided both as a plain-C interface and as an object-oriented C++ interface, which is the main API. All ports (jAssimp, Assimp.net, ...) and even the C-style API are just wrappers around this interface. Therefore, if your project allows the use of C++, you should use the C++-API.
+
+
+<h4>Is the library thread-safe?</h4>
+Yes. ASSIMP is completely thread-safe, as long as you create an extra 'Importer' instance for each thread. The C-API and all ports are doing this automatically for you.
+
+<h4>Who are you (the guys behind ASSIMP)?</h4>
+Most of use are professional software developers from Germany, but not in GameDev business. Graphics/game programming is a hobby for all of us.
+
+<h4>Can I write a new loader for the library?</h4>
+Sure, as long as you're able to write stable code and the format is not too exotic ... We'd highly appreciate any help. When you're finished, contact us and we're going to test it. If it works well, it will be included with the next release of the library.
+
+<h4>I love you. May I marry all of you?</h4>
+We're sorry, but this is absolutely not possible.
+
+<br><br><br><br><br><br><br>
+			<p><a href="http://sourceforge.net"><img src="http://sflogo.sourceforge.net/sflogo.php?group_id=226462&amp;type=2" width="125" height="37" border="0" alt="SourceForge.net Logo" 
+/></a></p>
+
+</div></div></div>
+
+</body>
+</html>

+ 243 - 0
web/root/LightBoxAndCSSTemplateLicense.txt

@@ -0,0 +1,243 @@
+Creative Commons </>
+
+Creative Commons Legal Code
+
+*Attribution 2.5*
+
+CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN
+ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION
+ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE
+INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+ITS USE.
+
+/License/
+
+THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE
+COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY
+COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS
+AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
+
+BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE
+TO BE BOUND BY THE TERMS OF THIS LICENSE. THE LICENSOR GRANTS YOU THE
+RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS
+AND CONDITIONS.
+
+*1. Definitions*
+
+   1. *"Collective Work"* means a work, such as a periodical issue,
+      anthology or encyclopedia, in which the Work in its entirety in
+      unmodified form, along with a number of other contributions,
+      constituting separate and independent works in themselves, are
+      assembled into a collective whole. A work that constitutes a
+      Collective Work will not be considered a Derivative Work (as
+      defined below) for the purposes of this License.
+   2. *"Derivative Work"* means a work based upon the Work or upon the
+      Work and other pre-existing works, such as a translation, musical
+      arrangement, dramatization, fictionalization, motion picture
+      version, sound recording, art reproduction, abridgment,
+      condensation, or any other form in which the Work may be recast,
+      transformed, or adapted, except that a work that constitutes a
+      Collective Work will not be considered a Derivative Work for the
+      purpose of this License. For the avoidance of doubt, where the
+      Work is a musical composition or sound recording, the
+      synchronization of the Work in timed-relation with a moving image
+      ("synching") will be considered a Derivative Work for the purpose
+      of this License.
+   3. *"Licensor"* means the individual or entity that offers the Work
+      under the terms of this License.
+   4. *"Original Author"* means the individual or entity who created the
+      Work.
+   5. *"Work"* means the copyrightable work of authorship offered under
+      the terms of this License.
+   6. *"You"* means an individual or entity exercising rights under this
+      License who has not previously violated the terms of this License
+      with respect to the Work, or who has received express permission
+      from the Licensor to exercise rights under this License despite a
+      previous violation.
+
+*2. Fair Use Rights.* Nothing in this license is intended to reduce,
+limit, or restrict any rights arising from fair use, first sale or other
+limitations on the exclusive rights of the copyright owner under
+copyright law or other applicable laws.
+
+*3. License Grant.* Subject to the terms and conditions of this License,
+Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
+perpetual (for the duration of the applicable copyright) license to
+exercise the rights in the Work as stated below:
+
+   1. to reproduce the Work, to incorporate the Work into one or more
+      Collective Works, and to reproduce the Work as incorporated in the
+      Collective Works;
+   2. to create and reproduce Derivative Works;
+   3. to distribute copies or phonorecords of, display publicly, perform
+      publicly, and perform publicly by means of a digital audio
+      transmission the Work including as incorporated in Collective Works;
+   4. to distribute copies or phonorecords of, display publicly, perform
+      publicly, and perform publicly by means of a digital audio
+      transmission Derivative Works.
+   5.
+
+      For the avoidance of doubt, where the work is a musical composition:
+
+         1. *Performance Royalties Under Blanket Licenses*. Licensor
+            waives the exclusive right to collect, whether individually
+            or via a performance rights society (e.g. ASCAP, BMI,
+            SESAC), royalties for the public performance or public
+            digital performance (e.g. webcast) of the Work.
+         2. *Mechanical Rights and Statutory Royalties*. Licensor waives
+            the exclusive right to collect, whether individually or via
+            a music rights agency or designated agent (e.g. Harry Fox
+            Agency), royalties for any phonorecord You create from the
+            Work ("cover version") and distribute, subject to the
+            compulsory license created by 17 USC Section 115 of the US
+            Copyright Act (or the equivalent in other jurisdictions).
+   6. *Webcasting Rights and Statutory Royalties*. For the avoidance of
+      doubt, where the Work is a sound recording, Licensor waives the
+      exclusive right to collect, whether individually or via a
+      performance-rights society (e.g. SoundExchange), royalties for the
+      public digital performance (e.g. webcast) of the Work, subject to
+      the compulsory license created by 17 USC Section 114 of the US
+      Copyright Act (or the equivalent in other jurisdictions).
+
+The above rights may be exercised in all media and formats whether now
+known or hereafter devised. The above rights include the right to make
+such modifications as are technically necessary to exercise the rights
+in other media and formats. All rights not expressly granted by Licensor
+are hereby reserved.
+
+*4. Restrictions.*The license granted in Section 3 above is expressly
+made subject to and limited by the following restrictions:
+
+   1. You may distribute, publicly display, publicly perform, or
+      publicly digitally perform the Work only under the terms of this
+      License, and You must include a copy of, or the Uniform Resource
+      Identifier for, this License with every copy or phonorecord of the
+      Work You distribute, publicly display, publicly perform, or
+      publicly digitally perform. You may not offer or impose any terms
+      on the Work that alter or restrict the terms of this License or
+      the recipients' exercise of the rights granted hereunder. You may
+      not sublicense the Work. You must keep intact all notices that
+      refer to this License and to the disclaimer of warranties. You may
+      not distribute, publicly display, publicly perform, or publicly
+      digitally perform the Work with any technological measures that
+      control access or use of the Work in a manner inconsistent with
+      the terms of this License Agreement. The above applies to the Work
+      as incorporated in a Collective Work, but this does not require
+      the Collective Work apart from the Work itself to be made subject
+      to the terms of this License. If You create a Collective Work,
+      upon notice from any Licensor You must, to the extent practicable,
+      remove from the Collective Work any credit as required by clause
+      4(b), as requested. If You create a Derivative Work, upon notice
+      from any Licensor You must, to the extent practicable, remove from
+      the Derivative Work any credit as required by clause 4(b), as
+      requested.
+   2. If you distribute, publicly display, publicly perform, or publicly
+      digitally perform the Work or any Derivative Works or Collective
+      Works, You must keep intact all copyright notices for the Work and
+      provide, reasonable to the medium or means You are utilizing: (i)
+      the name of the Original Author (or pseudonym, if applicable) if
+      supplied, and/or (ii) if the Original Author and/or Licensor
+      designate another party or parties (e.g. a sponsor institute,
+      publishing entity, journal) for attribution in Licensor's
+      copyright notice, terms of service or by other reasonable means,
+      the name of such party or parties; the title of the Work if
+      supplied; to the extent reasonably practicable, the Uniform
+      Resource Identifier, if any, that Licensor specifies to be
+      associated with the Work, unless such URI does not refer to the
+      copyright notice or licensing information for the Work; and in the
+      case of a Derivative Work, a credit identifying the use of the
+      Work in the Derivative Work (e.g., "French translation of the Work
+      by Original Author," or "Screenplay based on original Work by
+      Original Author"). Such credit may be implemented in any
+      reasonable manner; provided, however, that in the case of a
+      Derivative Work or Collective Work, at a minimum such credit will
+      appear where any other comparable authorship credit appears and in
+      a manner at least as prominent as such other comparable authorship
+      credit.
+
+*5. Representations, Warranties and Disclaimer*
+
+UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR
+OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY
+KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE,
+INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY,
+FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF
+LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS,
+WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE
+EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
+
+*6. Limitation on Liability.* EXCEPT TO THE EXTENT REQUIRED BY
+APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL
+THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY
+DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF
+LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+*7. Termination*
+
+   1. This License and the rights granted hereunder will terminate
+      automatically upon any breach by You of the terms of this License.
+      Individuals or entities who have received Derivative Works or
+      Collective Works from You under this License, however, will not
+      have their licenses terminated provided such individuals or
+      entities remain in full compliance with those licenses. Sections
+      1, 2, 5, 6, 7, and 8 will survive any termination of this License.
+   2. Subject to the above terms and conditions, the license granted
+      here is perpetual (for the duration of the applicable copyright in
+      the Work). Notwithstanding the above, Licensor reserves the right
+      to release the Work under different license terms or to stop
+      distributing the Work at any time; provided, however that any such
+      election will not serve to withdraw this License (or any other
+      license that has been, or is required to be, granted under the
+      terms of this License), and this License will continue in full
+      force and effect unless terminated as stated above.
+
+*8. Miscellaneous*
+
+   1. Each time You distribute or publicly digitally perform the Work or
+      a Collective Work, the Licensor offers to the recipient a license
+      to the Work on the same terms and conditions as the license
+      granted to You under this License.
+   2. Each time You distribute or publicly digitally perform a
+      Derivative Work, Licensor offers to the recipient a license to the
+      original Work on the same terms and conditions as the license
+      granted to You under this License.
+   3. If any provision of this License is invalid or unenforceable under
+      applicable law, it shall not affect the validity or enforceability
+      of the remainder of the terms of this License, and without further
+      action by the parties to this agreement, such provision shall be
+      reformed to the minimum extent necessary to make such provision
+      valid and enforceable.
+   4. No term or provision of this License shall be deemed waived and no
+      breach consented to unless such waiver or consent shall be in
+      writing and signed by the party to be charged with such waiver or
+      consent.
+   5. This License constitutes the entire agreement between the parties
+      with respect to the Work licensed here. There are no
+      understandings, agreements or representations with respect to the
+      Work not specified here. Licensor shall not be bound by any
+      additional provisions that may appear in any communication from
+      You. This License may not be modified without the mutual written
+      agreement of the Licensor and You.
+
+Creative Commons is not a party to this License, and makes no warranty
+whatsoever in connection with the Work. Creative Commons will not be
+liable to You or any party on any legal theory for any damages
+whatsoever, including without limitation any general, special,
+incidental or consequential damages arising in connection to this
+license. Notwithstanding the foregoing two (2) sentences, if Creative
+Commons has expressly identified itself as the Licensor hereunder, it
+shall have all rights and obligations of Licensor.
+
+Except for the limited purpose of indicating to the public that the Work
+is licensed under the CCPL, neither party will use the trademark
+"Creative Commons" or any related trademark or logo of Creative Commons
+without the prior written consent of Creative Commons. Any permitted use
+will be in compliance with Creative Commons' then-current trademark
+usage guidelines, as may be published on its website or otherwise made
+available upon request from time to time.
+
+Creative Commons may be contacted at http://creativecommons.org/
+<http://creativecommons.org>.
+
+« Back to Commons Deed <./>

BIN
web/root/avdoc/Reference.pdf


+ 177 - 0
web/root/css/faq_style.css

@@ -0,0 +1,177 @@
+body {
+	margin: 0;
+	padding: 0;
+	background: #777777;
+	font: normal 11px Tahoma, Arial, Helvetica, sans-serif;
+	color: #666666;
+}
+
+h1, h2, h3{
+	margin: 0;
+	padding: 0;
+	font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;
+	color: #F49500;
+}
+
+
+h4 {
+	font-size: 12px;
+}
+
+p, blockquote, ul, ol {
+	line-height: 18px;
+	text-align: justify;
+}
+
+blockquote {
+	padding-left: 20px;
+	background: url(../images/img05.gif) repeat-y;
+}
+
+a {
+	color: #F49500;
+}
+
+a:hover {
+	text-decoration: none;
+}
+
+/* Header */
+
+#header {
+	width: 760px;
+	height: 180px;
+	margin: 0 auto;
+}
+
+#header h1, #header h2 {
+	/*text-transform: lowercase;*/
+	font-weight: normal;
+	color: #ffffff; //#FFFFFF;
+}
+
+#header h1 {
+	float: left;
+	padding: 120px 0 0 20px;
+	font-size: 36px;
+}
+
+#header h2 {
+	float: right;
+	padding: 134px 20px 0 0;
+	font-size: 22px;
+}
+
+/* Menu */
+
+#menu {
+	width: 760px;
+	height: 50px;
+	margin: 0 auto;
+}
+
+#menu ul {
+	margin: 0;
+	padding: 0;
+	list-style: none;
+}
+
+#menu li {
+	display: inline;
+}
+
+#menu a {
+	display: block;
+	float: left;
+	padding: 17px 20px 0 20px;
+	background: url(../images/img03.gif) no-repeat;
+	text-transform: uppercase;
+	text-decoration: none;
+	font: bold 12px "Trebuchet MS", Arial, Helvetica, sans-serif;
+	color: #D8D8D8;
+}
+
+#menu a:hover {
+	color: #FFFFFF;
+}
+
+#menu .first a {
+	background: none;
+}
+
+/* Content */
+
+#content {
+	width: 720px;
+	margin: 0 auto 20px auto;
+	padding: 20px;
+	background: #FFFFFF url(../images/img04.jpg) repeat-x left bottom;
+}
+
+/* Posts */
+
+#posts {
+	float: left;
+	width: 480px;
+}
+
+/* Posts - full length */
+
+#posts_fl {
+	float: center;
+	width: 680px;
+}
+
+/* Links */
+
+#links {
+	float: right;
+	width: 200px;
+	padding-left: 20px;
+	background: url(../images/img05.gif) repeat-y;
+}
+
+#links ul {
+	margin: 0;
+	padding: 0;
+	list-style: none;
+}
+
+#links li ul {
+	padding: 5px 0 30px 0;
+}
+
+#links li li {
+	padding: 7px 10px;
+	background: url(../images/img06.gif) repeat-x left bottom;
+}
+
+#links li a {
+	text-decoration: none;
+}
+
+#links li a:hover {
+	text-decoration: underline;
+}
+
+#links li i {
+	font-style: normal;
+	font-size: 9px;
+}
+
+#links li h2 {
+	font-size: 14px;
+}
+
+/* Footer */
+
+#footer {
+	height: 100px;
+	padding: 20px;
+	background: #2D2D2D url(../images/img07.gif) repeat-x;
+}
+
+#footer p {
+	text-align: center;
+	color: #999999;
+}

+ 220 - 0
web/root/css/style.css

@@ -0,0 +1,220 @@
+#lightbox{	position: absolute;	left: 0; width: 100%; z-index: 100; text-align: center; line-height: 0;}
+#lightbox img{ width: auto; height: auto;}
+#lightbox a img{ border: none; }
+
+#outerImageContainer{ position: relative; background-color: #fff; width: 250px; height: 250px; margin: 0 auto; }
+#imageContainer{ padding: 10px; }
+
+#loading{ position: absolute; top: 40%; left: 0%; height: 25%; width: 100%; text-align: center; line-height: 0; }
+#hoverNav{ position: absolute; top: 0; left: 0; height: 100%; width: 100%; z-index: 10; }
+#imageContainer>#hoverNav{ left: 0;}
+#hoverNav a{ outline: none;}
+
+#prevLink, #nextLink{ width: 49%; height: 100%; background-image: url(data:image/gif;base64,AAAA); /* Trick IE into showing hover */ display: block; }
+#prevLink { left: 0; float: left;}
+#nextLink { right: 0; float: right;}
+#prevLink:hover, #prevLink:visited:hover { background: url(../images/prevlabel.gif) left 15% no-repeat; }
+#nextLink:hover, #nextLink:visited:hover { background: url(../images/nextlabel.gif) right 15% no-repeat; }
+
+#imageDataContainer{ font: 10px Verdana, Helvetica, sans-serif; background-color: #fff; margin: 0 auto; line-height: 1.4em; overflow: auto; width: 100%	; }
+
+#imageData{	padding:0 10px; color: #666; }
+#imageData #imageDetails{ width: 70%; float: left; text-align: left; }	
+#imageData #caption{ font-weight: bold;	}
+#imageData #numberDisplay{ display: block; clear: left; padding-bottom: 1.0em;	}			
+#imageData #bottomNavClose{ width: 66px; float: right;  padding-bottom: 0.7em; outline: none;}	 	
+
+#overlay{ position: absolute; top: 0; left: 0; z-index: 90; width: 100%; height: 500px; background-color: #000; }
+
+/*
+Design by Free CSS Templates
+http://www.freecsstemplates.org
+Released for free under a Creative Commons Attribution 2.5 License
+*/
+
+body {
+	margin: 0;
+	padding: 0;
+	background: #777777 url(../images/img01.jpg) repeat-x;
+	font: normal 11px Tahoma, Arial, Helvetica, sans-serif;
+	color: #666666;
+}
+
+h1, h2, h3 {
+	margin: 0;
+	padding: 0;
+	font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;
+	color: #F49500;
+}
+
+h2 {
+	text-transform: uppercase;
+	font-size: 18px;
+}
+
+h3 {
+	text-transform: uppercase;
+	letter-spacing: 2px;
+	font-size: 11px;
+}
+
+p, blockquote, ul, ol {
+	line-height: 18px;
+	text-align: justify;
+}
+
+blockquote {
+	padding-left: 20px;
+	background: url(../images/img05.gif) repeat-y;
+}
+
+a {
+	color: #F49500;
+}
+
+a:hover {
+	text-decoration: none;
+}
+
+/* Header */
+
+#header {
+	width: 760px;
+	height: 180px;
+	margin: 0 auto;
+	background: url(../images/img02.png);
+}
+
+#header h1, #header h2 {
+	/*text-transform: lowercase;*/
+	font-weight: normal;
+	color: #ffffff; //#FFFFFF;
+}
+
+#header h1 {
+	float: left;
+	padding: 120px 0 0 20px;
+	font-size: 36px;
+}
+
+#header h2 {
+	float: right;
+	padding: 134px 20px 0 0;
+	font-size: 22px;
+}
+
+/* Menu */
+
+#menu {
+	width: 760px;
+	height: 50px;
+	margin: 0 auto;
+}
+
+#menu ul {
+	margin: 0;
+	padding: 0;
+	list-style: none;
+}
+
+#menu li {
+	display: inline;
+}
+
+#menu a {
+	display: block;
+	float: left;
+	padding: 17px 20px 0 20px;
+	background: url(../images/img03.gif) no-repeat;
+	text-transform: uppercase;
+	text-decoration: none;
+	font: bold 12px "Trebuchet MS", Arial, Helvetica, sans-serif;
+	color: #D8D8D8;
+}
+
+#menu a:hover {
+	color: #FFFFFF;
+}
+
+#menu .first a {
+	background: none;
+}
+
+/* Content */
+
+#content {
+	width: 720px;
+	margin: 0 auto 20px auto;
+	padding: 20px;
+	background: #FFFFFF url(../images/img04.jpg) repeat-x left bottom;
+}
+
+/* Posts */
+
+#posts {
+	float: left;
+	width: 480px;
+}
+
+/* Posts - full length */
+
+#posts_fl {
+	float: center;
+	width: 680px;
+}
+
+/* Links */
+
+#links {
+	float: right;
+	width: 200px;
+	padding-left: 20px;
+	background: url(../images/img05.gif) repeat-y;
+}
+
+#links ul {
+	margin: 0;
+	padding: 0;
+	list-style: none;
+}
+
+#links li ul {
+	padding: 5px 0 30px 0;
+}
+
+#links li li {
+	padding: 7px 10px;
+	background: url(../images/img06.gif) repeat-x left bottom;
+}
+
+#links li a {
+	text-decoration: none;
+}
+
+#links li a:hover {
+	text-decoration: underline;
+}
+
+#links li i {
+	font-style: normal;
+	font-size: 9px;
+}
+
+#links li h2 {
+	font-size: 14px;
+}
+
+/* Footer */
+
+#footer {
+	height: 100px;
+	padding: 20px;
+	background: #2D2D2D url(../images/img07.gif) repeat-x;
+}
+
+#footer p {
+	text-align: center;
+	color: #999999;
+}
+
+

BIN
web/root/images/boost.png


BIN
web/root/images/bullet.gif


BIN
web/root/images/close.gif


BIN
web/root/images/closelabel.gif


BIN
web/root/images/donate-button.gif


BIN
web/root/images/download-icon.gif


BIN
web/root/images/image-1.jpg


BIN
web/root/images/img01.jpg


BIN
web/root/images/img02.jpg


BIN
web/root/images/img02.png


BIN
web/root/images/img03.gif


BIN
web/root/images/img04.jpg


BIN
web/root/images/img05.gif


BIN
web/root/images/img06.gif


BIN
web/root/images/img07.gif


BIN
web/root/images/loading.gif


BIN
web/root/images/nextlabel.gif


BIN
web/root/images/prevlabel.gif


BIN
web/root/images/thumb-1.jpg


+ 101 - 0
web/root/index.html

@@ -0,0 +1,101 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8" />
+<title>Open Asset Import Library</title>
+<meta name="keywords" content="ASSIMP Open Asset Import Library free Open Source 3d Model Import Library C C++ Java MDL X OBJ MD2 MD3 PLY " />
+<meta name="description" content="Open Asset Import Library" />
+<link href="css/style.css" rel="stylesheet" type="text/css" media="screen"/>
+<script src="js/prototype.js" type="text/javascript"></script>
+	<script src="js/scriptaculous.js?load=effects,builder" type="text/javascript"></script>
+	<script src="js/lightbox.js" type="text/javascript"></script>
+
+</head>
+<body>
+<div id="header">
+	
+	
+</div>
+<!-- end #header -->
+<div id="menu">
+	<ul>
+		<li class="first"><a href="#" accesskey="1" title="">About</a></li>
+		<li><a href="main_features.html" accesskey="2" title="">Features</a></li>
+		<li><a href="main_viewer.html" accesskey="7" title="">Viewer</a></li>
+		<li><a href="main_doc.html" accesskey="3" title="">Documentation</a></li>
+		<li><a href="main_downloads.html" accesskey="4" title="">Downloads</a></li>
+		<li><a href="main_license.html" accesskey="8" title="">License</a></li>
+		<li><a href="main_contact.html" accesskey="5" title="">Contact</a></li>
+	</ul>
+</div>
+<!-- end #menu -->
+<div id="content">
+	<div id="posts">
+		<div class="post">
+			<h2 class="title">Welcome</h2>
+			<div class="story">
+				<p>The <strong>Open Asset Import Library</strong> (Short name: ASSIMP) is a free library to import various well-known 3D model formats into applications. The library has been designed for maximum stability and efficiency. Written in C++, it is licensed under a <strong>modified BSD license</strong>. The API is provided for both C and C++, ports for other languages like Java, C# and Python are also available or in development. </p>
+
+			<p>The library is developed and maintained by Alexander "Aramis" Gessler, Thomas "Schrompf" Schulze, Kim "Kimmi" Kulling and Rainer "Guru" Schmidt.</p>
+
+			<p>Current version: <strong>1.0 beta</strong></p>
+
+			<p><a href="http://sourceforge.net"><img src="http://sflogo.sourceforge.net/sflogo.php?group_id=226462&amp;type=2" width="125" height="37" border="0" alt="SourceForge.net Logo" 
+/></a></p>
+
+			</div>
+		</div>
+		<div class="post">
+			<h2 class="title">Feature overview</h2>
+			<div class="story">
+				<ul>
+					<li>Imports all model formats in a shared in-memory data format which can easily be read and processed by applications. </li>
+					<li>PostProcessingSteps: Transform the imported models in the form that fits best to your needs (e.g. vertex layout optimization, tangent space calculation ...)</li>					<li>Fully featured model viewer (<a href="main_viewer.html">more</a>) included with the library</li>
+					<li>
+					No external dependencies except boost. All loaders are stand-alone implementations, no external format libs are required.
+					</li>
+				</ul>
+
+				<p>Supported file formats (see the <a href="main_features.html">Features</a> page for a complete list):</p>
+				<ul>
+					<li>Wavefront OBJ</li>
+					<li>Quake I MDL</li>
+					<li>3D Studio Max 3DS</li>
+					<li>3D Studio Max ASE</li>
+					<li>DirectX X</li>
+					<li>Conitec 3D GameStudio MDL (MDL2, MDl3, MDL4, MDL5, MDL7)</li>
+					<li>Conitec 3D GameStudio HMP (HMP4, HMP5, HMP7)</li>
+					<li>Quake II MD2</li>
+					<li>Quake III MD3</li>
+					<li>Return To Castle Wolfenstein MDC</li>
+					<li>Ravensoft MDR</li>
+					<li>Valve SMD</li>
+					<li>Stanford PLY</li>
+				</ul>
+			</div>
+		</div>
+	</div>
+	<!-- end #posts -->
+	<div id="links">
+		<ul>
+			<li>
+				<h2>Screenshots</h2>
+				<ul>
+					<li><a href="screenshots/sshot1.png" title="A futuristic weapon, modelled in 3ds max and exported to ASE" rel="lightbox"><img src="screenshots/sshot1s.png"> </a></li>
+					<li><a href="screenshots/sshot2.png" title="A low-poly tree in Conitec's MDL7 file format. Model by Rainer Prokein (http://reinerstileset.4players.de)" rel="lightbox"><img src="screenshots/sshot2s.png"> </a></li>
+					<li><a href="screenshots/sshot3.png"  title="Stanford dragon with 0.8 mio triangles loaded from a PLY file" rel="lightbox"><img src="screenshots/sshot3s.png"> </a></li>
+				</ul>
+			</li>
+			
+		</ul>
+	</div>
+	<!-- end #links -->
+	<div style="clear: both;">&nbsp;</div>
+</div>
+<!-- end #content -->
+<div id="footer">
+	<p id="legal">Copyright &copy; 2008 ASSIMP Team. Design template by <a href="http://www.freecsstemplates.org/">Free CSS Templates</a></p>
+</div>
+<!-- end #footer -->
+</body>
+</html>

+ 136 - 0
web/root/js/builder.js

@@ -0,0 +1,136 @@
+// script.aculo.us builder.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
+
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+var Builder = {
+  NODEMAP: {
+    AREA: 'map',
+    CAPTION: 'table',
+    COL: 'table',
+    COLGROUP: 'table',
+    LEGEND: 'fieldset',
+    OPTGROUP: 'select',
+    OPTION: 'select',
+    PARAM: 'object',
+    TBODY: 'table',
+    TD: 'table',
+    TFOOT: 'table',
+    TH: 'table',
+    THEAD: 'table',
+    TR: 'table'
+  },
+  // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
+  //       due to a Firefox bug
+  node: function(elementName) {
+    elementName = elementName.toUpperCase();
+    
+    // try innerHTML approach
+    var parentTag = this.NODEMAP[elementName] || 'div';
+    var parentElement = document.createElement(parentTag);
+    try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
+      parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
+    } catch(e) {}
+    var element = parentElement.firstChild || null;
+      
+    // see if browser added wrapping tags
+    if(element && (element.tagName.toUpperCase() != elementName))
+      element = element.getElementsByTagName(elementName)[0];
+    
+    // fallback to createElement approach
+    if(!element) element = document.createElement(elementName);
+    
+    // abort if nothing could be created
+    if(!element) return;
+
+    // attributes (or text)
+    if(arguments[1])
+      if(this._isStringOrNumber(arguments[1]) ||
+        (arguments[1] instanceof Array) ||
+        arguments[1].tagName) {
+          this._children(element, arguments[1]);
+        } else {
+          var attrs = this._attributes(arguments[1]);
+          if(attrs.length) {
+            try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
+              parentElement.innerHTML = "<" +elementName + " " +
+                attrs + "></" + elementName + ">";
+            } catch(e) {}
+            element = parentElement.firstChild || null;
+            // workaround firefox 1.0.X bug
+            if(!element) {
+              element = document.createElement(elementName);
+              for(attr in arguments[1]) 
+                element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
+            }
+            if(element.tagName.toUpperCase() != elementName)
+              element = parentElement.getElementsByTagName(elementName)[0];
+          }
+        } 
+
+    // text, or array of children
+    if(arguments[2])
+      this._children(element, arguments[2]);
+
+     return element;
+  },
+  _text: function(text) {
+     return document.createTextNode(text);
+  },
+
+  ATTR_MAP: {
+    'className': 'class',
+    'htmlFor': 'for'
+  },
+
+  _attributes: function(attributes) {
+    var attrs = [];
+    for(attribute in attributes)
+      attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) +
+          '="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'&quot;') + '"');
+    return attrs.join(" ");
+  },
+  _children: function(element, children) {
+    if(children.tagName) {
+      element.appendChild(children);
+      return;
+    }
+    if(typeof children=='object') { // array can hold nodes and text
+      children.flatten().each( function(e) {
+        if(typeof e=='object')
+          element.appendChild(e)
+        else
+          if(Builder._isStringOrNumber(e))
+            element.appendChild(Builder._text(e));
+      });
+    } else
+      if(Builder._isStringOrNumber(children))
+        element.appendChild(Builder._text(children));
+  },
+  _isStringOrNumber: function(param) {
+    return(typeof param=='string' || typeof param=='number');
+  },
+  build: function(html) {
+    var element = this.node('div');
+    $(element).update(html.strip());
+    return element.down();
+  },
+  dump: function(scope) { 
+    if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope 
+  
+    var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " +
+      "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " +
+      "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+
+      "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+
+      "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+
+      "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/);
+  
+    tags.each( function(tag){ 
+      scope[tag] = function() { 
+        return Builder.node.apply(Builder, [tag].concat($A(arguments)));  
+      } 
+    });
+  }
+}

+ 1122 - 0
web/root/js/effects.js

@@ -0,0 +1,1122 @@
+// script.aculo.us effects.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
+
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// Contributors:
+//  Justin Palmer (http://encytemedia.com/)
+//  Mark Pilgrim (http://diveintomark.org/)
+//  Martin Bialasinki
+// 
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/ 
+
+// converts rgb() and #xxx to #xxxxxx format,  
+// returns self (or first argument) if not convertable  
+String.prototype.parseColor = function() {  
+  var color = '#';
+  if (this.slice(0,4) == 'rgb(') {  
+    var cols = this.slice(4,this.length-1).split(',');  
+    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);  
+  } else {  
+    if (this.slice(0,1) == '#') {  
+      if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();  
+      if (this.length==7) color = this.toLowerCase();  
+    }  
+  }  
+  return (color.length==7 ? color : (arguments[0] || this));  
+};
+
+/*--------------------------------------------------------------------------*/
+
+Element.collectTextNodes = function(element) {  
+  return $A($(element).childNodes).collect( function(node) {
+    return (node.nodeType==3 ? node.nodeValue : 
+      (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
+  }).flatten().join('');
+};
+
+Element.collectTextNodesIgnoreClass = function(element, className) {  
+  return $A($(element).childNodes).collect( function(node) {
+    return (node.nodeType==3 ? node.nodeValue : 
+      ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? 
+        Element.collectTextNodesIgnoreClass(node, className) : ''));
+  }).flatten().join('');
+};
+
+Element.setContentZoom = function(element, percent) {
+  element = $(element);  
+  element.setStyle({fontSize: (percent/100) + 'em'});   
+  if (Prototype.Browser.WebKit) window.scrollBy(0,0);
+  return element;
+};
+
+Element.getInlineOpacity = function(element){
+  return $(element).style.opacity || '';
+};
+
+Element.forceRerendering = function(element) {
+  try {
+    element = $(element);
+    var n = document.createTextNode(' ');
+    element.appendChild(n);
+    element.removeChild(n);
+  } catch(e) { }
+};
+
+/*--------------------------------------------------------------------------*/
+
+var Effect = {
+  _elementDoesNotExistError: {
+    name: 'ElementDoesNotExistError',
+    message: 'The specified DOM element does not exist, but is required for this effect to operate'
+  },
+  Transitions: {
+    linear: Prototype.K,
+    sinoidal: function(pos) {
+      return (-Math.cos(pos*Math.PI)/2) + 0.5;
+    },
+    reverse: function(pos) {
+      return 1-pos;
+    },
+    flicker: function(pos) {
+      var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
+      return pos > 1 ? 1 : pos;
+    },
+    wobble: function(pos) {
+      return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
+    },
+    pulse: function(pos, pulses) { 
+      pulses = pulses || 5; 
+      return (
+        ((pos % (1/pulses)) * pulses).round() == 0 ? 
+              ((pos * pulses * 2) - (pos * pulses * 2).floor()) : 
+          1 - ((pos * pulses * 2) - (pos * pulses * 2).floor())
+        );
+    },
+    spring: function(pos) { 
+      return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); 
+    },
+    none: function(pos) {
+      return 0;
+    },
+    full: function(pos) {
+      return 1;
+    }
+  },
+  DefaultOptions: {
+    duration:   1.0,   // seconds
+    fps:        100,   // 100= assume 66fps max.
+    sync:       false, // true for combining
+    from:       0.0,
+    to:         1.0,
+    delay:      0.0,
+    queue:      'parallel'
+  },
+  tagifyText: function(element) {
+    var tagifyStyle = 'position:relative';
+    if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';
+    
+    element = $(element);
+    $A(element.childNodes).each( function(child) {
+      if (child.nodeType==3) {
+        child.nodeValue.toArray().each( function(character) {
+          element.insertBefore(
+            new Element('span', {style: tagifyStyle}).update(
+              character == ' ' ? String.fromCharCode(160) : character), 
+              child);
+        });
+        Element.remove(child);
+      }
+    });
+  },
+  multiple: function(element, effect) {
+    var elements;
+    if (((typeof element == 'object') || 
+        Object.isFunction(element)) && 
+       (element.length))
+      elements = element;
+    else
+      elements = $(element).childNodes;
+      
+    var options = Object.extend({
+      speed: 0.1,
+      delay: 0.0
+    }, arguments[2] || { });
+    var masterDelay = options.delay;
+
+    $A(elements).each( function(element, index) {
+      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
+    });
+  },
+  PAIRS: {
+    'slide':  ['SlideDown','SlideUp'],
+    'blind':  ['BlindDown','BlindUp'],
+    'appear': ['Appear','Fade']
+  },
+  toggle: function(element, effect) {
+    element = $(element);
+    effect = (effect || 'appear').toLowerCase();
+    var options = Object.extend({
+      queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
+    }, arguments[2] || { });
+    Effect[element.visible() ? 
+      Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
+  }
+};
+
+Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;
+
+/* ------------- core effects ------------- */
+
+Effect.ScopedQueue = Class.create(Enumerable, {
+  initialize: function() {
+    this.effects  = [];
+    this.interval = null;    
+  },
+  _each: function(iterator) {
+    this.effects._each(iterator);
+  },
+  add: function(effect) {
+    var timestamp = new Date().getTime();
+    
+    var position = Object.isString(effect.options.queue) ? 
+      effect.options.queue : effect.options.queue.position;
+    
+    switch(position) {
+      case 'front':
+        // move unstarted effects after this effect  
+        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
+            e.startOn  += effect.finishOn;
+            e.finishOn += effect.finishOn;
+          });
+        break;
+      case 'with-last':
+        timestamp = this.effects.pluck('startOn').max() || timestamp;
+        break;
+      case 'end':
+        // start effect after last queued effect has finished
+        timestamp = this.effects.pluck('finishOn').max() || timestamp;
+        break;
+    }
+    
+    effect.startOn  += timestamp;
+    effect.finishOn += timestamp;
+
+    if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
+      this.effects.push(effect);
+    
+    if (!this.interval)
+      this.interval = setInterval(this.loop.bind(this), 15);
+  },
+  remove: function(effect) {
+    this.effects = this.effects.reject(function(e) { return e==effect });
+    if (this.effects.length == 0) {
+      clearInterval(this.interval);
+      this.interval = null;
+    }
+  },
+  loop: function() {
+    var timePos = new Date().getTime();
+    for(var i=0, len=this.effects.length;i<len;i++) 
+      this.effects[i] && this.effects[i].loop(timePos);
+  }
+});
+
+Effect.Queues = {
+  instances: $H(),
+  get: function(queueName) {
+    if (!Object.isString(queueName)) return queueName;
+    
+    return this.instances.get(queueName) ||
+      this.instances.set(queueName, new Effect.ScopedQueue());
+  }
+};
+Effect.Queue = Effect.Queues.get('global');
+
+Effect.Base = Class.create({
+  position: null,
+  start: function(options) {
+    function codeForEvent(options,eventName){
+      return (
+        (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +
+        (options[eventName] ? 'this.options.'+eventName+'(this);' : '')
+      );
+    }
+    if (options && options.transition === false) options.transition = Effect.Transitions.linear;
+    this.options      = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });
+    this.currentFrame = 0;
+    this.state        = 'idle';
+    this.startOn      = this.options.delay*1000;
+    this.finishOn     = this.startOn+(this.options.duration*1000);
+    this.fromToDelta  = this.options.to-this.options.from;
+    this.totalTime    = this.finishOn-this.startOn;
+    this.totalFrames  = this.options.fps*this.options.duration;
+    
+    eval('this.render = function(pos){ '+
+      'if (this.state=="idle"){this.state="running";'+
+      codeForEvent(this.options,'beforeSetup')+
+      (this.setup ? 'this.setup();':'')+ 
+      codeForEvent(this.options,'afterSetup')+
+      '};if (this.state=="running"){'+
+      'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+
+      'this.position=pos;'+
+      codeForEvent(this.options,'beforeUpdate')+
+      (this.update ? 'this.update(pos);':'')+
+      codeForEvent(this.options,'afterUpdate')+
+      '}}');
+    
+    this.event('beforeStart');
+    if (!this.options.sync)
+      Effect.Queues.get(Object.isString(this.options.queue) ? 
+        'global' : this.options.queue.scope).add(this);
+  },
+  loop: function(timePos) {
+    if (timePos >= this.startOn) {
+      if (timePos >= this.finishOn) {
+        this.render(1.0);
+        this.cancel();
+        this.event('beforeFinish');
+        if (this.finish) this.finish(); 
+        this.event('afterFinish');
+        return;  
+      }
+      var pos   = (timePos - this.startOn) / this.totalTime,
+          frame = (pos * this.totalFrames).round();
+      if (frame > this.currentFrame) {
+        this.render(pos);
+        this.currentFrame = frame;
+      }
+    }
+  },
+  cancel: function() {
+    if (!this.options.sync)
+      Effect.Queues.get(Object.isString(this.options.queue) ? 
+        'global' : this.options.queue.scope).remove(this);
+    this.state = 'finished';
+  },
+  event: function(eventName) {
+    if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
+    if (this.options[eventName]) this.options[eventName](this);
+  },
+  inspect: function() {
+    var data = $H();
+    for(property in this)
+      if (!Object.isFunction(this[property])) data.set(property, this[property]);
+    return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
+  }
+});
+
+Effect.Parallel = Class.create(Effect.Base, {
+  initialize: function(effects) {
+    this.effects = effects || [];
+    this.start(arguments[1]);
+  },
+  update: function(position) {
+    this.effects.invoke('render', position);
+  },
+  finish: function(position) {
+    this.effects.each( function(effect) {
+      effect.render(1.0);
+      effect.cancel();
+      effect.event('beforeFinish');
+      if (effect.finish) effect.finish(position);
+      effect.event('afterFinish');
+    });
+  }
+});
+
+Effect.Tween = Class.create(Effect.Base, {
+  initialize: function(object, from, to) {
+    object = Object.isString(object) ? $(object) : object;
+    var args = $A(arguments), method = args.last(), 
+      options = args.length == 5 ? args[3] : null;
+    this.method = Object.isFunction(method) ? method.bind(object) :
+      Object.isFunction(object[method]) ? object[method].bind(object) : 
+      function(value) { object[method] = value };
+    this.start(Object.extend({ from: from, to: to }, options || { }));
+  },
+  update: function(position) {
+    this.method(position);
+  }
+});
+
+Effect.Event = Class.create(Effect.Base, {
+  initialize: function() {
+    this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
+  },
+  update: Prototype.emptyFunction
+});
+
+Effect.Opacity = Class.create(Effect.Base, {
+  initialize: function(element) {
+    this.element = $(element);
+    if (!this.element) throw(Effect._elementDoesNotExistError);
+    // make this work on IE on elements without 'layout'
+    if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
+      this.element.setStyle({zoom: 1});
+    var options = Object.extend({
+      from: this.element.getOpacity() || 0.0,
+      to:   1.0
+    }, arguments[1] || { });
+    this.start(options);
+  },
+  update: function(position) {
+    this.element.setOpacity(position);
+  }
+});
+
+Effect.Move = Class.create(Effect.Base, {
+  initialize: function(element) {
+    this.element = $(element);
+    if (!this.element) throw(Effect._elementDoesNotExistError);
+    var options = Object.extend({
+      x:    0,
+      y:    0,
+      mode: 'relative'
+    }, arguments[1] || { });
+    this.start(options);
+  },
+  setup: function() {
+    this.element.makePositioned();
+    this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
+    this.originalTop  = parseFloat(this.element.getStyle('top')  || '0');
+    if (this.options.mode == 'absolute') {
+      this.options.x = this.options.x - this.originalLeft;
+      this.options.y = this.options.y - this.originalTop;
+    }
+  },
+  update: function(position) {
+    this.element.setStyle({
+      left: (this.options.x  * position + this.originalLeft).round() + 'px',
+      top:  (this.options.y  * position + this.originalTop).round()  + 'px'
+    });
+  }
+});
+
+// for backwards compatibility
+Effect.MoveBy = function(element, toTop, toLeft) {
+  return new Effect.Move(element, 
+    Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));
+};
+
+Effect.Scale = Class.create(Effect.Base, {
+  initialize: function(element, percent) {
+    this.element = $(element);
+    if (!this.element) throw(Effect._elementDoesNotExistError);
+    var options = Object.extend({
+      scaleX: true,
+      scaleY: true,
+      scaleContent: true,
+      scaleFromCenter: false,
+      scaleMode: 'box',        // 'box' or 'contents' or { } with provided values
+      scaleFrom: 100.0,
+      scaleTo:   percent
+    }, arguments[2] || { });
+    this.start(options);
+  },
+  setup: function() {
+    this.restoreAfterFinish = this.options.restoreAfterFinish || false;
+    this.elementPositioning = this.element.getStyle('position');
+    
+    this.originalStyle = { };
+    ['top','left','width','height','fontSize'].each( function(k) {
+      this.originalStyle[k] = this.element.style[k];
+    }.bind(this));
+      
+    this.originalTop  = this.element.offsetTop;
+    this.originalLeft = this.element.offsetLeft;
+    
+    var fontSize = this.element.getStyle('font-size') || '100%';
+    ['em','px','%','pt'].each( function(fontSizeType) {
+      if (fontSize.indexOf(fontSizeType)>0) {
+        this.fontSize     = parseFloat(fontSize);
+        this.fontSizeType = fontSizeType;
+      }
+    }.bind(this));
+    
+    this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
+    
+    this.dims = null;
+    if (this.options.scaleMode=='box')
+      this.dims = [this.element.offsetHeight, this.element.offsetWidth];
+    if (/^content/.test(this.options.scaleMode))
+      this.dims = [this.element.scrollHeight, this.element.scrollWidth];
+    if (!this.dims)
+      this.dims = [this.options.scaleMode.originalHeight,
+                   this.options.scaleMode.originalWidth];
+  },
+  update: function(position) {
+    var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
+    if (this.options.scaleContent && this.fontSize)
+      this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
+    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
+  },
+  finish: function(position) {
+    if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
+  },
+  setDimensions: function(height, width) {
+    var d = { };
+    if (this.options.scaleX) d.width = width.round() + 'px';
+    if (this.options.scaleY) d.height = height.round() + 'px';
+    if (this.options.scaleFromCenter) {
+      var topd  = (height - this.dims[0])/2;
+      var leftd = (width  - this.dims[1])/2;
+      if (this.elementPositioning == 'absolute') {
+        if (this.options.scaleY) d.top = this.originalTop-topd + 'px';
+        if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
+      } else {
+        if (this.options.scaleY) d.top = -topd + 'px';
+        if (this.options.scaleX) d.left = -leftd + 'px';
+      }
+    }
+    this.element.setStyle(d);
+  }
+});
+
+Effect.Highlight = Class.create(Effect.Base, {
+  initialize: function(element) {
+    this.element = $(element);
+    if (!this.element) throw(Effect._elementDoesNotExistError);
+    var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });
+    this.start(options);
+  },
+  setup: function() {
+    // Prevent executing on elements not in the layout flow
+    if (this.element.getStyle('display')=='none') { this.cancel(); return; }
+    // Disable background image during the effect
+    this.oldStyle = { };
+    if (!this.options.keepBackgroundImage) {
+      this.oldStyle.backgroundImage = this.element.getStyle('background-image');
+      this.element.setStyle({backgroundImage: 'none'});
+    }
+    if (!this.options.endcolor)
+      this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
+    if (!this.options.restorecolor)
+      this.options.restorecolor = this.element.getStyle('background-color');
+    // init color calculations
+    this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
+    this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
+  },
+  update: function(position) {
+    this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
+      return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });
+  },
+  finish: function() {
+    this.element.setStyle(Object.extend(this.oldStyle, {
+      backgroundColor: this.options.restorecolor
+    }));
+  }
+});
+
+Effect.ScrollTo = function(element) {
+  var options = arguments[1] || { },
+    scrollOffsets = document.viewport.getScrollOffsets(),
+    elementOffsets = $(element).cumulativeOffset(),
+    max = (window.height || document.body.scrollHeight) - document.viewport.getHeight();  
+
+  if (options.offset) elementOffsets[1] += options.offset;
+
+  return new Effect.Tween(null,
+    scrollOffsets.top,
+    elementOffsets[1] > max ? max : elementOffsets[1],
+    options,
+    function(p){ scrollTo(scrollOffsets.left, p.round()) }
+  );
+};
+
+/* ------------- combination effects ------------- */
+
+Effect.Fade = function(element) {
+  element = $(element);
+  var oldOpacity = element.getInlineOpacity();
+  var options = Object.extend({
+    from: element.getOpacity() || 1.0,
+    to:   0.0,
+    afterFinishInternal: function(effect) { 
+      if (effect.options.to!=0) return;
+      effect.element.hide().setStyle({opacity: oldOpacity}); 
+    }
+  }, arguments[1] || { });
+  return new Effect.Opacity(element,options);
+};
+
+Effect.Appear = function(element) {
+  element = $(element);
+  var options = Object.extend({
+  from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
+  to:   1.0,
+  // force Safari to render floated elements properly
+  afterFinishInternal: function(effect) {
+    effect.element.forceRerendering();
+  },
+  beforeSetup: function(effect) {
+    effect.element.setOpacity(effect.options.from).show(); 
+  }}, arguments[1] || { });
+  return new Effect.Opacity(element,options);
+};
+
+Effect.Puff = function(element) {
+  element = $(element);
+  var oldStyle = { 
+    opacity: element.getInlineOpacity(), 
+    position: element.getStyle('position'),
+    top:  element.style.top,
+    left: element.style.left,
+    width: element.style.width,
+    height: element.style.height
+  };
+  return new Effect.Parallel(
+   [ new Effect.Scale(element, 200, 
+      { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), 
+     new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], 
+     Object.extend({ duration: 1.0, 
+      beforeSetupInternal: function(effect) {
+        Position.absolutize(effect.effects[0].element)
+      },
+      afterFinishInternal: function(effect) {
+         effect.effects[0].element.hide().setStyle(oldStyle); }
+     }, arguments[1] || { })
+   );
+};
+
+Effect.BlindUp = function(element) {
+  element = $(element);
+  element.makeClipping();
+  return new Effect.Scale(element, 0,
+    Object.extend({ scaleContent: false, 
+      scaleX: false, 
+      restoreAfterFinish: true,
+      afterFinishInternal: function(effect) {
+        effect.element.hide().undoClipping();
+      } 
+    }, arguments[1] || { })
+  );
+};
+
+Effect.BlindDown = function(element) {
+  element = $(element);
+  var elementDimensions = element.getDimensions();
+  return new Effect.Scale(element, 100, Object.extend({ 
+    scaleContent: false, 
+    scaleX: false,
+    scaleFrom: 0,
+    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+    restoreAfterFinish: true,
+    afterSetup: function(effect) {
+      effect.element.makeClipping().setStyle({height: '0px'}).show(); 
+    },  
+    afterFinishInternal: function(effect) {
+      effect.element.undoClipping();
+    }
+  }, arguments[1] || { }));
+};
+
+Effect.SwitchOff = function(element) {
+  element = $(element);
+  var oldOpacity = element.getInlineOpacity();
+  return new Effect.Appear(element, Object.extend({
+    duration: 0.4,
+    from: 0,
+    transition: Effect.Transitions.flicker,
+    afterFinishInternal: function(effect) {
+      new Effect.Scale(effect.element, 1, { 
+        duration: 0.3, scaleFromCenter: true,
+        scaleX: false, scaleContent: false, restoreAfterFinish: true,
+        beforeSetup: function(effect) { 
+          effect.element.makePositioned().makeClipping();
+        },
+        afterFinishInternal: function(effect) {
+          effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
+        }
+      })
+    }
+  }, arguments[1] || { }));
+};
+
+Effect.DropOut = function(element) {
+  element = $(element);
+  var oldStyle = {
+    top: element.getStyle('top'),
+    left: element.getStyle('left'),
+    opacity: element.getInlineOpacity() };
+  return new Effect.Parallel(
+    [ new Effect.Move(element, {x: 0, y: 100, sync: true }), 
+      new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
+    Object.extend(
+      { duration: 0.5,
+        beforeSetup: function(effect) {
+          effect.effects[0].element.makePositioned(); 
+        },
+        afterFinishInternal: function(effect) {
+          effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
+        } 
+      }, arguments[1] || { }));
+};
+
+Effect.Shake = function(element) {
+  element = $(element);
+  var options = Object.extend({
+    distance: 20,
+    duration: 0.5
+  }, arguments[1] || {});
+  var distance = parseFloat(options.distance);
+  var split = parseFloat(options.duration) / 10.0;
+  var oldStyle = {
+    top: element.getStyle('top'),
+    left: element.getStyle('left') };
+    return new Effect.Move(element,
+      { x:  distance, y: 0, duration: split, afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {
+        effect.element.undoPositioned().setStyle(oldStyle);
+  }}) }}) }}) }}) }}) }});
+};
+
+Effect.SlideDown = function(element) {
+  element = $(element).cleanWhitespace();
+  // SlideDown need to have the content of the element wrapped in a container element with fixed height!
+  var oldInnerBottom = element.down().getStyle('bottom');
+  var elementDimensions = element.getDimensions();
+  return new Effect.Scale(element, 100, Object.extend({ 
+    scaleContent: false, 
+    scaleX: false, 
+    scaleFrom: window.opera ? 0 : 1,
+    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+    restoreAfterFinish: true,
+    afterSetup: function(effect) {
+      effect.element.makePositioned();
+      effect.element.down().makePositioned();
+      if (window.opera) effect.element.setStyle({top: ''});
+      effect.element.makeClipping().setStyle({height: '0px'}).show(); 
+    },
+    afterUpdateInternal: function(effect) {
+      effect.element.down().setStyle({bottom:
+        (effect.dims[0] - effect.element.clientHeight) + 'px' }); 
+    },
+    afterFinishInternal: function(effect) {
+      effect.element.undoClipping().undoPositioned();
+      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
+    }, arguments[1] || { })
+  );
+};
+
+Effect.SlideUp = function(element) {
+  element = $(element).cleanWhitespace();
+  var oldInnerBottom = element.down().getStyle('bottom');
+  var elementDimensions = element.getDimensions();
+  return new Effect.Scale(element, window.opera ? 0 : 1,
+   Object.extend({ scaleContent: false, 
+    scaleX: false, 
+    scaleMode: 'box',
+    scaleFrom: 100,
+    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+    restoreAfterFinish: true,
+    afterSetup: function(effect) {
+      effect.element.makePositioned();
+      effect.element.down().makePositioned();
+      if (window.opera) effect.element.setStyle({top: ''});
+      effect.element.makeClipping().show();
+    },  
+    afterUpdateInternal: function(effect) {
+      effect.element.down().setStyle({bottom:
+        (effect.dims[0] - effect.element.clientHeight) + 'px' });
+    },
+    afterFinishInternal: function(effect) {
+      effect.element.hide().undoClipping().undoPositioned();
+      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});
+    }
+   }, arguments[1] || { })
+  );
+};
+
+// Bug in opera makes the TD containing this element expand for a instance after finish 
+Effect.Squish = function(element) {
+  return new Effect.Scale(element, window.opera ? 1 : 0, { 
+    restoreAfterFinish: true,
+    beforeSetup: function(effect) {
+      effect.element.makeClipping(); 
+    },  
+    afterFinishInternal: function(effect) {
+      effect.element.hide().undoClipping(); 
+    }
+  });
+};
+
+Effect.Grow = function(element) {
+  element = $(element);
+  var options = Object.extend({
+    direction: 'center',
+    moveTransition: Effect.Transitions.sinoidal,
+    scaleTransition: Effect.Transitions.sinoidal,
+    opacityTransition: Effect.Transitions.full
+  }, arguments[1] || { });
+  var oldStyle = {
+    top: element.style.top,
+    left: element.style.left,
+    height: element.style.height,
+    width: element.style.width,
+    opacity: element.getInlineOpacity() };
+
+  var dims = element.getDimensions();    
+  var initialMoveX, initialMoveY;
+  var moveX, moveY;
+  
+  switch (options.direction) {
+    case 'top-left':
+      initialMoveX = initialMoveY = moveX = moveY = 0; 
+      break;
+    case 'top-right':
+      initialMoveX = dims.width;
+      initialMoveY = moveY = 0;
+      moveX = -dims.width;
+      break;
+    case 'bottom-left':
+      initialMoveX = moveX = 0;
+      initialMoveY = dims.height;
+      moveY = -dims.height;
+      break;
+    case 'bottom-right':
+      initialMoveX = dims.width;
+      initialMoveY = dims.height;
+      moveX = -dims.width;
+      moveY = -dims.height;
+      break;
+    case 'center':
+      initialMoveX = dims.width / 2;
+      initialMoveY = dims.height / 2;
+      moveX = -dims.width / 2;
+      moveY = -dims.height / 2;
+      break;
+  }
+  
+  return new Effect.Move(element, {
+    x: initialMoveX,
+    y: initialMoveY,
+    duration: 0.01, 
+    beforeSetup: function(effect) {
+      effect.element.hide().makeClipping().makePositioned();
+    },
+    afterFinishInternal: function(effect) {
+      new Effect.Parallel(
+        [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
+          new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
+          new Effect.Scale(effect.element, 100, {
+            scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, 
+            sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
+        ], Object.extend({
+             beforeSetup: function(effect) {
+               effect.effects[0].element.setStyle({height: '0px'}).show(); 
+             },
+             afterFinishInternal: function(effect) {
+               effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); 
+             }
+           }, options)
+      )
+    }
+  });
+};
+
+Effect.Shrink = function(element) {
+  element = $(element);
+  var options = Object.extend({
+    direction: 'center',
+    moveTransition: Effect.Transitions.sinoidal,
+    scaleTransition: Effect.Transitions.sinoidal,
+    opacityTransition: Effect.Transitions.none
+  }, arguments[1] || { });
+  var oldStyle = {
+    top: element.style.top,
+    left: element.style.left,
+    height: element.style.height,
+    width: element.style.width,
+    opacity: element.getInlineOpacity() };
+
+  var dims = element.getDimensions();
+  var moveX, moveY;
+  
+  switch (options.direction) {
+    case 'top-left':
+      moveX = moveY = 0;
+      break;
+    case 'top-right':
+      moveX = dims.width;
+      moveY = 0;
+      break;
+    case 'bottom-left':
+      moveX = 0;
+      moveY = dims.height;
+      break;
+    case 'bottom-right':
+      moveX = dims.width;
+      moveY = dims.height;
+      break;
+    case 'center':  
+      moveX = dims.width / 2;
+      moveY = dims.height / 2;
+      break;
+  }
+  
+  return new Effect.Parallel(
+    [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
+      new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
+      new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
+    ], Object.extend({            
+         beforeStartInternal: function(effect) {
+           effect.effects[0].element.makePositioned().makeClipping(); 
+         },
+         afterFinishInternal: function(effect) {
+           effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
+       }, options)
+  );
+};
+
+Effect.Pulsate = function(element) {
+  element = $(element);
+  var options    = arguments[1] || { };
+  var oldOpacity = element.getInlineOpacity();
+  var transition = options.transition || Effect.Transitions.sinoidal;
+  var reverser   = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) };
+  reverser.bind(transition);
+  return new Effect.Opacity(element, 
+    Object.extend(Object.extend({  duration: 2.0, from: 0,
+      afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
+    }, options), {transition: reverser}));
+};
+
+Effect.Fold = function(element) {
+  element = $(element);
+  var oldStyle = {
+    top: element.style.top,
+    left: element.style.left,
+    width: element.style.width,
+    height: element.style.height };
+  element.makeClipping();
+  return new Effect.Scale(element, 5, Object.extend({   
+    scaleContent: false,
+    scaleX: false,
+    afterFinishInternal: function(effect) {
+    new Effect.Scale(element, 1, { 
+      scaleContent: false, 
+      scaleY: false,
+      afterFinishInternal: function(effect) {
+        effect.element.hide().undoClipping().setStyle(oldStyle);
+      } });
+  }}, arguments[1] || { }));
+};
+
+Effect.Morph = Class.create(Effect.Base, {
+  initialize: function(element) {
+    this.element = $(element);
+    if (!this.element) throw(Effect._elementDoesNotExistError);
+    var options = Object.extend({
+      style: { }
+    }, arguments[1] || { });
+    
+    if (!Object.isString(options.style)) this.style = $H(options.style);
+    else {
+      if (options.style.include(':'))
+        this.style = options.style.parseStyle();
+      else {
+        this.element.addClassName(options.style);
+        this.style = $H(this.element.getStyles());
+        this.element.removeClassName(options.style);
+        var css = this.element.getStyles();
+        this.style = this.style.reject(function(style) {
+          return style.value == css[style.key];
+        });
+        options.afterFinishInternal = function(effect) {
+          effect.element.addClassName(effect.options.style);
+          effect.transforms.each(function(transform) {
+            effect.element.style[transform.style] = '';
+          });
+        }
+      }
+    }
+    this.start(options);
+  },
+  
+  setup: function(){
+    function parseColor(color){
+      if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
+      color = color.parseColor();
+      return $R(0,2).map(function(i){
+        return parseInt( color.slice(i*2+1,i*2+3), 16 ) 
+      });
+    }
+    this.transforms = this.style.map(function(pair){
+      var property = pair[0], value = pair[1], unit = null;
+
+      if (value.parseColor('#zzzzzz') != '#zzzzzz') {
+        value = value.parseColor();
+        unit  = 'color';
+      } else if (property == 'opacity') {
+        value = parseFloat(value);
+        if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
+          this.element.setStyle({zoom: 1});
+      } else if (Element.CSS_LENGTH.test(value)) {
+          var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
+          value = parseFloat(components[1]);
+          unit = (components.length == 3) ? components[2] : null;
+      }
+
+      var originalValue = this.element.getStyle(property);
+      return { 
+        style: property.camelize(), 
+        originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), 
+        targetValue: unit=='color' ? parseColor(value) : value,
+        unit: unit
+      };
+    }.bind(this)).reject(function(transform){
+      return (
+        (transform.originalValue == transform.targetValue) ||
+        (
+          transform.unit != 'color' &&
+          (isNaN(transform.originalValue) || isNaN(transform.targetValue))
+        )
+      )
+    });
+  },
+  update: function(position) {
+    var style = { }, transform, i = this.transforms.length;
+    while(i--)
+      style[(transform = this.transforms[i]).style] = 
+        transform.unit=='color' ? '#'+
+          (Math.round(transform.originalValue[0]+
+            (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
+          (Math.round(transform.originalValue[1]+
+            (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
+          (Math.round(transform.originalValue[2]+
+            (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
+        (transform.originalValue +
+          (transform.targetValue - transform.originalValue) * position).toFixed(3) + 
+            (transform.unit === null ? '' : transform.unit);
+    this.element.setStyle(style, true);
+  }
+});
+
+Effect.Transform = Class.create({
+  initialize: function(tracks){
+    this.tracks  = [];
+    this.options = arguments[1] || { };
+    this.addTracks(tracks);
+  },
+  addTracks: function(tracks){
+    tracks.each(function(track){
+      track = $H(track);
+      var data = track.values().first();
+      this.tracks.push($H({
+        ids:     track.keys().first(),
+        effect:  Effect.Morph,
+        options: { style: data }
+      }));
+    }.bind(this));
+    return this;
+  },
+  play: function(){
+    return new Effect.Parallel(
+      this.tracks.map(function(track){
+        var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');
+        var elements = [$(ids) || $$(ids)].flatten();
+        return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) });
+      }).flatten(),
+      this.options
+    );
+  }
+});
+
+Element.CSS_PROPERTIES = $w(
+  'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + 
+  'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
+  'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
+  'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
+  'fontSize fontWeight height left letterSpacing lineHeight ' +
+  'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
+  'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
+  'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
+  'right textIndent top width wordSpacing zIndex');
+  
+Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
+
+String.__parseStyleElement = document.createElement('div');
+String.prototype.parseStyle = function(){
+  var style, styleRules = $H();
+  if (Prototype.Browser.WebKit)
+    style = new Element('div',{style:this}).style;
+  else {
+    String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>';
+    style = String.__parseStyleElement.childNodes[0].style;
+  }
+  
+  Element.CSS_PROPERTIES.each(function(property){
+    if (style[property]) styleRules.set(property, style[property]); 
+  });
+  
+  if (Prototype.Browser.IE && this.include('opacity'))
+    styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);
+
+  return styleRules;
+};
+
+if (document.defaultView && document.defaultView.getComputedStyle) {
+  Element.getStyles = function(element) {
+    var css = document.defaultView.getComputedStyle($(element), null);
+    return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {
+      styles[property] = css[property];
+      return styles;
+    });
+  };
+} else {
+  Element.getStyles = function(element) {
+    element = $(element);
+    var css = element.currentStyle, styles;
+    styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) {
+      results[property] = css[property];
+      return results;
+    });
+    if (!styles.opacity) styles.opacity = element.getOpacity();
+    return styles;
+  };
+};
+
+Effect.Methods = {
+  morph: function(element, style) {
+    element = $(element);
+    new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));
+    return element;
+  },
+  visualEffect: function(element, effect, options) {
+    element = $(element)
+    var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
+    new Effect[klass](element, options);
+    return element;
+  },
+  highlight: function(element, options) {
+    element = $(element);
+    new Effect.Highlight(element, options);
+    return element;
+  }
+};
+
+$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
+  'pulsate shake puff squish switchOff dropOut').each(
+  function(effect) { 
+    Effect.Methods[effect] = function(element, options){
+      element = $(element);
+      Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
+      return element;
+    }
+  }
+);
+
+$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each( 
+  function(f) { Effect.Methods[f] = Element[f]; }
+);
+
+Element.addMethods(Effect.Methods);

+ 497 - 0
web/root/js/lightbox.js

@@ -0,0 +1,497 @@
+// -----------------------------------------------------------------------------------
+//
+//	Lightbox v2.04
+//	by Lokesh Dhakar - http://www.lokeshdhakar.com
+//	Last Modification: 2/9/08
+//
+//	For more information, visit:
+//	http://lokeshdhakar.com/projects/lightbox2/
+//
+//	Licensed under the Creative Commons Attribution 2.5 License - http://creativecommons.org/licenses/by/2.5/
+//  	- Free for use in both personal and commercial projects
+//		- Attribution requires leaving author name, author link, and the license info intact.
+//	
+//  Thanks: Scott Upton(uptonic.com), Peter-Paul Koch(quirksmode.com), and Thomas Fuchs(mir.aculo.us) for ideas, libs, and snippets.
+//  		Artemy Tregubenko (arty.name) for cleanup and help in updating to latest ver of proto-aculous.
+//
+// -----------------------------------------------------------------------------------
+/*
+
+    Table of Contents
+    -----------------
+    Configuration
+
+    Lightbox Class Declaration
+    - initialize()
+    - updateImageList()
+    - start()
+    - changeImage()
+    - resizeImageContainer()
+    - showImage()
+    - updateDetails()
+    - updateNav()
+    - enableKeyboardNav()
+    - disableKeyboardNav()
+    - keyboardAction()
+    - preloadNeighborImages()
+    - end()
+    
+    Function Calls
+    - document.observe()
+   
+*/
+// -----------------------------------------------------------------------------------
+
+//
+//  Configurationl
+//
+LightboxOptions = Object.extend({
+    fileLoadingImage:        'images/loading.gif',     
+    fileBottomNavCloseImage: 'images/closelabel.gif',
+
+    overlayOpacity: 0.8,   // controls transparency of shadow overlay
+
+    animate: true,         // toggles resizing animations
+    resizeSpeed: 7,        // controls the speed of the image resizing animations (1=slowest and 10=fastest)
+
+    borderSize: 10,         //if you adjust the padding in the CSS, you will need to update this variable
+
+	// When grouping images this is used to write: Image # of #.
+	// Change it for non-english localization
+	labelImage: "Image",
+	labelOf: "of"
+}, window.LightboxOptions || {});
+
+// -----------------------------------------------------------------------------------
+
+var Lightbox = Class.create();
+
+Lightbox.prototype = {
+    imageArray: [],
+    activeImage: undefined,
+    
+    // initialize()
+    // Constructor runs on completion of the DOM loading. Calls updateImageList and then
+    // the function inserts html at the bottom of the page which is used to display the shadow 
+    // overlay and the image container.
+    //
+    initialize: function() {    
+        
+        this.updateImageList();
+        
+        this.keyboardAction = this.keyboardAction.bindAsEventListener(this);
+
+        if (LightboxOptions.resizeSpeed > 10) LightboxOptions.resizeSpeed = 10;
+        if (LightboxOptions.resizeSpeed < 1)  LightboxOptions.resizeSpeed = 1;
+
+	    this.resizeDuration = LightboxOptions.animate ? ((11 - LightboxOptions.resizeSpeed) * 0.15) : 0;
+	    this.overlayDuration = LightboxOptions.animate ? 0.2 : 0;  // shadow fade in/out duration
+
+        // When Lightbox starts it will resize itself from 250 by 250 to the current image dimension.
+        // If animations are turned off, it will be hidden as to prevent a flicker of a
+        // white 250 by 250 box.
+        var size = (LightboxOptions.animate ? 250 : 1) + 'px';
+        
+
+        // Code inserts html at the bottom of the page that looks similar to this:
+        //
+        //  <div id="overlay"></div>
+        //  <div id="lightbox">
+        //      <div id="outerImageContainer">
+        //          <div id="imageContainer">
+        //              <img id="lightboxImage">
+        //              <div style="" id="hoverNav">
+        //                  <a href="#" id="prevLink"></a>
+        //                  <a href="#" id="nextLink"></a>
+        //              </div>
+        //              <div id="loading">
+        //                  <a href="#" id="loadingLink">
+        //                      <img src="images/loading.gif">
+        //                  </a>
+        //              </div>
+        //          </div>
+        //      </div>
+        //      <div id="imageDataContainer">
+        //          <div id="imageData">
+        //              <div id="imageDetails">
+        //                  <span id="caption"></span>
+        //                  <span id="numberDisplay"></span>
+        //              </div>
+        //              <div id="bottomNav">
+        //                  <a href="#" id="bottomNavClose">
+        //                      <img src="images/close.gif">
+        //                  </a>
+        //              </div>
+        //          </div>
+        //      </div>
+        //  </div>
+
+
+        var objBody = $$('body')[0];
+
+		objBody.appendChild(Builder.node('div',{id:'overlay'}));
+	
+        objBody.appendChild(Builder.node('div',{id:'lightbox'}, [
+            Builder.node('div',{id:'outerImageContainer'}, 
+                Builder.node('div',{id:'imageContainer'}, [
+                    Builder.node('img',{id:'lightboxImage'}), 
+                    Builder.node('div',{id:'hoverNav'}, [
+                        Builder.node('a',{id:'prevLink', href: '#' }),
+                        Builder.node('a',{id:'nextLink', href: '#' })
+                    ]),
+                    Builder.node('div',{id:'loading'}, 
+                        Builder.node('a',{id:'loadingLink', href: '#' }, 
+                            Builder.node('img', {src: LightboxOptions.fileLoadingImage})
+                        )
+                    )
+                ])
+            ),
+            Builder.node('div', {id:'imageDataContainer'},
+                Builder.node('div',{id:'imageData'}, [
+                    Builder.node('div',{id:'imageDetails'}, [
+                        Builder.node('span',{id:'caption'}),
+                        Builder.node('span',{id:'numberDisplay'})
+                    ]),
+                    Builder.node('div',{id:'bottomNav'},
+                        Builder.node('a',{id:'bottomNavClose', href: '#' },
+                            Builder.node('img', { src: LightboxOptions.fileBottomNavCloseImage })
+                        )
+                    )
+                ])
+            )
+        ]));
+
+
+		$('overlay').hide().observe('click', (function() { this.end(); }).bind(this));
+		$('lightbox').hide().observe('click', (function(event) { if (event.element().id == 'lightbox') this.end(); }).bind(this));
+		$('outerImageContainer').setStyle({ width: size, height: size });
+		$('prevLink').observe('click', (function(event) { event.stop(); this.changeImage(this.activeImage - 1); }).bindAsEventListener(this));
+		$('nextLink').observe('click', (function(event) { event.stop(); this.changeImage(this.activeImage + 1); }).bindAsEventListener(this));
+		$('loadingLink').observe('click', (function(event) { event.stop(); this.end(); }).bind(this));
+		$('bottomNavClose').observe('click', (function(event) { event.stop(); this.end(); }).bind(this));
+
+        var th = this;
+        (function(){
+            var ids = 
+                'overlay lightbox outerImageContainer imageContainer lightboxImage hoverNav prevLink nextLink loading loadingLink ' + 
+                'imageDataContainer imageData imageDetails caption numberDisplay bottomNav bottomNavClose';   
+            $w(ids).each(function(id){ th[id] = $(id); });
+        }).defer();
+    },
+
+    //
+    // updateImageList()
+    // Loops through anchor tags looking for 'lightbox' references and applies onclick
+    // events to appropriate links. You can rerun after dynamically adding images w/ajax.
+    //
+    updateImageList: function() {   
+        this.updateImageList = Prototype.emptyFunction;
+
+        document.observe('click', (function(event){
+            var target = event.findElement('a[rel^=lightbox]') || event.findElement('area[rel^=lightbox]');
+            if (target) {
+                event.stop();
+                this.start(target);
+            }
+        }).bind(this));
+    },
+    
+    //
+    //  start()
+    //  Display overlay and lightbox. If image is part of a set, add siblings to imageArray.
+    //
+    start: function(imageLink) {    
+
+        $$('select', 'object', 'embed').each(function(node){ node.style.visibility = 'hidden' });
+
+        // stretch overlay to fill page and fade in
+        var arrayPageSize = this.getPageSize();
+        $('overlay').setStyle({ width: arrayPageSize[0] + 'px', height: arrayPageSize[1] + 'px' });
+
+        new Effect.Appear(this.overlay, { duration: this.overlayDuration, from: 0.0, to: LightboxOptions.overlayOpacity });
+
+        this.imageArray = [];
+        var imageNum = 0;       
+
+        if ((imageLink.rel == 'lightbox')){
+            // if image is NOT part of a set, add single image to imageArray
+            this.imageArray.push([imageLink.href, imageLink.title]);         
+        } else {
+            // if image is part of a set..
+            this.imageArray = 
+                $$(imageLink.tagName + '[href][rel="' + imageLink.rel + '"]').
+                collect(function(anchor){ return [anchor.href, anchor.title]; }).
+                uniq();
+            
+            while (this.imageArray[imageNum][0] != imageLink.href) { imageNum++; }
+        }
+
+        // calculate top and left offset for the lightbox 
+        var arrayPageScroll = document.viewport.getScrollOffsets();
+        var lightboxTop = arrayPageScroll[1] + (document.viewport.getHeight() / 10);
+        var lightboxLeft = arrayPageScroll[0];
+        this.lightbox.setStyle({ top: lightboxTop + 'px', left: lightboxLeft + 'px' }).show();
+        
+        this.changeImage(imageNum);
+    },
+
+    //
+    //  changeImage()
+    //  Hide most elements and preload image in preparation for resizing image container.
+    //
+    changeImage: function(imageNum) {   
+        
+        this.activeImage = imageNum; // update global var
+
+        // hide elements during transition
+        if (LightboxOptions.animate) this.loading.show();
+        this.lightboxImage.hide();
+        this.hoverNav.hide();
+        this.prevLink.hide();
+        this.nextLink.hide();
+		// HACK: Opera9 does not currently support scriptaculous opacity and appear fx
+        this.imageDataContainer.setStyle({opacity: .0001});
+        this.numberDisplay.hide();      
+        
+        var imgPreloader = new Image();
+        
+        // once image is preloaded, resize image container
+
+
+        imgPreloader.onload = (function(){
+            this.lightboxImage.src = this.imageArray[this.activeImage][0];
+            this.resizeImageContainer(imgPreloader.width, imgPreloader.height);
+        }).bind(this);
+        imgPreloader.src = this.imageArray[this.activeImage][0];
+    },
+
+    //
+    //  resizeImageContainer()
+    //
+    resizeImageContainer: function(imgWidth, imgHeight) {
+
+        // get current width and height
+        var widthCurrent  = this.outerImageContainer.getWidth();
+        var heightCurrent = this.outerImageContainer.getHeight();
+
+        // get new width and height
+        var widthNew  = (imgWidth  + LightboxOptions.borderSize * 2);
+        var heightNew = (imgHeight + LightboxOptions.borderSize * 2);
+
+        // scalars based on change from old to new
+        var xScale = (widthNew  / widthCurrent)  * 100;
+        var yScale = (heightNew / heightCurrent) * 100;
+
+        // calculate size difference between new and old image, and resize if necessary
+        var wDiff = widthCurrent - widthNew;
+        var hDiff = heightCurrent - heightNew;
+
+        if (hDiff != 0) new Effect.Scale(this.outerImageContainer, yScale, {scaleX: false, duration: this.resizeDuration, queue: 'front'}); 
+        if (wDiff != 0) new Effect.Scale(this.outerImageContainer, xScale, {scaleY: false, duration: this.resizeDuration, delay: this.resizeDuration}); 
+
+        // if new and old image are same size and no scaling transition is necessary, 
+        // do a quick pause to prevent image flicker.
+        var timeout = 0;
+        if ((hDiff == 0) && (wDiff == 0)){
+            timeout = 100;
+            if (Prototype.Browser.IE) timeout = 250;   
+        }
+
+        (function(){
+            this.prevLink.setStyle({ height: imgHeight + 'px' });
+            this.nextLink.setStyle({ height: imgHeight + 'px' });
+            this.imageDataContainer.setStyle({ width: widthNew + 'px' });
+
+            this.showImage();
+        }).bind(this).delay(timeout / 1000);
+    },
+    
+    //
+    //  showImage()
+    //  Display image and begin preloading neighbors.
+    //
+    showImage: function(){
+        this.loading.hide();
+        new Effect.Appear(this.lightboxImage, { 
+            duration: this.resizeDuration, 
+            queue: 'end', 
+            afterFinish: (function(){ this.updateDetails(); }).bind(this) 
+        });
+        this.preloadNeighborImages();
+    },
+
+    //
+    //  updateDetails()
+    //  Display caption, image number, and bottom nav.
+    //
+    updateDetails: function() {
+    
+        // if caption is not null
+        if (this.imageArray[this.activeImage][1] != ""){
+            this.caption.update(this.imageArray[this.activeImage][1]).show();
+        }
+        
+        // if image is part of set display 'Image x of x' 
+        if (this.imageArray.length > 1){
+            this.numberDisplay.update( LightboxOptions.labelImage + ' ' + (this.activeImage + 1) + ' ' + LightboxOptions.labelOf + '  ' + this.imageArray.length).show();
+        }
+
+        new Effect.Parallel(
+            [ 
+                new Effect.SlideDown(this.imageDataContainer, { sync: true, duration: this.resizeDuration, from: 0.0, to: 1.0 }), 
+                new Effect.Appear(this.imageDataContainer, { sync: true, duration: this.resizeDuration }) 
+            ], 
+            { 
+                duration: this.resizeDuration, 
+                afterFinish: (function() {
+	                // update overlay size and update nav
+	                var arrayPageSize = this.getPageSize();
+	                this.overlay.setStyle({ height: arrayPageSize[1] + 'px' });
+	                this.updateNav();
+                }).bind(this)
+            } 
+        );
+    },
+
+    //
+    //  updateNav()
+    //  Display appropriate previous and next hover navigation.
+    //
+    updateNav: function() {
+
+        this.hoverNav.show();               
+
+        // if not first image in set, display prev image button
+        if (this.activeImage > 0) this.prevLink.show();
+
+        // if not last image in set, display next image button
+        if (this.activeImage < (this.imageArray.length - 1)) this.nextLink.show();
+        
+        this.enableKeyboardNav();
+    },
+
+    //
+    //  enableKeyboardNav()
+    //
+    enableKeyboardNav: function() {
+        document.observe('keydown', this.keyboardAction); 
+    },
+
+    //
+    //  disableKeyboardNav()
+    //
+    disableKeyboardNav: function() {
+        document.stopObserving('keydown', this.keyboardAction); 
+    },
+
+    //
+    //  keyboardAction()
+    //
+    keyboardAction: function(event) {
+        var keycode = event.keyCode;
+
+        var escapeKey;
+        if (event.DOM_VK_ESCAPE) {  // mozilla
+            escapeKey = event.DOM_VK_ESCAPE;
+        } else { // ie
+            escapeKey = 27;
+        }
+
+        var key = String.fromCharCode(keycode).toLowerCase();
+        
+        if (key.match(/x|o|c/) || (keycode == escapeKey)){ // close lightbox
+            this.end();
+        } else if ((key == 'p') || (keycode == 37)){ // display previous image
+            if (this.activeImage != 0){
+                this.disableKeyboardNav();
+                this.changeImage(this.activeImage - 1);
+            }
+        } else if ((key == 'n') || (keycode == 39)){ // display next image
+            if (this.activeImage != (this.imageArray.length - 1)){
+                this.disableKeyboardNav();
+                this.changeImage(this.activeImage + 1);
+            }
+        }
+    },
+
+    //
+    //  preloadNeighborImages()
+    //  Preload previous and next images.
+    //
+    preloadNeighborImages: function(){
+        var preloadNextImage, preloadPrevImage;
+        if (this.imageArray.length > this.activeImage + 1){
+            preloadNextImage = new Image();
+            preloadNextImage.src = this.imageArray[this.activeImage + 1][0];
+        }
+        if (this.activeImage > 0){
+            preloadPrevImage = new Image();
+            preloadPrevImage.src = this.imageArray[this.activeImage - 1][0];
+        }
+    
+    },
+
+    //
+    //  end()
+    //
+    end: function() {
+        this.disableKeyboardNav();
+        this.lightbox.hide();
+        new Effect.Fade(this.overlay, { duration: this.overlayDuration });
+        $$('select', 'object', 'embed').each(function(node){ node.style.visibility = 'visible' });
+    },
+
+    //
+    //  getPageSize()
+    //
+    getPageSize: function() {
+	        
+	     var xScroll, yScroll;
+		
+		if (window.innerHeight && window.scrollMaxY) {	
+			xScroll = window.innerWidth + window.scrollMaxX;
+			yScroll = window.innerHeight + window.scrollMaxY;
+		} else if (document.body.scrollHeight > document.body.offsetHeight){ // all but Explorer Mac
+			xScroll = document.body.scrollWidth;
+			yScroll = document.body.scrollHeight;
+		} else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
+			xScroll = document.body.offsetWidth;
+			yScroll = document.body.offsetHeight;
+		}
+		
+		var windowWidth, windowHeight;
+		
+		if (self.innerHeight) {	// all except Explorer
+			if(document.documentElement.clientWidth){
+				windowWidth = document.documentElement.clientWidth; 
+			} else {
+				windowWidth = self.innerWidth;
+			}
+			windowHeight = self.innerHeight;
+		} else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
+			windowWidth = document.documentElement.clientWidth;
+			windowHeight = document.documentElement.clientHeight;
+		} else if (document.body) { // other Explorers
+			windowWidth = document.body.clientWidth;
+			windowHeight = document.body.clientHeight;
+		}	
+		
+		// for small pages with total height less then height of the viewport
+		if(yScroll < windowHeight){
+			pageHeight = windowHeight;
+		} else { 
+			pageHeight = yScroll;
+		}
+	
+		// for small pages with total width less then width of the viewport
+		if(xScroll < windowWidth){	
+			pageWidth = xScroll;		
+		} else {
+			pageWidth = windowWidth;
+		}
+
+		return [pageWidth,pageHeight];
+	}
+}
+
+document.observe('dom:loaded', function () { new Lightbox(); });

+ 4221 - 0
web/root/js/prototype.js

@@ -0,0 +1,4221 @@
+/*  Prototype JavaScript framework, version 1.6.0.2
+ *  (c) 2005-2008 Sam Stephenson
+ *
+ *  Prototype is freely distributable under the terms of an MIT-style license.
+ *  For details, see the Prototype web site: http://www.prototypejs.org/
+ *
+ *--------------------------------------------------------------------------*/
+
+var Prototype = {
+  Version: '1.6.0.2',
+
+  Browser: {
+    IE:     !!(window.attachEvent && !window.opera),
+    Opera:  !!window.opera,
+    WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
+    Gecko:  navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1,
+    MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
+  },
+
+  BrowserFeatures: {
+    XPath: !!document.evaluate,
+    ElementExtensions: !!window.HTMLElement,
+    SpecificElementExtensions:
+      document.createElement('div').__proto__ &&
+      document.createElement('div').__proto__ !==
+        document.createElement('form').__proto__
+  },
+
+  ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
+  JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
+
+  emptyFunction: function() { },
+  K: function(x) { return x }
+};
+
+if (Prototype.Browser.MobileSafari)
+  Prototype.BrowserFeatures.SpecificElementExtensions = false;
+
+
+/* Based on Alex Arnell's inheritance implementation. */
+var Class = {
+  create: function() {
+    var parent = null, properties = $A(arguments);
+    if (Object.isFunction(properties[0]))
+      parent = properties.shift();
+
+    function klass() {
+      this.initialize.apply(this, arguments);
+    }
+
+    Object.extend(klass, Class.Methods);
+    klass.superclass = parent;
+    klass.subclasses = [];
+
+    if (parent) {
+      var subclass = function() { };
+      subclass.prototype = parent.prototype;
+      klass.prototype = new subclass;
+      parent.subclasses.push(klass);
+    }
+
+    for (var i = 0; i < properties.length; i++)
+      klass.addMethods(properties[i]);
+
+    if (!klass.prototype.initialize)
+      klass.prototype.initialize = Prototype.emptyFunction;
+
+    klass.prototype.constructor = klass;
+
+    return klass;
+  }
+};
+
+Class.Methods = {
+  addMethods: function(source) {
+    var ancestor   = this.superclass && this.superclass.prototype;
+    var properties = Object.keys(source);
+
+    if (!Object.keys({ toString: true }).length)
+      properties.push("toString", "valueOf");
+
+    for (var i = 0, length = properties.length; i < length; i++) {
+      var property = properties[i], value = source[property];
+      if (ancestor && Object.isFunction(value) &&
+          value.argumentNames().first() == "$super") {
+        var method = value, value = Object.extend((function(m) {
+          return function() { return ancestor[m].apply(this, arguments) };
+        })(property).wrap(method), {
+          valueOf:  function() { return method },
+          toString: function() { return method.toString() }
+        });
+      }
+      this.prototype[property] = value;
+    }
+
+    return this;
+  }
+};
+
+var Abstract = { };
+
+Object.extend = function(destination, source) {
+  for (var property in source)
+    destination[property] = source[property];
+  return destination;
+};
+
+Object.extend(Object, {
+  inspect: function(object) {
+    try {
+      if (Object.isUndefined(object)) return 'undefined';
+      if (object === null) return 'null';
+      return object.inspect ? object.inspect() : String(object);
+    } catch (e) {
+      if (e instanceof RangeError) return '...';
+      throw e;
+    }
+  },
+
+  toJSON: function(object) {
+    var type = typeof object;
+    switch (type) {
+      case 'undefined':
+      case 'function':
+      case 'unknown': return;
+      case 'boolean': return object.toString();
+    }
+
+    if (object === null) return 'null';
+    if (object.toJSON) return object.toJSON();
+    if (Object.isElement(object)) return;
+
+    var results = [];
+    for (var property in object) {
+      var value = Object.toJSON(object[property]);
+      if (!Object.isUndefined(value))
+        results.push(property.toJSON() + ': ' + value);
+    }
+
+    return '{' + results.join(', ') + '}';
+  },
+
+  toQueryString: function(object) {
+    return $H(object).toQueryString();
+  },
+
+  toHTML: function(object) {
+    return object && object.toHTML ? object.toHTML() : String.interpret(object);
+  },
+
+  keys: function(object) {
+    var keys = [];
+    for (var property in object)
+      keys.push(property);
+    return keys;
+  },
+
+  values: function(object) {
+    var values = [];
+    for (var property in object)
+      values.push(object[property]);
+    return values;
+  },
+
+  clone: function(object) {
+    return Object.extend({ }, object);
+  },
+
+  isElement: function(object) {
+    return object && object.nodeType == 1;
+  },
+
+  isArray: function(object) {
+    return object != null && typeof object == "object" &&
+      'splice' in object && 'join' in object;
+  },
+
+  isHash: function(object) {
+    return object instanceof Hash;
+  },
+
+  isFunction: function(object) {
+    return typeof object == "function";
+  },
+
+  isString: function(object) {
+    return typeof object == "string";
+  },
+
+  isNumber: function(object) {
+    return typeof object == "number";
+  },
+
+  isUndefined: function(object) {
+    return typeof object == "undefined";
+  }
+});
+
+Object.extend(Function.prototype, {
+  argumentNames: function() {
+    var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip");
+    return names.length == 1 && !names[0] ? [] : names;
+  },
+
+  bind: function() {
+    if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
+    var __method = this, args = $A(arguments), object = args.shift();
+    return function() {
+      return __method.apply(object, args.concat($A(arguments)));
+    }
+  },
+
+  bindAsEventListener: function() {
+    var __method = this, args = $A(arguments), object = args.shift();
+    return function(event) {
+      return __method.apply(object, [event || window.event].concat(args));
+    }
+  },
+
+  curry: function() {
+    if (!arguments.length) return this;
+    var __method = this, args = $A(arguments);
+    return function() {
+      return __method.apply(this, args.concat($A(arguments)));
+    }
+  },
+
+  delay: function() {
+    var __method = this, args = $A(arguments), timeout = args.shift() * 1000;
+    return window.setTimeout(function() {
+      return __method.apply(__method, args);
+    }, timeout);
+  },
+
+  wrap: function(wrapper) {
+    var __method = this;
+    return function() {
+      return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));
+    }
+  },
+
+  methodize: function() {
+    if (this._methodized) return this._methodized;
+    var __method = this;
+    return this._methodized = function() {
+      return __method.apply(null, [this].concat($A(arguments)));
+    };
+  }
+});
+
+Function.prototype.defer = Function.prototype.delay.curry(0.01);
+
+Date.prototype.toJSON = function() {
+  return '"' + this.getUTCFullYear() + '-' +
+    (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
+    this.getUTCDate().toPaddedString(2) + 'T' +
+    this.getUTCHours().toPaddedString(2) + ':' +
+    this.getUTCMinutes().toPaddedString(2) + ':' +
+    this.getUTCSeconds().toPaddedString(2) + 'Z"';
+};
+
+var Try = {
+  these: function() {
+    var returnValue;
+
+    for (var i = 0, length = arguments.length; i < length; i++) {
+      var lambda = arguments[i];
+      try {
+        returnValue = lambda();
+        break;
+      } catch (e) { }
+    }
+
+    return returnValue;
+  }
+};
+
+RegExp.prototype.match = RegExp.prototype.test;
+
+RegExp.escape = function(str) {
+  return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
+};
+
+/*--------------------------------------------------------------------------*/
+
+var PeriodicalExecuter = Class.create({
+  initialize: function(callback, frequency) {
+    this.callback = callback;
+    this.frequency = frequency;
+    this.currentlyExecuting = false;
+
+    this.registerCallback();
+  },
+
+  registerCallback: function() {
+    this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
+  },
+
+  execute: function() {
+    this.callback(this);
+  },
+
+  stop: function() {
+    if (!this.timer) return;
+    clearInterval(this.timer);
+    this.timer = null;
+  },
+
+  onTimerEvent: function() {
+    if (!this.currentlyExecuting) {
+      try {
+        this.currentlyExecuting = true;
+        this.execute();
+      } finally {
+        this.currentlyExecuting = false;
+      }
+    }
+  }
+});
+Object.extend(String, {
+  interpret: function(value) {
+    return value == null ? '' : String(value);
+  },
+  specialChar: {
+    '\b': '\\b',
+    '\t': '\\t',
+    '\n': '\\n',
+    '\f': '\\f',
+    '\r': '\\r',
+    '\\': '\\\\'
+  }
+});
+
+Object.extend(String.prototype, {
+  gsub: function(pattern, replacement) {
+    var result = '', source = this, match;
+    replacement = arguments.callee.prepareReplacement(replacement);
+
+    while (source.length > 0) {
+      if (match = source.match(pattern)) {
+        result += source.slice(0, match.index);
+        result += String.interpret(replacement(match));
+        source  = source.slice(match.index + match[0].length);
+      } else {
+        result += source, source = '';
+      }
+    }
+    return result;
+  },
+
+  sub: function(pattern, replacement, count) {
+    replacement = this.gsub.prepareReplacement(replacement);
+    count = Object.isUndefined(count) ? 1 : count;
+
+    return this.gsub(pattern, function(match) {
+      if (--count < 0) return match[0];
+      return replacement(match);
+    });
+  },
+
+  scan: function(pattern, iterator) {
+    this.gsub(pattern, iterator);
+    return String(this);
+  },
+
+  truncate: function(length, truncation) {
+    length = length || 30;
+    truncation = Object.isUndefined(truncation) ? '...' : truncation;
+    return this.length > length ?
+      this.slice(0, length - truncation.length) + truncation : String(this);
+  },
+
+  strip: function() {
+    return this.replace(/^\s+/, '').replace(/\s+$/, '');
+  },
+
+  stripTags: function() {
+    return this.replace(/<\/?[^>]+>/gi, '');
+  },
+
+  stripScripts: function() {
+    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
+  },
+
+  extractScripts: function() {
+    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
+    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
+    return (this.match(matchAll) || []).map(function(scriptTag) {
+      return (scriptTag.match(matchOne) || ['', ''])[1];
+    });
+  },
+
+  evalScripts: function() {
+    return this.extractScripts().map(function(script) { return eval(script) });
+  },
+
+  escapeHTML: function() {
+    var self = arguments.callee;
+    self.text.data = this;
+    return self.div.innerHTML;
+  },
+
+  unescapeHTML: function() {
+    var div = new Element('div');
+    div.innerHTML = this.stripTags();
+    return div.childNodes[0] ? (div.childNodes.length > 1 ?
+      $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :
+      div.childNodes[0].nodeValue) : '';
+  },
+
+  toQueryParams: function(separator) {
+    var match = this.strip().match(/([^?#]*)(#.*)?$/);
+    if (!match) return { };
+
+    return match[1].split(separator || '&').inject({ }, function(hash, pair) {
+      if ((pair = pair.split('='))[0]) {
+        var key = decodeURIComponent(pair.shift());
+        var value = pair.length > 1 ? pair.join('=') : pair[0];
+        if (value != undefined) value = decodeURIComponent(value);
+
+        if (key in hash) {
+          if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
+          hash[key].push(value);
+        }
+        else hash[key] = value;
+      }
+      return hash;
+    });
+  },
+
+  toArray: function() {
+    return this.split('');
+  },
+
+  succ: function() {
+    return this.slice(0, this.length - 1) +
+      String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
+  },
+
+  times: function(count) {
+    return count < 1 ? '' : new Array(count + 1).join(this);
+  },
+
+  camelize: function() {
+    var parts = this.split('-'), len = parts.length;
+    if (len == 1) return parts[0];
+
+    var camelized = this.charAt(0) == '-'
+      ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
+      : parts[0];
+
+    for (var i = 1; i < len; i++)
+      camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
+
+    return camelized;
+  },
+
+  capitalize: function() {
+    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
+  },
+
+  underscore: function() {
+    return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
+  },
+
+  dasherize: function() {
+    return this.gsub(/_/,'-');
+  },
+
+  inspect: function(useDoubleQuotes) {
+    var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
+      var character = String.specialChar[match[0]];
+      return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
+    });
+    if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
+    return "'" + escapedString.replace(/'/g, '\\\'') + "'";
+  },
+
+  toJSON: function() {
+    return this.inspect(true);
+  },
+
+  unfilterJSON: function(filter) {
+    return this.sub(filter || Prototype.JSONFilter, '#{1}');
+  },
+
+  isJSON: function() {
+    var str = this;
+    if (str.blank()) return false;
+    str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
+    return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
+  },
+
+  evalJSON: function(sanitize) {
+    var json = this.unfilterJSON();
+    try {
+      if (!sanitize || json.isJSON()) return eval('(' + json + ')');
+    } catch (e) { }
+    throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
+  },
+
+  include: function(pattern) {
+    return this.indexOf(pattern) > -1;
+  },
+
+  startsWith: function(pattern) {
+    return this.indexOf(pattern) === 0;
+  },
+
+  endsWith: function(pattern) {
+    var d = this.length - pattern.length;
+    return d >= 0 && this.lastIndexOf(pattern) === d;
+  },
+
+  empty: function() {
+    return this == '';
+  },
+
+  blank: function() {
+    return /^\s*$/.test(this);
+  },
+
+  interpolate: function(object, pattern) {
+    return new Template(this, pattern).evaluate(object);
+  }
+});
+
+if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {
+  escapeHTML: function() {
+    return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
+  },
+  unescapeHTML: function() {
+    return this.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');
+  }
+});
+
+String.prototype.gsub.prepareReplacement = function(replacement) {
+  if (Object.isFunction(replacement)) return replacement;
+  var template = new Template(replacement);
+  return function(match) { return template.evaluate(match) };
+};
+
+String.prototype.parseQuery = String.prototype.toQueryParams;
+
+Object.extend(String.prototype.escapeHTML, {
+  div:  document.createElement('div'),
+  text: document.createTextNode('')
+});
+
+with (String.prototype.escapeHTML) div.appendChild(text);
+
+var Template = Class.create({
+  initialize: function(template, pattern) {
+    this.template = template.toString();
+    this.pattern = pattern || Template.Pattern;
+  },
+
+  evaluate: function(object) {
+    if (Object.isFunction(object.toTemplateReplacements))
+      object = object.toTemplateReplacements();
+
+    return this.template.gsub(this.pattern, function(match) {
+      if (object == null) return '';
+
+      var before = match[1] || '';
+      if (before == '\\') return match[2];
+
+      var ctx = object, expr = match[3];
+      var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
+      match = pattern.exec(expr);
+      if (match == null) return before;
+
+      while (match != null) {
+        var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];
+        ctx = ctx[comp];
+        if (null == ctx || '' == match[3]) break;
+        expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
+        match = pattern.exec(expr);
+      }
+
+      return before + String.interpret(ctx);
+    });
+  }
+});
+Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
+
+var $break = { };
+
+var Enumerable = {
+  each: function(iterator, context) {
+    var index = 0;
+    iterator = iterator.bind(context);
+    try {
+      this._each(function(value) {
+        iterator(value, index++);
+      });
+    } catch (e) {
+      if (e != $break) throw e;
+    }
+    return this;
+  },
+
+  eachSlice: function(number, iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var index = -number, slices = [], array = this.toArray();
+    while ((index += number) < array.length)
+      slices.push(array.slice(index, index+number));
+    return slices.collect(iterator, context);
+  },
+
+  all: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var result = true;
+    this.each(function(value, index) {
+      result = result && !!iterator(value, index);
+      if (!result) throw $break;
+    });
+    return result;
+  },
+
+  any: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var result = false;
+    this.each(function(value, index) {
+      if (result = !!iterator(value, index))
+        throw $break;
+    });
+    return result;
+  },
+
+  collect: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var results = [];
+    this.each(function(value, index) {
+      results.push(iterator(value, index));
+    });
+    return results;
+  },
+
+  detect: function(iterator, context) {
+    iterator = iterator.bind(context);
+    var result;
+    this.each(function(value, index) {
+      if (iterator(value, index)) {
+        result = value;
+        throw $break;
+      }
+    });
+    return result;
+  },
+
+  findAll: function(iterator, context) {
+    iterator = iterator.bind(context);
+    var results = [];
+    this.each(function(value, index) {
+      if (iterator(value, index))
+        results.push(value);
+    });
+    return results;
+  },
+
+  grep: function(filter, iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var results = [];
+
+    if (Object.isString(filter))
+      filter = new RegExp(filter);
+
+    this.each(function(value, index) {
+      if (filter.match(value))
+        results.push(iterator(value, index));
+    });
+    return results;
+  },
+
+  include: function(object) {
+    if (Object.isFunction(this.indexOf))
+      if (this.indexOf(object) != -1) return true;
+
+    var found = false;
+    this.each(function(value) {
+      if (value == object) {
+        found = true;
+        throw $break;
+      }
+    });
+    return found;
+  },
+
+  inGroupsOf: function(number, fillWith) {
+    fillWith = Object.isUndefined(fillWith) ? null : fillWith;
+    return this.eachSlice(number, function(slice) {
+      while(slice.length < number) slice.push(fillWith);
+      return slice;
+    });
+  },
+
+  inject: function(memo, iterator, context) {
+    iterator = iterator.bind(context);
+    this.each(function(value, index) {
+      memo = iterator(memo, value, index);
+    });
+    return memo;
+  },
+
+  invoke: function(method) {
+    var args = $A(arguments).slice(1);
+    return this.map(function(value) {
+      return value[method].apply(value, args);
+    });
+  },
+
+  max: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var result;
+    this.each(function(value, index) {
+      value = iterator(value, index);
+      if (result == null || value >= result)
+        result = value;
+    });
+    return result;
+  },
+
+  min: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var result;
+    this.each(function(value, index) {
+      value = iterator(value, index);
+      if (result == null || value < result)
+        result = value;
+    });
+    return result;
+  },
+
+  partition: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var trues = [], falses = [];
+    this.each(function(value, index) {
+      (iterator(value, index) ?
+        trues : falses).push(value);
+    });
+    return [trues, falses];
+  },
+
+  pluck: function(property) {
+    var results = [];
+    this.each(function(value) {
+      results.push(value[property]);
+    });
+    return results;
+  },
+
+  reject: function(iterator, context) {
+    iterator = iterator.bind(context);
+    var results = [];
+    this.each(function(value, index) {
+      if (!iterator(value, index))
+        results.push(value);
+    });
+    return results;
+  },
+
+  sortBy: function(iterator, context) {
+    iterator = iterator.bind(context);
+    return this.map(function(value, index) {
+      return {value: value, criteria: iterator(value, index)};
+    }).sort(function(left, right) {
+      var a = left.criteria, b = right.criteria;
+      return a < b ? -1 : a > b ? 1 : 0;
+    }).pluck('value');
+  },
+
+  toArray: function() {
+    return this.map();
+  },
+
+  zip: function() {
+    var iterator = Prototype.K, args = $A(arguments);
+    if (Object.isFunction(args.last()))
+      iterator = args.pop();
+
+    var collections = [this].concat(args).map($A);
+    return this.map(function(value, index) {
+      return iterator(collections.pluck(index));
+    });
+  },
+
+  size: function() {
+    return this.toArray().length;
+  },
+
+  inspect: function() {
+    return '#<Enumerable:' + this.toArray().inspect() + '>';
+  }
+};
+
+Object.extend(Enumerable, {
+  map:     Enumerable.collect,
+  find:    Enumerable.detect,
+  select:  Enumerable.findAll,
+  filter:  Enumerable.findAll,
+  member:  Enumerable.include,
+  entries: Enumerable.toArray,
+  every:   Enumerable.all,
+  some:    Enumerable.any
+});
+function $A(iterable) {
+  if (!iterable) return [];
+  if (iterable.toArray) return iterable.toArray();
+  var length = iterable.length || 0, results = new Array(length);
+  while (length--) results[length] = iterable[length];
+  return results;
+}
+
+if (Prototype.Browser.WebKit) {
+  $A = function(iterable) {
+    if (!iterable) return [];
+    if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') &&
+        iterable.toArray) return iterable.toArray();
+    var length = iterable.length || 0, results = new Array(length);
+    while (length--) results[length] = iterable[length];
+    return results;
+  };
+}
+
+Array.from = $A;
+
+Object.extend(Array.prototype, Enumerable);
+
+if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;
+
+Object.extend(Array.prototype, {
+  _each: function(iterator) {
+    for (var i = 0, length = this.length; i < length; i++)
+      iterator(this[i]);
+  },
+
+  clear: function() {
+    this.length = 0;
+    return this;
+  },
+
+  first: function() {
+    return this[0];
+  },
+
+  last: function() {
+    return this[this.length - 1];
+  },
+
+  compact: function() {
+    return this.select(function(value) {
+      return value != null;
+    });
+  },
+
+  flatten: function() {
+    return this.inject([], function(array, value) {
+      return array.concat(Object.isArray(value) ?
+        value.flatten() : [value]);
+    });
+  },
+
+  without: function() {
+    var values = $A(arguments);
+    return this.select(function(value) {
+      return !values.include(value);
+    });
+  },
+
+  reverse: function(inline) {
+    return (inline !== false ? this : this.toArray())._reverse();
+  },
+
+  reduce: function() {
+    return this.length > 1 ? this : this[0];
+  },
+
+  uniq: function(sorted) {
+    return this.inject([], function(array, value, index) {
+      if (0 == index || (sorted ? array.last() != value : !array.include(value)))
+        array.push(value);
+      return array;
+    });
+  },
+
+  intersect: function(array) {
+    return this.uniq().findAll(function(item) {
+      return array.detect(function(value) { return item === value });
+    });
+  },
+
+  clone: function() {
+    return [].concat(this);
+  },
+
+  size: function() {
+    return this.length;
+  },
+
+  inspect: function() {
+    return '[' + this.map(Object.inspect).join(', ') + ']';
+  },
+
+  toJSON: function() {
+    var results = [];
+    this.each(function(object) {
+      var value = Object.toJSON(object);
+      if (!Object.isUndefined(value)) results.push(value);
+    });
+    return '[' + results.join(', ') + ']';
+  }
+});
+
+// use native browser JS 1.6 implementation if available
+if (Object.isFunction(Array.prototype.forEach))
+  Array.prototype._each = Array.prototype.forEach;
+
+if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {
+  i || (i = 0);
+  var length = this.length;
+  if (i < 0) i = length + i;
+  for (; i < length; i++)
+    if (this[i] === item) return i;
+  return -1;
+};
+
+if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) {
+  i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
+  var n = this.slice(0, i).reverse().indexOf(item);
+  return (n < 0) ? n : i - n - 1;
+};
+
+Array.prototype.toArray = Array.prototype.clone;
+
+function $w(string) {
+  if (!Object.isString(string)) return [];
+  string = string.strip();
+  return string ? string.split(/\s+/) : [];
+}
+
+if (Prototype.Browser.Opera){
+  Array.prototype.concat = function() {
+    var array = [];
+    for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);
+    for (var i = 0, length = arguments.length; i < length; i++) {
+      if (Object.isArray(arguments[i])) {
+        for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
+          array.push(arguments[i][j]);
+      } else {
+        array.push(arguments[i]);
+      }
+    }
+    return array;
+  };
+}
+Object.extend(Number.prototype, {
+  toColorPart: function() {
+    return this.toPaddedString(2, 16);
+  },
+
+  succ: function() {
+    return this + 1;
+  },
+
+  times: function(iterator) {
+    $R(0, this, true).each(iterator);
+    return this;
+  },
+
+  toPaddedString: function(length, radix) {
+    var string = this.toString(radix || 10);
+    return '0'.times(length - string.length) + string;
+  },
+
+  toJSON: function() {
+    return isFinite(this) ? this.toString() : 'null';
+  }
+});
+
+$w('abs round ceil floor').each(function(method){
+  Number.prototype[method] = Math[method].methodize();
+});
+function $H(object) {
+  return new Hash(object);
+};
+
+var Hash = Class.create(Enumerable, (function() {
+
+  function toQueryPair(key, value) {
+    if (Object.isUndefined(value)) return key;
+    return key + '=' + encodeURIComponent(String.interpret(value));
+  }
+
+  return {
+    initialize: function(object) {
+      this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
+    },
+
+    _each: function(iterator) {
+      for (var key in this._object) {
+        var value = this._object[key], pair = [key, value];
+        pair.key = key;
+        pair.value = value;
+        iterator(pair);
+      }
+    },
+
+    set: function(key, value) {
+      return this._object[key] = value;
+    },
+
+    get: function(key) {
+      return this._object[key];
+    },
+
+    unset: function(key) {
+      var value = this._object[key];
+      delete this._object[key];
+      return value;
+    },
+
+    toObject: function() {
+      return Object.clone(this._object);
+    },
+
+    keys: function() {
+      return this.pluck('key');
+    },
+
+    values: function() {
+      return this.pluck('value');
+    },
+
+    index: function(value) {
+      var match = this.detect(function(pair) {
+        return pair.value === value;
+      });
+      return match && match.key;
+    },
+
+    merge: function(object) {
+      return this.clone().update(object);
+    },
+
+    update: function(object) {
+      return new Hash(object).inject(this, function(result, pair) {
+        result.set(pair.key, pair.value);
+        return result;
+      });
+    },
+
+    toQueryString: function() {
+      return this.map(function(pair) {
+        var key = encodeURIComponent(pair.key), values = pair.value;
+
+        if (values && typeof values == 'object') {
+          if (Object.isArray(values))
+            return values.map(toQueryPair.curry(key)).join('&');
+        }
+        return toQueryPair(key, values);
+      }).join('&');
+    },
+
+    inspect: function() {
+      return '#<Hash:{' + this.map(function(pair) {
+        return pair.map(Object.inspect).join(': ');
+      }).join(', ') + '}>';
+    },
+
+    toJSON: function() {
+      return Object.toJSON(this.toObject());
+    },
+
+    clone: function() {
+      return new Hash(this);
+    }
+  }
+})());
+
+Hash.prototype.toTemplateReplacements = Hash.prototype.toObject;
+Hash.from = $H;
+var ObjectRange = Class.create(Enumerable, {
+  initialize: function(start, end, exclusive) {
+    this.start = start;
+    this.end = end;
+    this.exclusive = exclusive;
+  },
+
+  _each: function(iterator) {
+    var value = this.start;
+    while (this.include(value)) {
+      iterator(value);
+      value = value.succ();
+    }
+  },
+
+  include: function(value) {
+    if (value < this.start)
+      return false;
+    if (this.exclusive)
+      return value < this.end;
+    return value <= this.end;
+  }
+});
+
+var $R = function(start, end, exclusive) {
+  return new ObjectRange(start, end, exclusive);
+};
+
+var Ajax = {
+  getTransport: function() {
+    return Try.these(
+      function() {return new XMLHttpRequest()},
+      function() {return new ActiveXObject('Msxml2.XMLHTTP')},
+      function() {return new ActiveXObject('Microsoft.XMLHTTP')}
+    ) || false;
+  },
+
+  activeRequestCount: 0
+};
+
+Ajax.Responders = {
+  responders: [],
+
+  _each: function(iterator) {
+    this.responders._each(iterator);
+  },
+
+  register: function(responder) {
+    if (!this.include(responder))
+      this.responders.push(responder);
+  },
+
+  unregister: function(responder) {
+    this.responders = this.responders.without(responder);
+  },
+
+  dispatch: function(callback, request, transport, json) {
+    this.each(function(responder) {
+      if (Object.isFunction(responder[callback])) {
+        try {
+          responder[callback].apply(responder, [request, transport, json]);
+        } catch (e) { }
+      }
+    });
+  }
+};
+
+Object.extend(Ajax.Responders, Enumerable);
+
+Ajax.Responders.register({
+  onCreate:   function() { Ajax.activeRequestCount++ },
+  onComplete: function() { Ajax.activeRequestCount-- }
+});
+
+Ajax.Base = Class.create({
+  initialize: function(options) {
+    this.options = {
+      method:       'post',
+      asynchronous: true,
+      contentType:  'application/x-www-form-urlencoded',
+      encoding:     'UTF-8',
+      parameters:   '',
+      evalJSON:     true,
+      evalJS:       true
+    };
+    Object.extend(this.options, options || { });
+
+    this.options.method = this.options.method.toLowerCase();
+
+    if (Object.isString(this.options.parameters))
+      this.options.parameters = this.options.parameters.toQueryParams();
+    else if (Object.isHash(this.options.parameters))
+      this.options.parameters = this.options.parameters.toObject();
+  }
+});
+
+Ajax.Request = Class.create(Ajax.Base, {
+  _complete: false,
+
+  initialize: function($super, url, options) {
+    $super(options);
+    this.transport = Ajax.getTransport();
+    this.request(url);
+  },
+
+  request: function(url) {
+    this.url = url;
+    this.method = this.options.method;
+    var params = Object.clone(this.options.parameters);
+
+    if (!['get', 'post'].include(this.method)) {
+      // simulate other verbs over post
+      params['_method'] = this.method;
+      this.method = 'post';
+    }
+
+    this.parameters = params;
+
+    if (params = Object.toQueryString(params)) {
+      // when GET, append parameters to URL
+      if (this.method == 'get')
+        this.url += (this.url.include('?') ? '&' : '?') + params;
+      else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
+        params += '&_=';
+    }
+
+    try {
+      var response = new Ajax.Response(this);
+      if (this.options.onCreate) this.options.onCreate(response);
+      Ajax.Responders.dispatch('onCreate', this, response);
+
+      this.transport.open(this.method.toUpperCase(), this.url,
+        this.options.asynchronous);
+
+      if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);
+
+      this.transport.onreadystatechange = this.onStateChange.bind(this);
+      this.setRequestHeaders();
+
+      this.body = this.method == 'post' ? (this.options.postBody || params) : null;
+      this.transport.send(this.body);
+
+      /* Force Firefox to handle ready state 4 for synchronous requests */
+      if (!this.options.asynchronous && this.transport.overrideMimeType)
+        this.onStateChange();
+
+    }
+    catch (e) {
+      this.dispatchException(e);
+    }
+  },
+
+  onStateChange: function() {
+    var readyState = this.transport.readyState;
+    if (readyState > 1 && !((readyState == 4) && this._complete))
+      this.respondToReadyState(this.transport.readyState);
+  },
+
+  setRequestHeaders: function() {
+    var headers = {
+      'X-Requested-With': 'XMLHttpRequest',
+      'X-Prototype-Version': Prototype.Version,
+      'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
+    };
+
+    if (this.method == 'post') {
+      headers['Content-type'] = this.options.contentType +
+        (this.options.encoding ? '; charset=' + this.options.encoding : '');
+
+      /* Force "Connection: close" for older Mozilla browsers to work
+       * around a bug where XMLHttpRequest sends an incorrect
+       * Content-length header. See Mozilla Bugzilla #246651.
+       */
+      if (this.transport.overrideMimeType &&
+          (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
+            headers['Connection'] = 'close';
+    }
+
+    // user-defined headers
+    if (typeof this.options.requestHeaders == 'object') {
+      var extras = this.options.requestHeaders;
+
+      if (Object.isFunction(extras.push))
+        for (var i = 0, length = extras.length; i < length; i += 2)
+          headers[extras[i]] = extras[i+1];
+      else
+        $H(extras).each(function(pair) { headers[pair.key] = pair.value });
+    }
+
+    for (var name in headers)
+      this.transport.setRequestHeader(name, headers[name]);
+  },
+
+  success: function() {
+    var status = this.getStatus();
+    return !status || (status >= 200 && status < 300);
+  },
+
+  getStatus: function() {
+    try {
+      return this.transport.status || 0;
+    } catch (e) { return 0 }
+  },
+
+  respondToReadyState: function(readyState) {
+    var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);
+
+    if (state == 'Complete') {
+      try {
+        this._complete = true;
+        (this.options['on' + response.status]
+         || this.options['on' + (this.success() ? 'Success' : 'Failure')]
+         || Prototype.emptyFunction)(response, response.headerJSON);
+      } catch (e) {
+        this.dispatchException(e);
+      }
+
+      var contentType = response.getHeader('Content-type');
+      if (this.options.evalJS == 'force'
+          || (this.options.evalJS && this.isSameOrigin() && contentType
+          && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
+        this.evalResponse();
+    }
+
+    try {
+      (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
+      Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
+    } catch (e) {
+      this.dispatchException(e);
+    }
+
+    if (state == 'Complete') {
+      // avoid memory leak in MSIE: clean up
+      this.transport.onreadystatechange = Prototype.emptyFunction;
+    }
+  },
+
+  isSameOrigin: function() {
+    var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
+    return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
+      protocol: location.protocol,
+      domain: document.domain,
+      port: location.port ? ':' + location.port : ''
+    }));
+  },
+
+  getHeader: function(name) {
+    try {
+      return this.transport.getResponseHeader(name) || null;
+    } catch (e) { return null }
+  },
+
+  evalResponse: function() {
+    try {
+      return eval((this.transport.responseText || '').unfilterJSON());
+    } catch (e) {
+      this.dispatchException(e);
+    }
+  },
+
+  dispatchException: function(exception) {
+    (this.options.onException || Prototype.emptyFunction)(this, exception);
+    Ajax.Responders.dispatch('onException', this, exception);
+  }
+});
+
+Ajax.Request.Events =
+  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
+
+Ajax.Response = Class.create({
+  initialize: function(request){
+    this.request = request;
+    var transport  = this.transport  = request.transport,
+        readyState = this.readyState = transport.readyState;
+
+    if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
+      this.status       = this.getStatus();
+      this.statusText   = this.getStatusText();
+      this.responseText = String.interpret(transport.responseText);
+      this.headerJSON   = this._getHeaderJSON();
+    }
+
+    if(readyState == 4) {
+      var xml = transport.responseXML;
+      this.responseXML  = Object.isUndefined(xml) ? null : xml;
+      this.responseJSON = this._getResponseJSON();
+    }
+  },
+
+  status:      0,
+  statusText: '',
+
+  getStatus: Ajax.Request.prototype.getStatus,
+
+  getStatusText: function() {
+    try {
+      return this.transport.statusText || '';
+    } catch (e) { return '' }
+  },
+
+  getHeader: Ajax.Request.prototype.getHeader,
+
+  getAllHeaders: function() {
+    try {
+      return this.getAllResponseHeaders();
+    } catch (e) { return null }
+  },
+
+  getResponseHeader: function(name) {
+    return this.transport.getResponseHeader(name);
+  },
+
+  getAllResponseHeaders: function() {
+    return this.transport.getAllResponseHeaders();
+  },
+
+  _getHeaderJSON: function() {
+    var json = this.getHeader('X-JSON');
+    if (!json) return null;
+    json = decodeURIComponent(escape(json));
+    try {
+      return json.evalJSON(this.request.options.sanitizeJSON ||
+        !this.request.isSameOrigin());
+    } catch (e) {
+      this.request.dispatchException(e);
+    }
+  },
+
+  _getResponseJSON: function() {
+    var options = this.request.options;
+    if (!options.evalJSON || (options.evalJSON != 'force' &&
+      !(this.getHeader('Content-type') || '').include('application/json')) ||
+        this.responseText.blank())
+          return null;
+    try {
+      return this.responseText.evalJSON(options.sanitizeJSON ||
+        !this.request.isSameOrigin());
+    } catch (e) {
+      this.request.dispatchException(e);
+    }
+  }
+});
+
+Ajax.Updater = Class.create(Ajax.Request, {
+  initialize: function($super, container, url, options) {
+    this.container = {
+      success: (container.success || container),
+      failure: (container.failure || (container.success ? null : container))
+    };
+
+    options = Object.clone(options);
+    var onComplete = options.onComplete;
+    options.onComplete = (function(response, json) {
+      this.updateContent(response.responseText);
+      if (Object.isFunction(onComplete)) onComplete(response, json);
+    }).bind(this);
+
+    $super(url, options);
+  },
+
+  updateContent: function(responseText) {
+    var receiver = this.container[this.success() ? 'success' : 'failure'],
+        options = this.options;
+
+    if (!options.evalScripts) responseText = responseText.stripScripts();
+
+    if (receiver = $(receiver)) {
+      if (options.insertion) {
+        if (Object.isString(options.insertion)) {
+          var insertion = { }; insertion[options.insertion] = responseText;
+          receiver.insert(insertion);
+        }
+        else options.insertion(receiver, responseText);
+      }
+      else receiver.update(responseText);
+    }
+  }
+});
+
+Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
+  initialize: function($super, container, url, options) {
+    $super(options);
+    this.onComplete = this.options.onComplete;
+
+    this.frequency = (this.options.frequency || 2);
+    this.decay = (this.options.decay || 1);
+
+    this.updater = { };
+    this.container = container;
+    this.url = url;
+
+    this.start();
+  },
+
+  start: function() {
+    this.options.onComplete = this.updateComplete.bind(this);
+    this.onTimerEvent();
+  },
+
+  stop: function() {
+    this.updater.options.onComplete = undefined;
+    clearTimeout(this.timer);
+    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
+  },
+
+  updateComplete: function(response) {
+    if (this.options.decay) {
+      this.decay = (response.responseText == this.lastText ?
+        this.decay * this.options.decay : 1);
+
+      this.lastText = response.responseText;
+    }
+    this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
+  },
+
+  onTimerEvent: function() {
+    this.updater = new Ajax.Updater(this.container, this.url, this.options);
+  }
+});
+function $(element) {
+  if (arguments.length > 1) {
+    for (var i = 0, elements = [], length = arguments.length; i < length; i++)
+      elements.push($(arguments[i]));
+    return elements;
+  }
+  if (Object.isString(element))
+    element = document.getElementById(element);
+  return Element.extend(element);
+}
+
+if (Prototype.BrowserFeatures.XPath) {
+  document._getElementsByXPath = function(expression, parentElement) {
+    var results = [];
+    var query = document.evaluate(expression, $(parentElement) || document,
+      null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+    for (var i = 0, length = query.snapshotLength; i < length; i++)
+      results.push(Element.extend(query.snapshotItem(i)));
+    return results;
+  };
+}
+
+/*--------------------------------------------------------------------------*/
+
+if (!window.Node) var Node = { };
+
+if (!Node.ELEMENT_NODE) {
+  // DOM level 2 ECMAScript Language Binding
+  Object.extend(Node, {
+    ELEMENT_NODE: 1,
+    ATTRIBUTE_NODE: 2,
+    TEXT_NODE: 3,
+    CDATA_SECTION_NODE: 4,
+    ENTITY_REFERENCE_NODE: 5,
+    ENTITY_NODE: 6,
+    PROCESSING_INSTRUCTION_NODE: 7,
+    COMMENT_NODE: 8,
+    DOCUMENT_NODE: 9,
+    DOCUMENT_TYPE_NODE: 10,
+    DOCUMENT_FRAGMENT_NODE: 11,
+    NOTATION_NODE: 12
+  });
+}
+
+(function() {
+  var element = this.Element;
+  this.Element = function(tagName, attributes) {
+    attributes = attributes || { };
+    tagName = tagName.toLowerCase();
+    var cache = Element.cache;
+    if (Prototype.Browser.IE && attributes.name) {
+      tagName = '<' + tagName + ' name="' + attributes.name + '">';
+      delete attributes.name;
+      return Element.writeAttribute(document.createElement(tagName), attributes);
+    }
+    if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
+    return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
+  };
+  Object.extend(this.Element, element || { });
+}).call(window);
+
+Element.cache = { };
+
+Element.Methods = {
+  visible: function(element) {
+    return $(element).style.display != 'none';
+  },
+
+  toggle: function(element) {
+    element = $(element);
+    Element[Element.visible(element) ? 'hide' : 'show'](element);
+    return element;
+  },
+
+  hide: function(element) {
+    $(element).style.display = 'none';
+    return element;
+  },
+
+  show: function(element) {
+    $(element).style.display = '';
+    return element;
+  },
+
+  remove: function(element) {
+    element = $(element);
+    element.parentNode.removeChild(element);
+    return element;
+  },
+
+  update: function(element, content) {
+    element = $(element);
+    if (content && content.toElement) content = content.toElement();
+    if (Object.isElement(content)) return element.update().insert(content);
+    content = Object.toHTML(content);
+    element.innerHTML = content.stripScripts();
+    content.evalScripts.bind(content).defer();
+    return element;
+  },
+
+  replace: function(element, content) {
+    element = $(element);
+    if (content && content.toElement) content = content.toElement();
+    else if (!Object.isElement(content)) {
+      content = Object.toHTML(content);
+      var range = element.ownerDocument.createRange();
+      range.selectNode(element);
+      content.evalScripts.bind(content).defer();
+      content = range.createContextualFragment(content.stripScripts());
+    }
+    element.parentNode.replaceChild(content, element);
+    return element;
+  },
+
+  insert: function(element, insertions) {
+    element = $(element);
+
+    if (Object.isString(insertions) || Object.isNumber(insertions) ||
+        Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
+          insertions = {bottom:insertions};
+
+    var content, insert, tagName, childNodes;
+
+    for (var position in insertions) {
+      content  = insertions[position];
+      position = position.toLowerCase();
+      insert = Element._insertionTranslations[position];
+
+      if (content && content.toElement) content = content.toElement();
+      if (Object.isElement(content)) {
+        insert(element, content);
+        continue;
+      }
+
+      content = Object.toHTML(content);
+
+      tagName = ((position == 'before' || position == 'after')
+        ? element.parentNode : element).tagName.toUpperCase();
+
+      childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+
+      if (position == 'top' || position == 'after') childNodes.reverse();
+      childNodes.each(insert.curry(element));
+
+      content.evalScripts.bind(content).defer();
+    }
+
+    return element;
+  },
+
+  wrap: function(element, wrapper, attributes) {
+    element = $(element);
+    if (Object.isElement(wrapper))
+      $(wrapper).writeAttribute(attributes || { });
+    else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
+    else wrapper = new Element('div', wrapper);
+    if (element.parentNode)
+      element.parentNode.replaceChild(wrapper, element);
+    wrapper.appendChild(element);
+    return wrapper;
+  },
+
+  inspect: function(element) {
+    element = $(element);
+    var result = '<' + element.tagName.toLowerCase();
+    $H({'id': 'id', 'className': 'class'}).each(function(pair) {
+      var property = pair.first(), attribute = pair.last();
+      var value = (element[property] || '').toString();
+      if (value) result += ' ' + attribute + '=' + value.inspect(true);
+    });
+    return result + '>';
+  },
+
+  recursivelyCollect: function(element, property) {
+    element = $(element);
+    var elements = [];
+    while (element = element[property])
+      if (element.nodeType == 1)
+        elements.push(Element.extend(element));
+    return elements;
+  },
+
+  ancestors: function(element) {
+    return $(element).recursivelyCollect('parentNode');
+  },
+
+  descendants: function(element) {
+    return $(element).select("*");
+  },
+
+  firstDescendant: function(element) {
+    element = $(element).firstChild;
+    while (element && element.nodeType != 1) element = element.nextSibling;
+    return $(element);
+  },
+
+  immediateDescendants: function(element) {
+    if (!(element = $(element).firstChild)) return [];
+    while (element && element.nodeType != 1) element = element.nextSibling;
+    if (element) return [element].concat($(element).nextSiblings());
+    return [];
+  },
+
+  previousSiblings: function(element) {
+    return $(element).recursivelyCollect('previousSibling');
+  },
+
+  nextSiblings: function(element) {
+    return $(element).recursivelyCollect('nextSibling');
+  },
+
+  siblings: function(element) {
+    element = $(element);
+    return element.previousSiblings().reverse().concat(element.nextSiblings());
+  },
+
+  match: function(element, selector) {
+    if (Object.isString(selector))
+      selector = new Selector(selector);
+    return selector.match($(element));
+  },
+
+  up: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(element.parentNode);
+    var ancestors = element.ancestors();
+    return Object.isNumber(expression) ? ancestors[expression] :
+      Selector.findElement(ancestors, expression, index);
+  },
+
+  down: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return element.firstDescendant();
+    return Object.isNumber(expression) ? element.descendants()[expression] :
+      element.select(expression)[index || 0];
+  },
+
+  previous: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
+    var previousSiblings = element.previousSiblings();
+    return Object.isNumber(expression) ? previousSiblings[expression] :
+      Selector.findElement(previousSiblings, expression, index);
+  },
+
+  next: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
+    var nextSiblings = element.nextSiblings();
+    return Object.isNumber(expression) ? nextSiblings[expression] :
+      Selector.findElement(nextSiblings, expression, index);
+  },
+
+  select: function() {
+    var args = $A(arguments), element = $(args.shift());
+    return Selector.findChildElements(element, args);
+  },
+
+  adjacent: function() {
+    var args = $A(arguments), element = $(args.shift());
+    return Selector.findChildElements(element.parentNode, args).without(element);
+  },
+
+  identify: function(element) {
+    element = $(element);
+    var id = element.readAttribute('id'), self = arguments.callee;
+    if (id) return id;
+    do { id = 'anonymous_element_' + self.counter++ } while ($(id));
+    element.writeAttribute('id', id);
+    return id;
+  },
+
+  readAttribute: function(element, name) {
+    element = $(element);
+    if (Prototype.Browser.IE) {
+      var t = Element._attributeTranslations.read;
+      if (t.values[name]) return t.values[name](element, name);
+      if (t.names[name]) name = t.names[name];
+      if (name.include(':')) {
+        return (!element.attributes || !element.attributes[name]) ? null :
+         element.attributes[name].value;
+      }
+    }
+    return element.getAttribute(name);
+  },
+
+  writeAttribute: function(element, name, value) {
+    element = $(element);
+    var attributes = { }, t = Element._attributeTranslations.write;
+
+    if (typeof name == 'object') attributes = name;
+    else attributes[name] = Object.isUndefined(value) ? true : value;
+
+    for (var attr in attributes) {
+      name = t.names[attr] || attr;
+      value = attributes[attr];
+      if (t.values[attr]) name = t.values[attr](element, value);
+      if (value === false || value === null)
+        element.removeAttribute(name);
+      else if (value === true)
+        element.setAttribute(name, name);
+      else element.setAttribute(name, value);
+    }
+    return element;
+  },
+
+  getHeight: function(element) {
+    return $(element).getDimensions().height;
+  },
+
+  getWidth: function(element) {
+    return $(element).getDimensions().width;
+  },
+
+  classNames: function(element) {
+    return new Element.ClassNames(element);
+  },
+
+  hasClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    var elementClassName = element.className;
+    return (elementClassName.length > 0 && (elementClassName == className ||
+      new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
+  },
+
+  addClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    if (!element.hasClassName(className))
+      element.className += (element.className ? ' ' : '') + className;
+    return element;
+  },
+
+  removeClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    element.className = element.className.replace(
+      new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
+    return element;
+  },
+
+  toggleClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    return element[element.hasClassName(className) ?
+      'removeClassName' : 'addClassName'](className);
+  },
+
+  // removes whitespace-only text node children
+  cleanWhitespace: function(element) {
+    element = $(element);
+    var node = element.firstChild;
+    while (node) {
+      var nextNode = node.nextSibling;
+      if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
+        element.removeChild(node);
+      node = nextNode;
+    }
+    return element;
+  },
+
+  empty: function(element) {
+    return $(element).innerHTML.blank();
+  },
+
+  descendantOf: function(element, ancestor) {
+    element = $(element), ancestor = $(ancestor);
+    var originalAncestor = ancestor;
+
+    if (element.compareDocumentPosition)
+      return (element.compareDocumentPosition(ancestor) & 8) === 8;
+
+    if (element.sourceIndex && !Prototype.Browser.Opera) {
+      var e = element.sourceIndex, a = ancestor.sourceIndex,
+       nextAncestor = ancestor.nextSibling;
+      if (!nextAncestor) {
+        do { ancestor = ancestor.parentNode; }
+        while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode);
+      }
+      if (nextAncestor && nextAncestor.sourceIndex)
+       return (e > a && e < nextAncestor.sourceIndex);
+    }
+
+    while (element = element.parentNode)
+      if (element == originalAncestor) return true;
+    return false;
+  },
+
+  scrollTo: function(element) {
+    element = $(element);
+    var pos = element.cumulativeOffset();
+    window.scrollTo(pos[0], pos[1]);
+    return element;
+  },
+
+  getStyle: function(element, style) {
+    element = $(element);
+    style = style == 'float' ? 'cssFloat' : style.camelize();
+    var value = element.style[style];
+    if (!value) {
+      var css = document.defaultView.getComputedStyle(element, null);
+      value = css ? css[style] : null;
+    }
+    if (style == 'opacity') return value ? parseFloat(value) : 1.0;
+    return value == 'auto' ? null : value;
+  },
+
+  getOpacity: function(element) {
+    return $(element).getStyle('opacity');
+  },
+
+  setStyle: function(element, styles) {
+    element = $(element);
+    var elementStyle = element.style, match;
+    if (Object.isString(styles)) {
+      element.style.cssText += ';' + styles;
+      return styles.include('opacity') ?
+        element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
+    }
+    for (var property in styles)
+      if (property == 'opacity') element.setOpacity(styles[property]);
+      else
+        elementStyle[(property == 'float' || property == 'cssFloat') ?
+          (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :
+            property] = styles[property];
+
+    return element;
+  },
+
+  setOpacity: function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1 || value === '') ? '' :
+      (value < 0.00001) ? 0 : value;
+    return element;
+  },
+
+  getDimensions: function(element) {
+    element = $(element);
+    var display = $(element).getStyle('display');
+    if (display != 'none' && display != null) // Safari bug
+      return {width: element.offsetWidth, height: element.offsetHeight};
+
+    // All *Width and *Height properties give 0 on elements with display none,
+    // so enable the element temporarily
+    var els = element.style;
+    var originalVisibility = els.visibility;
+    var originalPosition = els.position;
+    var originalDisplay = els.display;
+    els.visibility = 'hidden';
+    els.position = 'absolute';
+    els.display = 'block';
+    var originalWidth = element.clientWidth;
+    var originalHeight = element.clientHeight;
+    els.display = originalDisplay;
+    els.position = originalPosition;
+    els.visibility = originalVisibility;
+    return {width: originalWidth, height: originalHeight};
+  },
+
+  makePositioned: function(element) {
+    element = $(element);
+    var pos = Element.getStyle(element, 'position');
+    if (pos == 'static' || !pos) {
+      element._madePositioned = true;
+      element.style.position = 'relative';
+      // Opera returns the offset relative to the positioning context, when an
+      // element is position relative but top and left have not been defined
+      if (window.opera) {
+        element.style.top = 0;
+        element.style.left = 0;
+      }
+    }
+    return element;
+  },
+
+  undoPositioned: function(element) {
+    element = $(element);
+    if (element._madePositioned) {
+      element._madePositioned = undefined;
+      element.style.position =
+        element.style.top =
+        element.style.left =
+        element.style.bottom =
+        element.style.right = '';
+    }
+    return element;
+  },
+
+  makeClipping: function(element) {
+    element = $(element);
+    if (element._overflow) return element;
+    element._overflow = Element.getStyle(element, 'overflow') || 'auto';
+    if (element._overflow !== 'hidden')
+      element.style.overflow = 'hidden';
+    return element;
+  },
+
+  undoClipping: function(element) {
+    element = $(element);
+    if (!element._overflow) return element;
+    element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
+    element._overflow = null;
+    return element;
+  },
+
+  cumulativeOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      element = element.offsetParent;
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  positionedOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      element = element.offsetParent;
+      if (element) {
+        if (element.tagName == 'BODY') break;
+        var p = Element.getStyle(element, 'position');
+        if (p !== 'static') break;
+      }
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  absolutize: function(element) {
+    element = $(element);
+    if (element.getStyle('position') == 'absolute') return;
+    // Position.prepare(); // To be done manually by Scripty when it needs it.
+
+    var offsets = element.positionedOffset();
+    var top     = offsets[1];
+    var left    = offsets[0];
+    var width   = element.clientWidth;
+    var height  = element.clientHeight;
+
+    element._originalLeft   = left - parseFloat(element.style.left  || 0);
+    element._originalTop    = top  - parseFloat(element.style.top || 0);
+    element._originalWidth  = element.style.width;
+    element._originalHeight = element.style.height;
+
+    element.style.position = 'absolute';
+    element.style.top    = top + 'px';
+    element.style.left   = left + 'px';
+    element.style.width  = width + 'px';
+    element.style.height = height + 'px';
+    return element;
+  },
+
+  relativize: function(element) {
+    element = $(element);
+    if (element.getStyle('position') == 'relative') return;
+    // Position.prepare(); // To be done manually by Scripty when it needs it.
+
+    element.style.position = 'relative';
+    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
+    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
+
+    element.style.top    = top + 'px';
+    element.style.left   = left + 'px';
+    element.style.height = element._originalHeight;
+    element.style.width  = element._originalWidth;
+    return element;
+  },
+
+  cumulativeScrollOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.scrollTop  || 0;
+      valueL += element.scrollLeft || 0;
+      element = element.parentNode;
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  getOffsetParent: function(element) {
+    if (element.offsetParent) return $(element.offsetParent);
+    if (element == document.body) return $(element);
+
+    while ((element = element.parentNode) && element != document.body)
+      if (Element.getStyle(element, 'position') != 'static')
+        return $(element);
+
+    return $(document.body);
+  },
+
+  viewportOffset: function(forElement) {
+    var valueT = 0, valueL = 0;
+
+    var element = forElement;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+
+      // Safari fix
+      if (element.offsetParent == document.body &&
+        Element.getStyle(element, 'position') == 'absolute') break;
+
+    } while (element = element.offsetParent);
+
+    element = forElement;
+    do {
+      if (!Prototype.Browser.Opera || element.tagName == 'BODY') {
+        valueT -= element.scrollTop  || 0;
+        valueL -= element.scrollLeft || 0;
+      }
+    } while (element = element.parentNode);
+
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  clonePosition: function(element, source) {
+    var options = Object.extend({
+      setLeft:    true,
+      setTop:     true,
+      setWidth:   true,
+      setHeight:  true,
+      offsetTop:  0,
+      offsetLeft: 0
+    }, arguments[2] || { });
+
+    // find page position of source
+    source = $(source);
+    var p = source.viewportOffset();
+
+    // find coordinate system to use
+    element = $(element);
+    var delta = [0, 0];
+    var parent = null;
+    // delta [0,0] will do fine with position: fixed elements,
+    // position:absolute needs offsetParent deltas
+    if (Element.getStyle(element, 'position') == 'absolute') {
+      parent = element.getOffsetParent();
+      delta = parent.viewportOffset();
+    }
+
+    // correct by body offsets (fixes Safari)
+    if (parent == document.body) {
+      delta[0] -= document.body.offsetLeft;
+      delta[1] -= document.body.offsetTop;
+    }
+
+    // set position
+    if (options.setLeft)   element.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
+    if (options.setTop)    element.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
+    if (options.setWidth)  element.style.width = source.offsetWidth + 'px';
+    if (options.setHeight) element.style.height = source.offsetHeight + 'px';
+    return element;
+  }
+};
+
+Element.Methods.identify.counter = 1;
+
+Object.extend(Element.Methods, {
+  getElementsBySelector: Element.Methods.select,
+  childElements: Element.Methods.immediateDescendants
+});
+
+Element._attributeTranslations = {
+  write: {
+    names: {
+      className: 'class',
+      htmlFor:   'for'
+    },
+    values: { }
+  }
+};
+
+if (Prototype.Browser.Opera) {
+  Element.Methods.getStyle = Element.Methods.getStyle.wrap(
+    function(proceed, element, style) {
+      switch (style) {
+        case 'left': case 'top': case 'right': case 'bottom':
+          if (proceed(element, 'position') === 'static') return null;
+        case 'height': case 'width':
+          // returns '0px' for hidden elements; we want it to return null
+          if (!Element.visible(element)) return null;
+
+          // returns the border-box dimensions rather than the content-box
+          // dimensions, so we subtract padding and borders from the value
+          var dim = parseInt(proceed(element, style), 10);
+
+          if (dim !== element['offset' + style.capitalize()])
+            return dim + 'px';
+
+          var properties;
+          if (style === 'height') {
+            properties = ['border-top-width', 'padding-top',
+             'padding-bottom', 'border-bottom-width'];
+          }
+          else {
+            properties = ['border-left-width', 'padding-left',
+             'padding-right', 'border-right-width'];
+          }
+          return properties.inject(dim, function(memo, property) {
+            var val = proceed(element, property);
+            return val === null ? memo : memo - parseInt(val, 10);
+          }) + 'px';
+        default: return proceed(element, style);
+      }
+    }
+  );
+
+  Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(
+    function(proceed, element, attribute) {
+      if (attribute === 'title') return element.title;
+      return proceed(element, attribute);
+    }
+  );
+}
+
+else if (Prototype.Browser.IE) {
+  // IE doesn't report offsets correctly for static elements, so we change them
+  // to "relative" to get the values, then change them back.
+  Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(
+    function(proceed, element) {
+      element = $(element);
+      var position = element.getStyle('position');
+      if (position !== 'static') return proceed(element);
+      element.setStyle({ position: 'relative' });
+      var value = proceed(element);
+      element.setStyle({ position: position });
+      return value;
+    }
+  );
+
+  $w('positionedOffset viewportOffset').each(function(method) {
+    Element.Methods[method] = Element.Methods[method].wrap(
+      function(proceed, element) {
+        element = $(element);
+        var position = element.getStyle('position');
+        if (position !== 'static') return proceed(element);
+        // Trigger hasLayout on the offset parent so that IE6 reports
+        // accurate offsetTop and offsetLeft values for position: fixed.
+        var offsetParent = element.getOffsetParent();
+        if (offsetParent && offsetParent.getStyle('position') === 'fixed')
+          offsetParent.setStyle({ zoom: 1 });
+        element.setStyle({ position: 'relative' });
+        var value = proceed(element);
+        element.setStyle({ position: position });
+        return value;
+      }
+    );
+  });
+
+  Element.Methods.getStyle = function(element, style) {
+    element = $(element);
+    style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
+    var value = element.style[style];
+    if (!value && element.currentStyle) value = element.currentStyle[style];
+
+    if (style == 'opacity') {
+      if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
+        if (value[1]) return parseFloat(value[1]) / 100;
+      return 1.0;
+    }
+
+    if (value == 'auto') {
+      if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
+        return element['offset' + style.capitalize()] + 'px';
+      return null;
+    }
+    return value;
+  };
+
+  Element.Methods.setOpacity = function(element, value) {
+    function stripAlpha(filter){
+      return filter.replace(/alpha\([^\)]*\)/gi,'');
+    }
+    element = $(element);
+    var currentStyle = element.currentStyle;
+    if ((currentStyle && !currentStyle.hasLayout) ||
+      (!currentStyle && element.style.zoom == 'normal'))
+        element.style.zoom = 1;
+
+    var filter = element.getStyle('filter'), style = element.style;
+    if (value == 1 || value === '') {
+      (filter = stripAlpha(filter)) ?
+        style.filter = filter : style.removeAttribute('filter');
+      return element;
+    } else if (value < 0.00001) value = 0;
+    style.filter = stripAlpha(filter) +
+      'alpha(opacity=' + (value * 100) + ')';
+    return element;
+  };
+
+  Element._attributeTranslations = {
+    read: {
+      names: {
+        'class': 'className',
+        'for':   'htmlFor'
+      },
+      values: {
+        _getAttr: function(element, attribute) {
+          return element.getAttribute(attribute, 2);
+        },
+        _getAttrNode: function(element, attribute) {
+          var node = element.getAttributeNode(attribute);
+          return node ? node.value : "";
+        },
+        _getEv: function(element, attribute) {
+          attribute = element.getAttribute(attribute);
+          return attribute ? attribute.toString().slice(23, -2) : null;
+        },
+        _flag: function(element, attribute) {
+          return $(element).hasAttribute(attribute) ? attribute : null;
+        },
+        style: function(element) {
+          return element.style.cssText.toLowerCase();
+        },
+        title: function(element) {
+          return element.title;
+        }
+      }
+    }
+  };
+
+  Element._attributeTranslations.write = {
+    names: Object.extend({
+      cellpadding: 'cellPadding',
+      cellspacing: 'cellSpacing'
+    }, Element._attributeTranslations.read.names),
+    values: {
+      checked: function(element, value) {
+        element.checked = !!value;
+      },
+
+      style: function(element, value) {
+        element.style.cssText = value ? value : '';
+      }
+    }
+  };
+
+  Element._attributeTranslations.has = {};
+
+  $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
+      'encType maxLength readOnly longDesc').each(function(attr) {
+    Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
+    Element._attributeTranslations.has[attr.toLowerCase()] = attr;
+  });
+
+  (function(v) {
+    Object.extend(v, {
+      href:        v._getAttr,
+      src:         v._getAttr,
+      type:        v._getAttr,
+      action:      v._getAttrNode,
+      disabled:    v._flag,
+      checked:     v._flag,
+      readonly:    v._flag,
+      multiple:    v._flag,
+      onload:      v._getEv,
+      onunload:    v._getEv,
+      onclick:     v._getEv,
+      ondblclick:  v._getEv,
+      onmousedown: v._getEv,
+      onmouseup:   v._getEv,
+      onmouseover: v._getEv,
+      onmousemove: v._getEv,
+      onmouseout:  v._getEv,
+      onfocus:     v._getEv,
+      onblur:      v._getEv,
+      onkeypress:  v._getEv,
+      onkeydown:   v._getEv,
+      onkeyup:     v._getEv,
+      onsubmit:    v._getEv,
+      onreset:     v._getEv,
+      onselect:    v._getEv,
+      onchange:    v._getEv
+    });
+  })(Element._attributeTranslations.read.values);
+}
+
+else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {
+  Element.Methods.setOpacity = function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1) ? 0.999999 :
+      (value === '') ? '' : (value < 0.00001) ? 0 : value;
+    return element;
+  };
+}
+
+else if (Prototype.Browser.WebKit) {
+  Element.Methods.setOpacity = function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1 || value === '') ? '' :
+      (value < 0.00001) ? 0 : value;
+
+    if (value == 1)
+      if(element.tagName == 'IMG' && element.width) {
+        element.width++; element.width--;
+      } else try {
+        var n = document.createTextNode(' ');
+        element.appendChild(n);
+        element.removeChild(n);
+      } catch (e) { }
+
+    return element;
+  };
+
+  // Safari returns margins on body which is incorrect if the child is absolutely
+  // positioned.  For performance reasons, redefine Element#cumulativeOffset for
+  // KHTML/WebKit only.
+  Element.Methods.cumulativeOffset = function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      if (element.offsetParent == document.body)
+        if (Element.getStyle(element, 'position') == 'absolute') break;
+
+      element = element.offsetParent;
+    } while (element);
+
+    return Element._returnOffset(valueL, valueT);
+  };
+}
+
+if (Prototype.Browser.IE || Prototype.Browser.Opera) {
+  // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements
+  Element.Methods.update = function(element, content) {
+    element = $(element);
+
+    if (content && content.toElement) content = content.toElement();
+    if (Object.isElement(content)) return element.update().insert(content);
+
+    content = Object.toHTML(content);
+    var tagName = element.tagName.toUpperCase();
+
+    if (tagName in Element._insertionTranslations.tags) {
+      $A(element.childNodes).each(function(node) { element.removeChild(node) });
+      Element._getContentFromAnonymousElement(tagName, content.stripScripts())
+        .each(function(node) { element.appendChild(node) });
+    }
+    else element.innerHTML = content.stripScripts();
+
+    content.evalScripts.bind(content).defer();
+    return element;
+  };
+}
+
+if ('outerHTML' in document.createElement('div')) {
+  Element.Methods.replace = function(element, content) {
+    element = $(element);
+
+    if (content && content.toElement) content = content.toElement();
+    if (Object.isElement(content)) {
+      element.parentNode.replaceChild(content, element);
+      return element;
+    }
+
+    content = Object.toHTML(content);
+    var parent = element.parentNode, tagName = parent.tagName.toUpperCase();
+
+    if (Element._insertionTranslations.tags[tagName]) {
+      var nextSibling = element.next();
+      var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+      parent.removeChild(element);
+      if (nextSibling)
+        fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
+      else
+        fragments.each(function(node) { parent.appendChild(node) });
+    }
+    else element.outerHTML = content.stripScripts();
+
+    content.evalScripts.bind(content).defer();
+    return element;
+  };
+}
+
+Element._returnOffset = function(l, t) {
+  var result = [l, t];
+  result.left = l;
+  result.top = t;
+  return result;
+};
+
+Element._getContentFromAnonymousElement = function(tagName, html) {
+  var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
+  if (t) {
+    div.innerHTML = t[0] + html + t[1];
+    t[2].times(function() { div = div.firstChild });
+  } else div.innerHTML = html;
+  return $A(div.childNodes);
+};
+
+Element._insertionTranslations = {
+  before: function(element, node) {
+    element.parentNode.insertBefore(node, element);
+  },
+  top: function(element, node) {
+    element.insertBefore(node, element.firstChild);
+  },
+  bottom: function(element, node) {
+    element.appendChild(node);
+  },
+  after: function(element, node) {
+    element.parentNode.insertBefore(node, element.nextSibling);
+  },
+  tags: {
+    TABLE:  ['<table>',                '</table>',                   1],
+    TBODY:  ['<table><tbody>',         '</tbody></table>',           2],
+    TR:     ['<table><tbody><tr>',     '</tr></tbody></table>',      3],
+    TD:     ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
+    SELECT: ['<select>',               '</select>',                  1]
+  }
+};
+
+(function() {
+  Object.extend(this.tags, {
+    THEAD: this.tags.TBODY,
+    TFOOT: this.tags.TBODY,
+    TH:    this.tags.TD
+  });
+}).call(Element._insertionTranslations);
+
+Element.Methods.Simulated = {
+  hasAttribute: function(element, attribute) {
+    attribute = Element._attributeTranslations.has[attribute] || attribute;
+    var node = $(element).getAttributeNode(attribute);
+    return node && node.specified;
+  }
+};
+
+Element.Methods.ByTag = { };
+
+Object.extend(Element, Element.Methods);
+
+if (!Prototype.BrowserFeatures.ElementExtensions &&
+    document.createElement('div').__proto__) {
+  window.HTMLElement = { };
+  window.HTMLElement.prototype = document.createElement('div').__proto__;
+  Prototype.BrowserFeatures.ElementExtensions = true;
+}
+
+Element.extend = (function() {
+  if (Prototype.BrowserFeatures.SpecificElementExtensions)
+    return Prototype.K;
+
+  var Methods = { }, ByTag = Element.Methods.ByTag;
+
+  var extend = Object.extend(function(element) {
+    if (!element || element._extendedByPrototype ||
+        element.nodeType != 1 || element == window) return element;
+
+    var methods = Object.clone(Methods),
+      tagName = element.tagName, property, value;
+
+    // extend methods for specific tags
+    if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);
+
+    for (property in methods) {
+      value = methods[property];
+      if (Object.isFunction(value) && !(property in element))
+        element[property] = value.methodize();
+    }
+
+    element._extendedByPrototype = Prototype.emptyFunction;
+    return element;
+
+  }, {
+    refresh: function() {
+      // extend methods for all tags (Safari doesn't need this)
+      if (!Prototype.BrowserFeatures.ElementExtensions) {
+        Object.extend(Methods, Element.Methods);
+        Object.extend(Methods, Element.Methods.Simulated);
+      }
+    }
+  });
+
+  extend.refresh();
+  return extend;
+})();
+
+Element.hasAttribute = function(element, attribute) {
+  if (element.hasAttribute) return element.hasAttribute(attribute);
+  return Element.Methods.Simulated.hasAttribute(element, attribute);
+};
+
+Element.addMethods = function(methods) {
+  var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
+
+  if (!methods) {
+    Object.extend(Form, Form.Methods);
+    Object.extend(Form.Element, Form.Element.Methods);
+    Object.extend(Element.Methods.ByTag, {
+      "FORM":     Object.clone(Form.Methods),
+      "INPUT":    Object.clone(Form.Element.Methods),
+      "SELECT":   Object.clone(Form.Element.Methods),
+      "TEXTAREA": Object.clone(Form.Element.Methods)
+    });
+  }
+
+  if (arguments.length == 2) {
+    var tagName = methods;
+    methods = arguments[1];
+  }
+
+  if (!tagName) Object.extend(Element.Methods, methods || { });
+  else {
+    if (Object.isArray(tagName)) tagName.each(extend);
+    else extend(tagName);
+  }
+
+  function extend(tagName) {
+    tagName = tagName.toUpperCase();
+    if (!Element.Methods.ByTag[tagName])
+      Element.Methods.ByTag[tagName] = { };
+    Object.extend(Element.Methods.ByTag[tagName], methods);
+  }
+
+  function copy(methods, destination, onlyIfAbsent) {
+    onlyIfAbsent = onlyIfAbsent || false;
+    for (var property in methods) {
+      var value = methods[property];
+      if (!Object.isFunction(value)) continue;
+      if (!onlyIfAbsent || !(property in destination))
+        destination[property] = value.methodize();
+    }
+  }
+
+  function findDOMClass(tagName) {
+    var klass;
+    var trans = {
+      "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
+      "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
+      "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
+      "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
+      "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
+      "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
+      "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
+      "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
+      "FrameSet", "IFRAME": "IFrame"
+    };
+    if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
+    if (window[klass]) return window[klass];
+    klass = 'HTML' + tagName + 'Element';
+    if (window[klass]) return window[klass];
+    klass = 'HTML' + tagName.capitalize() + 'Element';
+    if (window[klass]) return window[klass];
+
+    window[klass] = { };
+    window[klass].prototype = document.createElement(tagName).__proto__;
+    return window[klass];
+  }
+
+  if (F.ElementExtensions) {
+    copy(Element.Methods, HTMLElement.prototype);
+    copy(Element.Methods.Simulated, HTMLElement.prototype, true);
+  }
+
+  if (F.SpecificElementExtensions) {
+    for (var tag in Element.Methods.ByTag) {
+      var klass = findDOMClass(tag);
+      if (Object.isUndefined(klass)) continue;
+      copy(T[tag], klass.prototype);
+    }
+  }
+
+  Object.extend(Element, Element.Methods);
+  delete Element.ByTag;
+
+  if (Element.extend.refresh) Element.extend.refresh();
+  Element.cache = { };
+};
+
+document.viewport = {
+  getDimensions: function() {
+    var dimensions = { };
+    var B = Prototype.Browser;
+    $w('width height').each(function(d) {
+      var D = d.capitalize();
+      dimensions[d] = (B.WebKit && !document.evaluate) ? self['inner' + D] :
+        (B.Opera) ? document.body['client' + D] : document.documentElement['client' + D];
+    });
+    return dimensions;
+  },
+
+  getWidth: function() {
+    return this.getDimensions().width;
+  },
+
+  getHeight: function() {
+    return this.getDimensions().height;
+  },
+
+  getScrollOffsets: function() {
+    return Element._returnOffset(
+      window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
+      window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
+  }
+};
+/* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
+ * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
+ * license.  Please see http://www.yui-ext.com/ for more information. */
+
+var Selector = Class.create({
+  initialize: function(expression) {
+    this.expression = expression.strip();
+    this.compileMatcher();
+  },
+
+  shouldUseXPath: function() {
+    if (!Prototype.BrowserFeatures.XPath) return false;
+
+    var e = this.expression;
+
+    // Safari 3 chokes on :*-of-type and :empty
+    if (Prototype.Browser.WebKit &&
+     (e.include("-of-type") || e.include(":empty")))
+      return false;
+
+    // XPath can't do namespaced attributes, nor can it read
+    // the "checked" property from DOM nodes
+    if ((/(\[[\w-]*?:|:checked)/).test(this.expression))
+      return false;
+
+    return true;
+  },
+
+  compileMatcher: function() {
+    if (this.shouldUseXPath())
+      return this.compileXPathMatcher();
+
+    var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
+        c = Selector.criteria, le, p, m;
+
+    if (Selector._cache[e]) {
+      this.matcher = Selector._cache[e];
+      return;
+    }
+
+    this.matcher = ["this.matcher = function(root) {",
+                    "var r = root, h = Selector.handlers, c = false, n;"];
+
+    while (e && le != e && (/\S/).test(e)) {
+      le = e;
+      for (var i in ps) {
+        p = ps[i];
+        if (m = e.match(p)) {
+          this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :
+    	      new Template(c[i]).evaluate(m));
+          e = e.replace(m[0], '');
+          break;
+        }
+      }
+    }
+
+    this.matcher.push("return h.unique(n);\n}");
+    eval(this.matcher.join('\n'));
+    Selector._cache[this.expression] = this.matcher;
+  },
+
+  compileXPathMatcher: function() {
+    var e = this.expression, ps = Selector.patterns,
+        x = Selector.xpath, le, m;
+
+    if (Selector._cache[e]) {
+      this.xpath = Selector._cache[e]; return;
+    }
+
+    this.matcher = ['.//*'];
+    while (e && le != e && (/\S/).test(e)) {
+      le = e;
+      for (var i in ps) {
+        if (m = e.match(ps[i])) {
+          this.matcher.push(Object.isFunction(x[i]) ? x[i](m) :
+            new Template(x[i]).evaluate(m));
+          e = e.replace(m[0], '');
+          break;
+        }
+      }
+    }
+
+    this.xpath = this.matcher.join('');
+    Selector._cache[this.expression] = this.xpath;
+  },
+
+  findElements: function(root) {
+    root = root || document;
+    if (this.xpath) return document._getElementsByXPath(this.xpath, root);
+    return this.matcher(root);
+  },
+
+  match: function(element) {
+    this.tokens = [];
+
+    var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
+    var le, p, m;
+
+    while (e && le !== e && (/\S/).test(e)) {
+      le = e;
+      for (var i in ps) {
+        p = ps[i];
+        if (m = e.match(p)) {
+          // use the Selector.assertions methods unless the selector
+          // is too complex.
+          if (as[i]) {
+            this.tokens.push([i, Object.clone(m)]);
+            e = e.replace(m[0], '');
+          } else {
+            // reluctantly do a document-wide search
+            // and look for a match in the array
+            return this.findElements(document).include(element);
+          }
+        }
+      }
+    }
+
+    var match = true, name, matches;
+    for (var i = 0, token; token = this.tokens[i]; i++) {
+      name = token[0], matches = token[1];
+      if (!Selector.assertions[name](element, matches)) {
+        match = false; break;
+      }
+    }
+
+    return match;
+  },
+
+  toString: function() {
+    return this.expression;
+  },
+
+  inspect: function() {
+    return "#<Selector:" + this.expression.inspect() + ">";
+  }
+});
+
+Object.extend(Selector, {
+  _cache: { },
+
+  xpath: {
+    descendant:   "//*",
+    child:        "/*",
+    adjacent:     "/following-sibling::*[1]",
+    laterSibling: '/following-sibling::*',
+    tagName:      function(m) {
+      if (m[1] == '*') return '';
+      return "[local-name()='" + m[1].toLowerCase() +
+             "' or local-name()='" + m[1].toUpperCase() + "']";
+    },
+    className:    "[contains(concat(' ', @class, ' '), ' #{1} ')]",
+    id:           "[@id='#{1}']",
+    attrPresence: function(m) {
+      m[1] = m[1].toLowerCase();
+      return new Template("[@#{1}]").evaluate(m);
+    },
+    attr: function(m) {
+      m[1] = m[1].toLowerCase();
+      m[3] = m[5] || m[6];
+      return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
+    },
+    pseudo: function(m) {
+      var h = Selector.xpath.pseudos[m[1]];
+      if (!h) return '';
+      if (Object.isFunction(h)) return h(m);
+      return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
+    },
+    operators: {
+      '=':  "[@#{1}='#{3}']",
+      '!=': "[@#{1}!='#{3}']",
+      '^=': "[starts-with(@#{1}, '#{3}')]",
+      '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
+      '*=': "[contains(@#{1}, '#{3}')]",
+      '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
+      '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
+    },
+    pseudos: {
+      'first-child': '[not(preceding-sibling::*)]',
+      'last-child':  '[not(following-sibling::*)]',
+      'only-child':  '[not(preceding-sibling::* or following-sibling::*)]',
+      'empty':       "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]",
+      'checked':     "[@checked]",
+      'disabled':    "[@disabled]",
+      'enabled':     "[not(@disabled)]",
+      'not': function(m) {
+        var e = m[6], p = Selector.patterns,
+            x = Selector.xpath, le, v;
+
+        var exclusion = [];
+        while (e && le != e && (/\S/).test(e)) {
+          le = e;
+          for (var i in p) {
+            if (m = e.match(p[i])) {
+              v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m);
+              exclusion.push("(" + v.substring(1, v.length - 1) + ")");
+              e = e.replace(m[0], '');
+              break;
+            }
+          }
+        }
+        return "[not(" + exclusion.join(" and ") + ")]";
+      },
+      'nth-child':      function(m) {
+        return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
+      },
+      'nth-last-child': function(m) {
+        return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
+      },
+      'nth-of-type':    function(m) {
+        return Selector.xpath.pseudos.nth("position() ", m);
+      },
+      'nth-last-of-type': function(m) {
+        return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
+      },
+      'first-of-type':  function(m) {
+        m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
+      },
+      'last-of-type':   function(m) {
+        m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
+      },
+      'only-of-type':   function(m) {
+        var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
+      },
+      nth: function(fragment, m) {
+        var mm, formula = m[6], predicate;
+        if (formula == 'even') formula = '2n+0';
+        if (formula == 'odd')  formula = '2n+1';
+        if (mm = formula.match(/^(\d+)$/)) // digit only
+          return '[' + fragment + "= " + mm[1] + ']';
+        if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
+          if (mm[1] == "-") mm[1] = -1;
+          var a = mm[1] ? Number(mm[1]) : 1;
+          var b = mm[2] ? Number(mm[2]) : 0;
+          predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
+          "((#{fragment} - #{b}) div #{a} >= 0)]";
+          return new Template(predicate).evaluate({
+            fragment: fragment, a: a, b: b });
+        }
+      }
+    }
+  },
+
+  criteria: {
+    tagName:      'n = h.tagName(n, r, "#{1}", c);      c = false;',
+    className:    'n = h.className(n, r, "#{1}", c);    c = false;',
+    id:           'n = h.id(n, r, "#{1}", c);           c = false;',
+    attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',
+    attr: function(m) {
+      m[3] = (m[5] || m[6]);
+      return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);
+    },
+    pseudo: function(m) {
+      if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
+      return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
+    },
+    descendant:   'c = "descendant";',
+    child:        'c = "child";',
+    adjacent:     'c = "adjacent";',
+    laterSibling: 'c = "laterSibling";'
+  },
+
+  patterns: {
+    // combinators must be listed first
+    // (and descendant needs to be last combinator)
+    laterSibling: /^\s*~\s*/,
+    child:        /^\s*>\s*/,
+    adjacent:     /^\s*\+\s*/,
+    descendant:   /^\s/,
+
+    // selectors follow
+    tagName:      /^\s*(\*|[\w\-]+)(\b|$)?/,
+    id:           /^#([\w\-\*]+)(\b|$)/,
+    className:    /^\.([\w\-\*]+)(\b|$)/,
+    pseudo:
+/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,
+    attrPresence: /^\[([\w]+)\]/,
+    attr:         /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
+  },
+
+  // for Selector.match and Element#match
+  assertions: {
+    tagName: function(element, matches) {
+      return matches[1].toUpperCase() == element.tagName.toUpperCase();
+    },
+
+    className: function(element, matches) {
+      return Element.hasClassName(element, matches[1]);
+    },
+
+    id: function(element, matches) {
+      return element.id === matches[1];
+    },
+
+    attrPresence: function(element, matches) {
+      return Element.hasAttribute(element, matches[1]);
+    },
+
+    attr: function(element, matches) {
+      var nodeValue = Element.readAttribute(element, matches[1]);
+      return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
+    }
+  },
+
+  handlers: {
+    // UTILITY FUNCTIONS
+    // joins two collections
+    concat: function(a, b) {
+      for (var i = 0, node; node = b[i]; i++)
+        a.push(node);
+      return a;
+    },
+
+    // marks an array of nodes for counting
+    mark: function(nodes) {
+      var _true = Prototype.emptyFunction;
+      for (var i = 0, node; node = nodes[i]; i++)
+        node._countedByPrototype = _true;
+      return nodes;
+    },
+
+    unmark: function(nodes) {
+      for (var i = 0, node; node = nodes[i]; i++)
+        node._countedByPrototype = undefined;
+      return nodes;
+    },
+
+    // mark each child node with its position (for nth calls)
+    // "ofType" flag indicates whether we're indexing for nth-of-type
+    // rather than nth-child
+    index: function(parentNode, reverse, ofType) {
+      parentNode._countedByPrototype = Prototype.emptyFunction;
+      if (reverse) {
+        for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
+          var node = nodes[i];
+          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
+        }
+      } else {
+        for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
+          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
+      }
+    },
+
+    // filters out duplicates and extends all nodes
+    unique: function(nodes) {
+      if (nodes.length == 0) return nodes;
+      var results = [], n;
+      for (var i = 0, l = nodes.length; i < l; i++)
+        if (!(n = nodes[i])._countedByPrototype) {
+          n._countedByPrototype = Prototype.emptyFunction;
+          results.push(Element.extend(n));
+        }
+      return Selector.handlers.unmark(results);
+    },
+
+    // COMBINATOR FUNCTIONS
+    descendant: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        h.concat(results, node.getElementsByTagName('*'));
+      return results;
+    },
+
+    child: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        for (var j = 0, child; child = node.childNodes[j]; j++)
+          if (child.nodeType == 1 && child.tagName != '!') results.push(child);
+      }
+      return results;
+    },
+
+    adjacent: function(nodes) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        var next = this.nextElementSibling(node);
+        if (next) results.push(next);
+      }
+      return results;
+    },
+
+    laterSibling: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        h.concat(results, Element.nextSiblings(node));
+      return results;
+    },
+
+    nextElementSibling: function(node) {
+      while (node = node.nextSibling)
+	      if (node.nodeType == 1) return node;
+      return null;
+    },
+
+    previousElementSibling: function(node) {
+      while (node = node.previousSibling)
+        if (node.nodeType == 1) return node;
+      return null;
+    },
+
+    // TOKEN FUNCTIONS
+    tagName: function(nodes, root, tagName, combinator) {
+      var uTagName = tagName.toUpperCase();
+      var results = [], h = Selector.handlers;
+      if (nodes) {
+        if (combinator) {
+          // fastlane for ordinary descendant combinators
+          if (combinator == "descendant") {
+            for (var i = 0, node; node = nodes[i]; i++)
+              h.concat(results, node.getElementsByTagName(tagName));
+            return results;
+          } else nodes = this[combinator](nodes);
+          if (tagName == "*") return nodes;
+        }
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node.tagName.toUpperCase() === uTagName) results.push(node);
+        return results;
+      } else return root.getElementsByTagName(tagName);
+    },
+
+    id: function(nodes, root, id, combinator) {
+      var targetNode = $(id), h = Selector.handlers;
+      if (!targetNode) return [];
+      if (!nodes && root == document) return [targetNode];
+      if (nodes) {
+        if (combinator) {
+          if (combinator == 'child') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (targetNode.parentNode == node) return [targetNode];
+          } else if (combinator == 'descendant') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (Element.descendantOf(targetNode, node)) return [targetNode];
+          } else if (combinator == 'adjacent') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (Selector.handlers.previousElementSibling(targetNode) == node)
+                return [targetNode];
+          } else nodes = h[combinator](nodes);
+        }
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node == targetNode) return [targetNode];
+        return [];
+      }
+      return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
+    },
+
+    className: function(nodes, root, className, combinator) {
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      return Selector.handlers.byClassName(nodes, root, className);
+    },
+
+    byClassName: function(nodes, root, className) {
+      if (!nodes) nodes = Selector.handlers.descendant([root]);
+      var needle = ' ' + className + ' ';
+      for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
+        nodeClassName = node.className;
+        if (nodeClassName.length == 0) continue;
+        if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
+          results.push(node);
+      }
+      return results;
+    },
+
+    attrPresence: function(nodes, root, attr, combinator) {
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      var results = [];
+      for (var i = 0, node; node = nodes[i]; i++)
+        if (Element.hasAttribute(node, attr)) results.push(node);
+      return results;
+    },
+
+    attr: function(nodes, root, attr, value, operator, combinator) {
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      var handler = Selector.operators[operator], results = [];
+      for (var i = 0, node; node = nodes[i]; i++) {
+        var nodeValue = Element.readAttribute(node, attr);
+        if (nodeValue === null) continue;
+        if (handler(nodeValue, value)) results.push(node);
+      }
+      return results;
+    },
+
+    pseudo: function(nodes, name, value, root, combinator) {
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      return Selector.pseudos[name](nodes, value, root);
+    }
+  },
+
+  pseudos: {
+    'first-child': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        if (Selector.handlers.previousElementSibling(node)) continue;
+          results.push(node);
+      }
+      return results;
+    },
+    'last-child': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        if (Selector.handlers.nextElementSibling(node)) continue;
+          results.push(node);
+      }
+      return results;
+    },
+    'only-child': function(nodes, value, root) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
+          results.push(node);
+      return results;
+    },
+    'nth-child':        function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root);
+    },
+    'nth-last-child':   function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, true);
+    },
+    'nth-of-type':      function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, false, true);
+    },
+    'nth-last-of-type': function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, true, true);
+    },
+    'first-of-type':    function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, "1", root, false, true);
+    },
+    'last-of-type':     function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, "1", root, true, true);
+    },
+    'only-of-type':     function(nodes, formula, root) {
+      var p = Selector.pseudos;
+      return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
+    },
+
+    // handles the an+b logic
+    getIndices: function(a, b, total) {
+      if (a == 0) return b > 0 ? [b] : [];
+      return $R(1, total).inject([], function(memo, i) {
+        if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
+        return memo;
+      });
+    },
+
+    // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
+    nth: function(nodes, formula, root, reverse, ofType) {
+      if (nodes.length == 0) return [];
+      if (formula == 'even') formula = '2n+0';
+      if (formula == 'odd')  formula = '2n+1';
+      var h = Selector.handlers, results = [], indexed = [], m;
+      h.mark(nodes);
+      for (var i = 0, node; node = nodes[i]; i++) {
+        if (!node.parentNode._countedByPrototype) {
+          h.index(node.parentNode, reverse, ofType);
+          indexed.push(node.parentNode);
+        }
+      }
+      if (formula.match(/^\d+$/)) { // just a number
+        formula = Number(formula);
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node.nodeIndex == formula) results.push(node);
+      } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
+        if (m[1] == "-") m[1] = -1;
+        var a = m[1] ? Number(m[1]) : 1;
+        var b = m[2] ? Number(m[2]) : 0;
+        var indices = Selector.pseudos.getIndices(a, b, nodes.length);
+        for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
+          for (var j = 0; j < l; j++)
+            if (node.nodeIndex == indices[j]) results.push(node);
+        }
+      }
+      h.unmark(nodes);
+      h.unmark(indexed);
+      return results;
+    },
+
+    'empty': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        // IE treats comments as element nodes
+        if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue;
+        results.push(node);
+      }
+      return results;
+    },
+
+    'not': function(nodes, selector, root) {
+      var h = Selector.handlers, selectorType, m;
+      var exclusions = new Selector(selector).findElements(root);
+      h.mark(exclusions);
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!node._countedByPrototype) results.push(node);
+      h.unmark(exclusions);
+      return results;
+    },
+
+    'enabled': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!node.disabled) results.push(node);
+      return results;
+    },
+
+    'disabled': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (node.disabled) results.push(node);
+      return results;
+    },
+
+    'checked': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (node.checked) results.push(node);
+      return results;
+    }
+  },
+
+  operators: {
+    '=':  function(nv, v) { return nv == v; },
+    '!=': function(nv, v) { return nv != v; },
+    '^=': function(nv, v) { return nv.startsWith(v); },
+    '$=': function(nv, v) { return nv.endsWith(v); },
+    '*=': function(nv, v) { return nv.include(v); },
+    '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
+    '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }
+  },
+
+  split: function(expression) {
+    var expressions = [];
+    expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
+      expressions.push(m[1].strip());
+    });
+    return expressions;
+  },
+
+  matchElements: function(elements, expression) {
+    var matches = $$(expression), h = Selector.handlers;
+    h.mark(matches);
+    for (var i = 0, results = [], element; element = elements[i]; i++)
+      if (element._countedByPrototype) results.push(element);
+    h.unmark(matches);
+    return results;
+  },
+
+  findElement: function(elements, expression, index) {
+    if (Object.isNumber(expression)) {
+      index = expression; expression = false;
+    }
+    return Selector.matchElements(elements, expression || '*')[index || 0];
+  },
+
+  findChildElements: function(element, expressions) {
+    expressions = Selector.split(expressions.join(','));
+    var results = [], h = Selector.handlers;
+    for (var i = 0, l = expressions.length, selector; i < l; i++) {
+      selector = new Selector(expressions[i].strip());
+      h.concat(results, selector.findElements(element));
+    }
+    return (l > 1) ? h.unique(results) : results;
+  }
+});
+
+if (Prototype.Browser.IE) {
+  Object.extend(Selector.handlers, {
+    // IE returns comment nodes on getElementsByTagName("*").
+    // Filter them out.
+    concat: function(a, b) {
+      for (var i = 0, node; node = b[i]; i++)
+        if (node.tagName !== "!") a.push(node);
+      return a;
+    },
+
+    // IE improperly serializes _countedByPrototype in (inner|outer)HTML.
+    unmark: function(nodes) {
+      for (var i = 0, node; node = nodes[i]; i++)
+        node.removeAttribute('_countedByPrototype');
+      return nodes;
+    }
+  });
+}
+
+function $$() {
+  return Selector.findChildElements(document, $A(arguments));
+}
+var Form = {
+  reset: function(form) {
+    $(form).reset();
+    return form;
+  },
+
+  serializeElements: function(elements, options) {
+    if (typeof options != 'object') options = { hash: !!options };
+    else if (Object.isUndefined(options.hash)) options.hash = true;
+    var key, value, submitted = false, submit = options.submit;
+
+    var data = elements.inject({ }, function(result, element) {
+      if (!element.disabled && element.name) {
+        key = element.name; value = $(element).getValue();
+        if (value != null && (element.type != 'submit' || (!submitted &&
+            submit !== false && (!submit || key == submit) && (submitted = true)))) {
+          if (key in result) {
+            // a key is already present; construct an array of values
+            if (!Object.isArray(result[key])) result[key] = [result[key]];
+            result[key].push(value);
+          }
+          else result[key] = value;
+        }
+      }
+      return result;
+    });
+
+    return options.hash ? data : Object.toQueryString(data);
+  }
+};
+
+Form.Methods = {
+  serialize: function(form, options) {
+    return Form.serializeElements(Form.getElements(form), options);
+  },
+
+  getElements: function(form) {
+    return $A($(form).getElementsByTagName('*')).inject([],
+      function(elements, child) {
+        if (Form.Element.Serializers[child.tagName.toLowerCase()])
+          elements.push(Element.extend(child));
+        return elements;
+      }
+    );
+  },
+
+  getInputs: function(form, typeName, name) {
+    form = $(form);
+    var inputs = form.getElementsByTagName('input');
+
+    if (!typeName && !name) return $A(inputs).map(Element.extend);
+
+    for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
+      var input = inputs[i];
+      if ((typeName && input.type != typeName) || (name && input.name != name))
+        continue;
+      matchingInputs.push(Element.extend(input));
+    }
+
+    return matchingInputs;
+  },
+
+  disable: function(form) {
+    form = $(form);
+    Form.getElements(form).invoke('disable');
+    return form;
+  },
+
+  enable: function(form) {
+    form = $(form);
+    Form.getElements(form).invoke('enable');
+    return form;
+  },
+
+  findFirstElement: function(form) {
+    var elements = $(form).getElements().findAll(function(element) {
+      return 'hidden' != element.type && !element.disabled;
+    });
+    var firstByIndex = elements.findAll(function(element) {
+      return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
+    }).sortBy(function(element) { return element.tabIndex }).first();
+
+    return firstByIndex ? firstByIndex : elements.find(function(element) {
+      return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
+    });
+  },
+
+  focusFirstElement: function(form) {
+    form = $(form);
+    form.findFirstElement().activate();
+    return form;
+  },
+
+  request: function(form, options) {
+    form = $(form), options = Object.clone(options || { });
+
+    var params = options.parameters, action = form.readAttribute('action') || '';
+    if (action.blank()) action = window.location.href;
+    options.parameters = form.serialize(true);
+
+    if (params) {
+      if (Object.isString(params)) params = params.toQueryParams();
+      Object.extend(options.parameters, params);
+    }
+
+    if (form.hasAttribute('method') && !options.method)
+      options.method = form.method;
+
+    return new Ajax.Request(action, options);
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+Form.Element = {
+  focus: function(element) {
+    $(element).focus();
+    return element;
+  },
+
+  select: function(element) {
+    $(element).select();
+    return element;
+  }
+};
+
+Form.Element.Methods = {
+  serialize: function(element) {
+    element = $(element);
+    if (!element.disabled && element.name) {
+      var value = element.getValue();
+      if (value != undefined) {
+        var pair = { };
+        pair[element.name] = value;
+        return Object.toQueryString(pair);
+      }
+    }
+    return '';
+  },
+
+  getValue: function(element) {
+    element = $(element);
+    var method = element.tagName.toLowerCase();
+    return Form.Element.Serializers[method](element);
+  },
+
+  setValue: function(element, value) {
+    element = $(element);
+    var method = element.tagName.toLowerCase();
+    Form.Element.Serializers[method](element, value);
+    return element;
+  },
+
+  clear: function(element) {
+    $(element).value = '';
+    return element;
+  },
+
+  present: function(element) {
+    return $(element).value != '';
+  },
+
+  activate: function(element) {
+    element = $(element);
+    try {
+      element.focus();
+      if (element.select && (element.tagName.toLowerCase() != 'input' ||
+          !['button', 'reset', 'submit'].include(element.type)))
+        element.select();
+    } catch (e) { }
+    return element;
+  },
+
+  disable: function(element) {
+    element = $(element);
+    element.blur();
+    element.disabled = true;
+    return element;
+  },
+
+  enable: function(element) {
+    element = $(element);
+    element.disabled = false;
+    return element;
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+var Field = Form.Element;
+var $F = Form.Element.Methods.getValue;
+
+/*--------------------------------------------------------------------------*/
+
+Form.Element.Serializers = {
+  input: function(element, value) {
+    switch (element.type.toLowerCase()) {
+      case 'checkbox':
+      case 'radio':
+        return Form.Element.Serializers.inputSelector(element, value);
+      default:
+        return Form.Element.Serializers.textarea(element, value);
+    }
+  },
+
+  inputSelector: function(element, value) {
+    if (Object.isUndefined(value)) return element.checked ? element.value : null;
+    else element.checked = !!value;
+  },
+
+  textarea: function(element, value) {
+    if (Object.isUndefined(value)) return element.value;
+    else element.value = value;
+  },
+
+  select: function(element, index) {
+    if (Object.isUndefined(index))
+      return this[element.type == 'select-one' ?
+        'selectOne' : 'selectMany'](element);
+    else {
+      var opt, value, single = !Object.isArray(index);
+      for (var i = 0, length = element.length; i < length; i++) {
+        opt = element.options[i];
+        value = this.optionValue(opt);
+        if (single) {
+          if (value == index) {
+            opt.selected = true;
+            return;
+          }
+        }
+        else opt.selected = index.include(value);
+      }
+    }
+  },
+
+  selectOne: function(element) {
+    var index = element.selectedIndex;
+    return index >= 0 ? this.optionValue(element.options[index]) : null;
+  },
+
+  selectMany: function(element) {
+    var values, length = element.length;
+    if (!length) return null;
+
+    for (var i = 0, values = []; i < length; i++) {
+      var opt = element.options[i];
+      if (opt.selected) values.push(this.optionValue(opt));
+    }
+    return values;
+  },
+
+  optionValue: function(opt) {
+    // extend element because hasAttribute may not be native
+    return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
+  initialize: function($super, element, frequency, callback) {
+    $super(callback, frequency);
+    this.element   = $(element);
+    this.lastValue = this.getValue();
+  },
+
+  execute: function() {
+    var value = this.getValue();
+    if (Object.isString(this.lastValue) && Object.isString(value) ?
+        this.lastValue != value : String(this.lastValue) != String(value)) {
+      this.callback(this.element, value);
+      this.lastValue = value;
+    }
+  }
+});
+
+Form.Element.Observer = Class.create(Abstract.TimedObserver, {
+  getValue: function() {
+    return Form.Element.getValue(this.element);
+  }
+});
+
+Form.Observer = Class.create(Abstract.TimedObserver, {
+  getValue: function() {
+    return Form.serialize(this.element);
+  }
+});
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.EventObserver = Class.create({
+  initialize: function(element, callback) {
+    this.element  = $(element);
+    this.callback = callback;
+
+    this.lastValue = this.getValue();
+    if (this.element.tagName.toLowerCase() == 'form')
+      this.registerFormCallbacks();
+    else
+      this.registerCallback(this.element);
+  },
+
+  onElementEvent: function() {
+    var value = this.getValue();
+    if (this.lastValue != value) {
+      this.callback(this.element, value);
+      this.lastValue = value;
+    }
+  },
+
+  registerFormCallbacks: function() {
+    Form.getElements(this.element).each(this.registerCallback, this);
+  },
+
+  registerCallback: function(element) {
+    if (element.type) {
+      switch (element.type.toLowerCase()) {
+        case 'checkbox':
+        case 'radio':
+          Event.observe(element, 'click', this.onElementEvent.bind(this));
+          break;
+        default:
+          Event.observe(element, 'change', this.onElementEvent.bind(this));
+          break;
+      }
+    }
+  }
+});
+
+Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
+  getValue: function() {
+    return Form.Element.getValue(this.element);
+  }
+});
+
+Form.EventObserver = Class.create(Abstract.EventObserver, {
+  getValue: function() {
+    return Form.serialize(this.element);
+  }
+});
+if (!window.Event) var Event = { };
+
+Object.extend(Event, {
+  KEY_BACKSPACE: 8,
+  KEY_TAB:       9,
+  KEY_RETURN:   13,
+  KEY_ESC:      27,
+  KEY_LEFT:     37,
+  KEY_UP:       38,
+  KEY_RIGHT:    39,
+  KEY_DOWN:     40,
+  KEY_DELETE:   46,
+  KEY_HOME:     36,
+  KEY_END:      35,
+  KEY_PAGEUP:   33,
+  KEY_PAGEDOWN: 34,
+  KEY_INSERT:   45,
+
+  cache: { },
+
+  relatedTarget: function(event) {
+    var element;
+    switch(event.type) {
+      case 'mouseover': element = event.fromElement; break;
+      case 'mouseout':  element = event.toElement;   break;
+      default: return null;
+    }
+    return Element.extend(element);
+  }
+});
+
+Event.Methods = (function() {
+  var isButton;
+
+  if (Prototype.Browser.IE) {
+    var buttonMap = { 0: 1, 1: 4, 2: 2 };
+    isButton = function(event, code) {
+      return event.button == buttonMap[code];
+    };
+
+  } else if (Prototype.Browser.WebKit) {
+    isButton = function(event, code) {
+      switch (code) {
+        case 0: return event.which == 1 && !event.metaKey;
+        case 1: return event.which == 1 && event.metaKey;
+        default: return false;
+      }
+    };
+
+  } else {
+    isButton = function(event, code) {
+      return event.which ? (event.which === code + 1) : (event.button === code);
+    };
+  }
+
+  return {
+    isLeftClick:   function(event) { return isButton(event, 0) },
+    isMiddleClick: function(event) { return isButton(event, 1) },
+    isRightClick:  function(event) { return isButton(event, 2) },
+
+    element: function(event) {
+      var node = Event.extend(event).target;
+      return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node);
+    },
+
+    findElement: function(event, expression) {
+      var element = Event.element(event);
+      if (!expression) return element;
+      var elements = [element].concat(element.ancestors());
+      return Selector.findElement(elements, expression, 0);
+    },
+
+    pointer: function(event) {
+      return {
+        x: event.pageX || (event.clientX +
+          (document.documentElement.scrollLeft || document.body.scrollLeft)),
+        y: event.pageY || (event.clientY +
+          (document.documentElement.scrollTop || document.body.scrollTop))
+      };
+    },
+
+    pointerX: function(event) { return Event.pointer(event).x },
+    pointerY: function(event) { return Event.pointer(event).y },
+
+    stop: function(event) {
+      Event.extend(event);
+      event.preventDefault();
+      event.stopPropagation();
+      event.stopped = true;
+    }
+  };
+})();
+
+Event.extend = (function() {
+  var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
+    m[name] = Event.Methods[name].methodize();
+    return m;
+  });
+
+  if (Prototype.Browser.IE) {
+    Object.extend(methods, {
+      stopPropagation: function() { this.cancelBubble = true },
+      preventDefault:  function() { this.returnValue = false },
+      inspect: function() { return "[object Event]" }
+    });
+
+    return function(event) {
+      if (!event) return false;
+      if (event._extendedByPrototype) return event;
+
+      event._extendedByPrototype = Prototype.emptyFunction;
+      var pointer = Event.pointer(event);
+      Object.extend(event, {
+        target: event.srcElement,
+        relatedTarget: Event.relatedTarget(event),
+        pageX:  pointer.x,
+        pageY:  pointer.y
+      });
+      return Object.extend(event, methods);
+    };
+
+  } else {
+    Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__;
+    Object.extend(Event.prototype, methods);
+    return Prototype.K;
+  }
+})();
+
+Object.extend(Event, (function() {
+  var cache = Event.cache;
+
+  function getEventID(element) {
+    if (element._prototypeEventID) return element._prototypeEventID[0];
+    arguments.callee.id = arguments.callee.id || 1;
+    return element._prototypeEventID = [++arguments.callee.id];
+  }
+
+  function getDOMEventName(eventName) {
+    if (eventName && eventName.include(':')) return "dataavailable";
+    return eventName;
+  }
+
+  function getCacheForID(id) {
+    return cache[id] = cache[id] || { };
+  }
+
+  function getWrappersForEventName(id, eventName) {
+    var c = getCacheForID(id);
+    return c[eventName] = c[eventName] || [];
+  }
+
+  function createWrapper(element, eventName, handler) {
+    var id = getEventID(element);
+    var c = getWrappersForEventName(id, eventName);
+    if (c.pluck("handler").include(handler)) return false;
+
+    var wrapper = function(event) {
+      if (!Event || !Event.extend ||
+        (event.eventName && event.eventName != eventName))
+          return false;
+
+      Event.extend(event);
+      handler.call(element, event);
+    };
+
+    wrapper.handler = handler;
+    c.push(wrapper);
+    return wrapper;
+  }
+
+  function findWrapper(id, eventName, handler) {
+    var c = getWrappersForEventName(id, eventName);
+    return c.find(function(wrapper) { return wrapper.handler == handler });
+  }
+
+  function destroyWrapper(id, eventName, handler) {
+    var c = getCacheForID(id);
+    if (!c[eventName]) return false;
+    c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));
+  }
+
+  function destroyCache() {
+    for (var id in cache)
+      for (var eventName in cache[id])
+        cache[id][eventName] = null;
+  }
+
+  if (window.attachEvent) {
+    window.attachEvent("onunload", destroyCache);
+  }
+
+  return {
+    observe: function(element, eventName, handler) {
+      element = $(element);
+      var name = getDOMEventName(eventName);
+
+      var wrapper = createWrapper(element, eventName, handler);
+      if (!wrapper) return element;
+
+      if (element.addEventListener) {
+        element.addEventListener(name, wrapper, false);
+      } else {
+        element.attachEvent("on" + name, wrapper);
+      }
+
+      return element;
+    },
+
+    stopObserving: function(element, eventName, handler) {
+      element = $(element);
+      var id = getEventID(element), name = getDOMEventName(eventName);
+
+      if (!handler && eventName) {
+        getWrappersForEventName(id, eventName).each(function(wrapper) {
+          element.stopObserving(eventName, wrapper.handler);
+        });
+        return element;
+
+      } else if (!eventName) {
+        Object.keys(getCacheForID(id)).each(function(eventName) {
+          element.stopObserving(eventName);
+        });
+        return element;
+      }
+
+      var wrapper = findWrapper(id, eventName, handler);
+      if (!wrapper) return element;
+
+      if (element.removeEventListener) {
+        element.removeEventListener(name, wrapper, false);
+      } else {
+        element.detachEvent("on" + name, wrapper);
+      }
+
+      destroyWrapper(id, eventName, handler);
+
+      return element;
+    },
+
+    fire: function(element, eventName, memo) {
+      element = $(element);
+      if (element == document && document.createEvent && !element.dispatchEvent)
+        element = document.documentElement;
+
+      var event;
+      if (document.createEvent) {
+        event = document.createEvent("HTMLEvents");
+        event.initEvent("dataavailable", true, true);
+      } else {
+        event = document.createEventObject();
+        event.eventType = "ondataavailable";
+      }
+
+      event.eventName = eventName;
+      event.memo = memo || { };
+
+      if (document.createEvent) {
+        element.dispatchEvent(event);
+      } else {
+        element.fireEvent(event.eventType, event);
+      }
+
+      return Event.extend(event);
+    }
+  };
+})());
+
+Object.extend(Event, Event.Methods);
+
+Element.addMethods({
+  fire:          Event.fire,
+  observe:       Event.observe,
+  stopObserving: Event.stopObserving
+});
+
+Object.extend(document, {
+  fire:          Element.Methods.fire.methodize(),
+  observe:       Element.Methods.observe.methodize(),
+  stopObserving: Element.Methods.stopObserving.methodize(),
+  loaded:        false
+});
+
+(function() {
+  /* Support for the DOMContentLoaded event is based on work by Dan Webb,
+     Matthias Miller, Dean Edwards and John Resig. */
+
+  var timer;
+
+  function fireContentLoadedEvent() {
+    if (document.loaded) return;
+    if (timer) window.clearInterval(timer);
+    document.fire("dom:loaded");
+    document.loaded = true;
+  }
+
+  if (document.addEventListener) {
+    if (Prototype.Browser.WebKit) {
+      timer = window.setInterval(function() {
+        if (/loaded|complete/.test(document.readyState))
+          fireContentLoadedEvent();
+      }, 0);
+
+      Event.observe(window, "load", fireContentLoadedEvent);
+
+    } else {
+      document.addEventListener("DOMContentLoaded",
+        fireContentLoadedEvent, false);
+    }
+
+  } else {
+    document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");
+    $("__onDOMContentLoaded").onreadystatechange = function() {
+      if (this.readyState == "complete") {
+        this.onreadystatechange = null;
+        fireContentLoadedEvent();
+      }
+    };
+  }
+})();
+/*------------------------------- DEPRECATED -------------------------------*/
+
+Hash.toQueryString = Object.toQueryString;
+
+var Toggle = { display: Element.toggle };
+
+Element.Methods.childOf = Element.Methods.descendantOf;
+
+var Insertion = {
+  Before: function(element, content) {
+    return Element.insert(element, {before:content});
+  },
+
+  Top: function(element, content) {
+    return Element.insert(element, {top:content});
+  },
+
+  Bottom: function(element, content) {
+    return Element.insert(element, {bottom:content});
+  },
+
+  After: function(element, content) {
+    return Element.insert(element, {after:content});
+  }
+};
+
+var $continue = new Error('"throw $continue" is deprecated, use "return" instead');
+
+// This should be moved to script.aculo.us; notice the deprecated methods
+// further below, that map to the newer Element methods.
+var Position = {
+  // set to true if needed, warning: firefox performance problems
+  // NOT neeeded for page scrolling, only if draggable contained in
+  // scrollable elements
+  includeScrollOffsets: false,
+
+  // must be called before calling withinIncludingScrolloffset, every time the
+  // page is scrolled
+  prepare: function() {
+    this.deltaX =  window.pageXOffset
+                || document.documentElement.scrollLeft
+                || document.body.scrollLeft
+                || 0;
+    this.deltaY =  window.pageYOffset
+                || document.documentElement.scrollTop
+                || document.body.scrollTop
+                || 0;
+  },
+
+  // caches x/y coordinate pair to use with overlap
+  within: function(element, x, y) {
+    if (this.includeScrollOffsets)
+      return this.withinIncludingScrolloffsets(element, x, y);
+    this.xcomp = x;
+    this.ycomp = y;
+    this.offset = Element.cumulativeOffset(element);
+
+    return (y >= this.offset[1] &&
+            y <  this.offset[1] + element.offsetHeight &&
+            x >= this.offset[0] &&
+            x <  this.offset[0] + element.offsetWidth);
+  },
+
+  withinIncludingScrolloffsets: function(element, x, y) {
+    var offsetcache = Element.cumulativeScrollOffset(element);
+
+    this.xcomp = x + offsetcache[0] - this.deltaX;
+    this.ycomp = y + offsetcache[1] - this.deltaY;
+    this.offset = Element.cumulativeOffset(element);
+
+    return (this.ycomp >= this.offset[1] &&
+            this.ycomp <  this.offset[1] + element.offsetHeight &&
+            this.xcomp >= this.offset[0] &&
+            this.xcomp <  this.offset[0] + element.offsetWidth);
+  },
+
+  // within must be called directly before
+  overlap: function(mode, element) {
+    if (!mode) return 0;
+    if (mode == 'vertical')
+      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
+        element.offsetHeight;
+    if (mode == 'horizontal')
+      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
+        element.offsetWidth;
+  },
+
+  // Deprecation layer -- use newer Element methods now (1.5.2).
+
+  cumulativeOffset: Element.Methods.cumulativeOffset,
+
+  positionedOffset: Element.Methods.positionedOffset,
+
+  absolutize: function(element) {
+    Position.prepare();
+    return Element.absolutize(element);
+  },
+
+  relativize: function(element) {
+    Position.prepare();
+    return Element.relativize(element);
+  },
+
+  realOffset: Element.Methods.cumulativeScrollOffset,
+
+  offsetParent: Element.Methods.getOffsetParent,
+
+  page: Element.Methods.viewportOffset,
+
+  clone: function(source, target, options) {
+    options = options || { };
+    return Element.clonePosition(target, source, options);
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
+  function iter(name) {
+    return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
+  }
+
+  instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
+  function(element, className) {
+    className = className.toString().strip();
+    var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
+    return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
+  } : function(element, className) {
+    className = className.toString().strip();
+    var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
+    if (!classNames && !className) return elements;
+
+    var nodes = $(element).getElementsByTagName('*');
+    className = ' ' + className + ' ';
+
+    for (var i = 0, child, cn; child = nodes[i]; i++) {
+      if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||
+          (classNames && classNames.all(function(name) {
+            return !name.toString().blank() && cn.include(' ' + name + ' ');
+          }))))
+        elements.push(Element.extend(child));
+    }
+    return elements;
+  };
+
+  return function(className, parentElement) {
+    return $(parentElement || document.body).getElementsByClassName(className);
+  };
+}(Element.Methods);
+
+/*--------------------------------------------------------------------------*/
+
+Element.ClassNames = Class.create();
+Element.ClassNames.prototype = {
+  initialize: function(element) {
+    this.element = $(element);
+  },
+
+  _each: function(iterator) {
+    this.element.className.split(/\s+/).select(function(name) {
+      return name.length > 0;
+    })._each(iterator);
+  },
+
+  set: function(className) {
+    this.element.className = className;
+  },
+
+  add: function(classNameToAdd) {
+    if (this.include(classNameToAdd)) return;
+    this.set($A(this).concat(classNameToAdd).join(' '));
+  },
+
+  remove: function(classNameToRemove) {
+    if (!this.include(classNameToRemove)) return;
+    this.set($A(this).without(classNameToRemove).join(' '));
+  },
+
+  toString: function() {
+    return $A(this).join(' ');
+  }
+};
+
+Object.extend(Element.ClassNames.prototype, Enumerable);
+
+/*--------------------------------------------------------------------------*/
+
+Element.addMethods();

+ 58 - 0
web/root/js/scriptaculous.js

@@ -0,0 +1,58 @@
+// script.aculo.us scriptaculous.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
+
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// 
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+var Scriptaculous = {
+  Version: '1.8.1',
+  require: function(libraryName) {
+    // inserting via DOM fails in Safari 2.0, so brute force approach
+    document.write('<script type="text/javascript" src="'+libraryName+'"><\/script>');
+  },
+  REQUIRED_PROTOTYPE: '1.6.0',
+  load: function() {
+    function convertVersionString(versionString){
+      var r = versionString.split('.');
+      return parseInt(r[0])*100000 + parseInt(r[1])*1000 + parseInt(r[2]);
+    }
+ 
+    if((typeof Prototype=='undefined') || 
+       (typeof Element == 'undefined') || 
+       (typeof Element.Methods=='undefined') ||
+       (convertVersionString(Prototype.Version) < 
+        convertVersionString(Scriptaculous.REQUIRED_PROTOTYPE)))
+       throw("script.aculo.us requires the Prototype JavaScript framework >= " +
+        Scriptaculous.REQUIRED_PROTOTYPE);
+    
+    $A(document.getElementsByTagName("script")).findAll( function(s) {
+      return (s.src && s.src.match(/scriptaculous\.js(\?.*)?$/))
+    }).each( function(s) {
+      var path = s.src.replace(/scriptaculous\.js(\?.*)?$/,'');
+      var includes = s.src.match(/\?.*load=([a-z,]*)/);
+      (includes ? includes[1] : 'builder,effects,dragdrop,controls,slider,sound').split(',').each(
+       function(include) { Scriptaculous.require(path+include+'.js') });
+    });
+  }
+}
+
+Scriptaculous.load();

+ 0 - 0
web/root/main_contact.html


+ 68 - 0
web/root/main_doc.html

@@ -0,0 +1,68 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8" />
+<title>Open Asset Import Library : Documentation & FAQ</title>
+<meta name="keywords" content="ASSIMP Open Asset Import Library free Open Source 3d Model Import Library C C++ Java MDL X OBJ MD2 MD3 PLY " />
+<meta name="description" content="Open Asset Import Library" />
+<link href="css/style.css" rel="stylesheet" type="text/css" media="screen"/>
+<script src="js/prototype.js" type="text/javascript"></script>
+	<script src="js/scriptaculous.js?load=effects,builder" type="text/javascript"></script>
+	<script src="js/lightbox.js" type="text/javascript"></script>
+
+
+</head>
+<body>
+<div id="header">
+	
+	
+</div>
+<!-- end #header -->
+<div id="menu">
+	<ul>
+		<li class="first"><a href="index.html" accesskey="1" title="">About</a></li>
+		<li><a href="main_features.html" accesskey="2" title="">Features</a></li>
+		<li><a href="main_viewer.html" accesskey="7" title="">Viewer</a></li>
+		<li><a href="main_doc.html" accesskey="3" title="">Documentation</a></li>
+		<li><a href="main_downloads.html" accesskey="4" title="">Downloads</a></li>
+		<li><a href="main_license.html" accesskey="8" title="">License</a></li>
+		<li><a href="main_contact.html" accesskey="5" title="">Contact</a></li>
+	</ul>
+</div>
+<!-- end #menu -->
+<div id="content">
+	<div id="posts_fl">
+		<div class="post_fl">
+
+			<h2 class="title">General</h2>
+			<p><a href="FAQ.html" target="_blank">Frequently asked questions</a> (FAQ)</p>
+
+			<h2 class="title">C/C++</h2>
+			<div class="story">
+				<p><a href="lib_html/index.html">Assimp Documentation and C/C++ Reference</a> generated with the help of <a href="http://www.doxygen.org/">Doxygen</a>. <br></p>
+				<p><a href="http://assimp.svn.sourceforge.net/viewvc/assimp/trunk/tools/assimp_view/">AssimpView Source</a> (Sample implementation)<br></p>
+
+			<h2 class="title">Java</h2>
+				<p><a href="javadoc/index.html">jAssimp JavaDoc</a> (under development, subject to future changes)<br></p>
+			<h2 class="title">C#</h2>
+
+				<p>Under development</p>
+			<h2 class="title">AssimpView</h2>
+				<p><a href="avdoc/Reference.pdf">AssimpView Reference Manual</a> (also available via the <tt>?|Help</tt> menu item)<br></p>
+
+<hr>
+<br><br><br><br><br><br><br>
+			<p><a href="http://sourceforge.net"><img src="http://sflogo.sourceforge.net/sflogo.php?group_id=226462&amp;type=2" width="125" height="37" border="0" alt="SourceForge.net Logo" 
+/></a></p>
+			</div>
+		</div>
+	</div>
+	<div style="clear: both;">&nbsp;</div>
+</div>
+<!-- end #content -->
+<div id="footer">
+	<p id="legal">Copyright &copy; 2008 ASSIMP Team. Design template by <a href="http://www.freecsstemplates.org/">Free CSS Templates</a></p>
+</div>
+<!-- end #footer -->
+</body>
+</html>

+ 70 - 0
web/root/main_downloads.html

@@ -0,0 +1,70 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8" />
+<title>Open Asset Import Library : Downloads</title>
+<meta name="keywords" content="ASSIMP Open Asset Import Library free Open Source 3d Model Import Library C C++ Java MDL X OBJ MD2 MD3 PLY " />
+<meta name="description" content="Open Asset Import Library" />
+<link href="css/style.css" rel="stylesheet" type="text/css" media="screen"/>
+<script src="js/prototype.js" type="text/javascript"></script>
+	<script src="js/scriptaculous.js?load=effects,builder" type="text/javascript"></script>
+	<script src="js/lightbox.js" type="text/javascript"></script>
+
+</head>
+<body>
+<div id="header">
+	
+	
+</div>
+<!-- end #header -->
+<div id="menu">
+	<ul>
+		<li class="first"><a href="index.html" accesskey="1" title="">About</a></li>
+		<li><a href="main_features.html" accesskey="2" title="">Features</a></li>
+		<li><a href="main_viewer.html" accesskey="7" title="">Viewer</a></li>
+		<li><a href="main_doc.html" accesskey="3" title="">Documentation</a></li>
+		<li><a href="main_downloads.html" accesskey="4" title="">Downloads</a></li>
+		<li><a href="main_license.html" accesskey="8" title="">License</a></li>
+		<li><a href="main_contact.html" accesskey="5" title="">Contact</a></li>
+	</ul>
+</div>
+<!-- end #menu -->
+<div id="content">
+	<div id="posts_fl">
+		<div class="post_fl">
+			<h2 class="title">Subversion</h2>
+			<div class="story">
+				<p>Developers can directly obtain ASSIMP's source code via SVN. An anonymous checkout can be get using:<br>
+				<tt>svn co https://assimp.svn.sourceforge.net/svnroot/assimp/trunk assimp </tt><br><br>
+				To view the source code in your browser, <a href="http://assimp.svn.sourceforge.net/viewvc/assimp/trunk/">click here</a>. <small>Just remember: don't read source code :-) </small>
+
+				</p>
+				
+
+			<h2 class="title">Download</img></h2>
+				<p>
+				We provide both source code and precompiled binary packages for most platforms. Don't forget that you'll need <a href=http://www.boost.org/>boost</a> to compile ASSIMP from scratch.
+<br>
+ <a href="https://sourceforge.net/project/showfiles.php?group_id=226462"><strong>Download page</strong></a><br>
+<br>
+<img src="images/boost.png" alt="Boost logo"></img>
+</p>
+			
+
+<hr>
+<br><br><br><br><br><br><br>
+			<p><a href="http://sourceforge.net">
+<img src="http://sflogo.sourceforge.net/sflogo.php?group_id=226462&amp;type=2" width="125" height="37" border="0" alt="SourceForge.net Logo" 
+/></a></p>
+			</div>
+		</div>
+	</div>
+	<div style="clear: both;">&nbsp;</div>
+</div>
+<!-- end #content -->
+<div id="footer">
+	<p id="legal">Copyright &copy; 2008 ASSIMP Team. Design template by <a href="http://www.freecsstemplates.org/">Free CSS Templates</a></p>
+</div>
+<!-- end #footer -->
+</body>
+</html>

+ 0 - 0
web/root/main_features.html


+ 71 - 0
web/root/main_license.html

@@ -0,0 +1,71 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8" />
+<title>Open Asset Import Library : License</title>
+<meta name="keywords" content="ASSIMP Open Asset Import Library free Open Source 3d Model Import Library C C++ Java MDL X OBJ MD2 MD3 PLY " />
+<meta name="description" content="Open Asset Import Library" />
+<link href="css/style.css" rel="stylesheet" type="text/css" media="screen"/>
+<script src="js/prototype.js" type="text/javascript"></script>
+	<script src="js/scriptaculous.js?load=effects,builder" type="text/javascript"></script>
+	<script src="js/lightbox.js" type="text/javascript"></script>
+
+</head>
+<body>
+<div id="header">
+	
+	
+</div>
+<!-- end #header -->
+<div id="menu">
+	<ul>
+		<li class="first"><a href="index.html" accesskey="1" title="">About</a></li>
+		<li><a href="main_features.html" accesskey="2" title="">Features</a></li>
+		<li><a href="main_viewer.html" accesskey="7" title="">Viewer</a></li>
+		<li><a href="main_doc.html" accesskey="3" title="">Documentation</a></li>
+		<li><a href="main_downloads.html" accesskey="4" title="">Downloads</a></li>
+		<li><a href="main_license.html" accesskey="8" title="">License</a></li>
+		<li><a href="main_contact.html" accesskey="5" title="">Contact</a></li>
+	</ul>
+</div>
+<!-- end #menu -->
+<div id="content">
+	<div id="posts_fl">
+		<div class="post_fl">
+			<h2 class="title">License</h2>
+			<div class="story">
+				<p>ASSIMP is released as Open Source under the terms of a modified 3-clause BSD license.</p><hr>
+
+
+				<p><tt>
+Copyright (c) 2008 ASSIMP Development Team
+All rights reserved.<br>
+<br>
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:<br><br>
+<ul>
+<li>Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.</li> <br>
+<li>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.</li> <br>
+<li>Neither the name of the ASSIMP Development Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. </li><br>
+</ul>
+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.
+
+
+</tt></p><hr>
+				
+
+
+			<p><a href="http://sourceforge.net"><img src="http://sflogo.sourceforge.net/sflogo.php?group_id=226462&amp;type=2" width="125" height="37" border="0" alt="SourceForge.net Logo" 
+/></a></p>
+
+			</div>
+		</div>
+	</div>
+	<div style="clear: both;">&nbsp;</div>
+</div>
+<!-- end #content -->
+<div id="footer">
+	<p id="legal">Copyright &copy; 2008 ASSIMP Team. Design template by <a href="http://www.freecsstemplates.org/">Free CSS Templates</a></p>
+</div>
+<!-- end #footer -->
+</body>
+</html>

+ 0 - 0
web/root/main_viewer.html


BIN
web/root/screenshots/sshot1.png


BIN
web/root/screenshots/sshot1s.png


BIN
web/root/screenshots/sshot2.png


BIN
web/root/screenshots/sshot2s.png


BIN
web/root/screenshots/sshot3.png


BIN
web/root/screenshots/sshot3s.png


BIN
web/root/screenshots/sshot4.png


BIN
web/root/screenshots/sshot4s.png


+ 8 - 0
workspaces/vc8/assimp.vcproj

@@ -1042,6 +1042,10 @@
 				RelativePath="..\..\code\qnan.h"
 				RelativePath="..\..\code\qnan.h"
 				>
 				>
 			</File>
 			</File>
+			<File
+				RelativePath="..\..\code\RemoveComments.h"
+				>
+			</File>
 			<File
 			<File
 				RelativePath="..\..\code\SpatialSort.h"
 				RelativePath="..\..\code\SpatialSort.h"
 				>
 				>
@@ -1358,6 +1362,10 @@
 				RelativePath="..\..\code\PretransformVertices.cpp"
 				RelativePath="..\..\code\PretransformVertices.cpp"
 				>
 				>
 			</File>
 			</File>
+			<File
+				RelativePath="..\..\code\RemoveComments.cpp"
+				>
+			</File>
 			<File
 			<File
 				RelativePath="..\..\code\SpatialSort.cpp"
 				RelativePath="..\..\code\SpatialSort.cpp"
 				>
 				>