ソースを参照

ASE: Added WIP support for *SMOOTHSKINMESH elements in ASE/ASC files. Fixes in the ASE loader. Fixed animation parsing. Temporary implementation of target lights and cameras, including animations.
3DS: Fixed transformation problems (Pivot points), added WIP animation support. No target animation yet (cameras, spot lights). Not yet fully tested, but static models that worked before should still work now, except all look correct now :-) (some problems with very large models remaining)
Further work on the IRR and IRRMESH loaders. IRR still WIP, IRRMESH more stable now.
Work on the LWo loader. Added support for the "one-layer-only" mode. Hierarchy bug still unfixed, UV coords bug still unfixed.
Further work on the FindInvalidDataprocess. Improved validation for normals, no false positives anymore.
Further work on the MDR loader, still WIP.
Moved DeterminePType-Step to ScenePreprocessor.
aiAnimation::mDuration is optional now, ScenePreprocessor computes it automatically if set to -1.
Fixes in the SMD loader. Still crashes on some files.
Updated animation documentation.

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

aramis_acg 17 年 前
コミット
f7aa836330
50 ファイル変更4046 行追加539 行削除
  1. 128 62
      code/3DSConverter.cpp
  2. 36 6
      code/3DSHelper.h
  3. 115 72
      code/3DSLoader.cpp
  4. 125 60
      code/ASELoader.cpp
  5. 322 145
      code/ASEParser.cpp
  6. 33 3
      code/ASEParser.h
  7. 2 0
      code/AssimpPCH.h
  8. 3 0
      code/BaseImporter.h
  9. 12 7
      code/FindInvalidDataProcess.cpp
  10. 583 27
      code/IRRLoader.cpp
  11. 115 20
      code/IRRLoader.h
  12. 1 8
      code/IRRMeshLoader.cpp
  13. 7 3
      code/LWOFileData.h
  14. 69 25
      code/LWOLoader.cpp
  15. 1 0
      code/LWOLoader.h
  16. 16 13
      code/MDRFileData.h
  17. 107 31
      code/MDRLoader.cpp
  18. 11 0
      code/MDRLoader.h
  19. 2 0
      code/ObjTools.h
  20. 38 25
      code/SMDLoader.cpp
  21. 36 0
      code/ScenePreprocessor.cpp
  22. 12 0
      code/SplitLargeMeshes.cpp
  23. 205 0
      code/TargetAnimation.cpp
  24. 173 0
      code/TargetAnimation.h
  25. 39 9
      code/ValidateDataStructure.cpp
  26. 2 1
      code/makefile
  27. 2 1
      code/makefile.mingw
  28. 55 11
      include/aiAnim.h
  29. 21 2
      include/aiConfig.h
  30. 7 1
      include/aiLight.h
  31. 28 4
      include/aiMaterial.h
  32. 7 1
      include/aiMesh.h
  33. 30 0
      include/aiQuaternion.h
  34. BIN
      test/3DSFiles/CameraRollAnim.3ds
  35. BIN
      test/3DSFiles/CameraRollAnimWithChildObject.3ds
  36. BIN
      test/3DSFiles/RotatingCube.3DS
  37. BIN
      test/3DSFiles/TargetCameraAnim.3ds
  38. 316 0
      test/ASEFiles/CameraRollAnim.ase
  39. 655 0
      test/ASEFiles/CameraRollAnimWithChildObject.ase
  40. 333 0
      test/ASEFiles/RotatingCube.ASE
  41. 385 0
      test/ASEFiles/TargetCameraAnim.ase
  42. 5 1
      test/ASEFiles/credits.txt
  43. BIN
      test/IRR/multipleAnimators.irr
  44. BIN
      test/IRR/skybox.xml
  45. BIN
      test/SourceFiles/CameraRollAnim.max
  46. BIN
      test/SourceFiles/CameraRollAnimWithChildObject.max
  47. BIN
      test/SourceFiles/RotatingCube.max
  48. BIN
      test/SourceFiles/TargetCameraAnim.max
  49. 1 1
      tools/assimp_view/LogDisplay.cpp
  50. 8 0
      workspaces/vc8/assimp.vcproj

+ 128 - 62
code/3DSConverter.cpp

@@ -47,6 +47,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 // internal headers
 #include "3DSLoader.h"
 #include "TextureTransform.h"
+#include "TargetAnimation.h"
 
 using namespace Assimp;
 
@@ -528,7 +529,7 @@ void Discreet3DSImporter::AddNodeToGraph(aiScene* pcSOut,aiNode* pcOut,D3DS::Nod
 	aiMatrix4x4 abs;
 	if (pcIn->mName == "$$$DUMMY")
 	{
-		// append the "real" name of the dummy to the string
+		// Append the "real" name of the dummy to the string
 		pcIn->mName.append(pcIn->mDummyName);
 	}
 	else // if (pcIn->mName != "$$$DUMMY")
@@ -538,25 +539,18 @@ void Discreet3DSImporter::AddNodeToGraph(aiScene* pcSOut,aiNode* pcOut,D3DS::Nod
 			const D3DS::Mesh* pcMesh = (const D3DS::Mesh*)pcSOut->mMeshes[a]->mColors[0];
 			ai_assert(NULL != pcMesh);
 
-			// do case independent comparisons here, just for safety
-			if (!ASSIMP_stricmp(pcIn->mName,pcMesh->mName))
+			if (pcIn->mName == pcMesh->mName)
 				iArray.push_back(a);
 		}
 		if (!iArray.empty())
 		{
-			// The matrix should be identical for all meshes.
+			// The matrix should be identical for all meshes with the same name.
 			// It HAS to be identical for all meshes ........
 			aiMatrix4x4& mTrafo = ((D3DS::Mesh*)pcSOut->mMeshes[iArray[0]]->mColors[0])->mMat;
 			aiMatrix4x4 mInv = mTrafo;
 			if (!configSkipPivot)
 				mInv.Inverse();
 
-		/*	abs = mTrafo;
-			pcOut->mTransformation = absTrafo;
-			pcOut->mTransformation = pcOut->mTransformation.Inverse() * mTrafo;
-			const aiVector3D& pivot = pcIn->vPivot;
-			aiMatrix4x4 trans;
-		*/
 			const aiVector3D& pivot = pcIn->vPivot;
 
 			pcOut->mNumMeshes = (unsigned int)iArray.size();
@@ -566,7 +560,8 @@ void Discreet3DSImporter::AddNodeToGraph(aiScene* pcSOut,aiNode* pcOut,D3DS::Nod
 				const unsigned int iIndex = iArray[i];
 				aiMesh* const mesh = pcSOut->mMeshes[iIndex];
 
-				// http://www.zfx.info/DisplayThread.php?MID=235690#235690
+				// Pivot point adjustment.
+				// See: http://www.zfx.info/DisplayThread.php?MID=235690#235690
 				const aiVector3D* const pvEnd = mesh->mVertices+mesh->mNumVertices;
 				aiVector3D* pvCurrent = mesh->mVertices;
 
@@ -578,11 +573,9 @@ void Discreet3DSImporter::AddNodeToGraph(aiScene* pcSOut,aiNode* pcOut,D3DS::Nod
 						pvCurrent->x -= pivot.x;
 						pvCurrent->y -= pivot.y;
 						pvCurrent->z -= pivot.z;
-						*pvCurrent = mTrafo * (*pvCurrent);
 						++pvCurrent;
 					}
 				}
-#if 0
 				else
 				{
 					while (pvCurrent != pvEnd)
@@ -591,7 +584,6 @@ void Discreet3DSImporter::AddNodeToGraph(aiScene* pcSOut,aiNode* pcOut,D3DS::Nod
 						++pvCurrent;
 					}
 				}
-#endif
 
 				// Setup the mesh index
 				pcOut->mMeshes[i] = iIndex;
@@ -599,75 +591,143 @@ void Discreet3DSImporter::AddNodeToGraph(aiScene* pcSOut,aiNode* pcOut,D3DS::Nod
 		}
 	}
 
+	// Now build the transformation matrix of the node
+	// ROTATION
+	if (pcIn->aRotationKeys.size())
+	{
+		pcOut->mTransformation = aiMatrix4x4( pcIn->aRotationKeys[0].mValue.GetMatrix() );
+	}
+	else if (pcIn->aCameraRollKeys.size())
+	{
+		aiMatrix4x4::RotationZ(AI_DEG_TO_RAD(- pcIn->aCameraRollKeys[0].mValue),
+			pcOut->mTransformation);
+	}
+
+	// SCALING
+	aiMatrix4x4& m = pcOut->mTransformation;
+	if (pcIn->aScalingKeys.size())
+	{
+		const aiVector3D& v = pcIn->aScalingKeys[0].mValue;
+		m.a1 *= v.x; m.b1 *= v.x; m.c1 *= v.x;
+		m.a2 *= v.y; m.b2 *= v.y; m.c2 *= v.y;
+		m.a3 *= v.z; m.b3 *= v.z; m.c3 *= v.z;
+	}
+
+	// TRANSLATION
+	if (pcIn->aPositionKeys.size())
+	{
+		const aiVector3D& v = pcIn->aPositionKeys[0].mValue;
+		m.a4 += v.x;
+		m.b4 += v.y;
+		m.c4 += v.z;
+	}
+
 	// Generate animation channels for the node
-	if (pcIn->aPositionKeys.size()  > 0  || pcIn->aRotationKeys.size()   > 0   ||
-		pcIn->aScalingKeys.size()   > 0  || pcIn->aCameraRollKeys.size() > 0 ||
-		pcIn->aTargetPositionKeys.size() > 0)
+	if (pcIn->aPositionKeys.size()  > 1  || pcIn->aRotationKeys.size()   > 1   ||
+		pcIn->aScalingKeys.size()   > 1  || pcIn->aCameraRollKeys.size() > 1 ||
+		pcIn->aTargetPositionKeys.size() > 1)
 	{
 		aiAnimation* anim = pcSOut->mAnimations[0];
 		ai_assert(NULL != anim);
 
-		// Allocate a new channel, increment the channel index
-		aiNodeAnim* channel = anim->mChannels[anim->mNumChannels++] = new aiNodeAnim();
-
-		// POSITION keys
-		if (pcIn->aPositionKeys.size()  > 0)
+		if (pcIn->aCameraRollKeys.size() > 1)
 		{
-			// Sort all keys with ascending time values
-			std::sort(pcIn->aPositionKeys.begin(),pcIn->aPositionKeys.end());
+			DefaultLogger::get()->debug("3DS: Converting camera roll track ...");
 
-			channel->mNumPositionKeys = (unsigned int)pcIn->aPositionKeys.size();
-			channel->mPositionKeys = new aiVectorKey[channel->mNumPositionKeys];
-			::memcpy(channel->mPositionKeys,&pcIn->aPositionKeys[0],
-				sizeof(aiVectorKey)*channel->mNumPositionKeys);
+			// Camera roll keys - in fact they're just rotations
+			// around the camera's z axis. The angles are given
+			// in degrees (and they're clockwise).
+			pcIn->aRotationKeys.resize(pcIn->aCameraRollKeys.size());
+			for (unsigned int i = 0; i < pcIn->aCameraRollKeys.size();++i)
+			{
+				aiQuatKey&  q = pcIn->aRotationKeys[i];
+				aiFloatKey& f = pcIn->aCameraRollKeys[i];
 
-			// Get the maximum key
-			anim->mDuration = std::max(anim->mDuration,channel->
-				mPositionKeys[channel->mNumPositionKeys-1].mTime);
+				q.mTime  = f.mTime;
+				q.mValue = aiQuaternion(0.f,0.f,AI_DEG_TO_RAD(- f.mValue));
+			}
 		}
 
-		// ROTATION keys
-		if (pcIn->aRotationKeys.size()  > 0)
+		if (pcIn->aTargetPositionKeys.size() > 1)
 		{
-			// Sort all keys with ascending time values
-			std::sort(pcIn->aRotationKeys.begin(),pcIn->aRotationKeys.end());
+			//DefaultLogger::get()->debug("3DS: Converting target track ...");
 
-			channel->mNumRotationKeys = (unsigned int)pcIn->aRotationKeys.size();
-			channel->mRotationKeys = new aiQuatKey[channel->mNumRotationKeys];
-			::memcpy(channel->mRotationKeys,&pcIn->aRotationKeys[0],
-				sizeof(aiQuatKey)*channel->mNumRotationKeys);
+			//// Camera or spot light - need to convert the separate
+			//// target position channel to our representation
+			//TargetAnimationHelper helper;
 
-			// Get the maximum key
-			anim->mDuration = std::max(anim->mDuration,channel->
-				mRotationKeys[channel->mNumRotationKeys-1].mTime);
+			//helper.SetTargetAnimationChannel(&pcIn->aTargetPositionKeys);
+			//helper.SetMainAnimationChannel(&pcIn->aPositionKeys);
+
+			//// Do the conversion
+			//std::vector<aiVectorKey> distanceTrack;
+			//helper.Process(&distanceTrack);
+
+			//// Now add a new node as child, name it <ourName>.Target
+			//// and assign the distance track to it. This is that the
+			//// information where the target is and how it moves is
+			//// not lost
+			//D3DS::Node* nd = new D3DS::Node();
+			//pcIn->push_back(nd);
+
+			//nd->mName = pcIn->mName + ".Target";
+
+			//aiNodeAnim* nda = anim->mChannels[anim->mNumChannels++] = new aiNodeAnim();
+			//nda->mNodeName.Set(nd->mName);
+
+			//nda->mNumPositionKeys = (unsigned int)distanceTrack.size();
+			//nda->mPositionKeys = new aiVectorKey[nda->mNumPositionKeys];
+			//::memcpy(nda->mPositionKeys,&distanceTrack[0],
+			//	sizeof(aiVectorKey)*nda->mNumPositionKeys);
 		}
 
-		// SCALING keys
-		if (pcIn->aScalingKeys.size()  > 0)
+		// Just for safety ... we *should* have at least one track here
+		if (pcIn->aPositionKeys.size()  > 1  || pcIn->aRotationKeys.size()   > 1   ||
+			pcIn->aScalingKeys.size()   > 1)
 		{
-			// Sort all keys with ascending time values
-			std::sort(pcIn->aScalingKeys.begin(),pcIn->aScalingKeys.end());
+			// Allocate a new nda, increment the nda index
+			aiNodeAnim* nda = anim->mChannels[anim->mNumChannels++] = new aiNodeAnim();
+			nda->mNodeName.Set(pcIn->mName);
 
-			channel->mNumScalingKeys = (unsigned int)pcIn->aScalingKeys.size();
-			channel->mScalingKeys = new aiVectorKey[channel->mNumScalingKeys];
-			::memcpy(channel->mScalingKeys,&pcIn->aScalingKeys[0],
-				sizeof(aiVectorKey)*channel->mNumScalingKeys);
+			// POSITION keys
+			if (pcIn->aPositionKeys.size()  > 0)
+			{
+				nda->mNumPositionKeys = (unsigned int)pcIn->aPositionKeys.size();
+				nda->mPositionKeys = new aiVectorKey[nda->mNumPositionKeys];
+				::memcpy(nda->mPositionKeys,&pcIn->aPositionKeys[0],
+					sizeof(aiVectorKey)*nda->mNumPositionKeys);
+			}
 
-			// Get the maximum key
-			anim->mDuration = std::max(anim->mDuration,channel->
-				mScalingKeys[channel->mNumScalingKeys-1].mTime);
+			// ROTATION keys
+			if (pcIn->aRotationKeys.size()  > 0)
+			{
+				nda->mNumRotationKeys = (unsigned int)pcIn->aRotationKeys.size();
+				nda->mRotationKeys = new aiQuatKey[nda->mNumRotationKeys];
+				::memcpy(nda->mRotationKeys,&pcIn->aRotationKeys[0],
+					sizeof(aiQuatKey)*nda->mNumRotationKeys);
+			}
+
+			// SCALING keys
+			if (pcIn->aScalingKeys.size()  > 0)
+			{
+				nda->mNumScalingKeys = (unsigned int)pcIn->aScalingKeys.size();
+				nda->mScalingKeys = new aiVectorKey[nda->mNumScalingKeys];
+				::memcpy(nda->mScalingKeys,&pcIn->aScalingKeys[0],
+					sizeof(aiVectorKey)*nda->mNumScalingKeys);
+			}
 		}
 	}
 
 	// Setup the name of the node
 	pcOut->mName.Set(pcIn->mName);
 
-	// Allocate storage for children
+	// Allocate storage for children 
 	pcOut->mNumChildren = (unsigned int)pcIn->mChildren.size();
 	pcOut->mChildren = new aiNode*[pcIn->mChildren.size()];
 
 	// Recursively process all children
-	for (unsigned int i = 0; i < pcIn->mChildren.size();++i)
+	const unsigned int size = pcIn->mChildren.size();
+	for (unsigned int i = 0; i < size;++i)
 	{
 		pcOut->mChildren[i] = new aiNode();
 		pcOut->mChildren[i]->mParent = pcOut;
@@ -683,11 +743,13 @@ void CountTracks(D3DS::Node* node, unsigned int& cnt)
 	// We will never generate more than one channel for a node, so
 	// this is rather easy here.
 
-	if (node->aPositionKeys.size()  > 0  || node->aRotationKeys.size()   > 0   ||
-		node->aScalingKeys.size()   > 0  || node->aCameraRollKeys.size() > 0 ||
-		node->aTargetPositionKeys.size()  > 0)
+	if (node->aPositionKeys.size()  > 1  || node->aRotationKeys.size()   > 1   ||
+		node->aScalingKeys.size()   > 1  || node->aCameraRollKeys.size() > 1 ||
+		node->aTargetPositionKeys.size()  > 1)
 	{
 		++cnt;
+
+		if (node->aTargetPositionKeys.size()  > 1)++cnt;
 	}
 
 	// Recursively process all children
@@ -778,6 +840,14 @@ void Discreet3DSImporter::GenerateNodeGraph(aiScene* pcOut)
 
 		aiMatrix4x4 m;
 		AddNodeToGraph(pcOut,  pcOut->mRootNode, mRootNode,m);
+
+		// If the root node is unnamed name it "<3DSRoot>"
+		if (::strstr( pcOut->mRootNode->mName.data, "UNNAMED" )
+			|| pcOut->mRootNode->mName.data[0] == '$' 
+			&& pcOut->mRootNode->mName.data[1] == '$')
+		{
+			pcOut->mRootNode->mName.Set("<3DSRoot>");
+		}
 	}
 
 	// We used the first vertex color set to store some
@@ -795,10 +865,6 @@ void Discreet3DSImporter::GenerateNodeGraph(aiScene* pcOut)
 		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>");
-
 #if 0
 	// modify the transformation of the root node to change
 	// the coordinate system of the whole scene from Max' to OpenGL

+ 36 - 6
code/3DSHelper.h

@@ -423,6 +423,7 @@ struct Mesh : public MeshWithSmoothingGroups<D3DS::Face>
 	{
 		static int iCnt = 0;
 		
+		// Generate a default name for the mesh
 		char szTemp[128];
 		::sprintf(szTemp,"UNNAMED_%i",iCnt++);
 		mName = szTemp;
@@ -441,7 +442,30 @@ struct Mesh : public MeshWithSmoothingGroups<D3DS::Face>
 	aiMatrix4x4 mMat;
 };
 
-typedef std::pair<double, float> aiFloatKey;
+// ---------------------------------------------------------------------------
+/** Float key - quite similar to aiVectorKey and aiQuatKey. Both are in the
+    C-API, so it would be difficult to make them a template. */
+struct aiFloatKey 
+{
+	double mTime;      ///< The time of this key
+	float mValue;	///< The value of this key
+
+#ifdef __cplusplus
+
+	// time is not compared
+	bool operator == (const aiFloatKey& o) const
+		{return o.mValue == this->mValue;}
+
+	bool operator != (const aiFloatKey& o) const
+		{return o.mValue != this->mValue;}
+
+	// Only time is compared. This operator is defined
+	// for use with std::sort
+	bool operator < (const aiFloatKey& o) const
+		{return mTime < o.mTime;}
+
+#endif
+};
 
 // ---------------------------------------------------------------------------
 /** Helper structure to represent a 3ds file node */
@@ -449,11 +473,13 @@ struct Node
 {
 	Node()
 
-		: mHierarchyPos(0),mHierarchyIndex(0)
+		:	mHierarchyPos		(0)
+		,	mHierarchyIndex		(0)
 
 	{
 		static int iCnt = 0;
 		
+		// Generate a default name for the node
 		char szTemp[128];
 		::sprintf(szTemp,"UNNAMED_%i",iCnt++);
 		mName = szTemp;
@@ -463,6 +489,12 @@ struct Node
 		aScalingKeys.reserve  (20);
 	}
 
+	~Node()
+	{
+		for (unsigned int i = 0; i < mChildren.size();++i)
+			delete mChildren[i];
+	}
+
 	//! Pointer to the parent node
 	Node* mParent;
 
@@ -481,7 +513,6 @@ struct Node
 	//! Index of the node
 	int16_t mHierarchyIndex;
 
-
 	//! Rotation keys loaded from the file
 	std::vector<aiQuatKey> aRotationKeys;
 
@@ -508,7 +539,6 @@ struct Node
 	{
 		mChildren.push_back(pc);
 		pc->mParent = this;
-		//pc->mHierarchyPos = this->mHierarchyPos+1;
 		return *this;
 	}
 };
@@ -516,7 +546,6 @@ struct Node
 /** Helper structure analogue to aiScene */
 struct Scene
 {
-
 	//! List of all materials loaded
 	//! NOTE: 3ds references materials globally
 	std::vector<Material> mMaterials;
@@ -531,7 +560,8 @@ struct Scene
 	std::vector<aiLight*> mLights;
 
 	//! Pointer to the root node of the scene
-	Node* pcRootNode;
+	// --- moved to main class
+	// Node* pcRootNode;
 };
 
 

+ 115 - 72
code/3DSLoader.cpp

@@ -169,8 +169,14 @@ void Discreet3DSImporter::InternReadFile( const std::string& pFile,
 	// Now apply the master scaling factor to the scene
 	ApplyMasterScale(pScene);
 
-	// We're finished here. Everything destructs automatically
-	// and the output scene should be valid.
+	// Delete our internal scene representation and the root
+	// node, so the whole hierarchy will follow
+	delete mRootNode;
+	delete mScene;
+
+	AI_DEBUG_INVALIDATE_PTR(mRootNode);
+	AI_DEBUG_INVALIDATE_PTR(mScene);
+	AI_DEBUG_INVALIDATE_PTR(this->stream);
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -181,12 +187,14 @@ void Discreet3DSImporter::ApplyMasterScale(aiScene* pScene)
 	if (!mMasterScale)mMasterScale = 1.0f;
 	else mMasterScale = 1.0f / mMasterScale;
 
-	// construct an uniform scaling matrix and multiply with it
+	// Construct an uniform scaling matrix and multiply with it
 	pScene->mRootNode->mTransformation *= aiMatrix4x4( 
 		mMasterScale,0.0f, 0.0f, 0.0f,
 		0.0f, mMasterScale,0.0f, 0.0f,
 		0.0f, 0.0f, mMasterScale,0.0f,
 		0.0f, 0.0f, 0.0f, 1.0f);
+
+	// Check whether a scaling track is assigned to the root node.
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -416,6 +424,11 @@ void Discreet3DSImporter::ParseChunk(const char* name, unsigned int num)
 
 		camera->mName.Set(std::string(name, num));
 
+		// The camera position and look-at vector are
+		// difficult to handle. Later we'll copy these
+		// values to the local transformation of the
+		// camera's node.
+
 		// First read the position of the camera
 		camera->mPosition.x = stream->GetF4();
 		camera->mPosition.y = stream->GetF4();
@@ -540,6 +553,14 @@ D3DS::Node* FindNode(D3DS::Node* root, const std::string& name)
 	return NULL;
 }
 
+// ------------------------------------------------------------------------------------------------
+// Binary predicate for std::unique()
+template <class T>
+bool KeyUniqueCompare(const T& first, const T& second)
+{
+	return first.mTime == second.mTime;
+}
+
 // ------------------------------------------------------------------------------------------------
 void Discreet3DSImporter::ParseHierarchyChunk(uint16_t parent)
 {
@@ -638,11 +659,14 @@ void Discreet3DSImporter::ParseHierarchyChunk(uint16_t parent)
 		break;
 
 
+		// **************************************************************
+		// POSITION KEYFRAME
 	case Discreet3DS::CHUNK_TRACKPOS:
 		{
 		stream->IncPtr(10);
 		unsigned int numFrames = stream->GetI2();
 		stream->IncPtr(2);
+		bool sortKeys = false;
 
 		// This could also be meant as the target position for
 		// (targeted) lights and cameras
@@ -667,22 +691,26 @@ void Discreet3DSImporter::ParseHierarchyChunk(uint16_t parent)
 			v.mValue.y = stream->GetF4();
 			v.mValue.z = stream->GetF4();
 
-			// Check whether we do already have this keyframe
-			for (std::vector<aiVectorKey>::const_iterator
-				i =  l->begin();i != l->end();++i)
-			{
-				if ((*i).mTime == v.mTime)
-				{
-					DefaultLogger::get()->error("3DS: Found duplicate position keyframe");
-					v.mTime = -10e10f;
-					break;
-				}
-			}
+			// check whether we'll need to sort the keys
+			if (!l->empty() && v.mTime <= l->back().mTime)
+				sortKeys = true;
+
 			// Add the new keyframe to the list
-			if (v.mTime != -10e10f)l->push_back(v);
+			l->push_back(v);
+		}
+
+		// Sort all keys with ascending time values?
+		if (sortKeys)
+		{
+			std::sort   (l->begin(),l->end());
+			std::unique (l->begin(),l->end(),
+				std::ptr_fun(&KeyUniqueCompare<aiVectorKey>));
 		}}
+
 		break;
 
+		// **************************************************************
+		// CAMERA ROLL KEYFRAME
 	case Discreet3DS::CHUNK_TRACKROLL:
 		{
 		// roll keys are accepted for cameras only
@@ -691,6 +719,8 @@ void Discreet3DSImporter::ParseHierarchyChunk(uint16_t parent)
 			DefaultLogger::get()->warn("3DS: Ignoring roll track for non-camera object");
 			break;
 		}
+		bool sortKeys = false;
+		std::vector<aiFloatKey>* l = &mCurrentNode->aCameraRollKeys;
 
 		stream->IncPtr(10);
 		unsigned int numFrames = stream->GetI2();
@@ -702,40 +732,57 @@ void Discreet3DSImporter::ParseHierarchyChunk(uint16_t parent)
 
 			// Setup a new position key
 			aiFloatKey v;
-			v.first = (double)fidx;
+			v.mTime = (double)fidx;
 
 			// This is just a single float 
 			stream->IncPtr(4);
-			v.second = stream->GetF4();
+			v.mValue = stream->GetF4();
+
+			// Check whether we'll need to sort the keys
+			if (!l->empty() && v.mTime <= l->back().mTime)
+				sortKeys = true;
 
-			// Check whether we do already have this keyframe
-			for (std::vector<aiFloatKey>::const_iterator
-				i =  mCurrentNode->aCameraRollKeys.begin();
-				i != mCurrentNode->aCameraRollKeys.end();++i)
-			{
-				if ((*i).first == v.first)
-				{
-					DefaultLogger::get()->error("3DS: Found duplicate camera roll keyframe");
-					v.first = -10e10f;
-					break;
-				}
-			}
 			// Add the new keyframe to the list
-			if (v.first != -10e10f)
-				mCurrentNode->aCameraRollKeys.push_back(v);
+			l->push_back(v);
+		}
+
+		// Sort all keys with ascending time values?
+		if (sortKeys)
+		{
+			std::sort   (l->begin(),l->end());
+			std::unique (l->begin(),l->end(),
+				std::ptr_fun(&KeyUniqueCompare<aiFloatKey>));
 		}}
 		break;
 
+
+		// **************************************************************
+		// CAMERA FOV KEYFRAME
+	case Discreet3DS::CHUNK_TRACKFOV:
+		{
+			DefaultLogger::get()->error("3DS: Skipping FOV animation track. "
+				"This is not supported");
+		}
+		break;
+
+
+		// **************************************************************
+		// ROTATION KEYFRAME
 	case Discreet3DS::CHUNK_TRACKROTATE:
 		{
 		stream->IncPtr(10);
 		unsigned int numFrames = stream->GetI2();
 		stream->IncPtr(2);
 
+		bool sortKeys = false;
+		std::vector<aiQuatKey>* l = &mCurrentNode->aRotationKeys;
+
 		for (unsigned int i = 0; i < numFrames;++i)
 		{
 			unsigned int fidx = stream->GetI2();
 
+			stream->IncPtr(4);
+
 			aiQuatKey v;
 			v.mTime = (double)fidx;
 
@@ -746,38 +793,43 @@ void Discreet3DSImporter::ParseHierarchyChunk(uint16_t parent)
 			axis.y = stream->GetF4();
 			axis.z = stream->GetF4();
 
+			if (!axis.x && !axis.y && !axis.z)
+				axis.y = 1.f;
+
 			// Construct a rotation quaternion from the axis-angle pair
 			v.mValue = aiQuaternion(axis,rad);
 
-			// check whether we do already have this keyframe
-			for (std::vector<aiQuatKey>::const_iterator
-				i =  mCurrentNode->aRotationKeys.begin();
-				i != mCurrentNode->aRotationKeys.end();++i)
-			{
-				if ((*i).mTime == v.mTime)
-				{
-					DefaultLogger::get()->error("3DS: Found duplicate rotation keyframe");
-					v.mTime = -10e10f;
-					break;
-				}
-			}
+			// Check whether we'll need to sort the keys
+			if (!l->empty() && v.mTime <= l->back().mTime)
+				sortKeys = true;
+
 			// add the new keyframe to the list
-			if (v.mTime != -10e10f)
-				mCurrentNode->aRotationKeys.push_back(v);
+			l->push_back(v);
+		}
+		// Sort all keys with ascending time values?
+		if (sortKeys)
+		{
+			std::sort   (l->begin(),l->end());
+			std::unique (l->begin(),l->end(),
+				std::ptr_fun(&KeyUniqueCompare<aiQuatKey>));
 		}}
 		break;
 
+		// **************************************************************
+		// SCALING KEYFRAME
 	case Discreet3DS::CHUNK_TRACKSCALE:
 		{
-		unsigned int invalid = 0;
-
 		stream->IncPtr(10);
 		unsigned int numFrames = stream->GetI2();
 		stream->IncPtr(2);
 
+		bool sortKeys = false;
+		std::vector<aiVectorKey>* l = &mCurrentNode->aScalingKeys;
+
 		for (unsigned int i = 0; i < numFrames;++i)
 		{
 			unsigned int fidx = stream->GetI2();
+			stream->IncPtr(4);
 
 			// Setup a new key
 			aiVectorKey v;
@@ -788,34 +840,23 @@ void Discreet3DSImporter::ParseHierarchyChunk(uint16_t parent)
 			v.mValue.y = stream->GetF4();
 			v.mValue.z = stream->GetF4();
 
-			// check whether we do already have this keyframe
-			for (std::vector<aiVectorKey>::const_iterator
-				i =  mCurrentNode->aScalingKeys.begin();
-				i != mCurrentNode->aScalingKeys.end();++i)
-			{
-				if ((*i).mTime == v.mTime)
-				{
-					DefaultLogger::get()->error("3DS: Found duplicate scaling keyframe");
-					v.mTime = -10e10f;
-					break;
-				}
-			}
-			// add the new keyframe
-			if (v.mTime != -10e10f)
-				mCurrentNode->aScalingKeys.push_back(v);
+			// check whether we'll need to sort the keys
+			if (!l->empty() && v.mTime <= l->back().mTime)
+				sortKeys = true;
+			
+			// Remove zero-scalings
+			if (!v.mValue.x)v.mValue.x = 1.f;
+			if (!v.mValue.y)v.mValue.y = 1.f;
+			if (!v.mValue.z)v.mValue.z = 1.f;
 
-			// Check whether this is a zero scaling keyframe
-			if (!v.mValue.x && !v.mValue.y && !v.mValue.z)
-			{
-				DefaultLogger::get()->warn("3DS: Found zero scaled axis in scaling keyframe");
-				++invalid;
-			}
+			l->push_back(v);
 		}
-		// there are 3DS files that have only zero scalings
-		if (numFrames == invalid)
+		// Sort all keys with ascending time values?
+		if (sortKeys)
 		{
-			DefaultLogger::get()->warn("3DS: All scaling keys are zero. Ignoring them ...");
-			mCurrentNode->aScalingKeys.clear();
+			std::sort   (l->begin(),l->end());
+			std::unique (l->begin(),l->end(),
+				std::ptr_fun(&KeyUniqueCompare<aiVectorKey>));
 		}}
 		break;
 	};
@@ -874,10 +915,12 @@ void Discreet3DSImporter::ParseFaceChunk()
 		{
 			DefaultLogger::get()->error(std::string("3DS: Unknown material: ") + sz);
 
+			// *********************************************************
 			// This material is not known. Ignore this. We will later
 			// assign the default material to all faces using *this*
 			// material. Use 0xcdcdcdcd as special value to indicate
 			// this.
+			// *********************************************************
 		}
 
 		// Now continue and read all material indices

+ 125 - 60
code/ASELoader.cpp

@@ -77,6 +77,7 @@ bool ASEImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
 	// no file extension - can't read
 	if( pos == std::string::npos)
 		return false;
+
 	std::string extension = pFile.substr( pos);
 
 	// Either ASE, ASC or ASK
@@ -109,7 +110,7 @@ void ASEImporter::InternReadFile( const std::string& pFile,
 
 	size_t fileSize = file->FileSize();
 
-	// allocate storage and copy the contents of the file to a memory buffer
+	// Allocate storage and copy the contents of the file to a memory buffer
 	// (terminate it with zero)
 	std::vector<char> mBuffer2(fileSize+1);
 	file->Read( &mBuffer2[0], 1, fileSize);
@@ -118,17 +119,35 @@ void ASEImporter::InternReadFile( const std::string& pFile,
 	this->mBuffer = &mBuffer2[0];
 	this->pcScene = pScene;
 
-	// construct an ASE parser and parse the file
-	// TODO: clean this up, mParser should be a reference, not a pointer ...
-	ASE::Parser parser(this->mBuffer);
+	// *****************************************************************
+	// Guess the file format by looking at the extension
+	// ASC is considered to be the older format 110,
+	// ASE is the actual version 200 (that is currently written by max)
+	// *****************************************************************
+	unsigned int defaultFormat;
+	std::string::size_type s = pFile.length()-1;
+	switch (pFile.c_str()[s])
+	{
+	case 'C':
+	case 'c':
+		defaultFormat = AI_ASE_OLD_FILE_FORMAT;
+		break;
+	default:
+		defaultFormat = AI_ASE_NEW_FILE_FORMAT;
+	};
+
+	// Construct an ASE parser and parse the file
+	ASE::Parser parser(mBuffer,defaultFormat);
 	mParser = &parser;
 	mParser->Parse();
 
+	// *****************************************************************
 	// Check whether we loaded at least one mesh. If we did - generate
 	// materials and copy meshes. 
+	// *****************************************************************
 	if ( !mParser->m_vMeshes.empty())
 	{
-		// if absolutely no material has been loaded from the file
+		// If absolutely no material has been loaded from the file
 		// we need to generate a default material
 		GenerateDefaultMaterial();
 
@@ -142,16 +161,16 @@ void ASEImporter::InternReadFile( const std::string& pFile,
 		{
 			if ((*i).bSkip)continue;
 
-			// now we need to create proper meshes from the import we 
+			// Now we need to create proper meshes from the import we 
 			// need to split them by materials, build valid vertex/
 			// face lists ...
 			BuildUniqueRepresentation(*i);
 
-			// need to generate proper vertex normals if necessary
+			// Need to generate proper vertex normals if necessary
 			if(GenerateNormals(*i))
 				tookNormals = true;
 
-			// convert all meshes to aiMesh objects
+			// Convert all meshes to aiMesh objects
 			ConvertMeshes(*i,avOutMeshes);
 		}
 		if (tookNormals)
@@ -161,7 +180,7 @@ void ASEImporter::InternReadFile( const std::string& pFile,
 				"experience problems");
 		}
 
-		// now build the output mesh list. Remove dummies
+		// Now build the output mesh list. Remove dummies
 		pScene->mNumMeshes = (unsigned int)avOutMeshes.size();
 		aiMesh** pp = pScene->mMeshes = new aiMesh*[pScene->mNumMeshes];
 		for (std::vector<aiMesh*>::const_iterator
@@ -173,28 +192,33 @@ void ASEImporter::InternReadFile( const std::string& pFile,
 		}
 		pScene->mNumMeshes = (unsigned int)(pp - pScene->mMeshes);
 
-		// build final material indices (remove submaterials and setup
+		// Build final material indices (remove submaterials and setup
 		// the final list)
 		BuildMaterialIndices();
 	}
 
+	// *****************************************************************
 	// Copy all scene graph nodes - lights, cameras, dummies and meshes
 	// into one large array
+	// *****************************************************************
 	nodes.reserve(mParser->m_vMeshes.size() +mParser->m_vLights.size()
 		+ mParser->m_vCameras.size() + mParser->m_vDummies.size());
 
+	// Lights
 	for (std::vector<ASE::Light>::iterator it = mParser->m_vLights.begin(), 
 		 end = mParser->m_vLights.end();it != end; ++it)nodes.push_back(&(*it));
+
+	// Cameras
 	for (std::vector<ASE::Camera>::iterator it = mParser->m_vCameras.begin(), 
 		 end = mParser->m_vCameras.end();it != end; ++it)nodes.push_back(&(*it));
+
+	// Meshes
 	for (std::vector<ASE::Mesh>::iterator it = mParser->m_vMeshes.begin(),
 		end = mParser->m_vMeshes.end();it != end; ++it)nodes.push_back(&(*it));
-		for (std::vector<ASE::Dummy>::iterator it = mParser->m_vDummies.begin(),
-		end = mParser->m_vDummies.end();it != end; ++it)nodes.push_back(&(*it));
 
-	// process target cameras and target lights (
-	// generate animation channels for them and adjust the node graph)
-	// ProcessTargets();
+	// Dummies
+	for (std::vector<ASE::Dummy>::iterator it = mParser->m_vDummies.begin(),
+		end = mParser->m_vDummies.end();it != end; ++it)nodes.push_back(&(*it));
 
 	// build the final node graph
 	BuildNodes();
@@ -209,8 +233,11 @@ void ASEImporter::InternReadFile( const std::string& pFile,
 	BuildLights();
 
 	// TODO: STRANGE RESULTS ATM
+
+	// *****************************************************************
 	// If we have no meshes use the SkeletonMeshBuilder helper class
 	// to build a mesh for the animation skeleton
+	// *****************************************************************
 	if (!pScene->mNumMeshes)
 	{
 		pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
@@ -278,7 +305,9 @@ void ASEImporter::BuildAnimations()
 		// that represent the node transformation.
 		if ((*i)->mAnim.akeyPositions.size() > 1 || 
 			(*i)->mAnim.akeyRotations.size() > 1 ||
-			(*i)->mAnim.akeyScaling.size()   > 1)
+			(*i)->mAnim.akeyScaling.size()   > 1 ||
+			(*i)->mTargetAnim.akeyPositions.size() > 1 
+			 && is_not_qnan( (*i)->mTargetPosition.x ))
 		{
 			++iNum;
 		}
@@ -298,62 +327,79 @@ void ASEImporter::BuildAnimations()
 		// Now iterate through all meshes and collect all data we can find
 		for (i =  nodes.begin();i != nodes.end();++i)
 		{
-			if ((*i)->mAnim.akeyPositions.size() > 1 || (*i)->mAnim.akeyRotations.size() > 1)
+			ASE::BaseNode* me = *i;
+			if ( me->mTargetAnim.akeyPositions.size() > 1 
+				 && is_not_qnan( me->mTargetPosition.x ))
+			{
+				// Generate an extra channel for the camera/light target.
+				// BuildNodes() does also generate an extra node, named
+				// <baseName>.Target.
+				aiNodeAnim* nd = pcAnim->mChannels[iNum++] = new aiNodeAnim();
+				nd->mNodeName.Set(me->mName + ".Target");
+
+				// Allocate the key array and fill it
+				nd->mNumPositionKeys = (unsigned int) me->mTargetAnim.akeyPositions.size();
+				nd->mPositionKeys    = new aiVectorKey[nd->mNumPositionKeys];
+
+				::memcpy(nd->mPositionKeys,&me->mTargetAnim.akeyPositions[0],
+					nd->mNumPositionKeys * sizeof(aiVectorKey));
+			}
+
+			if (me->mAnim.akeyPositions.size() > 1 || me->mAnim.akeyRotations.size() > 1)
 			{
 				// Begin a new node animation channel for this node
-				aiNodeAnim* pcNodeAnim = pcAnim->mChannels[iNum++] = new aiNodeAnim();
-				pcNodeAnim->mNodeName.Set((*i)->mName);
+				aiNodeAnim* nd = pcAnim->mChannels[iNum++] = new aiNodeAnim();
+				nd->mNodeName.Set(me->mName);
 
 				// copy position keys
-				if ((*i)->mAnim.akeyPositions.size() > 1 )
+				if (me->mAnim.akeyPositions.size() > 1 )
 				{
 					// Allocate the key array and fill it
-					pcNodeAnim->mNumPositionKeys = (unsigned int) (*i)->mAnim.akeyPositions.size();
-					pcNodeAnim->mPositionKeys    = new aiVectorKey[pcNodeAnim->mNumPositionKeys];
-
-					::memcpy(pcNodeAnim->mPositionKeys,&(*i)->mAnim.akeyPositions[0],
-						pcNodeAnim->mNumPositionKeys * sizeof(aiVectorKey));
+					nd->mNumPositionKeys = (unsigned int) me->mAnim.akeyPositions.size();
+					nd->mPositionKeys    = new aiVectorKey[nd->mNumPositionKeys];
 
-					// get the longest node anim channel 
-					for (unsigned int qq = 0; qq < pcNodeAnim->mNumPositionKeys;++qq)
-					{
-						pcAnim->mDuration = std::max(pcAnim->mDuration,
-							pcNodeAnim->mPositionKeys[qq].mTime);
-					}
+					::memcpy(nd->mPositionKeys,&me->mAnim.akeyPositions[0],
+						nd->mNumPositionKeys * sizeof(aiVectorKey));
 				}
 				// copy rotation keys
-				if ((*i)->mAnim.akeyRotations.size() > 1 )
+				if (me->mAnim.akeyRotations.size() > 1 )
 				{
 					// Allocate the key array and fill it
-					pcNodeAnim->mNumRotationKeys = (unsigned int) (*i)->mAnim.akeyRotations.size();
-					pcNodeAnim->mRotationKeys    = new aiQuatKey[pcNodeAnim->mNumRotationKeys];
-
-					::memcpy(pcNodeAnim->mRotationKeys,&(*i)->mAnim.akeyRotations[0],
-						pcNodeAnim->mNumRotationKeys * sizeof(aiQuatKey));
-
-					// get the longest node anim channel
-					for (unsigned int qq = 0; qq < pcNodeAnim->mNumRotationKeys;++qq)
+					nd->mNumRotationKeys = (unsigned int) me->mAnim.akeyRotations.size();
+					nd->mRotationKeys    = new aiQuatKey[nd->mNumRotationKeys];
+
+					// **************************************************************
+					// Rotation keys are offsets to the previous keys.
+					// We have the quaternion representations of all 
+					// of them, so we just need to concatenate all
+					// (unit-length) quaternions to get the absolute
+					// rotations.
+					// FIX: Rotation keys are ABSOLUTE for the older
+					// file format 110 (ASC)
+					// **************************************************************
+
+					aiQuaternion cur;
+					for (unsigned int a = 0; a < nd->mNumRotationKeys;++a)
 					{
-						pcAnim->mDuration = std::max(pcAnim->mDuration,
-							pcNodeAnim->mRotationKeys[qq].mTime);
+						aiQuatKey q = me->mAnim.akeyRotations[a];
+
+						if (mParser->iFileFormat > 110)
+						{
+							cur = (a ? cur*q.mValue : q.mValue);
+							q.mValue = cur.Normalize();
+						}
+						nd->mRotationKeys[a] = q; 
 					}
 				}
 				// copy scaling keys
-				if ((*i)->mAnim.akeyScaling.size() > 1 )
+				if (me->mAnim.akeyScaling.size() > 1 )
 				{
 					// Allocate the key array and fill it
-					pcNodeAnim->mNumScalingKeys = (unsigned int) (*i)->mAnim.akeyScaling.size();
-					pcNodeAnim->mScalingKeys    = new aiVectorKey[pcNodeAnim->mNumScalingKeys];
+					nd->mNumScalingKeys = (unsigned int) me->mAnim.akeyScaling.size();
+					nd->mScalingKeys    = new aiVectorKey[nd->mNumScalingKeys];
 
-					::memcpy(pcNodeAnim->mScalingKeys,&(*i)->mAnim.akeyScaling[0],
-						pcNodeAnim->mNumScalingKeys * sizeof(aiVectorKey));
-
-					// get the longest node anim channel 
-					for (unsigned int qq = 0; qq < pcNodeAnim->mNumScalingKeys;++qq)
-					{
-						pcAnim->mDuration = std::max(pcAnim->mDuration,
-							pcNodeAnim->mScalingKeys[qq].mTime);
-					}
+					::memcpy(nd->mScalingKeys,&me->mAnim.akeyScaling[0],
+						nd->mNumScalingKeys * sizeof(aiVectorKey));
 				}
 			}
 		}
@@ -534,15 +580,34 @@ void ASEImporter::AddNodes (std::vector<BaseNode*>& nodes,
 		mParentAdjust.Inverse();
 		node->mTransformation = mParentAdjust*snode->mTransform;
 
-		// If the type of this node is "Mesh" we need to search
-		// the list of output meshes in the data structure for
-		// all those that belonged to this node once. This is
-		// slightly inconvinient here and a better solution should
-		// be used when this code is refactored next.
-		if (snode->mType == BaseNode::Mesh)
+		// Further processing depends on the type of the node
+		if (snode->mType == ASE::BaseNode::Mesh)
 		{
+			// If the type of this node is "Mesh" we need to search
+			// the list of output meshes in the data structure for
+			// all those that belonged to this node once. This is
+			// slightly inconvinient here and a better solution should
+			// be used when this code is refactored next.
 			AddMeshes(snode,node);
 		}
+		else if (is_not_qnan( snode->mTargetPosition.x ))
+		{
+			// If this is a target camera or light we generate a small
+			// child node which marks the position of the camera
+			// target (the direction information is contained in *this*
+			// node's animation track but the exact target position
+			// would be lost otherwise)
+			apcNodes.push_back(new aiNode());
+			aiNode* node = apcNodes.back();
+
+			node->mName.Set ( snode->mName + ".Target" );
+			node->mTransformation.a4 = snode->mTargetPosition.x;
+			node->mTransformation.b4 = snode->mTargetPosition.y;
+			node->mTransformation.c4 = snode->mTargetPosition.z;
+
+			node->mParent = pcParent;
+		}
+
 
 		// add sub nodes
 		// aiMatrix4x4 mNewAbs =  mat * node->mTransformation;

ファイルの差分が大きいため隠しています
+ 322 - 145
code/ASEParser.cpp


+ 33 - 3
code/ASEParser.h

@@ -130,11 +130,17 @@ struct Bone
 	{
 		static int iCnt = 0;
 		
+		// Generate a default name for the bone
 		char szTemp[128];
 		::sprintf(szTemp,"UNNAMED_%i",iCnt++);
 		mName = szTemp;
 	}
 
+	//! Construction from an existing name
+	Bone( const std::string& name)
+		:	mName	(name)
+	{}
+
 	//! Name of the bone
 	std::string mName;
 };
@@ -224,6 +230,10 @@ struct BaseNode
 		char szTemp[128]; // should be sufficiently large
 		::sprintf(szTemp,"UNNAMED_%i",iCnt++);
 		mName = szTemp;
+
+		// Set mTargetPosition to qnan
+		const float qnan = std::numeric_limits<float>::quiet_NaN();
+		mTargetPosition.x = qnan;
 	}
 
 	//! Name of the mesh
@@ -357,7 +367,15 @@ struct Dummy : public BaseNode
 	}
 };
 
-// ---------------------------------------------------------------------------------
+// Parameters to Parser::Parse()
+#define AI_ASE_NEW_FILE_FORMAT 200
+#define AI_ASE_OLD_FILE_FORMAT 110
+
+// Internally we're a little bit more tolerant
+#define AI_ASE_IS_NEW_FILE_FORMAT()	(iFileFormat >= 200)
+#define AI_ASE_IS_OLD_FILE_FORMAT()	(iFileFormat < 200)
+
+// -------------------------------------------------------------------------------
 /** \brief Class to parse ASE files
  */
 class Parser
@@ -369,9 +387,14 @@ private:
 
 public:
 
+	// -------------------------------------------------------------------
 	//! Construct a parser from a given input file which is
 	//! guaranted to be terminated with zero.
-	Parser (const char* szFile);
+	//! @param szFile Input file
+	//! @param fileFormatDefault Assumed file format version. If the
+	//!   file format is specified in the file the new value replaces
+	//!   the default value.
+	Parser (const char* szFile, unsigned int fileFormatDefault);
 
 	// -------------------------------------------------------------------
 	//! Parses the file into the parsers internal representation
@@ -384,6 +407,10 @@ private:
 	//! Parse the *SCENE block in a file
 	void ParseLV1SceneBlock();
 
+	// -------------------------------------------------------------------
+	//! Parse the *MESH_SOFTSKINVERTS block in a file
+	void ParseLV1SoftSkinBlock();
+
 	// -------------------------------------------------------------------
 	//! Parse the *MATERIAL_LIST block in a file
 	void ParseLV1MaterialListBlock();
@@ -593,7 +620,7 @@ private:
 public:
 
 	//! Pointer to current data
-	const char* m_szFile;
+	const char* filePtr;
 
 	//! background color to be passed to the viewer
 	//! QNAN if none was found
@@ -635,6 +662,9 @@ public:
 
 	//! true if the last character read was an end-line character
 	bool bLastWasEndLine;
+
+	//! File format version
+	unsigned int iFileFormat;
 };
 
 

+ 2 - 0
code/AssimpPCH.h

@@ -108,11 +108,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #	include "../include/BoostWorkaround/boost/scoped_ptr.hpp"
 #	include "../include/BoostWorkaround/boost/format.hpp"
+#	include "../include/BoostWorkaround/boost/common_factor_rt.hpp"
 
 #else
 
 #	include <boost/scoped_ptr.hpp>
 #	include <boost/format.hpp>
+#	include	<boost/math/common_factor_rt.hpp> 
 
 #endif
 

+ 3 - 0
code/BaseImporter.h

@@ -177,6 +177,9 @@ protected:
 	 *   In fact this means that every vertex that is referenced by
 	 *   a face is unique. Or the other way round: a vertex index may
 	 *   not occur twice in a single aiMesh.
+	 * - aiAnimation::mDuration may be -1. Assimp determines the length
+	 *   of the animation automatically in this case as the length of
+	 *   the longest animation channel.
 	 *
 	 * If the AI_SCENE_FLAGS_INCOMPLETE-Flag is not set:<br>
 	 * - at least one mesh must be there<br>

+ 12 - 7
code/FindInvalidDataProcess.cpp

@@ -160,7 +160,7 @@ void FindInvalidDataProcess::Execute( aiScene* pScene)
 // ------------------------------------------------------------------------------------------------
 template <typename T>
 inline const char* ValidateArrayContents(const T* arr, unsigned int size,
-	const std::vector<bool>& dirtyMask)
+	const std::vector<bool>& dirtyMask, bool mayBeIdentical = false, bool mayBeZero = true)
 {
 	return NULL;
 }
@@ -168,7 +168,7 @@ inline const char* ValidateArrayContents(const T* arr, unsigned int size,
 // ------------------------------------------------------------------------------------------------
 template <>
 inline const char* ValidateArrayContents<aiVector3D>(const aiVector3D* arr, unsigned int size,
-	const std::vector<bool>& dirtyMask)
+	const std::vector<bool>& dirtyMask, bool mayBeIdentical , bool mayBeZero )
 {
 	bool b = false;
 	for (unsigned int i = 0; i < size;++i)
@@ -180,9 +180,13 @@ inline const char* ValidateArrayContents<aiVector3D>(const aiVector3D* arr, unsi
 		{
 			return "INF/NAN was found in a vector component";
 		}
+		if (!mayBeZero && !v.x && !v.y && !v.z )
+		{
+			return "Found zero-length vector";
+		}
 		if (i && v != arr[i-1])b = true;
 	}
-	if (!b)
+	if (!b && !mayBeIdentical)
 		return "All vectors are identical";
 	return NULL;
 }
@@ -190,9 +194,10 @@ inline const char* ValidateArrayContents<aiVector3D>(const aiVector3D* arr, unsi
 // ------------------------------------------------------------------------------------------------
 template <typename T>
 inline bool ProcessArray(T*& in, unsigned int num,const char* name,
-	const std::vector<bool>& dirtyMask)
+	const std::vector<bool>& dirtyMask, bool mayBeIdentical = false, bool mayBeZero = true)
 {
-	const char* err = ValidateArrayContents(in,num,dirtyMask);
+	const char* err = ValidateArrayContents(in,num,dirtyMask,
+		mayBeIdentical,mayBeZero);
 	if (err)
 	{
 		DefaultLogger::get()->error(std::string("FindInvalidDataProcess fails on mesh ") + name + ": " + err);
@@ -231,7 +236,7 @@ void FindInvalidDataProcess::ProcessAnimationChannel (aiNodeAnim* anim)
 	int i = 0;
 
 	// ScenePreprocessor's work ...
-	ai_assert(0 != anim->mPositionKeys && 0 != anim->mRotationKeys && 0 != anim->mScalingKeys);
+	ai_assert((0 != anim->mPositionKeys && 0 != anim->mRotationKeys && 0 != anim->mScalingKeys));
 
 	// Check whether all values in a tracks are identical - in this case
 	// we can remove al keys except one.
@@ -334,7 +339,7 @@ int FindInvalidDataProcess::ProcessMesh (aiMesh* pMesh)
 
 		// process mesh normals
 		if (pMesh->mNormals && ProcessArray(pMesh->mNormals,pMesh->mNumVertices,
-			"normals",dirtyMask))
+			"normals",dirtyMask,true,false))
 			ret = true;
 
 		// process mesh tangents

+ 583 - 27
code/IRRLoader.cpp

@@ -102,16 +102,445 @@ bool IRRImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
 	return false;
 }
 
+// ------------------------------------------------------------------------------------------------
+void IRRImporter::GetExtensionList(std::string& append)
+{
+	/*  NOTE: The file extenxsion .xml is too generic. We'll 
+	*  need to open the file in CanRead() and check whether it is 
+	*  a real irrlicht file
+	*/
+	append.append("*.xml;*.irr");
+}
+
+// ------------------------------------------------------------------------------------------------
+void IRRImporter::SetupProperties(const Importer* pImp)
+{
+	// read the output frame rate of all node animation channels
+	fps = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_IRR_ANIM_FPS,100);
+	if (!fps)
+	{
+		DefaultLogger::get()->error("IRR: Invalid FPS configuration");
+		fps = 100;
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+// Build a mesh tha consists of a single squad (a side of a skybox)
+aiMesh* IRRImporter::BuildSingleQuadMesh(const SkyboxVertex& v1,
+	const SkyboxVertex& v2,
+	const SkyboxVertex& v3,
+	const SkyboxVertex& v4)
+{
+	// allocate and prepare the mesh
+	aiMesh* out = new aiMesh();
+
+	out->mPrimitiveTypes = aiPrimitiveType_POLYGON;
+	out->mNumFaces = 1;
+
+	// build the face
+	out->mFaces    = new aiFace[1];
+	aiFace& face   = out->mFaces[0];
+	
+	face.mNumIndices = 4;
+	face.mIndices    = new unsigned int[4];
+	for (unsigned int i = 0; i < 4;++i)
+		face.mIndices[i] = i;
+
+	out->mNumVertices = 4;
+
+	// copy vertex positions
+	aiVector3D* vec = out->mVertices = new aiVector3D[4];
+	*vec++ = v1.position;
+	*vec++ = v2.position;
+	*vec++ = v3.position;
+	*vec   = v4.position;
+
+	// copy vertex normals
+	vec = out->mNormals = new aiVector3D[4];
+	*vec++ = v1.normal;
+	*vec++ = v2.normal;
+	*vec++ = v3.normal;
+	*vec   = v4.normal;
+
+	// copy texture coordinates
+	out->mTextureCoords[0] = new aiVector3D[4];
+	*vec++ = v1.uv;
+	*vec++ = v2.uv;
+	*vec++ = v3.uv;
+	*vec   = v4.uv;
+
+	return out;
+}
+
+// ------------------------------------------------------------------------------------------------
+void IRRImporter::BuildSkybox(std::vector<aiMesh*>& meshes, std::vector<aiMaterial*> materials)
+{
+	// Update the material of the skybox - replace the name
+	// and disable shading for skyboxes.
+	for (unsigned int i = 0; i < 6;++i)
+	{
+		MaterialHelper* out = ( MaterialHelper* ) (*(materials.end()-(6-i)));
+
+		aiString s;
+		s.length = ::sprintf( s.data, "SkyboxSide_%i",i );
+		out->AddProperty(&s,AI_MATKEY_NAME);
+
+		int shading = aiShadingMode_NoShading;
+		out->AddProperty(&shading,1,AI_MATKEY_SHADING_MODEL);
+	}
+
+	// Skyboxes are much more difficult. They are represented
+	// by six single planes with different textures, so we'll
+	// need to build six meshes.
+
+	const float l = 10.f; // the size used by Irrlicht
+
+	// FRONT SIDE
+	meshes.push_back( BuildSingleQuadMesh( 
+		SkyboxVertex(-l,-l,-l,  0, 0, 1,   1.f,1.f),
+		SkyboxVertex( l,-l,-l,  0, 0, 1,   0.f,1.f),
+		SkyboxVertex( l, l,-l,  0, 0, 1,   0.f,0.f),
+		SkyboxVertex(-l, l,-l,  0, 0, 1,   1.f,0.f)) );
+	meshes.back()->mMaterialIndex = materials.size()-6u;
+
+	// LEFT SIDE
+	meshes.push_back( BuildSingleQuadMesh( 
+		SkyboxVertex( l,-l,-l,  -1, 0, 0,   1.f,1.f),
+		SkyboxVertex( l,-l, l,  -1, 0, 0,   0.f,1.f),
+		SkyboxVertex( l, l, l,  -1, 0, 0,   0.f,0.f),
+		SkyboxVertex( l, l,-l,  -1, 0, 0,   1.f,0.f)) );
+	meshes.back()->mMaterialIndex = materials.size()-5u;
+
+	// BACK SIDE
+	meshes.push_back( BuildSingleQuadMesh( 
+		SkyboxVertex( l,-l, l,  0, 0, -1,   1.f,1.f),
+		SkyboxVertex(-l,-l, l,  0, 0, -1,   0.f,1.f),
+		SkyboxVertex(-l, l, l,  0, 0, -1,   0.f,0.f),
+		SkyboxVertex( l, l, l,  0, 0, -1,   1.f,0.f)) );
+	meshes.back()->mMaterialIndex = materials.size()-4u;
+
+	// RIGHT SIDE
+	meshes.push_back( BuildSingleQuadMesh( 
+		SkyboxVertex(-l,-l, l,  1, 0, 0,   1.f,1.f),
+		SkyboxVertex(-l,-l,-l,  1, 0, 0,   0.f,1.f),
+		SkyboxVertex(-l, l,-l,  1, 0, 0,   0.f,0.f),
+		SkyboxVertex(-l, l, l,  1, 0, 0,   1.f,0.f)) );
+	meshes.back()->mMaterialIndex = materials.size()-3u;
+
+	// TOP SIDE
+	meshes.push_back( BuildSingleQuadMesh( 
+		SkyboxVertex( l, l,-l,  0, -1, 0,   1.f,1.f),
+		SkyboxVertex( l, l, l,  0, -1, 0,   0.f,1.f),
+		SkyboxVertex(-l, l, l,  0, -1, 0,   0.f,0.f),
+		SkyboxVertex(-l, l,-l,  0, -1, 0,   1.f,0.f)) );
+	meshes.back()->mMaterialIndex = materials.size()-2u;
+
+	// BOTTOM SIDE
+	meshes.push_back( BuildSingleQuadMesh( 
+		SkyboxVertex( l,-l, l,  0,  1, 0,   0.f,0.f),
+		SkyboxVertex( l,-l,-l,  0,  1, 0,   1.f,0.f),
+		SkyboxVertex(-l,-l,-l,  0,  1, 0,   1.f,1.f),
+		SkyboxVertex(-l,-l,-l,  0,  1, 0,   0.f,1.f)) );
+	meshes.back()->mMaterialIndex = materials.size()-1u;
+}
+
+// ------------------------------------------------------------------------------------------------
+void IRRImporter::CopyMaterial(std::vector<aiMaterial*>	 materials,
+	std::vector< std::pair<aiMaterial*, unsigned int> >& inmaterials,
+	unsigned int& defMatIdx,
+	aiMesh* mesh)
+{
+	if (inmaterials.empty())
+	{
+		// Do we have a default material? If not we need to create one
+		if (0xffffffff == defMatIdx)
+		{
+			defMatIdx = (unsigned int)materials.size();
+			MaterialHelper* mat = new MaterialHelper();
+
+			aiString s;
+			s.Set(AI_DEFAULT_MATERIAL_NAME);
+			mat->AddProperty(&s,AI_MATKEY_NAME);
+
+			aiColor3D c(0.6f,0.6f,0.6f);
+			mat->AddProperty(&c,1,AI_MATKEY_COLOR_DIFFUSE);
+		}
+		mesh->mMaterialIndex = defMatIdx;
+		return;
+	}
+	else if (inmaterials.size() > 1)
+	{
+		DefaultLogger::get()->info("IRR: Skipping additional materials");
+	}
+
+	mesh->mMaterialIndex = (unsigned int)materials.size();
+	materials.push_back(inmaterials[0].first);
+}
+
+
+// ------------------------------------------------------------------------------------------------
+inline int ClampSpline(int idx, int size)
+{
+	return ( idx<0 ? size+idx : ( idx>=size ? idx-size : idx ) );
+}
+
+// ------------------------------------------------------------------------------------------------
+inline void FindSuitableMultiple(int& angle)
+{
+	if (angle < 3)angle = 3;
+	else if (angle < 10) angle = 10;
+	else if (angle < 20) angle = 20;
+	else if (angle < 30) angle = 30;
+	else
+	{
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+void IRRImporter::ComputeAnimations(Node* root, std::vector<aiNodeAnim*>& anims,
+	const aiMatrix4x4& transform)
+{
+	ai_assert(NULL != root);
+
+	if (root->animators.empty())return;
+
+	typedef std::pair< TemporaryAnim, Animator* > AnimPair;
+	const unsigned int resolution = 1;
+
+	std::vector<AnimPair> temp;
+	temp.reserve(root->animators.size());
+
+	for (std::list<Animator>::iterator it = root->animators.begin();
+		it != root->animators.end(); ++it)
+	{
+		if ((*it).type == Animator::UNKNOWN ||
+			(*it).type == Animator::OTHER)
+		{
+			DefaultLogger::get()->warn("IRR: Skipping unknown or unsupported animator");
+			continue;
+		}
+		temp.push_back(AnimPair(TemporaryAnim(),&(*it)));
+	}
+
+	if (temp.empty())return;
+
+	// All animators are applied one after another. We generate a set of
+	// transformation matrices for each of it. Then we combine all 
+	// transformation matrices, decompose them and build an output animation.
+
+	for (std::vector<AnimPair>::iterator it = temp.begin();
+		 it != temp.end(); ++it)
+	{
+		TemporaryAnim& out = (*it).first;
+		Animator* in       = (*it).second;
+
+		switch (in->type)
+		{
+		case Animator::ROTATION:
+			{
+				// -----------------------------------------------------
+				// find out how long a full rotation will take
+				// This is the least common multiple of 360.f and all 
+				// three euler angles. Although we'll surely find a 
+				// possible multiple (haha) it could be somewhat large
+				// for our purposes. So we need to modify the angles
+				// here in order to get good results.
+				// -----------------------------------------------------
+				int angles[3];
+				angles[0] = (int)(in->direction.x*100);
+				angles[1] = (int)(in->direction.y*100);
+				angles[2] = (int)(in->direction.z*100);
+
+				angles[0] %= 360;
+				angles[1] %= 360;
+				angles[2] %= 360;
+
+				FindSuitableMultiple(angles[0]);
+				FindSuitableMultiple(angles[1]);
+				FindSuitableMultiple(angles[2]);
+
+				int lcm = 360;
+
+				if (angles[0])
+					lcm  = boost::math::lcm(lcm,angles[0]);
+
+				if (angles[1])
+					lcm  = boost::math::lcm(lcm,angles[1]);
+
+				if (angles[2])
+					lcm  = boost::math::lcm(lcm,angles[2]);
+
+				if (360 == lcm)
+					break;
+
+				// This can be a division through zero, but we don't care
+				float f1 = (float)lcm / angles[0];
+				float f2 = (float)lcm / angles[1];
+				float f3 = (float)lcm / angles[2];
+				
+				// find out how many time units we'll need for the finest
+				// track (in seconds) - this defines the number of output
+				// keys (fps * seconds)
+				float max ;
+				if (angles[0])
+					max = (float)lcm / angles[0];
+				if (angles[1])
+					max = std::max(max, (float)lcm / angles[1]);
+				if (angles[2])
+					max = std::max(max, (float)lcm / angles[2]);
+
+				
+				// Allocate transformation matrices
+				out.SetupMatrices((unsigned int)(max*fps));
+
+				// begin with a zero angle
+				aiVector3D angle;
+				for (unsigned int i = 0; i < out.last;++i)
+				{
+					// build the rotation matrix for the given euler angles
+					aiMatrix4x4& m = out.matrices[i];
+
+					// we start with the node transformation
+					m = transform; 
+
+					aiMatrix4x4 m2;
+
+					if (angle.x)
+						m *= aiMatrix4x4::RotationX(angle.x,m2);
+
+					if (angle.y)
+						m *= aiMatrix4x4::RotationX(angle.y,m2);
+
+					if (angle.z)
+						m *= aiMatrix4x4::RotationZ(angle.z,m2);
+
+					// increase the angle
+					angle += in->direction;
+				}
+
+				// This animation is repeated and repeated ...
+				out.post = aiAnimBehaviour_REPEAT;
+			}
+			break;
+
+		case Animator::FLY_CIRCLE:
+			{
+				
+			}
+			break;
+
+		case Animator::FLY_STRAIGHT:
+			{
+				
+			}
+			break;
+
+		case Animator::FOLLOW_SPLINE:
+			{
+				out.post = aiAnimBehaviour_REPEAT;
+				const int size = (int)in->splineKeys.size();
+				if (!size)
+				{
+					// We have no point in the spline. That's bad. Really bad.
+					DefaultLogger::get()->warn("IRR: Spline animators with no points defined");
+					break;
+				}
+				else if (size == 1)
+				{
+					// We have just one point in the spline
+					out.SetupMatrices(1);
+					out.matrices[0].a4 = in->splineKeys[0].mValue.x;
+					out.matrices[0].b4 = in->splineKeys[0].mValue.y;
+					out.matrices[0].c4 = in->splineKeys[0].mValue.z;
+					break;
+				}
+
+				unsigned int ticksPerFull = 15;
+				out.SetupMatrices(ticksPerFull*fps);
+
+				for (unsigned int i = 0; i < out.last;++i)
+				{
+					aiMatrix4x4& m = out.matrices[i];
+
+					const float dt = (i * in->speed * 0.001f );
+					const float u = dt - floor(dt);
+					const int idx = (int)floor(dt) % size;
+
+					// get the 4 current points to evaluate the spline
+					const aiVector3D& p0 = in->splineKeys[ ClampSpline( idx - 1, size ) ].mValue;
+					const aiVector3D& p1 = in->splineKeys[ ClampSpline( idx + 0, size ) ].mValue; 
+					const aiVector3D& p2 = in->splineKeys[ ClampSpline( idx + 1, size ) ].mValue; 
+					const aiVector3D& p3 = in->splineKeys[ ClampSpline( idx + 2, size ) ].mValue;
+
+					// compute polynomials
+					const float u2 = u*u;
+					const float u3 = u2*2;
+
+					const float h1 = 2.0f * u3 - 3.0f * u2 + 1.0f;
+					const float h2 = -2.0f * u3 + 3.0f * u3;
+					const float h3 = u3 - 2.0f * u3;
+					const float h4 = u3 - u2;
+
+					// compute the spline tangents
+					const aiVector3D t1 = ( p2 - p0 ) * in->tightness;
+					aiVector3D t2 = ( p3 - p1 ) * in->tightness;
+
+					// and use them to get the interpolated point
+					t2 = (h1 * p1 + p2 * h2 + t1 * h3 + h4 * t2);
+
+					// build a simple translation matrix from it
+					m.a4 = t2.x;
+					m.b4 = t2.y;
+					m.c4 = t2.z;
+				}
+			}
+			break;
+		};
+	}
+
+
+	aiNodeAnim* out = new aiNodeAnim();
+	out->mNodeName.Set(root->name);
+
+	if (temp.size() == 1)
+	{
+		// If there's just one animator to be processed our 
+		// task is quite easy
+		TemporaryAnim& one = temp[0].first;
+		
+		out->mPostState = one.post;
+		out->mNumPositionKeys = one.last;
+		out->mNumScalingKeys  = one.last;
+		out->mNumRotationKeys = one.last;
+
+		out->mPositionKeys = new aiVectorKey[one.last];
+		out->mScalingKeys  = new aiVectorKey[one.last];
+		out->mRotationKeys = new aiQuatKey[one.last];
+
+		for (unsigned int i = 0; i < one.last;++i)
+		{
+			aiVectorKey& scaling  = out->mScalingKeys[i];
+			aiVectorKey& position = out->mPositionKeys[i];
+			aiQuatKey&   rotation = out->mRotationKeys[i];
+
+			scaling.mTime = position.mTime = rotation.mTime = (double)i;
+			one.matrices[i].Decompose(scaling.mValue, rotation.mValue, position.mValue);
+		}
+	}
+
+	// NOTE: It is possible that some of the tracks we're returning
+	// are dummy tracks, but the ScenePreprocessor will fix that, hopefully
+}
+
 // ------------------------------------------------------------------------------------------------
 void IRRImporter::GenerateGraph(Node* root,aiNode* rootOut ,aiScene* scene,
 	BatchLoader& batch,
 	std::vector<aiMesh*>&        meshes,
 	std::vector<aiNodeAnim*>&    anims,
-	std::vector<AttachmentInfo>& attach)
+	std::vector<AttachmentInfo>& attach,
+	std::vector<aiMaterial*>	 materials,
+	unsigned int&				 defMatIdx)
 {
-	// Setup the name of this node
-	rootOut->mName.Set(root->name);
-
 	unsigned int oldMeshSize = (unsigned int)meshes.size();
 
 	// Now determine the type of the node 
@@ -126,7 +555,8 @@ void IRRImporter::GenerateGraph(Node* root,aiNode* rootOut ,aiScene* scene,
 			aiScene* scene = batch.GetImport(root->meshPath);
 			if (!scene)
 			{
-				DefaultLogger::get()->error("IRR: Unable to load external file: " + root->meshPath);
+				DefaultLogger::get()->error("IRR: Unable to load external file: " 
+					+ root->meshPath);
 				break;
 			}
 			attach.push_back(AttachmentInfo(scene,rootOut));
@@ -150,12 +580,70 @@ void IRRImporter::GenerateGraph(Node* root,aiNode* rootOut ,aiScene* scene,
 
 				std::pair<aiMaterial*, unsigned int>& src = root->materials[i];
 				scene->mMaterials[i] = src.first;
+			}
+
+			// NOTE: Each mesh should have exactly one material assigned,
+			// but we do it in a separate loop if this behaviour changes
+			// in the future.
+			for (unsigned int i = 0; i < scene->mNumMeshes;++i)
+			{
+				// Process material flags 
+				aiMesh* mesh = scene->mMeshes[i];
+
+
+				// If "trans_vertex_alpha" mode is enabled, search all vertex colors 
+				// and check whether they have a common alpha value. This is quite
+				// often the case so we can simply extract it to a shared oacity
+				// value.
+				std::pair<aiMaterial*, unsigned int>& src = root->materials[
+					mesh->mMaterialIndex];
+
+				MaterialHelper* mat = (MaterialHelper*)src.first;
+				if (mesh->HasVertexColors(0) && 
+					src.second & AI_IRRMESH_MAT_trans_vertex_alpha)
+				{
+					bool bdo = true;
+					for (unsigned int a = 1; a < mesh->mNumVertices;++a)
+					{
+						if (mesh->mColors[0][a].a != mesh->mColors[0][a-1].a)
+						{
+							bdo = false;
+							break;
+						}
+					}
+					if (bdo)
+					{
+						DefaultLogger::get()->info("IRR: Replacing mesh vertex "
+							"alpha with common opacity");
 
-				// Process material flags (e.g. lightmapping)
+						for (unsigned int a = 0; a < mesh->mNumVertices;++a)
+							mesh->mColors[0][a].a = 1.f;
+
+						mat->AddProperty(& mesh->mColors[0][0].a, 1, AI_MATKEY_OPACITY);
+					}
+				}
+
+				// If we have a second texture coordinate set and a second texture
+				// (either lightmap, normalmap, 2layered material we need to
+				// setup the correct UV index for it). The texture can either
+				// be diffuse (lightmap & 2layer) or a normal map (normal & parallax)
+				if (mesh->HasTextureCoords(1))
+				{
+					int idx = 1;
+					if (src.second & (AI_IRRMESH_MAT_solid_2layer |
+						AI_IRRMESH_MAT_lightmap))
+					{
+						mat->AddProperty(&idx,1,AI_MATKEY_UVWSRC_DIFFUSE(0));
+					}
+					else if (src.second & AI_IRRMESH_MAT_normalmap_solid)
+					{
+						mat->AddProperty(&idx,1,AI_MATKEY_UVWSRC_NORMALS(0));
+					}
+				}
 			}
 		}
 		break;
-	
+
 	case Node::LIGHT:
 	case Node::CAMERA:
 
@@ -175,39 +663,85 @@ void IRRImporter::GenerateGraph(Node* root,aiNode* rootOut ,aiScene* scene,
 			else if (mul < 300)mul = 3;
 			else               mul = 4;
 
-			meshes.push_back(StandardShapes::MakeMesh(mul,&StandardShapes::MakeSphere));
+			meshes.push_back(StandardShapes::MakeMesh(mul,
+				&StandardShapes::MakeSphere));
 
 			// Adjust scaling
 			root->scaling *= root->sphereRadius;
+
+			// Copy one output material
+			CopyMaterial(materials, root->materials, defMatIdx, meshes.back());
 		}
 		break;
 
 	case Node::CUBE:
-	case Node::SKYBOX:
 		{
-			// Skyboxes and normal cubes - generate the cube first
-			meshes.push_back(StandardShapes::MakeMesh(&StandardShapes::MakeHexahedron));
+			// Generate an unit cube first
+			meshes.push_back(StandardShapes::MakeMesh(
+				&StandardShapes::MakeHexahedron));
 
 			// Adjust scaling
 			root->scaling *= root->sphereRadius;
+
+			// Copy one output material
+			CopyMaterial(materials, root->materials, defMatIdx, meshes.back());
+		}
+		break;
+
+
+	case Node::SKYBOX:
+		{
+			// A skybox is defined by six materials
+			if (root->materials.size() < 6)
+			{
+				DefaultLogger::get()->error("IRR: There should be six materials "
+					"for a skybox");
+
+				break;
+			}
+
+			// copy those materials and generate 6 meshes for our new skybox
+			materials.reserve(materials.size() + 6);
+			for (unsigned int i = 0; i < 6;++i)
+				materials.insert(materials.end(),root->materials[i].first);
+
+			BuildSkybox(meshes,materials);
+
+			// *************************************************************
+			// Skyboxes will require a different code path for rendering,
+			// so there must be a way for the user to add special support
+			// for IRR skyboxes. We add a 'IRR.SkyBox_' prefix to the node.
+			// *************************************************************
+			root->name = "IRR.SkyBox_" + root->name;
+			DefaultLogger::get()->info("IRR: Loading skybox, this will "
+				"require special handling to be displayed correctly");
 		}
 		break;
 
 	case Node::TERRAIN:
 		{
+			// to support terrains, we'd need to have a texture decoder
+			DefaultLogger::get()->error("IRR: Unsupported node - TERRAIN");
 		}
 		break;
 	};
 
-	// Check whether we added a mesh. In this case we'll also
-	// need to attach it to the node
+	// Check whether we added a mesh (or more than one ...). In this case 
+	// we'll also need to attach it to the node
 	if (oldMeshSize != (unsigned int) meshes.size())
 	{
-		rootOut->mNumMeshes = 1;
-		rootOut->mMeshes    = new unsigned int[1];
-		rootOut->mMeshes[0] = oldMeshSize;
+		rootOut->mNumMeshes = (unsigned int)meshes.size() - oldMeshSize;
+		rootOut->mMeshes    = new unsigned int[rootOut->mNumMeshes];
+
+		for (unsigned int a = 0; a  < rootOut->mNumMeshes;++a)
+		{
+			rootOut->mMeshes[a] = oldMeshSize+a;
+		}
 	}
 
+	// Setup the name of this node
+	rootOut->mName.Set(root->name);
+
 	// Now compute the final local transformation matrix of the
 	// node from the given translation, rotation and scaling values.
 	// (the rotation is given in Euler angles, XYZ order)
@@ -233,6 +767,9 @@ void IRRImporter::GenerateGraph(Node* root,aiNode* rootOut ,aiScene* scene,
 	mat.b4 = root->position.y; 
 	mat.c4 = root->position.z;
 
+	// now compute animations for the node
+	ComputeAnimations(root,anims,mat);
+
 	// Add all children recursively. First allocate enough storage
 	// for them, then call us again
 	rootOut->mNumChildren = (unsigned int)root->children.size();
@@ -243,7 +780,8 @@ void IRRImporter::GenerateGraph(Node* root,aiNode* rootOut ,aiScene* scene,
 		{
 			aiNode* node = rootOut->mChildren[i] =  new aiNode();
 			node->mParent = rootOut;
-			GenerateGraph(root->children[i],node,scene,batch,meshes,anims,attach);
+			GenerateGraph(root->children[i],node,scene,batch,meshes,
+				anims,attach,materials,defMatIdx);
 		}
 	}
 }
@@ -286,7 +824,7 @@ void IRRImporter::InternReadFile( const std::string& pFile,
 	lights.reserve(5);
 
 	bool inMaterials = false, inAnimator = false;
-	unsigned int guessedAnimCnt = 0, guessedMeshCnt = 0;
+	unsigned int guessedAnimCnt = 0, guessedMeshCnt = 0, guessedMatCnt = 0;
 
 	// Parse the XML file
 	while (reader->read())
@@ -297,6 +835,7 @@ void IRRImporter::InternReadFile( const std::string& pFile,
 			
 			if (!ASSIMP_stricmp(reader->getNodeName(),"node"))
 			{
+				// ***********************************************************************
 				/*  What we're going to do with the node depends
 				 *  on its type:
 				 *
@@ -310,8 +849,10 @@ void IRRImporter::InternReadFile( const std::string& pFile,
 				 *  "empty" - A dummy node
 				 *  "camera" - A camera
 				 *
-				 *  Each of these nodes can be animated.
+				 *  Each of these nodes can be animated and all can have multiple
+				 *  materials assigned (except lights, cameras and dummies, of course).
 				 */
+				// ***********************************************************************
 				const char* sz = reader->getAttributeValueSafe("type");
 				Node* nd;
 				if (!ASSIMP_stricmp(sz,"mesh"))
@@ -398,8 +939,9 @@ void IRRImporter::InternReadFile( const std::string& pFile,
 
 				Animator* curAnim = NULL;
 
-				if (inMaterials && curNode->type == Node::ANIMMESH ||
-					curNode->type == Node::MESH )
+				// FIX: Materials can occur for nearly any type of node
+				if (inMaterials /* && curNode->type == Node::ANIMMESH ||
+					curNode->type == Node::MESH  */)
 				{
 					/*  This is a material description - parse it!
 					 */
@@ -407,6 +949,8 @@ void IRRImporter::InternReadFile( const std::string& pFile,
 					std::pair< aiMaterial*, unsigned int >& p = curNode->materials.back();
 
 					p.first = ParseMaterial(p.second);
+
+					++guessedMatCnt;
 					continue;
 				}
 				else if (inAnimator)
@@ -467,6 +1011,14 @@ void IRRImporter::InternReadFile( const std::string& pFile,
 									else if (prop.name == "Direction")
 									{
 										curAnim->direction = prop.value;
+
+										// From Irrlicht source - a workaround for backward
+										// compatibility with Irrlicht 1.1
+										if (curAnim->direction == aiVector3D())
+										{
+											curAnim->direction = aiVector3D(0.f,1.f,0.f);
+										}
+										else curAnim->direction.Normalize();
 									}
 								}
 								else if (curAnim->type == Animator::FLY_STRAIGHT)
@@ -781,18 +1333,22 @@ void IRRImporter::InternReadFile( const std::string& pFile,
 	::memcpy(tempScene->mLights,&lights[0],sizeof(void*)*tempScene->mNumLights);
 
 	// temporary data
-	std::vector< aiNodeAnim*> anims;
-	std::vector< AttachmentInfo > attach;
-	std::vector<aiMesh*> meshes;
+	std::vector< aiNodeAnim*>		anims;
+	std::vector< aiMaterial*>		materials;
+	std::vector< AttachmentInfo >	attach;
+	std::vector<aiMesh*>			meshes;
 
-	anims.reserve(guessedAnimCnt + (guessedAnimCnt >> 2));
-	meshes.reserve(guessedMeshCnt + (guessedMeshCnt >> 2));
+	// try to guess how much storage we'll need
+	anims.reserve     (guessedAnimCnt + (guessedAnimCnt >> 2));
+	meshes.reserve    (guessedMeshCnt + (guessedMeshCnt >> 2));
+	materials.reserve (guessedMatCnt  + (guessedMatCnt >> 2));
 
 	/* Now process our scenegraph recursively: generate final
 	 * meshes and generate animation channels for all nodes.
 	 */
+	unsigned int defMatIdx = 0xffffffff;
 	GenerateGraph(root,tempScene->mRootNode, tempScene,
-		batch, meshes, anims, attach);
+		batch, meshes, anims, attach, materials, defMatIdx);
 
 	if (!anims.empty())
 	{

+ 115 - 20
code/IRRLoader.h

@@ -38,8 +38,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ----------------------------------------------------------------------
 */
 
+
 /** @file Declaration of the .irrMesh (Irrlight Engine Mesh Format)
-    importer class. */
+    importer class.
+ */
 #ifndef AI_IRRLOADER_H_INCLUDED
 #define AI_IRRLOADER_H_INCLUDED
 
@@ -78,27 +80,21 @@ public:
 protected:
 
 	// -------------------------------------------------------------------
-	/** Called by Importer::GetExtensionList() for each loaded importer.
-	 * See BaseImporter::GetExtensionList() for details
+	/**
 	 */
-	void GetExtensionList(std::string& append)
-	{
-
-		/*  NOTE: The file extenxsion .xml is too generic. We'll 
-		 *  need to open the file in CanRead() and check whether it is 
-		 *  a real irrlicht file
-		 */
-
-		append.append("*.xml;*.irr");
-	}
+	void GetExtensionList(std::string& append);
 
 	// -------------------------------------------------------------------
-	/** Imports the given file into the given scene structure. 
-	 * See BaseImporter::InternReadFile() for details
+	/** 
 	 */
 	void InternReadFile( const std::string& pFile, aiScene* pScene, 
 		IOSystem* pIOHandler);
 
+	// -------------------------------------------------------------------
+	/** 
+	*/
+	void SetupProperties(const Importer* pImp);
+
 private:
 
 	/** Data structure for a scenegraph node animator
@@ -170,7 +166,6 @@ private:
 		Node(ET t)
 			:	type				(t)
 			,	scaling				(1.f,1.f,1.f) // assume uniform scaling by default
-			,	animation			(NULL)
 			,	framesPerSecond		(0.f)
 			,	sphereRadius		(1.f)
 			,	spherePolyCountX	(100)
@@ -202,9 +197,6 @@ private:
 		// Parent node
 		Node* parent;
 
-		// Animation channels that belongs to this node
-		aiNodeAnim* animation;
-
 		// Animated meshes: frames per second
 		// 0.f if not specified
 		float framesPerSecond;
@@ -226,14 +218,117 @@ private:
 		std::list<Animator> animators;
 	};
 
+	/** Data structure for a vertex in an IRR skybox
+	 */
+	struct SkyboxVertex
+	{
+		SkyboxVertex()
+		{}
+
+		//! Construction from single vertex components
+		SkyboxVertex(float px, float py, float pz,
+			float nx, float ny, float nz, 
+			float uvx, float uvy)
+
+			:	position	(px,py,pz)
+			,	normal		(nx,ny,nz)
+			,	uv			(uvx,uvy,0.f)
+		{}
+
+		aiVector3D position, normal, uv;
+	};
+
+	/** Temporary data structure to describe an IRR animator
+	 *
+	 *  Irrlicht animations always start at the beginning, so
+	 *  we don't need "first" and "pre" for the moment.
+	 */
+	struct TemporaryAnim
+	{
+		TemporaryAnim()
+			:	last		(0)
+			,	post		(aiAnimBehaviour_DEFAULT)
+			,	matrices	(NULL)
+		{}
+
+		~TemporaryAnim()
+		{
+			delete[] matrices;
+		}
+
+		void SetupMatrices(unsigned int num)
+		{
+			last = num;
+			matrices = new aiMatrix4x4[num];
+		}
+
+		unsigned int last;   
+		aiAnimBehaviour post;
 
+		aiMatrix4x4* matrices;
+	};
+
+	// -------------------------------------------------------------------
 	/** Fill the scenegraph recursively
 	 */
 	void GenerateGraph(Node* root,aiNode* rootOut ,aiScene* scene,
 		BatchLoader& batch,
 		std::vector<aiMesh*>& meshes,
 		std::vector<aiNodeAnim*>& anims,
-		std::vector<AttachmentInfo>& attach);
+		std::vector<AttachmentInfo>& attach,
+		std::vector<aiMaterial*> materials,
+		unsigned int& defaultMatIdx);
+
+
+	// -------------------------------------------------------------------
+	/** Generate a mesh that consists of just a single quad
+	 */
+	aiMesh* BuildSingleQuadMesh(const SkyboxVertex& v1,
+		const SkyboxVertex& v2,
+		const SkyboxVertex& v3,
+		const SkyboxVertex& v4);
+
+
+	// -------------------------------------------------------------------
+	/** Build a skybox 
+	 *
+	 *  @param meshes Receives 6 output meshes
+	 *  @param materials The last 6 materials are assigned to the newly
+	 *    created meshes. The names of the materials are adjusted.
+	 */
+	void BuildSkybox(std::vector<aiMesh*>& meshes, 
+		std::vector<aiMaterial*> materials);
+
+
+	// -------------------------------------------------------------------
+	/** Copy a material for a mesh to the output material list 
+	 *
+	 *  @param materials Receives an output material
+	 *  @param inmaterials List of input materials
+	 *  @param defMatIdx Default material index - 0xffffffff if not there
+	 *  @param mesh Mesh to work on
+	 */
+	void CopyMaterial(std::vector<aiMaterial*>	 materials,
+		std::vector< std::pair<aiMaterial*, unsigned int> >& inmaterials,
+		unsigned int& defMatIdx,
+		aiMesh* mesh);
+
+
+	// -------------------------------------------------------------------
+	/** Compute animations for a specific node
+	 *
+	 *  @param root Node to be processed
+	 *  @param anims The list of output animations
+	 *  @param transform Transformation matrix of the current node
+	 *    (relative to the parent's coordinate space)
+	 */
+	void ComputeAnimations(Node* root, std::vector<aiNodeAnim*>& anims,
+		const aiMatrix4x4& transform);
+
+
+private:
+
+	unsigned int fps;
 
 };
 

+ 1 - 8
code/IRRMeshLoader.cpp

@@ -870,20 +870,13 @@ void IRRMeshImporter::InternReadFile( const std::string& pFile,
 					DefaultLogger::get()->error("IRRMESH: Not enough indices");
 
 				// Finish processing the mesh - do some small material workarounds
-				if (curMatFlags == AI_IRRMESH_MAT_trans_vertex_alpha && !useColors)
+				if (curMatFlags & AI_IRRMESH_MAT_trans_vertex_alpha && !useColors)
 				{
 					// Take the opacity value of the current material
 					// from the common vertex color alpha
 					MaterialHelper* mat = (MaterialHelper*)curMat;
 					mat->AddProperty(&curColors[0].a,1,AI_MATKEY_OPACITY);
 				}
-
-				// store the material flags for later use
-				curMesh->mNumUVComponents[3] = curMatFlags;
-				if ( curMatFlags & AI_IRRMESH_MAT_lightmap )
-				{
-					needLightMap = true;
-				}
 			}}
 			break;
 

+ 7 - 3
code/LWOFileData.h

@@ -583,9 +583,10 @@ typedef std::vector	<	Clip			>	ClipList;
 struct Layer
 {
 	Layer()
-		: mFaceIDXOfs(0)
-		, mPointIDXOfs(0)
-		, mParent (0x0)
+		: mFaceIDXOfs	(0)
+		, mPointIDXOfs	(0)
+		, mParent		(0x0)
+		, skip			(false)
 	{}
 
 	/** Temporary point list from the file */
@@ -622,6 +623,9 @@ struct Layer
 
 	/** Pivot point of the layer */
 	aiVector3D mPivot;
+
+	/** Skip this layer? */
+	bool skip;
 };
 
 typedef std::list<LWO::Layer>		LayerList;

+ 69 - 25
code/LWOLoader.cpp

@@ -122,20 +122,22 @@ void LWOImporter::InternReadFile( const std::string& pFile,
 
 	mFileBuffer = &mBuffer[0] + 12;
 	fileSize -= 12;
+	hasNamedLayer = false;
 
 	// create temporary storage on the stack but store pointers to it in the class 
 	// instance. Therefore everything will be destructed properly if an exception 
 	// is thrown and we needn't take care of that.
-	LayerList _mLayers;
-	mLayers = &_mLayers;
-	TagList _mTags;				
-	mTags = &_mTags;
+	LayerList		_mLayers;
+	SurfaceList		_mSurfaces;		
+	TagList			_mTags;		
 	TagMappingTable _mMapping;	
-	mMapping = &_mMapping;
-	SurfaceList _mSurfaces;		
-	mSurfaces = &_mSurfaces;
 
-	// allocate a default layer
+	mLayers			= &_mLayers;
+	mTags			= &_mTags;
+	mMapping		= &_mMapping;
+	mSurfaces		= &_mSurfaces;
+
+	// Allocate a default layer (layer indices are 1-based from now)
 	mLayers->push_back(Layer());
 	mCurLayer = &mLayers->back();
 	mCurLayer->mName = "<LWODefault>";
@@ -146,7 +148,7 @@ void LWOImporter::InternReadFile( const std::string& pFile,
 		DefaultLogger::get()->info("LWO file format: LWOB (<= LightWave 5.5)");
 
 		mIsLWO2 = false;
-		this->LoadLWOBFile();
+		LoadLWOBFile();
 	}
 
 	// new lightwave format
@@ -155,7 +157,19 @@ void LWOImporter::InternReadFile( const std::string& pFile,
 		DefaultLogger::get()->info("LWO file format: LWO2 (>= LightWave 6)");
 
 		mIsLWO2 = true;
-		this->LoadLWO2File();
+		LoadLWO2File();
+
+		// The newer lightwave format allows the user to configure the
+		// loader that just one layer is used. If this is the case
+		// we need to check now whether the requested layer has been found.
+		if (0xffffffff != configLayerIndex && configLayerIndex > mLayers->size())
+			throw new ImportErrorException("LWO2: The requested layer was not found");
+
+		if (configLayerName.length() && !hasNamedLayer)
+		{
+			throw new ImportErrorException("LWO2: Unable to find the requested layer: " 
+				+ configLayerName);
+		}
 	}
 
 	// we don't know this format
@@ -176,15 +190,15 @@ void LWOImporter::InternReadFile( const std::string& pFile,
 	// now process all layers and build meshes and nodes
 	std::vector<aiMesh*> apcMeshes;
 	std::vector<aiNode*> apcNodes;
-	apcNodes.reserve(mLayers->size());
+	apcNodes. reserve(mLayers->size());
 	apcMeshes.reserve(mLayers->size()*std::min(((unsigned int)mSurfaces->size()/2u), 1u));
 
-
 	unsigned int iDefaultSurface = 0xffffffff; // index of the default surface
 	for (LayerList::iterator lit = mLayers->begin(), lend = mLayers->end();
 		lit != lend;++lit)
 	{
 		LWO::Layer& layer = *lit;
+		if (layer.skip)continue;
 
 		// I don't know whether there could be dummy layers, but it would be possible
 		const unsigned int meshStart = (unsigned int)apcMeshes.size();
@@ -218,15 +232,6 @@ void LWOImporter::InternReadFile( const std::string& pFile,
 				pSorted[idx].push_back(i);
 			}
 			if (0xffffffff == iDefaultSurface)pSorted.erase(pSorted.end()-1);
-
-			// now generate output meshes
-			for (unsigned int p = 0; p < mSurfaces->size();++p)
-				if (!pSorted[p].empty())pScene->mNumMeshes++;
-
-			if (!pScene->mNumMeshes)
-				throw new ImportErrorException("LWO: There are no meshes");
-
-			pScene->mMeshes = new aiMesh*[pScene->mNumMeshes];
 			for (unsigned int p = 0,i = 0;i < mSurfaces->size();++i)
 			{
 				SortedRep& sorted = pSorted[i];
@@ -311,7 +316,7 @@ void LWOImporter::InternReadFile( const std::string& pFile,
 							aiVector3D*& pp = pvUV[w];
 							const aiVector2D& src = ((aiVector2D*)&layer.mUVChannels[vUVChannelIndices[w]].rawData[0])[idx];
 							pp->x = src.x;
-							pp->y = src.y; // DX to OGL
+							pp->y = src.y; 
 							pp++;
 						}
 
@@ -338,8 +343,9 @@ void LWOImporter::InternReadFile( const std::string& pFile,
 					pf++;
 				}
 
-				// compute normal vectors for the mesh - we can't use our GenSmoothNormal-Step here
-				// since it wouldn't handle smoothing groups correctly
+				// compute normal vectors for the mesh - we can't use our GenSmoothNormal-
+				// Step here since it wouldn't handle smoothing groups correctly for LWO.
+				// So we use a separate implementation.
 				ComputeNormals(mesh,smoothingGroups,_mSurfaces[i]);
 
 				++p;
@@ -358,6 +364,9 @@ void LWOImporter::InternReadFile( const std::string& pFile,
 			pcNode->mMeshes[p] = p + meshStart;
 	}
 
+	if (apcNodes.empty() || apcMeshes.empty())
+		throw new ImportErrorException("LWO: No meshes loaded");
+
 	// the RemoveRedundantMaterials step will clean this up later
 	pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials = (unsigned int)mSurfaces->size()];
 	for (unsigned int mat = 0; mat < pScene->mNumMaterials;++mat)
@@ -501,6 +510,7 @@ void LWOImporter::ComputeNormals(aiMesh* mesh, const std::vector<unsigned int>&
 // ------------------------------------------------------------------------------------------------
 void LWOImporter::AddChildren(aiNode* node, uintptr_t parent, std::vector<aiNode*>& apcNodes)
 {
+	parent -= 1;
 	for (uintptr_t i  = 0; i < (uintptr_t)apcNodes.size();++i)
 	{
 		if (i == parent)continue;
@@ -987,11 +997,15 @@ void LWOImporter::LoadLWO2Clip(unsigned int length)
 	switch (head->type)
 	{
 	case AI_LWO_STIL:
+
+		// "Normal" texture
 		GetS0(clip.path,head->length);
 		clip.type = Clip::STILL;
 		break;
 
 	case AI_LWO_ISEQ:
+
+		// Image sequence. We'll later take the first.
 		{
 			uint8_t digits = GetU1();  mFileBuffer++;
 			int16_t offset = GetU2();  mFileBuffer+=4;
@@ -999,7 +1013,8 @@ void LWOImporter::LoadLWO2Clip(unsigned int length)
 
 			std::string s;std::stringstream ss;
 			GetS0(s,head->length);
-      head->length -= (unsigned int)s.length()+1;
+
+			head->length -= (unsigned int)s.length()+1;
 			ss << s;
 			ss << std::setw(digits) << offset + start;
 			GetS0(s,head->length);
@@ -1018,6 +1033,8 @@ void LWOImporter::LoadLWO2Clip(unsigned int length)
 		break;
 
 	case AI_LWO_XREF:
+
+		// Just a cross-reference to another CLIp
 		clip.type = Clip::REF;
 		clip.clipRef = GetU4();
 		break;
@@ -1030,6 +1047,8 @@ void LWOImporter::LoadLWO2Clip(unsigned int length)
 // ------------------------------------------------------------------------------------------------
 void LWOImporter::LoadLWO2File()
 {
+	bool skip = false;
+
 	LE_NCONST uint8_t* const end = mFileBuffer + fileSize;
 	while (true)
 	{
@@ -1043,6 +1062,7 @@ void LWOImporter::LoadLWO2File()
 		}
 		uint8_t* const next = mFileBuffer+head->length;
 		unsigned int iUnnamed = 0;
+
 		switch (head->type)
 		{
 			// new layer
@@ -1053,6 +1073,15 @@ void LWOImporter::LoadLWO2File()
 				LWO::Layer& layer = mLayers->back();
 				mCurLayer = &layer;
 
+				// load this layer or ignore it? Check the layer index property
+				// NOTE: The first layer is the default layer, so the layer
+				// index is one-based now
+				if (0xffffffff != configLayerIndex && configLayerIndex != mLayers->size())
+				{
+					skip = true;
+				}
+				else skip = false;
+
 				AI_LWO_VALIDATE_CHUNK_LENGTH(head->length,LAYR,16);
 
 				// and parse its properties, e.g. the pivot point
@@ -1071,6 +1100,13 @@ void LWOImporter::LoadLWO2File()
 					layer.mName = buffer;
 				}
 
+				// load this layer or ignore it? Check the layer name property
+				if (configLayerName.length() && configLayerName != layer.mName)
+				{
+					skip = true;
+				}
+				else hasNamedLayer = true;
+
 				if (mFileBuffer + 2 <= next)
 					layer.mParent = GetU2();
 
@@ -1080,6 +1116,8 @@ void LWOImporter::LoadLWO2File()
 			// vertex list
 		case AI_LWO_PNTS:
 			{
+				if (skip)break;
+
 				unsigned int old = (unsigned int)mCurLayer->mTempPoints.size();
 				LoadLWOPoints(head->length);
 				mCurLayer->mPointIDXOfs = old;
@@ -1095,6 +1133,8 @@ void LWOImporter::LoadLWO2File()
 			// --- intentionally no break here
 		case AI_LWO_VMAP:
 			{
+				if (skip)break;
+
 				if (mCurLayer->mTempPoints.empty())
 					DefaultLogger::get()->warn("LWO2: Unexpected VMAP chunk");
 				else LoadLWO2VertexMap(head->length,head->type == AI_LWO_VMAD);
@@ -1103,6 +1143,8 @@ void LWOImporter::LoadLWO2File()
 			// face list
 		case AI_LWO_POLS:
 			{
+				if (skip)break;
+
 				unsigned int old = (unsigned int)mCurLayer->mFaces.size();
 				LoadLWO2Polygons(head->length);
 				mCurLayer->mFaceIDXOfs = old;
@@ -1111,6 +1153,8 @@ void LWOImporter::LoadLWO2File()
 			// polygon tags 
 		case AI_LWO_PTAG:
 			{
+				if (skip)break;
+
 				if (mCurLayer->mFaces.empty())
 					DefaultLogger::get()->warn("LWO2: Unexpected PTAG");
 				else LoadLWO2PolygonTags(head->length);

+ 1 - 0
code/LWOLoader.h

@@ -386,6 +386,7 @@ protected:
 	bool configSpeedFlag;
 	unsigned int configLayerIndex;
 	std::string  configLayerName;
+	bool hasNamedLayer;
 };
 
 

+ 16 - 13
code/MDRFileData.h

@@ -57,11 +57,12 @@ namespace MDR {
 #define AI_MDR_MAGIC_NUMBER_BE	MDR_MAKE("RDM5")
 #define AI_MDR_MAGIC_NUMBER_LE	MDR_MAKE("5MDR")
 
-// common limitations
+// common limitations for MDR - not validated for the moment
 #define AI_MDR_VERSION			2
 #define AI_MDR_MAXQPATH			64
 #define	AI_MDR_MAX_BONES		128
 
+
 // ---------------------------------------------------------------------------
 /** \brief Data structure for a vertex weight in a MDR file
  */
@@ -146,6 +147,9 @@ struct Frame {
 	aiVector3D	localOrigin;		// midpoint of bounds, used for sphere cull
 	float		radius;				// dist from localOrigin to corner
 	char		name[16];
+
+	// bones follow here
+
 } PACK_STRUCT;
 
 
@@ -165,8 +169,7 @@ struct CompFrame
 {
         aiVector3D  bounds0,bounds1;	// bounds of all surfaces of all LOD's for this frame
         aiVector3D  localOrigin;		// midpoint of bounds, used for sphere cull
-        float      radius;			// dist from localOrigin to corner
-        CompBone   bones[1];		// [numBones]
+        float      radius;				// dist from localOrigin to corner
 } PACK_STRUCT;
 
 
@@ -196,24 +199,24 @@ struct Tag
  */
 struct Header
 {
-	uint32_t	ident;
-	uint32_t	version;
+	int32_t	ident;
+	int32_t	version;
 
 	char		name[AI_MDR_MAXQPATH];	
 
 	// frames and bones are shared by all levels of detail
-	uint32_t	numFrames;
-	uint32_t	numBones;
-	uint32_t	ofsFrames;			
+	int32_t	numFrames;
+	int32_t	numBones;
+	int32_t	ofsFrames;			
 
 	// each level of detail has completely separate sets of surfaces
-	uint32_t	numLODs;
-	uint32_t	ofsLODs;
+	int32_t	numLODs;
+	int32_t	ofsLODs;
 
-    uint32_t    numTags;
-	uint32_t    ofsTags;
+    int32_t    numTags;
+	int32_t    ofsTags;
 
-	uint32_t	ofsEnd;				
+	int32_t	ofsEnd;				
 } PACK_STRUCT;
 
 

+ 107 - 31
code/MDRLoader.cpp

@@ -47,6 +47,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 using namespace Assimp;
 using namespace Assimp::MDR;
 
+
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
 MDRImporter::MDRImporter()
@@ -76,10 +77,39 @@ bool MDRImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
 			 extension[3] != 'r' && extension[3] != 'R');
 }
 
+// ------------------------------------------------------------------------------------------------
+// Uncompress a matrix
+void MDRImporter::MatrixUncompress(aiMatrix4x4& mat,const uint8_t * compressed)
+{
+	int value;
+
+	// First decompress the translation part
+	for (unsigned int n = 0; n < 3;++n)
+	{
+		value     = (int)((uint16_t *)(compressed))[n];
+		mat[0][n] = ((float)(value-(1<<15)))/64.f;
+	}
+
+	// Then decompress the rotation matrix
+	for (unsigned int n = 0, p = 3; n < 3;++n)
+	{
+		for (unsigned int m = 0; m < 3;++m,++p)
+		{
+			value =  (int)((uint16_t *)(compressed))[p];
+			mat[n][m]=((float)(value-(1<<15)))*(1.0f/(float)((1<<(15))-2));
+		}
+	}
+
+	// now zero the final row of the matrix
+	mat[3][0] = mat[3][1] = mat[3][2] = 0.f;
+	mat[3][3] = 1.f;
+}
+
 // ------------------------------------------------------------------------------------------------
 // Validate the header of the given MDR file
 void MDRImporter::ValidateHeader()
 {
+	// Check the magic word - '5MDR'
 	if (pcHeader->ident != AI_MDR_MAGIC_NUMBER_BE &&
 		pcHeader->ident != AI_MDR_MAGIC_NUMBER_LE)
 	{
@@ -94,39 +124,70 @@ void MDRImporter::ValidateHeader()
 			"magic word found is " + std::string( szBuffer ));
 	}
 
+	// Big endian - swap the fields in the header
+	AI_SWAP4(pcHeader->numBones);
+	AI_SWAP4(pcHeader->numFrames);
+	AI_SWAP4(pcHeader->ofsFrames);
+	AI_SWAP4(pcHeader->ofsLODs);
+	AI_SWAP4(pcHeader->ofsTags);
+	AI_SWAP4(pcHeader->version);
+	AI_SWAP4(pcHeader->numTags);
+	AI_SWAP4(pcHeader->numLODs);
+
+	// MDR file version should always be 2
 	if (pcHeader->version != AI_MDR_VERSION)
-		DefaultLogger::get()->warn("Unsupported MDR file version (2 (AI_MDR_VERSION) was expected)");
+		DefaultLogger::get()->warn("Unsupported MDR file version (2 was expected)");
 
+	// We compute the vertex positions from the bones,
+	// so we need at least one bone.
 	if (!pcHeader->numBones)
 		DefaultLogger::get()->warn("MDR: At least one bone must be there");
 
-	// validate all LODs
-	if (pcHeader->ofsLODs > fileSize) 
+	// We should have at least the first LOD in the valid range
+	if (pcHeader->ofsLODs > (int)fileSize) 
+		throw new ImportErrorException("MDR: header is invalid - LOD out of range");
+
+	// header::ofsFrames is negative if the frames are compressed
+	if (pcHeader->ofsFrames < 0)
+	{
+		// Ugly, but it will be our only change to make further
+		// reading easier
+		int32_t* p = const_cast<int32_t*>(&pcHeader->ofsFrames);
+		*p = -pcHeader->ofsFrames;
+		compressed = true;
+		DefaultLogger::get()->info("MDR: Compressed frames");
+	}
+	else compressed = false;
 
 	// validate all frames
-	if (pcHeader->ofsFrames + sizeof(MDR::Frame) * (pcHeader->numBones-1) *
-		sizeof(MDR::Bone) * pcHeader->numFrames > fileSize)
+	if ( pcHeader->ofsFrames +    sizeof(MDR::Frame) * 
+		(pcHeader->numBones -1) * sizeof(MDR::Bone)  * 
+		 pcHeader->numFrames > fileSize)
 	{
 		throw new ImportErrorException("MDR: header is invalid - frame out of range");
 	}
 
-	// check whether the requested frame is existing
-	if (this->configFrameID >= pcHeader->numFrames)
+	// Check whether the requested frame is existing
+	if (configFrameID >= (unsigned int) pcHeader->numFrames)
 		throw new ImportErrorException("The requested frame is not available");
 }
 
 // ------------------------------------------------------------------------------------------------
-// Validate the header of a given MDR file LOD
+// Validate the surface header of a given MDR file LOD
 void MDRImporter::ValidateLODHeader(BE_NCONST MDR::LOD* pcLOD)
 {
 	AI_SWAP4(pcLOD->ofsSurfaces);
 	AI_SWAP4(pcLOD->numSurfaces);
 	AI_SWAP4(pcLOD->ofsEnd);
 
-    const unsigned int iMax = this->fileSize - (unsigned int)((int8_t*)pcLOD-(int8_t*)pcHeader);
+    const unsigned int iMax = fileSize - (unsigned int)((int8_t*)pcLOD-(int8_t*)pcHeader);
 
+	// We should have at least one surface here
 	if (!pcLOD->numSurfaces)
 		throw new ImportErrorException("MDR: LOD has zero surfaces assigned");
+
+	if (pcLOD->ofsSurfaces > iMax)
+		throw new ImportErrorException("MDR: LOD header is invalid - surface out of range");
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -143,9 +204,10 @@ void MDRImporter::ValidateSurfaceHeader(BE_NCONST MDR::Surface* pcSurf)
 	AI_SWAP4(pcSurf->ofsVerts);
 	AI_SWAP4(pcSurf->shaderIndex);
 
-    const unsigned int iMax = this->fileSize - (unsigned int)((int8_t*)pcSurf-(int8_t*)pcHeader);
+	// Find out how many bytes
+    const unsigned int iMax = fileSize - (unsigned int)((int8_t*)pcSurf-(int8_t*)pcHeader);
 
-	// not exact - there could be extra data in the vertices.
+	// Not exact - there could be extra data in the vertices.
 	if (pcSurf->ofsTriangles + pcSurf->numTriangles*sizeof(MDR::Triangle) > iMax ||
 		pcSurf->ofsVerts + pcSurf->numVerts*sizeof(MDR::Vertex) > iMax)
     {
@@ -157,13 +219,14 @@ void MDRImporter::ValidateSurfaceHeader(BE_NCONST MDR::Surface* pcSurf)
 // Setup configuration properties
 void MDRImporter::SetupProperties(const Importer* pImp)
 {
-	// The AI_CONFIG_IMPORT_MDR_KEYFRAME option overrides the
+	// **************************************************************
+	// The AI_CONFIG_IMPORT_MDR_KEYFRAME    option overrides the
 	//     AI_CONFIG_IMPORT_GLOBAL_KEYFRAME option.
-	if(0xffffffff == (this->configFrameID = pImp->GetPropertyInteger(
-		AI_CONFIG_IMPORT_MDR_KEYFRAME,0xffffffff)))
-	{
-		this->configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_GLOBAL_KEYFRAME,0);
-	}
+	// **************************************************************
+	configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MDR_KEYFRAME,0xffffffff);
+
+	if(0xffffffff == configFrameID)
+		configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_GLOBAL_KEYFRAME,0);
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -171,34 +234,36 @@ void MDRImporter::SetupProperties(const Importer* pImp)
 void MDRImporter::InternReadFile( const std::string& pFile, 
 	aiScene* pScene, IOSystem* pIOHandler)
 {
-	boost::scoped_ptr<IOStream> file( pIOHandler->Open( pFile));
+	boost::scoped_ptr<IOStream> file( pIOHandler->Open( pFile, "rb"));
 
 	// Check whether we can read from the file
 	if( file.get() == NULL)
 		throw new ImportErrorException( "Failed to open MDR file " + pFile + ".");
 
-	// check whether the mdr file is large enough to contain the file header
+	// Check whether the mdr file is large enough to contain the file header
 	fileSize = (unsigned int)file->FileSize();
 	if( fileSize < sizeof(MDR::Header))
 		throw new ImportErrorException( "MDR File is too small.");
 
+	// Copy the contents of the file to a buffer
 	std::vector<unsigned char> mBuffer2(fileSize);
 	file->Read( &mBuffer2[0], 1, fileSize);
 	mBuffer = &mBuffer2[0];
 
-	// validate the file header and do BigEndian byte swapping for all sub headers
-	this->pcHeader = (BE_NCONST MDR::Header*)this->mBuffer;
-	this->ValidateHeader();
+	// Validate the file header and do BigEndian byte swapping for all sub headers
+	pcHeader = (BE_NCONST MDR::Header*)mBuffer;
+	ValidateHeader();
 
-	// go to the first LOD
-	LE_NCONST MDR::LOD* lod = (LE_NCONST MDR::LOD*)((uint8_t*)this->pcHeader+pcHeader->ofsLODs);
+	// Go to the first LOD
+	LE_NCONST MDR::LOD* lod = (LE_NCONST MDR::LOD*)((uint8_t*)pcHeader+pcHeader->ofsLODs);
 	std::vector<aiMesh*> outMeshes;
 	outMeshes.reserve(lod->numSurfaces);
 
-	// get a pointer to the first surface
+	// Get a pointer to the first surface and continue processing them all
 	LE_NCONST MDR::Surface* surf = (LE_NCONST MDR::Surface*)((uint8_t*)lod+lod->ofsSurfaces);
 	for (uint32_t i = 0; i < lod->numSurfaces; ++i)
 	{
+		// The surface must have a) faces b) vertices and c) bone references
 		if (surf->numTriangles && surf->numVerts && surf->numBoneReferences)
 		{
 			outMeshes.push_back(new aiMesh());
@@ -213,7 +278,7 @@ void MDRImporter::InternReadFile( const std::string& pFile,
 			mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices];
 			mesh->mBones = new aiBone*[mesh->mNumBones];
 
-			// allocate output bones
+			// Allocate output bones and generate proper names for them
 			for (unsigned int p = 0; p < mesh->mNumBones;++p)
 			{
 				aiBone* bone = mesh->mBones[p] = new aiBone();
@@ -232,30 +297,40 @@ void MDRImporter::InternReadFile( const std::string& pFile,
 				// get a pointer to the next vertex
 				v = (LE_NCONST MDR::Vertex*)((uint8_t*)(v+1) + v->numWeights*sizeof(MDR::Weight));
 
+				// Big Endian - swap the vertex data structure
 #ifndef AI_BUILD_BIG_ENDIAN
 				AI_SWAP4(v->numWeights);
-				AI_SWAP4(v->normal.x);AI_SWAP4(v->normal.y);AI_SWAP4(v->normal.z);
-				AI_SWAP4(v->texCoords.x);AI_SWAP4(v->texCoords.y);
+				AI_SWAP4(v->normal.x);
+				AI_SWAP4(v->normal.y);
+				AI_SWAP4(v->normal.z);
+				AI_SWAP4(v->texCoords.x);
+				AI_SWAP4(v->texCoords.y);
 #endif        
 
+				// Fill out output structure
 				VertexInfo& vert = mVertices[m];
+
 				vert.uv.x = v->texCoords.x;  vert.uv.y = v->texCoords.y; 
 				vert.normal = v->normal;
 				vert.start  = (unsigned int)mWeights.size();
 				vert.num    = v->numWeights;
 
+				// Now compute the final vertex position by averaging
+				// the positions affecting this vertex, weighting by
+				// the given vertex weights.
 				for (unsigned int l = 0; l < vert.num; ++l)
 				{
 				}
 			}
 
-			// find out how large the output weight buffers must be
+			// Find out how large the output weight buffers must be
 			LE_NCONST MDR::Triangle* tri = (LE_NCONST MDR::Triangle*)((uint8_t*)surf+surf->ofsTriangles);
 			LE_NCONST MDR::Triangle* const triEnd = tri + surf->numTriangles;
 			for (; tri != triEnd; ++tri)
 			{
 				for (unsigned int o = 0; o < 3;++o)
 				{
+					// Big endian: swap the 32 Bit index
 #ifndef AI_BUILD_BIG_ENDIAN        
 					AI_SWAP4(tri->indexes[o]);
 #endif          
@@ -274,7 +349,7 @@ void MDRImporter::InternReadFile( const std::string& pFile,
 				}
 			}
 
-			// allocate storage for output bone weights
+			// Allocate storage for output bone weights
 			for (unsigned int p = 0; p < mesh->mNumBones;++p)
 			{
 				aiBone* bone = mesh->mBones[p];
@@ -285,10 +360,11 @@ void MDRImporter::InternReadFile( const std::string& pFile,
 			// and build the final output buffers
 		}
 
-		// get a pointer to the next surface
+		// Get a pointer to the next surface and continue
 		surf = (LE_NCONST MDR::Surface*)((uint8_t*)surf + surf->ofsEnd);	
 	}
 
+	// Copy the vector to the C-style output array
 	pScene->mNumMeshes = (unsigned int) outMeshes.size();
 	pScene->mMeshes = new aiMesh*[pScene->mNumMeshes];
 	::memcpy(pScene->mMeshes,&outMeshes[0],sizeof(void*)*pScene->mNumMeshes);

+ 11 - 0
code/MDRLoader.h

@@ -117,6 +117,14 @@ protected:
 	*/
 	void ValidateLODHeader(BE_NCONST MDR::LOD* pcLOD);
 
+	// -------------------------------------------------------------------
+	/** Uncompress a matrix
+	 *
+	 *  @param mat Destination matrix
+	 *  @param compressed Pointer to 24 bytesof compressed data
+	*/
+	void MatrixUncompress(aiMatrix4x4& mat,const uint8_t * compressed);
+
 protected:
 
 
@@ -141,6 +149,9 @@ protected:
 
 	/** size of the file, in bytes */
 	unsigned int fileSize;
+
+	/** compressed frames? */
+	bool compressed;
 };
 
 } // end of namespace Assimp

+ 2 - 0
code/ObjTools.h

@@ -44,6 +44,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #ifndef OBJ_TOOLS_H_INC
 #define OBJ_TOOLS_H_INC
 
+#include "fast_atof.h"
+
 namespace Assimp
 {
 

+ 38 - 25
code/SMDLoader.cpp

@@ -347,7 +347,11 @@ void SMDImporter::CreateOutputMeshes()
 				for (unsigned int iBone = 0;iBone < face.avVertices[iVert].aiBoneLinks.size();++iBone)
 				{
 					TempWeightListEntry& pairval = face.avVertices[iVert].aiBoneLinks[iBone];
-					if (pairval.first >= asBones.size())
+
+					// FIX: The second check is here just to make sure we won't 
+					// assign more than one weight to a single vertex index
+					if (pairval.first >= asBones.size() || 
+						pairval.first == face.avVertices[iVert].iParentNode)
 					{
 						DefaultLogger::get()->error("[SMD/VTA] Bone index overflow. "
 							"The bone index will be ignored, the weight will be assigned "
@@ -357,10 +361,16 @@ void SMDImporter::CreateOutputMeshes()
 					aaiBones[pairval.first].push_back(TempWeightListEntry(iNum,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 
 				// we should ...
-				if (fSum <= 1.0f)
+				// FIX: We use 0.975 as limit, floating-point inaccuracies seem to
+				// be very strong in some SMD exporters. Furthermore it is possible
+				// that the parent of a vertex is 0xffffffff (if the corresponding
+				// entry in the file was unreadable)
+				// ******************************************************************
+				if (fSum < 0.975f && face.avVertices[iVert].iParentNode != 0xffffffff)
 				{
 					if (face.avVertices[iVert].iParentNode >= asBones.size())
 					{
@@ -385,7 +395,6 @@ void SMDImporter::CreateOutputMeshes()
 							TempWeightListEntry(iNum,1.0f-fSum));
 					}
 				}
-
 				pcMesh->mFaces[iFace].mIndices[iVert] = iNum++;
 			}
 		}
@@ -396,27 +405,30 @@ void SMDImporter::CreateOutputMeshes()
 		{
 			if (!aaiBones[iBone].empty())++iNum;
 		}
-		pcMesh->mNumBones = iNum;
-		pcMesh->mBones = new aiBone*[pcMesh->mNumBones];
-		iNum = 0;
-		for (unsigned int iBone = 0; iBone < asBones.size();++iBone)
+		if (iNum)
 		{
-			if (aaiBones[iBone].empty())continue;
-			aiBone*& bone = pcMesh->mBones[iNum] = new aiBone();
+			pcMesh->mNumBones = iNum;
+			pcMesh->mBones = new aiBone*[pcMesh->mNumBones];
+			iNum = 0;
+			for (unsigned int iBone = 0; iBone < asBones.size();++iBone)
+			{
+				if (aaiBones[iBone].empty())continue;
+				aiBone*& bone = pcMesh->mBones[iNum] = new aiBone();
 
-			bone->mNumWeights = (unsigned int)aaiBones[iBone].size();
-			bone->mWeights = new aiVertexWeight[bone->mNumWeights];
-			bone->mOffsetMatrix = asBones[iBone].mOffsetMatrix;
-			bone->mName.Set( asBones[iBone].mName );
+				bone->mNumWeights = (unsigned int)aaiBones[iBone].size();
+				bone->mWeights = new aiVertexWeight[bone->mNumWeights];
+				bone->mOffsetMatrix = asBones[iBone].mOffsetMatrix;
+				bone->mName.Set( asBones[iBone].mName );
 
-			asBones[iBone].bIsUsed = true;
+				asBones[iBone].bIsUsed = true;
 
-			for (unsigned int iWeight = 0; iWeight < bone->mNumWeights;++iWeight)
-			{
-				bone->mWeights[iWeight].mVertexId = aaiBones[iBone][iWeight].first;
-				bone->mWeights[iWeight].mWeight = aaiBones[iBone][iWeight].second;
+				for (unsigned int iWeight = 0; iWeight < bone->mNumWeights;++iWeight)
+				{
+					bone->mWeights[iWeight].mVertexId = aaiBones[iBone][iWeight].first;
+					bone->mWeights[iWeight].mWeight = aaiBones[iBone][iWeight].second;
+				}
+				++iNum;
 			}
-			++iNum;
 		}
 
 		delete[] aaiBones;
@@ -530,7 +542,7 @@ void SMDImporter::CreateOutputAnimations()
 		aiNodeAnim* p = pp[a] = new aiNodeAnim();
 
 		// copy the name of the bone
-    p->mNodeName.Set( i->mName);
+		p->mNodeName.Set( i->mName);
 
 		p->mNumRotationKeys = (unsigned int) (*i).sAnim.asKeys.size();
 		if (p->mNumRotationKeys)
@@ -631,11 +643,7 @@ void SMDImporter::CreateOutputMaterials()
 		pScene->mMaterials[iMat] = pcMat;
 
 		aiString szName;
-#if _MSC_VER >= 1400
-		szName.length = (size_t)::sprintf_s(szName.data,"Texture_%i",iMat);
-#else
 		szName.length = (size_t)::sprintf(szName.data,"Texture_%i",iMat);
-#endif
 		pcMat->AddProperty(&szName,AI_MATKEY_NAME);
 
 		::strcpy(szName.data, aszTextures[iMat].c_str() );
@@ -1071,6 +1079,11 @@ void SMDImporter::ParseVertex(const char* szCurrent,
 	const char** szCurrentOut, SMD::Vertex& vertex,
 	bool bVASection /*= false*/)
 {
+	if (SkipSpaces(&szCurrent) && IsLineEnd(*szCurrent))
+	{
+		SkipSpacesAndLineEnd(szCurrent,&szCurrent);
+		return ParseVertex(szCurrent,szCurrentOut,vertex,bVASection);
+	}
 	if(!ParseSignedInt(szCurrent,&szCurrent,(int&)vertex.iParentNode))
 	{
 		LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.parent");

+ 36 - 0
code/ScenePreprocessor.cpp

@@ -98,10 +98,41 @@ void ScenePreprocessor::ProcessMesh (aiMesh* mesh)
 // ---------------------------------------------------------------------------
 void ScenePreprocessor::ProcessAnimation (aiAnimation* anim)
 {
+	double first = 10e10, last = -10e10;
 	for (unsigned int i = 0; i < anim->mNumChannels;++i)
 	{
 		aiNodeAnim* channel = anim->mChannels[i];
 
+		/*  If the exact duration of the animation is not given
+		 *  compute it now.
+		 */
+		if (anim->mDuration == -1.)
+		{
+			// Position keys
+			for (unsigned int i = 0; i < channel->mNumPositionKeys;++i)
+			{
+				aiVectorKey& key = channel->mPositionKeys[i];
+				first = std::min (first, key.mTime);
+				last  = std::max (last,  key.mTime);
+			}
+
+			// Scaling keys
+			for (unsigned int i = 0; i < channel->mNumScalingKeys;++i)
+			{
+				aiVectorKey& key = channel->mScalingKeys[i];
+				first = std::min (first, key.mTime);
+				last  = std::max (last,  key.mTime);
+			}
+
+			// Rotation keys
+			for (unsigned int i = 0; i < channel->mNumRotationKeys;++i)
+			{
+				aiQuatKey& key = channel->mRotationKeys[i];
+				first = std::min (first, key.mTime);
+				last  = std::max (last,  key.mTime);
+			}
+		}
+
 		/*  Check whether the animation channel has no rotation
 		 *  or position tracks. In this case we generate a dummy
 		 *  track from the information we have in the transformation
@@ -160,4 +191,9 @@ void ScenePreprocessor::ProcessAnimation (aiAnimation* anim)
 			}
 		}
 	}
+	if (anim->mDuration == -1.)
+	{
+		DefaultLogger::get()->debug("Setting animation duration");
+		anim->mDuration = last - first;
+	}
 }

+ 12 - 0
code/SplitLargeMeshes.cpp

@@ -56,17 +56,20 @@ SplitLargeMeshesProcess_Triangle::SplitLargeMeshesProcess_Triangle()
 {
 	LIMIT = AI_SLM_DEFAULT_MAX_TRIANGLES;
 }
+
 // ------------------------------------------------------------------------------------------------
 SplitLargeMeshesProcess_Triangle::~SplitLargeMeshesProcess_Triangle()
 {
 	// nothing to do here
 }
+
 // ------------------------------------------------------------------------------------------------
 // Returns whether the processing step is present in the given flag field.
 bool SplitLargeMeshesProcess_Triangle::IsActive( unsigned int pFlags) const
 {
 	return (pFlags & aiProcess_SplitLargeMeshes) != 0;
 }
+
 // ------------------------------------------------------------------------------------------------
 // Executes the post processing step on the given imported data.
 void SplitLargeMeshesProcess_Triangle::Execute( aiScene* pScene)
@@ -96,6 +99,7 @@ void SplitLargeMeshesProcess_Triangle::Execute( aiScene* pScene)
 	else DefaultLogger::get()->debug("SplitLargeMeshesProcess_Triangle finished. There was nothing to do");
 	return;
 }
+
 // ------------------------------------------------------------------------------------------------
 // Setup properties
 void SplitLargeMeshesProcess_Triangle::SetupProperties( const Importer* pImp)
@@ -103,6 +107,7 @@ void SplitLargeMeshesProcess_Triangle::SetupProperties( const Importer* pImp)
     // get the current value of the split property
 	this->LIMIT = pImp->GetPropertyInteger(AI_CONFIG_PP_SLM_TRIANGLE_LIMIT,AI_SLM_DEFAULT_MAX_TRIANGLES);
 }
+
 // ------------------------------------------------------------------------------------------------
 // Update a node after some meshes have been split
 void SplitLargeMeshesProcess_Triangle::UpdateNode(aiNode* pcNode,
@@ -137,6 +142,7 @@ void SplitLargeMeshesProcess_Triangle::UpdateNode(aiNode* pcNode,
 	}
 	return;
 }
+
 // ------------------------------------------------------------------------------------------------
 // Executes the post processing step on the given imported data.
 void SplitLargeMeshesProcess_Triangle::SplitMesh(
@@ -339,22 +345,26 @@ void SplitLargeMeshesProcess_Triangle::SplitMesh(
 	else avList.push_back(std::pair<aiMesh*, unsigned int>(pMesh,a));
 	return;
 }
+
 // ------------------------------------------------------------------------------------------------
 SplitLargeMeshesProcess_Vertex::SplitLargeMeshesProcess_Vertex()
 {
 	LIMIT = AI_SLM_DEFAULT_MAX_VERTICES;
 }
+
 // ------------------------------------------------------------------------------------------------
 SplitLargeMeshesProcess_Vertex::~SplitLargeMeshesProcess_Vertex()
 {
 	// nothing to do here
 }
+
 // ------------------------------------------------------------------------------------------------
 // Returns whether the processing step is present in the given flag field.
 bool SplitLargeMeshesProcess_Vertex::IsActive( unsigned int pFlags) const
 {
 	return (pFlags & aiProcess_SplitLargeMeshes) != 0;
 }
+
 // ------------------------------------------------------------------------------------------------
 // Executes the post processing step on the given imported data.
 void SplitLargeMeshesProcess_Vertex::Execute( aiScene* pScene)
@@ -384,12 +394,14 @@ void SplitLargeMeshesProcess_Vertex::Execute( aiScene* pScene)
 	else DefaultLogger::get()->debug("SplitLargeMeshesProcess_Vertex finished. There was nothing to do");
 	return;
 }
+
 // ------------------------------------------------------------------------------------------------
 // Setup properties
 void SplitLargeMeshesProcess_Vertex::SetupProperties( const Importer* pImp)
 {
 	this->LIMIT = pImp->GetPropertyInteger(AI_CONFIG_PP_SLM_VERTEX_LIMIT,AI_SLM_DEFAULT_MAX_VERTICES);
 }
+
 // ------------------------------------------------------------------------------------------------
 // Executes the post processing step on the given imported data.
 void SplitLargeMeshesProcess_Vertex::SplitMesh(

+ 205 - 0
code/TargetAnimation.cpp

@@ -0,0 +1,205 @@
+/*
+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.
+
+----------------------------------------------------------------------
+*/
+
+#include "AssimpPCH.h"
+#include "TargetAnimation.h"
+
+using namespace Assimp;
+
+
+// ---------------------------------------------------------------------------
+KeyIterator::KeyIterator(std::vector<aiVectorKey>* _objPos,
+	const std::vector<aiVectorKey>* _targetObjPos,
+	const aiVector3D*  defaultObjectPos /*= NULL*/,
+	const aiVector3D*  defaultTargetPos /*= NULL*/)
+		
+		:	reachedEnd		(false)
+		,	curTime			(-1.)
+		,	objPos(_objPos)
+		,	targetObjPos	(_targetObjPos)
+		,	nextObjPos		(0)
+		,	nextTargetObjPos(0)
+{
+	// Generate default transformation tracks if necessary
+	if (!objPos)
+	{
+		defaultObjPos.resize(1);
+		defaultObjPos.front().mTime  = 10e10;
+
+		if (defaultObjectPos)
+			defaultObjPos.front().mValue = *defaultObjectPos;
+
+		objPos = & defaultObjPos;
+	}
+	if (!targetObjPos)
+	{
+		defaultTargetObjPos.resize(1);
+		defaultTargetObjPos.front().mTime  = 10e10;
+
+		if (defaultTargetPos)
+			defaultTargetObjPos.front().mValue = *defaultTargetPos;
+
+		targetObjPos = & defaultTargetObjPos;
+	}
+}
+
+// ---------------------------------------------------------------------------
+void KeyIterator::operator ++()
+{
+	// If we are already at the end of all keyframes, return
+	if (reachedEnd)return;
+
+	int breakThisUglyStuff = 0;
+
+	// Now search in all arrays for the time value closest
+	// to our current position on the time line
+	double d0,d1;
+	
+	d0 = objPos->at(nextObjPos).mTime;
+	if (nextObjPos == objPos->size()-1)
+		++breakThisUglyStuff;
+
+	d1 = targetObjPos->at(nextTargetObjPos).mTime;	
+	if (nextTargetObjPos == targetObjPos->size()-1)
+		++breakThisUglyStuff;
+
+	// Easiest case - all are identical. In this
+	// case we don't need to interpolate so we can
+	// return earlier
+	if ( d0 == d1 )
+	{
+		curTime = d0;
+		curPosition = objPos->at(nextObjPos).mValue;
+		curTargetPosition = targetObjPos->at(nextTargetObjPos).mValue;
+
+		// Increment all counters, regardless whether
+		// the corresponding arrays are there
+		++nextObjPos;
+		++nextTargetObjPos;
+	}
+	// An object position key is closest to us
+	else if (d0 < d1)
+	{
+		curTime = d0;
+
+		// interpolate the other
+		if (1 == targetObjPos->size() || !nextTargetObjPos)
+		{
+			curTargetPosition = targetObjPos->at(0).mValue;
+		}
+		else
+		{
+			const aiVectorKey& last  = targetObjPos->at(nextTargetObjPos);
+			const aiVectorKey& first = targetObjPos->at(nextTargetObjPos-1);
+
+			/*curTargetPosition = Interpolate(first.mValue, last.mValue,
+				(curTime-first.mTime) / (last.mTime-first.mTime));*/
+		}
+
+		// increment counters
+		if (objPos->size() != nextObjPos-1)
+			++nextObjPos;
+
+		if (targetObjPos->size() != nextTargetObjPos-1)
+			++nextTargetObjPos;
+	}
+	// A target position key is closest to us
+	else
+	{
+		curTime = d1;
+
+		// interpolate the other
+		if (1 == objPos->size() || !nextObjPos)
+		{
+			curPosition = objPos->at(0).mValue;
+		}
+		else
+		{
+			const aiVectorKey& last  = objPos->at(nextObjPos);
+			const aiVectorKey& first = objPos->at(nextObjPos-1);
+
+			/*curPosition = Interpolate(first.mValue, last.mValue,
+				(curTime-first.mTime) / (last.mTime-first.mTime));*/
+		}
+	}
+
+	if (2 == breakThisUglyStuff)
+	{
+		// We reached the very last keyframe
+		reachedEnd = true;
+	}
+}
+
+// ---------------------------------------------------------------------------
+void TargetAnimationHelper::SetTargetAnimationChannel (
+	const std::vector<aiVectorKey>* _targetPositions)
+{
+	ai_assert(NULL != _targetPositions);
+	targetPositions = _targetPositions;
+}
+
+// ---------------------------------------------------------------------------
+void TargetAnimationHelper::SetMainAnimationChannel (
+	std::vector<aiVectorKey>* _objectPositions)
+{
+	ai_assert(NULL != _objectPositions);
+	objectPositions = _objectPositions;
+}
+
+// ---------------------------------------------------------------------------
+void TargetAnimationHelper::Process(std::vector<aiVectorKey>* distanceTrack)
+{
+	ai_assert(NULL != objectPositions);
+
+	// Iterate through all object keys and interpolate their values if necessary.
+	// Then get the corresponding target position, compute the difference
+	// vector between object and target position. Then compute a rotation matrix
+	// that rotates the base vector of the object coordinate system at that time
+	// to match the diff vector. 
+	KeyIterator iter(objectPositions,targetPositions);
+	unsigned int curTarget;
+	for (;!iter.Finished();++iter)
+	{
+		const aiVector3D&  position  = iter.GetCurPosition();
+		const aiVector3D&  tposition = iter.GetCurTargetPosition();
+
+	
+	}
+}

+ 173 - 0
code/TargetAnimation.h

@@ -0,0 +1,173 @@
+/*
+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 for the ASE and 3DS loaders to
+ help them compute camera and spot light animation channels */
+#ifndef AI_TARGET_ANIMATION_H_INC
+#define AI_TARGET_ANIMATION_H_INC
+
+
+namespace Assimp	{
+
+
+
+// ---------------------------------------------------------------------------
+/** Helper class to iterate through all keys in an animation channel.
+ *
+ *  Missing tracks are interpolated. This is a helper class for
+ *  TargetAnimationHelper, but it can be freely used for other purposes.
+*/
+class ASSIMP_API KeyIterator
+{
+public:
+
+	
+	// ------------------------------------------------------------------
+	/** Constructs a new key iterator
+	 *
+	 *  @param _objPos Object position track. May be NULL.
+	 *  @param _targetObjPos Target object position track. May be NULL.
+	 *  @param defaultObjectPos Default object position to be used if
+	 *	  no animated track is available. May be NULL.
+	 *  @param defaultTargetPos Default target position to be used if
+	 *	  no animated track is available. May be NULL.
+	 */
+	KeyIterator(std::vector<aiVectorKey>* _objPos,
+		const std::vector<aiVectorKey>* _targetObjPos,
+		const aiVector3D*  defaultObjectPos = NULL,
+		const aiVector3D*  defaultTargetPos = NULL);
+
+	// ------------------------------------------------------------------
+	/** Returns true if all keys have been processed
+	 */
+	bool Finished() const
+		{return reachedEnd;}
+
+	// ------------------------------------------------------------------
+	/** Increment the iterator
+	 */
+	void operator++();
+	inline void operator++(int)
+		{return ++(*this);}
+
+
+
+	// ------------------------------------------------------------------
+	/** Getters to retrieve the current state of the iterator
+	 */
+	inline const aiVector3D& GetCurPosition() const
+		{return curPosition;}
+
+	inline const aiVector3D& GetCurTargetPosition() const
+		{return curTargetPosition;}
+
+private:
+
+	//! Did we reach the end?
+	bool reachedEnd;
+
+	//! Represents the current position of the iterator
+	aiVector3D curPosition, curTargetPosition;
+
+	double curTime;
+
+	//! Input tracks and the next key to process
+	const std::vector<aiVectorKey>* objPos,*targetObjPos;
+
+	unsigned int nextObjPos, nextTargetObjPos;
+	std::vector<aiVectorKey> defaultObjPos,defaultTargetObjPos;
+};
+
+// ---------------------------------------------------------------------------
+/** Helper class for the 3DS and ASE loaders to compute camera and spot light
+ *  animations.
+ *
+ * 3DS and ASE store the differently to Assimp - there is an animation
+ * channel for the camera/spot light itself and a separate position
+ * animation channels specifying the position of the camera/spot light
+ * look-at target
+*/
+class ASSIMP_API TargetAnimationHelper
+{
+public:
+
+	TargetAnimationHelper()
+		:	objectPositions		(NULL)
+	{}
+
+
+	// ------------------------------------------------------------------
+	/** Sets the target animation channel
+	 *
+	 *  This channel specifies the position of the camera/spot light
+	 *  target at a specific position.
+	 *
+	 *  @param targetPositions Translation channel
+	 */
+	void SetTargetAnimationChannel (
+		const std::vector<aiVectorKey>* targetPositions);
+
+
+	// ------------------------------------------------------------------
+	/** Sets the main animation channel
+	 *
+	 *  @param objectPositions Translation channel
+	 */
+	void SetMainAnimationChannel (
+		std::vector<aiVectorKey>* objectPositions);
+
+
+	// ------------------------------------------------------------------
+	/** Computes final animation channels
+	 *
+	 */
+	void Process( std::vector<aiVectorKey>* distanceTrack );
+
+
+private:
+
+	const std::vector<aiVectorKey>* targetPositions;
+	std::vector<aiVectorKey> *objectPositions;
+};
+
+
+} // ! end namespace Assimp
+
+#endif // include guard

+ 39 - 9
code/ValidateDataStructure.cpp

@@ -242,6 +242,10 @@ void ValidateDSProcess::Execute( aiScene* pScene)
 	{
 		ReportError("aiScene::mNumMeshes is 0. At least one mesh must be there");
 	}
+	else if (pScene->mMeshes)
+	{
+		ReportError("aiScene::mMeshes is non-null although there are no meshes");
+	}
 	
 	// validate all animations
 	if (pScene->mNumAnimations) 
@@ -250,7 +254,11 @@ void ValidateDSProcess::Execute( aiScene* pScene)
 		DoValidation(pScene->mAnimations,pScene->mNumAnimations,
 			"mAnimations","mNumAnimations");
 	}
-	
+	else if (pScene->mAnimations)
+	{
+		ReportError("aiScene::mAnimations is non-null although there are no animations");
+	}
+
 	// validate all cameras
 	if (pScene->mNumCameras) 
 	{
@@ -258,6 +266,10 @@ void ValidateDSProcess::Execute( aiScene* pScene)
 		DoValidationWithNameCheck(pScene->mCameras,pScene->mNumCameras,
 			"mCameras","mNumCameras");
 	}
+	else if (pScene->mCameras)
+	{
+		ReportError("aiScene::mCameras is non-null although there are no cameras");
+	}
 
 	// validate all lights
 	if (pScene->mNumLights) 
@@ -266,6 +278,10 @@ void ValidateDSProcess::Execute( aiScene* pScene)
 		DoValidationWithNameCheck(pScene->mLights,pScene->mNumLights,
 			"mLights","mNumLights");
 	}
+	else if (pScene->mLights)
+	{
+		ReportError("aiScene::mLights is non-null although there are no lights");
+	}
 	
 	// validate all materials
 	if (pScene->mNumMaterials) 
@@ -277,6 +293,10 @@ void ValidateDSProcess::Execute( aiScene* pScene)
 	{
 		ReportError("aiScene::mNumMaterials is 0. At least one material must be there");
 	}
+	else if (pScene->mMaterials)
+	{
+		ReportError("aiScene::mMaterials is non-null although there are no materials");
+	}
 
 	if (!has)ReportError("The aiScene data structure is empty");
 	DefaultLogger::get()->debug("ValidateDataStructureProcess end");
@@ -452,7 +472,7 @@ void ValidateDSProcess::Validate( const aiMesh* pMesh)
 
 
 	// now validate all bones
-	if (pMesh->HasBones())
+	if (pMesh->mNumBones)
 	{
 		if (!pMesh->mBones)
 		{
@@ -498,6 +518,10 @@ void ValidateDSProcess::Validate( const aiMesh* pMesh)
 		}
 		delete[] afSum;
 	}
+	else if (pMesh->mBones)
+	{
+		ReportError("aiMesh::mBones is non-null although there are no bones");
+	}
 }
 // ------------------------------------------------------------------------------------------------
 void ValidateDSProcess::Validate( const aiMesh* pMesh,
@@ -513,7 +537,7 @@ void ValidateDSProcess::Validate( const aiMesh* pMesh,
 	// check whether all vertices affected by this bone are valid
 	for (unsigned int i = 0; i < pBone->mNumWeights;++i)
 	{
-		if (pBone->mWeights[i].mVertexId > pMesh->mNumVertices)
+		if (pBone->mWeights[i].mVertexId >= pMesh->mNumVertices)
 		{
 			this->ReportError("aiBone::mWeights[%i].mVertexId is out of range",i);
 		}
@@ -832,14 +856,14 @@ __break_out:
 		{
 			if (pNodeAnim->mPositionKeys[i].mTime > pAnimation->mDuration)
 			{
-				this->ReportError("aiNodeAnim::mPositionKeys[%i].mTime (%.5f) is larger "
+				ReportError("aiNodeAnim::mPositionKeys[%i].mTime (%.5f) is larger "
 					"than aiAnimation::mDuration (which is %.5f)",i,
 					(float)pNodeAnim->mPositionKeys[i].mTime,
 					(float)pAnimation->mDuration);
 			}
 			if (pNodeAnim->mPositionKeys[i].mTime <= dLast)
 			{
-				this->ReportWarning("aiNodeAnim::mPositionKeys[%i].mTime (%.5f) is smaller "
+				ReportWarning("aiNodeAnim::mPositionKeys[%i].mTime (%.5f) is smaller "
 					"than aiAnimation::mPositionKeys[%i] (which is %.5f)",i,
 					(float)pNodeAnim->mPositionKeys[i].mTime,
 					i-1, (float)dLast);
@@ -860,14 +884,14 @@ __break_out:
 		{
 			if (pNodeAnim->mRotationKeys[i].mTime > pAnimation->mDuration)
 			{
-				this->ReportError("aiNodeAnim::mRotationKeys[%i].mTime (%.5f) is larger "
+				ReportError("aiNodeAnim::mRotationKeys[%i].mTime (%.5f) is larger "
 					"than aiAnimation::mDuration (which is %.5f)",i,
 					(float)pNodeAnim->mRotationKeys[i].mTime,
 					(float)pAnimation->mDuration);
 			}
 			if (pNodeAnim->mRotationKeys[i].mTime <= dLast)
 			{
-				this->ReportWarning("aiNodeAnim::mRotationKeys[%i].mTime (%.5f) is smaller "
+				ReportWarning("aiNodeAnim::mRotationKeys[%i].mTime (%.5f) is smaller "
 					"than aiAnimation::mRotationKeys[%i] (which is %.5f)",i,
 					(float)pNodeAnim->mRotationKeys[i].mTime,
 					i-1, (float)dLast);
@@ -888,14 +912,14 @@ __break_out:
 		{
 			if (pNodeAnim->mScalingKeys[i].mTime > pAnimation->mDuration)
 			{
-				this->ReportError("aiNodeAnim::mScalingKeys[%i].mTime (%.5f) is larger "
+				ReportError("aiNodeAnim::mScalingKeys[%i].mTime (%.5f) is larger "
 					"than aiAnimation::mDuration (which is %.5f)",i,
 					(float)pNodeAnim->mScalingKeys[i].mTime,
 					(float)pAnimation->mDuration);
 			}
 			if (pNodeAnim->mScalingKeys[i].mTime <= dLast)
 			{
-				this->ReportWarning("aiNodeAnim::mScalingKeys[%i].mTime (%.5f) is smaller "
+				ReportWarning("aiNodeAnim::mScalingKeys[%i].mTime (%.5f) is smaller "
 					"than aiAnimation::mScalingKeys[%i] (which is %.5f)",i,
 					(float)pNodeAnim->mScalingKeys[i].mTime,
 					i-1, (float)dLast);
@@ -903,6 +927,12 @@ __break_out:
 			dLast = pNodeAnim->mScalingKeys[i].mTime;
 		}
 	}
+
+	if (!pNodeAnim->mNumScalingKeys && !pNodeAnim->mNumRotationKeys &&
+		!pNodeAnim->mNumPositionKeys)
+	{
+		ReportError("A node animation channel must have at least one subtrack");
+	}
 }
 // ------------------------------------------------------------------------------------------------
 void ValidateDSProcess::Validate( const aiNode* pNode)

+ 2 - 1
code/makefile

@@ -73,7 +73,8 @@ SOURCES = AssimpPCH.cpp \
 	IRRLoader.cpp \
 	Q3DLoader.cpp \
 	ScenePreprocessor.cpp \
-	B3DImporter.cpp
+	B3DImporter.cpp \
+	TargetAnimation.cpp
 
 OBJECTS = $(SOURCES:.cpp=.o)
 

+ 2 - 1
code/makefile.mingw

@@ -73,7 +73,8 @@ SOURCES = AssimpPCH.cpp \
 	IRRLoader.cpp \
 	Q3DLoader.cpp \
 	ScenePreprocessor.cpp \
-	B3DImporter.cpp
+	B3DImporter.cpp \
+	TargetAnimation.cpp
 	
 
 OBJECTS = $(SOURCES:.cpp=.o)

+ 55 - 11
include/aiAnim.h

@@ -51,6 +51,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 extern "C" {
 #endif
 
+// ---------------------------------------------------------------------------
 /** A time-value pair specifying a certain 3D vector for the given time. */
 struct aiVectorKey
 {
@@ -77,6 +78,8 @@ struct aiVectorKey
 #endif
 };
 
+
+// ---------------------------------------------------------------------------
 /** A time-value pair specifying a rotation for the given time. For joint 
  *  animations the rotation is usually expressed using a quaternion.
  */
@@ -105,34 +108,58 @@ struct aiQuatKey
 #endif
 };
 
+// ---------------------------------------------------------------------------
+/** Defines how an animation channel behaves outside the defined time
+ *  range. This corresponds to aiNodeAnim::mPreState and 
+ *  aiNodeAnim::mPostState.
+ */
 enum aiAnimBehaviour
 {
-	// --- Wert aus Node-Transformation wird übernommen
+	/** The value from the default node transformation is taken
+	 */
 	aiAnimBehaviour_DEFAULT  = 0x0,  
 
-	// -- Nächster Key wird verwendet
+	/** The nearest key is used
+	 */
 	aiAnimBehaviour_CONSTANT = 0x1,
 
-	// -- Nächste beiden Keys werden linear extrapoliert
+	/** The value of the nearest two keys is linearly
+	 *  extrapolated for the current time value.
+	 */
 	aiAnimBehaviour_LINEAR   = 0x2,
 
-	// -- Animation wird wiederholt
-	// Und das solange bis die Animationszeit (aiAnimation::mDuration)
-	// abgelaufen ist. Ist diese 0 läuft das ganze ewig.
-	aiAnimBehaviour_REPEAT   = 0x3
+	/** The animation is repeated.
+	 *
+	 *  If the animation key go from n to m and the current
+	 *  time is t, use the value at (t-n) % (|m-n|).
+	 */
+	aiAnimBehaviour_REPEAT   = 0x3,
+
+
+
+	/** This value is not used, it is just here to force the
+	 *  the compiler to map this enum to a 32 Bit integer 
+	 */
+	_aiAnimBehaviour_Force32Bit = 0x8fffffff
 };
 
+// ---------------------------------------------------------------------------
 /** Describes the animation of a single node. The name specifies the 
  *  bone/node which is affected by this animation channel. The keyframes
  *  are given in three separate series of values, one each for position, 
  *  rotation and scaling. The transformation matrix computed from these
  *  values replaces the node's original transformation matrix at a
- *  spefific time. 
+ *  spefific time. The order in which the transformations are applied is
+ *  - as usual - scaling, rotation, translation.
+ *
+ *  @note All keys are returned in their correct, chronological order.
+ *  Duplicate keys don't pass the validation step. Most likely there
+ *  will be no negative time keys, but they are not forbidden ...
  */
 struct aiNodeAnim
 {
 	/** The name of the node affected by this animation. The node 
-	 *  must exist anf it must be unique.
+	 *  must exist and it must be unique.
 	 */
 	C_STRUCT aiString mNodeName;
 
@@ -172,7 +199,21 @@ struct aiNodeAnim
 	C_STRUCT aiVectorKey* mScalingKeys;
 
 
-	aiAnimBehaviour mPrePostState;
+	/** Defines how the animation behaves before the first
+	 *  key is encountered.
+	 *
+	 *  The default value is aiAnimBehaviour_DEFAULT (the original
+	 *  transformation matrix of the affacted node is taken).
+	 */
+	aiAnimBehaviour mPreState;
+
+	/** Defines how the animation behaves after the last 
+	 *  kway was encountered.
+	 *
+	 *  The default value is aiAnimBehaviour_DEFAULT (the original
+	 *  transformation matrix of the affacted node is taken).
+	 */
+	aiAnimBehaviour mPostState;
 
 #ifdef __cplusplus
 	aiNodeAnim()
@@ -180,6 +221,8 @@ struct aiNodeAnim
 		mNumPositionKeys = 0; mPositionKeys = NULL; 
 		mNumRotationKeys= 0; mRotationKeys = NULL; 
 		mNumScalingKeys = 0; mScalingKeys = NULL; 
+
+		mPreState = mPostState = aiAnimBehaviour_DEFAULT;
 	}
 
 	~aiNodeAnim()
@@ -191,6 +234,7 @@ struct aiNodeAnim
 #endif // __cplusplus
 };
 
+// ---------------------------------------------------------------------------
 /** An animation consists of keyframe data for a number of nodes. For 
  *  each node affected by the animation a separate series of data is given.
  */
@@ -223,7 +267,7 @@ struct aiAnimation
 #ifdef __cplusplus
 	aiAnimation()
 	{
-		mDuration = 0;
+		mDuration = -1.;
 		mTicksPerSecond = 0;
 		mNumChannels = 0; mChannels = NULL;
 	}

+ 21 - 2
include/aiConfig.h

@@ -131,6 +131,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #define AI_CONFIG_IMPORT_ASE_RECONSTRUCT_NORMALS	"imp.ase.reconn"
 
 
+
 // ---------------------------------------------------------------------------
 /** \brief Configures the LWO loader to load just one layer from the model.
  * 
@@ -138,11 +139,23 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  * only one of them. This property can be either a string - which specifies
  * the name of the layer - or an integer - the index of the layer. If the
  * property is not set the whole LWO model is loaded. Loading fails if the
- * requested layer is not available.
+ * requested layer is not available. The layer index is zero-based and the
+ * layer name may not be empty.
  */
 #define AI_CONFIG_IMPORT_LWO_ONE_LAYER_ONLY			"imp.lwo.layer"
 
 
+// ---------------------------------------------------------------------------
+/** \brief Defines the output frame rate of the IRR loader.
+ * 
+ * IRR animations are difficult to convert for Assimp and there will
+ * always be a loss of quality. This setting defines how many keys per second
+ * the converter will compute.<br>
+ * Property type: integer. Default value: 100
+ */
+#define AI_CONFIG_IMPORT_IRR_ANIM_FPS				"imp.irr.fps"
+
+
 // ---------------------------------------------------------------------------
 /** \brief Specifies the maximum angle that may be between two vertex tangents
  *         that their tangents and bitangents are smoothed.
@@ -253,7 +266,13 @@ enum aiComponent
 	//! Removes all materials. One default material will
 	//! be generated, so aiScene::mNumMaterials will be 1.
 	//! This makes no real sense without the aiComponent_TEXTURES flag.
-	aiComponent_MATERIALS = 0x800
+	aiComponent_MATERIALS = 0x800,
+
+
+	/** This value is not used. It is just there to force the
+	 *  compiler to map this enum to a 32 Bit integer.
+	 */
+	_aiComponent_Force32Bit = 0x9fffffff
 };
 
 #define aiComponent_COLORSn(n) (1u << (n+20u))

+ 7 - 1
include/aiLight.h

@@ -72,7 +72,13 @@ enum aiLightSourceType
 	//! angle. It has a position and a direction it is pointing to.
 	//! A good example for a spot light is a light spot in
 	//! sport arenas.
-	aiLightSource_SPOT          = 0x3
+	aiLightSource_SPOT          = 0x3,
+
+
+	/** This value is not used. It is just there to force the
+	 *  compiler to map this enum to a 32 Bit integer.
+	 */
+	_aiLightSource_Force32Bit = 0x9fffffff
 };
 
 // ---------------------------------------------------------------------------

+ 28 - 4
include/aiMaterial.h

@@ -76,7 +76,13 @@ enum aiPropertyTypeInfo
 
     /** Simple binary buffer
     */
-    aiPTI_Buffer = 0x5
+    aiPTI_Buffer = 0x5,
+
+
+	/** This value is not used. It is just there to force the
+	 *  compiler to map this enum to a 32 Bit integer.
+	 */
+	_aiPTI_Force32Bit = 0x9fffffff
 };
 
 // ---------------------------------------------------------------------------
@@ -108,7 +114,13 @@ enum aiTextureOp
 
     /** T = T1 + (T2-0.5)
      */
-    aiTextureOp_SignedAdd = 0x5
+    aiTextureOp_SignedAdd = 0x5,
+
+
+	/** This value is not used. It is just there to force the
+	 *  compiler to map this enum to a 32 Bit integer.
+	 */
+	_aiTextureOp_Force32Bit = 0x9fffffff
 };
 
 // ---------------------------------------------------------------------------
@@ -130,7 +142,13 @@ enum aiTextureMapMode
     /** A texture coordinate u|v becomes u%1|v%1 if (u-(u%1))%2 is zero and
      *  1-(u%1)|1-(v%1) otherwise
      */
-    aiTextureMapMode_Mirror = 0x2
+    aiTextureMapMode_Mirror = 0x2,
+
+
+	/** This value is not used. It is just there to force the
+	 *  compiler to map this enum to a 32 Bit integer.
+	 */
+	_aiTextureMapMode_Force32Bit = 0x9fffffff
 };
 
 // ---------------------------------------------------------------------------
@@ -201,7 +219,13 @@ enum aiShadingMode
 
 	/** Fresnel shading
     */
-    aiShadingMode_Fresnel = 0xa
+    aiShadingMode_Fresnel = 0xa,
+
+
+	/** This value is not used. It is just there to force the
+	 *  compiler to map this enum to a 32 Bit integer.
+	 */
+	_aiShadingMode_Force32Bit = 0x9fffffff
 };
 
 

+ 7 - 1
include/aiMesh.h

@@ -275,7 +275,13 @@ enum aiPrimitiveType
 	 * is provided for your convinience, it splits all polygons in
 	 * triangles (which are much easier to handle).
 	 */
-	aiPrimitiveType_POLYGON     = 0x8
+	aiPrimitiveType_POLYGON     = 0x8,
+
+
+	/** This value is not used. It is just there to force the
+	 *  compiler to map this enum to a 32 Bit integer.
+	 */
+	_aiPrimitiveType_Force32Bit = 0x9fffffff
 };
 
 

+ 30 - 0
include/aiQuaternion.h

@@ -79,6 +79,12 @@ struct aiQuaternion
 	bool operator!= (const aiQuaternion& o) const
 		{return !(*this == o);}
 
+	/** Normalize the quaternion */
+	aiQuaternion& Normalize();
+
+	/** Multiply two quaternions */
+	aiQuaternion operator* (const aiQuaternion& two) const;
+
 	/** Performs a spherical interpolation between two quaternions and writes the result into the third.
 	 * @param pOut Target object to received the interpolated rotation.
 	 * @param pStart Start rotation of the interpolation at factor == 0.
@@ -248,6 +254,30 @@ inline void aiQuaternion::Interpolate( aiQuaternion& pOut, const aiQuaternion& p
   pOut.w = sclp * pStart.w + sclq * end.w;
 }
 
+// ---------------------------------------------------------------------------
+inline aiQuaternion& aiQuaternion::Normalize()
+{
+	// compute the magnitude and divide through it
+	const float mag = x*x+y*y+z*z+w*w;
+	if (mag)
+	{
+		x /= mag;
+		y /= mag;
+		z /= mag;
+		w /= mag;
+	}
+	return *this;
+}
+
+// ---------------------------------------------------------------------------
+inline aiQuaternion aiQuaternion::operator* (const aiQuaternion& t) const
+{
+	return aiQuaternion(w*t.w - x*t.x - y*t.y - z*t.z,
+		w*t.x + x*t.w + y*t.z - z*t.y,
+		w*t.y + y*t.w + z*t.x - x*t.z,
+		w*t.z + z*t.w + x*t.y - y*t.x);
+}
+
 } // end extern "C"
 #endif // __cplusplus
 

BIN
test/3DSFiles/CameraRollAnim.3ds


BIN
test/3DSFiles/CameraRollAnimWithChildObject.3ds


BIN
test/3DSFiles/RotatingCube.3DS


BIN
test/3DSFiles/TargetCameraAnim.3ds


+ 316 - 0
test/ASEFiles/CameraRollAnim.ase

@@ -0,0 +1,316 @@
+*3DSMAX_ASCIIEXPORT	200
+*COMMENT "AsciiExport Version  2,00 - Sat Nov 08 17:10:10 2008"
+*SCENE {
+	*SCENE_FILENAME "CameraRollAnim.max"
+	*SCENE_FIRSTFRAME 0
+	*SCENE_LASTFRAME 300
+	*SCENE_FRAMESPEED 30
+	*SCENE_TICKSPERFRAME 160
+	*SCENE_BACKGROUND_STATIC 0.0000	0.0000	0.0000
+	*SCENE_AMBIENT_STATIC 0.0000	0.0000	0.0000
+}
+*MATERIAL_LIST {
+	*MATERIAL_COUNT 0
+}
+*GEOMOBJECT {
+	*NODE_NAME "Box01"
+	*NODE_TM {
+		*NODE_NAME "Box01"
+		*INHERIT_POS 0 0 0
+		*INHERIT_ROT 0 0 0
+		*INHERIT_SCL 0 0 0
+		*TM_ROW0 1.0000	0.0000	0.0000
+		*TM_ROW1 0.0000	1.0000	0.0000
+		*TM_ROW2 0.0000	0.0000	1.0000
+		*TM_ROW3 10.5413	-0.8547	0.0000
+		*TM_POS 10.5413	-0.8547	0.0000
+		*TM_ROTAXIS 0.0000	0.0000	0.0000
+		*TM_ROTANGLE 0.0000
+		*TM_SCALE 1.0000	1.0000	1.0000
+		*TM_SCALEAXIS 0.0000	0.0000	0.0000
+		*TM_SCALEAXISANG 0.0000
+	}
+	*MESH {
+		*TIMEVALUE 0
+		*MESH_NUMVERTEX 8
+		*MESH_NUMFACES 12
+		*MESH_VERTEX_LIST {
+			*MESH_VERTEX    0	-24.7863	-24.7863	0.0000
+			*MESH_VERTEX    1	45.8689	-24.7863	0.0000
+			*MESH_VERTEX    2	-24.7863	23.0769	0.0000
+			*MESH_VERTEX    3	45.8689	23.0769	0.0000
+			*MESH_VERTEX    4	-24.7863	-24.7863	38.7464
+			*MESH_VERTEX    5	45.8689	-24.7863	38.7464
+			*MESH_VERTEX    6	-24.7863	23.0769	38.7464
+			*MESH_VERTEX    7	45.8689	23.0769	38.7464
+		}
+		*MESH_FACE_LIST {
+			*MESH_FACE    0:    A:    0 B:    2 C:    3 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 2 	*MESH_MTLID 1
+			*MESH_FACE    1:    A:    3 B:    1 C:    0 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 2 	*MESH_MTLID 1
+			*MESH_FACE    2:    A:    4 B:    5 C:    7 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 3 	*MESH_MTLID 0
+			*MESH_FACE    3:    A:    7 B:    6 C:    4 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 3 	*MESH_MTLID 0
+			*MESH_FACE    4:    A:    0 B:    1 C:    5 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 4 	*MESH_MTLID 4
+			*MESH_FACE    5:    A:    5 B:    4 C:    0 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 4 	*MESH_MTLID 4
+			*MESH_FACE    6:    A:    1 B:    3 C:    7 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 5 	*MESH_MTLID 3
+			*MESH_FACE    7:    A:    7 B:    5 C:    1 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 5 	*MESH_MTLID 3
+			*MESH_FACE    8:    A:    3 B:    2 C:    6 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 6 	*MESH_MTLID 5
+			*MESH_FACE    9:    A:    6 B:    7 C:    3 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 6 	*MESH_MTLID 5
+			*MESH_FACE   10:    A:    2 B:    0 C:    4 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 7 	*MESH_MTLID 2
+			*MESH_FACE   11:    A:    4 B:    6 C:    2 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 7 	*MESH_MTLID 2
+		}
+		*MESH_NUMTVERTEX 12
+		*MESH_TVERTLIST {
+			*MESH_TVERT 0	0.0000	0.0000	0.0000
+			*MESH_TVERT 1	1.0000	0.0000	0.0000
+			*MESH_TVERT 2	0.0000	1.0000	0.0000
+			*MESH_TVERT 3	1.0000	1.0000	0.0000
+			*MESH_TVERT 4	0.0000	0.0000	0.0000
+			*MESH_TVERT 5	1.0000	0.0000	0.0000
+			*MESH_TVERT 6	0.0000	1.0000	0.0000
+			*MESH_TVERT 7	1.0000	1.0000	0.0000
+			*MESH_TVERT 8	0.0000	0.0000	0.0000
+			*MESH_TVERT 9	1.0000	0.0000	0.0000
+			*MESH_TVERT 10	0.0000	1.0000	0.0000
+			*MESH_TVERT 11	1.0000	1.0000	0.0000
+		}
+		*MESH_NUMTVFACES 12
+		*MESH_TFACELIST {
+			*MESH_TFACE 0	9	11	10
+			*MESH_TFACE 1	10	8	9
+			*MESH_TFACE 2	8	9	11
+			*MESH_TFACE 3	11	10	8
+			*MESH_TFACE 4	4	5	7
+			*MESH_TFACE 5	7	6	4
+			*MESH_TFACE 6	0	1	3
+			*MESH_TFACE 7	3	2	0
+			*MESH_TFACE 8	4	5	7
+			*MESH_TFACE 9	7	6	4
+			*MESH_TFACE 10	0	1	3
+			*MESH_TFACE 11	3	2	0
+		}
+		*MESH_NUMCVERTEX 0
+		*MESH_NORMALS {
+			*MESH_FACENORMAL 0	0.0000	0.0000	-1.0000
+				*MESH_VERTEXNORMAL 0	0.0000	0.0000	-1.0000
+				*MESH_VERTEXNORMAL 2	0.0000	0.0000	-1.0000
+				*MESH_VERTEXNORMAL 3	0.0000	0.0000	-1.0000
+			*MESH_FACENORMAL 1	0.0000	0.0000	-1.0000
+				*MESH_VERTEXNORMAL 3	0.0000	0.0000	-1.0000
+				*MESH_VERTEXNORMAL 1	0.0000	0.0000	-1.0000
+				*MESH_VERTEXNORMAL 0	0.0000	0.0000	-1.0000
+			*MESH_FACENORMAL 2	0.0000	-0.0000	1.0000
+				*MESH_VERTEXNORMAL 4	0.0000	0.0000	1.0000
+				*MESH_VERTEXNORMAL 5	0.0000	-0.0000	1.0000
+				*MESH_VERTEXNORMAL 7	0.0000	0.0000	1.0000
+			*MESH_FACENORMAL 3	-0.0000	0.0000	1.0000
+				*MESH_VERTEXNORMAL 7	0.0000	0.0000	1.0000
+				*MESH_VERTEXNORMAL 6	-0.0000	0.0000	1.0000
+				*MESH_VERTEXNORMAL 4	0.0000	0.0000	1.0000
+			*MESH_FACENORMAL 4	0.0000	-1.0000	0.0000
+				*MESH_VERTEXNORMAL 0	0.0000	-1.0000	0.0000
+				*MESH_VERTEXNORMAL 1	0.0000	-1.0000	0.0000
+				*MESH_VERTEXNORMAL 5	0.0000	-1.0000	0.0000
+			*MESH_FACENORMAL 5	0.0000	-1.0000	0.0000
+				*MESH_VERTEXNORMAL 5	0.0000	-1.0000	0.0000
+				*MESH_VERTEXNORMAL 4	0.0000	-1.0000	0.0000
+				*MESH_VERTEXNORMAL 0	0.0000	-1.0000	0.0000
+			*MESH_FACENORMAL 6	1.0000	0.0000	-0.0000
+				*MESH_VERTEXNORMAL 1	1.0000	0.0000	0.0000
+				*MESH_VERTEXNORMAL 3	1.0000	0.0000	-0.0000
+				*MESH_VERTEXNORMAL 7	1.0000	0.0000	0.0000
+			*MESH_FACENORMAL 7	1.0000	-0.0000	0.0000
+				*MESH_VERTEXNORMAL 7	1.0000	0.0000	0.0000
+				*MESH_VERTEXNORMAL 5	1.0000	-0.0000	0.0000
+				*MESH_VERTEXNORMAL 1	1.0000	0.0000	0.0000
+			*MESH_FACENORMAL 8	0.0000	1.0000	0.0000
+				*MESH_VERTEXNORMAL 3	0.0000	1.0000	0.0000
+				*MESH_VERTEXNORMAL 2	0.0000	1.0000	0.0000
+				*MESH_VERTEXNORMAL 6	0.0000	1.0000	0.0000
+			*MESH_FACENORMAL 9	0.0000	1.0000	0.0000
+				*MESH_VERTEXNORMAL 6	0.0000	1.0000	0.0000
+				*MESH_VERTEXNORMAL 7	0.0000	1.0000	0.0000
+				*MESH_VERTEXNORMAL 3	0.0000	1.0000	0.0000
+			*MESH_FACENORMAL 10	-1.0000	0.0000	0.0000
+				*MESH_VERTEXNORMAL 2	-1.0000	0.0000	0.0000
+				*MESH_VERTEXNORMAL 0	-1.0000	0.0000	0.0000
+				*MESH_VERTEXNORMAL 4	-1.0000	0.0000	0.0000
+			*MESH_FACENORMAL 11	-1.0000	-0.0000	-0.0000
+				*MESH_VERTEXNORMAL 4	-1.0000	0.0000	0.0000
+				*MESH_VERTEXNORMAL 6	-1.0000	-0.0000	-0.0000
+				*MESH_VERTEXNORMAL 2	-1.0000	0.0000	0.0000
+		}
+	}
+	*PROP_MOTIONBLUR 0
+	*PROP_CASTSHADOW 1
+	*PROP_RECVSHADOW 1
+	*WIREFRAME_COLOR 0.6941	0.3451	0.1059
+}
+*CAMERAOBJECT {
+	*NODE_NAME "Camera01"
+	*CAMERA_TYPE Target
+	*NODE_TM {
+		*NODE_NAME "Camera01"
+		*INHERIT_POS 0 0 0
+		*INHERIT_ROT 0 0 0
+		*INHERIT_SCL 1 1 1
+		*TM_ROW0 -0.0301	-0.9995	0.0009
+		*TM_ROW1 0.0000	0.0009	1.0000
+		*TM_ROW2 -0.9995	0.0301	0.0000
+		*TM_ROW3 -153.0771	3.2720	22.7776
+		*TM_POS -153.0771	3.2720	22.7776
+		*TM_ROTAXIS 0.5656	-0.5834	-0.5829
+		*TM_ROTANGLE 4.1718
+		*TM_SCALE 1.0000	1.0000	1.0000
+		*TM_SCALEAXIS 0.0000	0.0000	0.0000
+		*TM_SCALEAXISANG 0.0000
+	}
+	*NODE_TM {
+		*NODE_NAME "Camera01.Target"
+		*INHERIT_POS 0 0 0
+		*INHERIT_ROT 0 0 0
+		*INHERIT_SCL 0 0 0
+		*TM_ROW0 1.0000	0.0000	0.0000
+		*TM_ROW1 0.0000	1.0000	0.0000
+		*TM_ROW2 0.0000	0.0000	1.0000
+		*TM_ROW3 -37.2279	-0.2205	22.7776
+		*TM_POS -37.2279	-0.2205	22.7776
+		*TM_ROTAXIS 0.0000	0.0000	0.0000
+		*TM_ROTANGLE 0.0000
+		*TM_SCALE 1.0000	1.0000	1.0000
+		*TM_SCALEAXIS 0.0000	0.0000	0.0000
+		*TM_SCALEAXISANG 0.0000
+	}
+	*CAMERA_SETTINGS {
+		*TIMEVALUE 0
+		*CAMERA_NEAR 0.0000
+		*CAMERA_FAR 1000.0000
+		*CAMERA_FOV 0.7854
+		*CAMERA_TDIST 115.9018
+	}
+	*TM_ANIMATION {
+		*NODE_NAME "Camera01"
+		*CONTROL_ROT_TRACK {
+			*CONTROL_ROT_SAMPLE 0	0.5656	-0.5834	-0.5829	4.1718
+			*CONTROL_ROT_SAMPLE 160	0.9996	-0.0300	-0.0001	0.0003
+			*CONTROL_ROT_SAMPLE 320	0.9995	-0.0302	-0.0001	0.0008
+			*CONTROL_ROT_SAMPLE 480	0.9995	-0.0301	-0.0001	0.0014
+			*CONTROL_ROT_SAMPLE 640	0.9995	-0.0301	0.0000	0.0019
+			*CONTROL_ROT_SAMPLE 800	0.9995	-0.0301	-0.0000	0.0024
+			*CONTROL_ROT_SAMPLE 960	0.9995	-0.0301	0.0000	0.0029
+			*CONTROL_ROT_SAMPLE 1120	0.9995	-0.0301	-0.0000	0.0035
+			*CONTROL_ROT_SAMPLE 1280	0.9995	-0.0301	-0.0000	0.0039
+			*CONTROL_ROT_SAMPLE 1440	0.9995	-0.0301	-0.0000	0.0044
+			*CONTROL_ROT_SAMPLE 1600	0.9995	-0.0301	0.0000	0.0049
+			*CONTROL_ROT_SAMPLE 1760	-0.9995	0.0301	0.0000	6.2778
+			*CONTROL_ROT_SAMPLE 1920	0.9995	-0.0302	-0.0000	0.0058
+			*CONTROL_ROT_SAMPLE 2080	0.9995	-0.0301	0.0000	0.0063
+			*CONTROL_ROT_SAMPLE 2240	0.9995	-0.0301	0.0000	0.0067
+			*CONTROL_ROT_SAMPLE 2400	0.9995	-0.0301	-0.0000	0.0072
+			*CONTROL_ROT_SAMPLE 2560	0.9995	-0.0301	0.0000	0.0076
+			*CONTROL_ROT_SAMPLE 2720	0.9995	-0.0301	0.0000	0.0080
+			*CONTROL_ROT_SAMPLE 2880	0.9995	-0.0301	0.0000	0.0084
+			*CONTROL_ROT_SAMPLE 3040	0.9995	-0.0301	-0.0000	0.0088
+			*CONTROL_ROT_SAMPLE 3200	0.9995	-0.0301	0.0000	0.0092
+			*CONTROL_ROT_SAMPLE 3360	0.9995	-0.0301	-0.0000	0.0095
+			*CONTROL_ROT_SAMPLE 3520	0.9995	-0.0301	0.0000	0.0099
+			*CONTROL_ROT_SAMPLE 3680	0.9995	-0.0301	0.0000	0.0103
+			*CONTROL_ROT_SAMPLE 3840	0.9995	-0.0301	-0.0000	0.0106
+			*CONTROL_ROT_SAMPLE 4000	0.9995	-0.0301	-0.0000	0.0110
+			*CONTROL_ROT_SAMPLE 4160	0.9995	-0.0301	0.0000	0.0113
+			*CONTROL_ROT_SAMPLE 4320	0.9995	-0.0301	0.0000	0.0116
+			*CONTROL_ROT_SAMPLE 4480	0.9995	-0.0301	0.0000	0.0119
+			*CONTROL_ROT_SAMPLE 4640	0.9995	-0.0301	-0.0000	0.0122
+			*CONTROL_ROT_SAMPLE 4800	0.9995	-0.0301	0.0000	0.0125
+			*CONTROL_ROT_SAMPLE 4960	0.9995	-0.0301	0.0000	0.0128
+			*CONTROL_ROT_SAMPLE 5120	0.9995	-0.0301	0.0000	0.0131
+			*CONTROL_ROT_SAMPLE 5280	0.9995	-0.0301	-0.0000	0.0133
+			*CONTROL_ROT_SAMPLE 5440	0.9995	-0.0301	0.0000	0.0136
+			*CONTROL_ROT_SAMPLE 5600	0.9995	-0.0301	-0.0000	0.0138
+			*CONTROL_ROT_SAMPLE 5760	0.9995	-0.0301	0.0000	0.0140
+			*CONTROL_ROT_SAMPLE 5920	0.9995	-0.0301	-0.0000	0.0143
+			*CONTROL_ROT_SAMPLE 6080	0.9995	-0.0301	0.0000	0.0145
+			*CONTROL_ROT_SAMPLE 6240	0.9995	-0.0301	0.0000	0.0147
+			*CONTROL_ROT_SAMPLE 6400	0.9995	-0.0301	-0.0000	0.0149
+			*CONTROL_ROT_SAMPLE 6560	0.9995	-0.0301	0.0000	0.0151
+			*CONTROL_ROT_SAMPLE 6720	0.9995	-0.0301	0.0000	0.0153
+			*CONTROL_ROT_SAMPLE 6880	0.9995	-0.0301	-0.0000	0.0154
+			*CONTROL_ROT_SAMPLE 7040	0.9995	-0.0301	-0.0000	0.0156
+			*CONTROL_ROT_SAMPLE 7200	0.9995	-0.0301	0.0000	0.0157
+			*CONTROL_ROT_SAMPLE 7360	0.9995	-0.0301	-0.0000	0.0159
+			*CONTROL_ROT_SAMPLE 7520	0.9995	-0.0301	0.0000	0.0160
+			*CONTROL_ROT_SAMPLE 7680	0.9995	-0.0301	-0.0000	0.0161
+			*CONTROL_ROT_SAMPLE 7840	0.9995	-0.0301	0.0000	0.0162
+			*CONTROL_ROT_SAMPLE 8000	0.9995	-0.0301	-0.0000	0.0163
+			*CONTROL_ROT_SAMPLE 8160	0.9995	-0.0301	-0.0000	0.0164
+			*CONTROL_ROT_SAMPLE 8320	0.9995	-0.0301	0.0000	0.0165
+			*CONTROL_ROT_SAMPLE 8480	0.9995	-0.0301	-0.0000	0.0166
+			*CONTROL_ROT_SAMPLE 8640	0.9995	-0.0301	0.0000	0.0167
+			*CONTROL_ROT_SAMPLE 8800	0.9995	-0.0301	-0.0000	0.0167
+			*CONTROL_ROT_SAMPLE 8960	0.9995	-0.0301	0.0000	0.0168
+			*CONTROL_ROT_SAMPLE 9120	0.9995	-0.0301	0.0000	0.0168
+			*CONTROL_ROT_SAMPLE 9280	0.9995	-0.0301	0.0000	0.0168
+			*CONTROL_ROT_SAMPLE 9440	0.9995	-0.0301	0.0000	0.0168
+			*CONTROL_ROT_SAMPLE 9600	0.9995	-0.0301	0.0000	0.0169
+			*CONTROL_ROT_SAMPLE 9760	0.9995	-0.0301	-0.0000	0.0169
+			*CONTROL_ROT_SAMPLE 9920	0.9995	-0.0301	-0.0000	0.0168
+			*CONTROL_ROT_SAMPLE 10080	0.9995	-0.0301	0.0000	0.0168
+			*CONTROL_ROT_SAMPLE 10240	0.9995	-0.0301	-0.0000	0.0168
+			*CONTROL_ROT_SAMPLE 10400	0.9995	-0.0301	-0.0000	0.0168
+			*CONTROL_ROT_SAMPLE 10560	0.9995	-0.0301	0.0000	0.0167
+			*CONTROL_ROT_SAMPLE 10720	0.9995	-0.0301	0.0000	0.0167
+			*CONTROL_ROT_SAMPLE 10880	0.9995	-0.0301	0.0000	0.0166
+			*CONTROL_ROT_SAMPLE 11040	0.9995	-0.0301	0.0000	0.0165
+			*CONTROL_ROT_SAMPLE 11200	0.9995	-0.0301	-0.0000	0.0164
+			*CONTROL_ROT_SAMPLE 11360	0.9995	-0.0301	0.0000	0.0163
+			*CONTROL_ROT_SAMPLE 11520	0.9995	-0.0301	-0.0000	0.0162
+			*CONTROL_ROT_SAMPLE 11680	0.9995	-0.0301	0.0000	0.0161
+			*CONTROL_ROT_SAMPLE 11840	0.9995	-0.0301	-0.0000	0.0160
+			*CONTROL_ROT_SAMPLE 12000	0.9995	-0.0301	-0.0000	0.0159
+			*CONTROL_ROT_SAMPLE 12160	0.9995	-0.0301	0.0000	0.0157
+			*CONTROL_ROT_SAMPLE 12320	0.9995	-0.0301	0.0000	0.0156
+			*CONTROL_ROT_SAMPLE 12480	0.9995	-0.0301	0.0000	0.0154
+			*CONTROL_ROT_SAMPLE 12640	0.9995	-0.0301	0.0000	0.0153
+			*CONTROL_ROT_SAMPLE 12800	0.9995	-0.0301	0.0000	0.0151
+			*CONTROL_ROT_SAMPLE 12960	0.9995	-0.0301	0.0000	0.0149
+			*CONTROL_ROT_SAMPLE 13120	0.9995	-0.0301	0.0000	0.0147
+			*CONTROL_ROT_SAMPLE 13280	0.9995	-0.0301	0.0000	0.0145
+			*CONTROL_ROT_SAMPLE 13440	0.9995	-0.0301	-0.0000	0.0143
+			*CONTROL_ROT_SAMPLE 13600	0.9995	-0.0301	-0.0000	0.0140
+			*CONTROL_ROT_SAMPLE 13760	0.9995	-0.0301	0.0000	0.0138
+			*CONTROL_ROT_SAMPLE 13920	0.9995	-0.0301	0.0000	0.0136
+			*CONTROL_ROT_SAMPLE 14080	0.9995	-0.0301	-0.0000	0.0133
+			*CONTROL_ROT_SAMPLE 14240	0.9995	-0.0301	0.0000	0.0131
+			*CONTROL_ROT_SAMPLE 14400	0.9995	-0.0301	0.0000	0.0128
+			*CONTROL_ROT_SAMPLE 14560	0.9995	-0.0301	0.0000	0.0125
+			*CONTROL_ROT_SAMPLE 14720	0.9995	-0.0301	0.0000	0.0122
+			*CONTROL_ROT_SAMPLE 14880	0.9995	-0.0301	-0.0000	0.0119
+			*CONTROL_ROT_SAMPLE 15040	0.9995	-0.0301	0.0000	0.0116
+			*CONTROL_ROT_SAMPLE 15200	0.9995	-0.0301	0.0000	0.0113
+			*CONTROL_ROT_SAMPLE 15360	0.9995	-0.0301	0.0000	0.0110
+			*CONTROL_ROT_SAMPLE 15520	0.9995	-0.0301	0.0000	0.0106
+			*CONTROL_ROT_SAMPLE 15680	0.9995	-0.0301	0.0000	0.0103
+			*CONTROL_ROT_SAMPLE 15840	0.9995	-0.0301	0.0000	0.0099
+			*CONTROL_ROT_SAMPLE 16000	0.9995	-0.0301	0.0000	0.0095
+			*CONTROL_ROT_SAMPLE 16160	0.9995	-0.0301	0.0000	0.0092
+			*CONTROL_ROT_SAMPLE 16320	0.9995	-0.0301	-0.0000	0.0088
+			*CONTROL_ROT_SAMPLE 16480	0.9995	-0.0301	0.0000	0.0084
+			*CONTROL_ROT_SAMPLE 16640	0.9995	-0.0301	0.0000	0.0080
+			*CONTROL_ROT_SAMPLE 16800	0.9995	-0.0301	0.0000	0.0076
+			*CONTROL_ROT_SAMPLE 16960	0.9995	-0.0301	-0.0000	0.0072
+			*CONTROL_ROT_SAMPLE 17120	0.9995	-0.0301	0.0000	0.0067
+			*CONTROL_ROT_SAMPLE 17280	0.9995	-0.0301	0.0000	0.0063
+			*CONTROL_ROT_SAMPLE 17440	0.9995	-0.0301	-0.0000	0.0058
+			*CONTROL_ROT_SAMPLE 17600	0.9995	-0.0301	-0.0000	0.0054
+			*CONTROL_ROT_SAMPLE 17760	0.9995	-0.0302	0.0000	0.0049
+			*CONTROL_ROT_SAMPLE 17920	0.9995	-0.0301	0.0000	0.0044
+			*CONTROL_ROT_SAMPLE 18080	0.9995	-0.0302	-0.0000	0.0039
+			*CONTROL_ROT_SAMPLE 18240	0.9995	-0.0301	0.0000	0.0035
+			*CONTROL_ROT_SAMPLE 18400	0.9995	-0.0301	0.0000	0.0029
+			*CONTROL_ROT_SAMPLE 18560	0.9995	-0.0301	0.0000	0.0024
+			*CONTROL_ROT_SAMPLE 18720	0.9995	-0.0301	0.0000	0.0019
+			*CONTROL_ROT_SAMPLE 18880	0.9995	-0.0301	-0.0000	0.0014
+			*CONTROL_ROT_SAMPLE 19040	0.9995	-0.0301	0.0000	0.0008
+			*CONTROL_ROT_SAMPLE 19200	0.9995	-0.0302	0.0000	0.0003
+		}
+	}
+}

+ 655 - 0
test/ASEFiles/CameraRollAnimWithChildObject.ase

@@ -0,0 +1,655 @@
+*3DSMAX_ASCIIEXPORT	200
+*COMMENT "AsciiExport Version  2,00 - Sat Nov 08 17:59:18 2008"
+*SCENE {
+	*SCENE_FILENAME "CameraRollAnimWithChildObject.max"
+	*SCENE_FIRSTFRAME 0
+	*SCENE_LASTFRAME 300
+	*SCENE_FRAMESPEED 30
+	*SCENE_TICKSPERFRAME 160
+	*SCENE_BACKGROUND_STATIC 0.0000	0.0000	0.0000
+	*SCENE_AMBIENT_STATIC 0.0000	0.0000	0.0000
+}
+*MATERIAL_LIST {
+	*MATERIAL_COUNT 0
+}
+*GEOMOBJECT {
+	*NODE_NAME "Box01"
+	*NODE_TM {
+		*NODE_NAME "Box01"
+		*INHERIT_POS 0 0 0
+		*INHERIT_ROT 0 0 0
+		*INHERIT_SCL 0 0 0
+		*TM_ROW0 1.0000	0.0000	0.0000
+		*TM_ROW1 0.0000	1.0000	0.0000
+		*TM_ROW2 0.0000	0.0000	1.0000
+		*TM_ROW3 10.5413	-0.8547	0.0000
+		*TM_POS 10.5413	-0.8547	0.0000
+		*TM_ROTAXIS 0.0000	0.0000	0.0000
+		*TM_ROTANGLE 0.0000
+		*TM_SCALE 1.0000	1.0000	1.0000
+		*TM_SCALEAXIS 0.0000	0.0000	0.0000
+		*TM_SCALEAXISANG 0.0000
+	}
+	*MESH {
+		*TIMEVALUE 0
+		*MESH_NUMVERTEX 8
+		*MESH_NUMFACES 12
+		*MESH_VERTEX_LIST {
+			*MESH_VERTEX    0	-24.7863	-24.7863	0.0000
+			*MESH_VERTEX    1	45.8689	-24.7863	0.0000
+			*MESH_VERTEX    2	-24.7863	23.0769	0.0000
+			*MESH_VERTEX    3	45.8689	23.0769	0.0000
+			*MESH_VERTEX    4	-24.7863	-24.7863	38.7464
+			*MESH_VERTEX    5	45.8689	-24.7863	38.7464
+			*MESH_VERTEX    6	-24.7863	23.0769	38.7464
+			*MESH_VERTEX    7	45.8689	23.0769	38.7464
+		}
+		*MESH_FACE_LIST {
+			*MESH_FACE    0:    A:    0 B:    2 C:    3 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 2 	*MESH_MTLID 1
+			*MESH_FACE    1:    A:    3 B:    1 C:    0 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 2 	*MESH_MTLID 1
+			*MESH_FACE    2:    A:    4 B:    5 C:    7 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 3 	*MESH_MTLID 0
+			*MESH_FACE    3:    A:    7 B:    6 C:    4 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 3 	*MESH_MTLID 0
+			*MESH_FACE    4:    A:    0 B:    1 C:    5 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 4 	*MESH_MTLID 4
+			*MESH_FACE    5:    A:    5 B:    4 C:    0 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 4 	*MESH_MTLID 4
+			*MESH_FACE    6:    A:    1 B:    3 C:    7 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 5 	*MESH_MTLID 3
+			*MESH_FACE    7:    A:    7 B:    5 C:    1 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 5 	*MESH_MTLID 3
+			*MESH_FACE    8:    A:    3 B:    2 C:    6 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 6 	*MESH_MTLID 5
+			*MESH_FACE    9:    A:    6 B:    7 C:    3 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 6 	*MESH_MTLID 5
+			*MESH_FACE   10:    A:    2 B:    0 C:    4 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 7 	*MESH_MTLID 2
+			*MESH_FACE   11:    A:    4 B:    6 C:    2 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 7 	*MESH_MTLID 2
+		}
+		*MESH_NUMTVERTEX 12
+		*MESH_TVERTLIST {
+			*MESH_TVERT 0	0.0000	0.0000	0.0000
+			*MESH_TVERT 1	1.0000	0.0000	0.0000
+			*MESH_TVERT 2	0.0000	1.0000	0.0000
+			*MESH_TVERT 3	1.0000	1.0000	0.0000
+			*MESH_TVERT 4	0.0000	0.0000	0.0000
+			*MESH_TVERT 5	1.0000	0.0000	0.0000
+			*MESH_TVERT 6	0.0000	1.0000	0.0000
+			*MESH_TVERT 7	1.0000	1.0000	0.0000
+			*MESH_TVERT 8	0.0000	0.0000	0.0000
+			*MESH_TVERT 9	1.0000	0.0000	0.0000
+			*MESH_TVERT 10	0.0000	1.0000	0.0000
+			*MESH_TVERT 11	1.0000	1.0000	0.0000
+		}
+		*MESH_NUMTVFACES 12
+		*MESH_TFACELIST {
+			*MESH_TFACE 0	9	11	10
+			*MESH_TFACE 1	10	8	9
+			*MESH_TFACE 2	8	9	11
+			*MESH_TFACE 3	11	10	8
+			*MESH_TFACE 4	4	5	7
+			*MESH_TFACE 5	7	6	4
+			*MESH_TFACE 6	0	1	3
+			*MESH_TFACE 7	3	2	0
+			*MESH_TFACE 8	4	5	7
+			*MESH_TFACE 9	7	6	4
+			*MESH_TFACE 10	0	1	3
+			*MESH_TFACE 11	3	2	0
+		}
+		*MESH_NUMCVERTEX 0
+		*MESH_NORMALS {
+			*MESH_FACENORMAL 0	0.0000	0.0000	-1.0000
+				*MESH_VERTEXNORMAL 0	0.0000	0.0000	-1.0000
+				*MESH_VERTEXNORMAL 2	0.0000	0.0000	-1.0000
+				*MESH_VERTEXNORMAL 3	0.0000	0.0000	-1.0000
+			*MESH_FACENORMAL 1	0.0000	0.0000	-1.0000
+				*MESH_VERTEXNORMAL 3	0.0000	0.0000	-1.0000
+				*MESH_VERTEXNORMAL 1	0.0000	0.0000	-1.0000
+				*MESH_VERTEXNORMAL 0	0.0000	0.0000	-1.0000
+			*MESH_FACENORMAL 2	0.0000	-0.0000	1.0000
+				*MESH_VERTEXNORMAL 4	0.0000	0.0000	1.0000
+				*MESH_VERTEXNORMAL 5	0.0000	-0.0000	1.0000
+				*MESH_VERTEXNORMAL 7	0.0000	0.0000	1.0000
+			*MESH_FACENORMAL 3	-0.0000	0.0000	1.0000
+				*MESH_VERTEXNORMAL 7	0.0000	0.0000	1.0000
+				*MESH_VERTEXNORMAL 6	-0.0000	0.0000	1.0000
+				*MESH_VERTEXNORMAL 4	0.0000	0.0000	1.0000
+			*MESH_FACENORMAL 4	0.0000	-1.0000	0.0000
+				*MESH_VERTEXNORMAL 0	0.0000	-1.0000	0.0000
+				*MESH_VERTEXNORMAL 1	0.0000	-1.0000	0.0000
+				*MESH_VERTEXNORMAL 5	0.0000	-1.0000	0.0000
+			*MESH_FACENORMAL 5	0.0000	-1.0000	0.0000
+				*MESH_VERTEXNORMAL 5	0.0000	-1.0000	0.0000
+				*MESH_VERTEXNORMAL 4	0.0000	-1.0000	0.0000
+				*MESH_VERTEXNORMAL 0	0.0000	-1.0000	0.0000
+			*MESH_FACENORMAL 6	1.0000	0.0000	-0.0000
+				*MESH_VERTEXNORMAL 1	1.0000	0.0000	0.0000
+				*MESH_VERTEXNORMAL 3	1.0000	0.0000	-0.0000
+				*MESH_VERTEXNORMAL 7	1.0000	0.0000	0.0000
+			*MESH_FACENORMAL 7	1.0000	-0.0000	0.0000
+				*MESH_VERTEXNORMAL 7	1.0000	0.0000	0.0000
+				*MESH_VERTEXNORMAL 5	1.0000	-0.0000	0.0000
+				*MESH_VERTEXNORMAL 1	1.0000	0.0000	0.0000
+			*MESH_FACENORMAL 8	0.0000	1.0000	0.0000
+				*MESH_VERTEXNORMAL 3	0.0000	1.0000	0.0000
+				*MESH_VERTEXNORMAL 2	0.0000	1.0000	0.0000
+				*MESH_VERTEXNORMAL 6	0.0000	1.0000	0.0000
+			*MESH_FACENORMAL 9	0.0000	1.0000	0.0000
+				*MESH_VERTEXNORMAL 6	0.0000	1.0000	0.0000
+				*MESH_VERTEXNORMAL 7	0.0000	1.0000	0.0000
+				*MESH_VERTEXNORMAL 3	0.0000	1.0000	0.0000
+			*MESH_FACENORMAL 10	-1.0000	0.0000	0.0000
+				*MESH_VERTEXNORMAL 2	-1.0000	0.0000	0.0000
+				*MESH_VERTEXNORMAL 0	-1.0000	0.0000	0.0000
+				*MESH_VERTEXNORMAL 4	-1.0000	0.0000	0.0000
+			*MESH_FACENORMAL 11	-1.0000	-0.0000	-0.0000
+				*MESH_VERTEXNORMAL 4	-1.0000	0.0000	0.0000
+				*MESH_VERTEXNORMAL 6	-1.0000	-0.0000	-0.0000
+				*MESH_VERTEXNORMAL 2	-1.0000	0.0000	0.0000
+		}
+	}
+	*PROP_MOTIONBLUR 0
+	*PROP_CASTSHADOW 1
+	*PROP_RECVSHADOW 1
+	*WIREFRAME_COLOR 0.6941	0.3451	0.1059
+}
+*CAMERAOBJECT {
+	*NODE_NAME "Camera01"
+	*CAMERA_TYPE Target
+	*NODE_TM {
+		*NODE_NAME "Camera01"
+		*INHERIT_POS 0 0 0
+		*INHERIT_ROT 0 0 0
+		*INHERIT_SCL 1 1 1
+		*TM_ROW0 -0.0301	-0.9995	-0.0000
+		*TM_ROW1 -0.0194	0.0006	0.9998
+		*TM_ROW2 -0.9994	0.0301	-0.0194
+		*TM_ROW3 -152.3626	3.2505	22.7915
+		*TM_POS -152.3626	3.2505	22.7915
+		*TM_ROTAXIS 0.5694	-0.5869	-0.5756
+		*TM_ROTANGLE 4.1603
+		*TM_SCALE 1.0000	1.0000	1.0000
+		*TM_SCALEAXIS 0.2700	-0.9628	-0.0060
+		*TM_SCALEAXISANG 0.0243
+	}
+	*NODE_TM {
+		*NODE_NAME "Camera01.Target"
+		*INHERIT_POS 0 0 0
+		*INHERIT_ROT 0 0 0
+		*INHERIT_SCL 0 0 0
+		*TM_ROW0 1.0000	0.0000	0.0000
+		*TM_ROW1 0.0000	1.0000	0.0000
+		*TM_ROW2 0.0000	0.0000	1.0000
+		*TM_ROW3 -36.5134	-0.2420	25.0386
+		*TM_POS -36.5134	-0.2420	25.0386
+		*TM_ROTAXIS 0.0000	0.0000	0.0000
+		*TM_ROTANGLE 0.0000
+		*TM_SCALE 1.0000	1.0000	1.0000
+		*TM_SCALEAXIS 0.0000	0.0000	0.0000
+		*TM_SCALEAXISANG 0.0000
+	}
+	*CAMERA_SETTINGS {
+		*TIMEVALUE 0
+		*CAMERA_NEAR 0.0000
+		*CAMERA_FAR 1000.0000
+		*CAMERA_FOV 0.7854
+		*CAMERA_TDIST 115.9236
+	}
+	*TM_ANIMATION {
+		*NODE_NAME "Camera01"
+		*CONTROL_ROT_TRACK {
+			*CONTROL_ROT_SAMPLE 0	0.5694	-0.5869	-0.5756	4.1603
+			*CONTROL_ROT_SAMPLE 160	0.9869	0.1603	0.0186	0.0003
+			*CONTROL_ROT_SAMPLE 320	0.9869	0.1602	0.0193	0.0008
+			*CONTROL_ROT_SAMPLE 480	0.9869	0.1599	0.0194	0.0014
+			*CONTROL_ROT_SAMPLE 640	0.9870	0.1595	0.0197	0.0019
+			*CONTROL_ROT_SAMPLE 800	0.9870	0.1592	0.0201	0.0025
+			*CONTROL_ROT_SAMPLE 960	0.9871	0.1588	0.0206	0.0030
+			*CONTROL_ROT_SAMPLE 1120	0.9871	0.1585	0.0212	0.0035
+			*CONTROL_ROT_SAMPLE 1280	0.9872	0.1582	0.0219	0.0040
+			*CONTROL_ROT_SAMPLE 1440	0.9872	0.1578	0.0227	0.0045
+			*CONTROL_ROT_SAMPLE 1600	0.9872	0.1575	0.0236	0.0050
+			*CONTROL_ROT_SAMPLE 1760	0.9873	0.1571	0.0245	0.0055
+			*CONTROL_ROT_SAMPLE 1920	0.9873	0.1567	0.0256	0.0059
+			*CONTROL_ROT_SAMPLE 2080	0.9873	0.1563	0.0267	0.0064
+			*CONTROL_ROT_SAMPLE 2240	0.9874	0.1559	0.0279	0.0068
+			*CONTROL_ROT_SAMPLE 2400	0.9874	0.1555	0.0292	0.0073
+			*CONTROL_ROT_SAMPLE 2560	-0.9874	-0.1551	-0.0306	6.2755
+			*CONTROL_ROT_SAMPLE 2720	0.9875	0.1546	0.0320	0.0081
+			*CONTROL_ROT_SAMPLE 2880	0.9875	0.1542	0.0335	0.0085
+			*CONTROL_ROT_SAMPLE 3040	0.9875	0.1538	0.0351	0.0089
+			*CONTROL_ROT_SAMPLE 3200	0.9875	0.1533	0.0368	0.0093
+			*CONTROL_ROT_SAMPLE 3360	0.9875	0.1528	0.0385	0.0097
+			*CONTROL_ROT_SAMPLE 3520	0.9875	0.1524	0.0403	0.0101
+			*CONTROL_ROT_SAMPLE 3680	0.9875	0.1519	0.0421	0.0104
+			*CONTROL_ROT_SAMPLE 3840	0.9875	0.1514	0.0440	0.0108
+			*CONTROL_ROT_SAMPLE 4000	0.9875	0.1509	0.0459	0.0111
+			*CONTROL_ROT_SAMPLE 4160	0.9875	0.1504	0.0479	0.0115
+			*CONTROL_ROT_SAMPLE 4320	0.9874	0.1498	0.0500	0.0118
+			*CONTROL_ROT_SAMPLE 4480	0.9874	0.1493	0.0521	0.0121
+			*CONTROL_ROT_SAMPLE 4640	0.9874	0.1487	0.0543	0.0124
+			*CONTROL_ROT_SAMPLE 4800	0.9874	0.1481	0.0565	0.0127
+			*CONTROL_ROT_SAMPLE 4960	0.9873	0.1475	0.0587	0.0130
+			*CONTROL_ROT_SAMPLE 5120	0.9873	0.1469	0.0610	0.0133
+			*CONTROL_ROT_SAMPLE 5280	0.9872	0.1463	0.0633	0.0135
+			*CONTROL_ROT_SAMPLE 5440	0.9871	0.1457	0.0657	0.0138
+			*CONTROL_ROT_SAMPLE 5600	0.9871	0.1450	0.0681	0.0140
+			*CONTROL_ROT_SAMPLE 5760	0.9870	0.1443	0.0705	0.0143
+			*CONTROL_ROT_SAMPLE 5920	0.9869	0.1437	0.0730	0.0145
+			*CONTROL_ROT_SAMPLE 6080	0.9868	0.1430	0.0755	0.0147
+			*CONTROL_ROT_SAMPLE 6240	0.9868	0.1422	0.0780	0.0149
+			*CONTROL_ROT_SAMPLE 6400	0.9867	0.1415	0.0805	0.0151
+			*CONTROL_ROT_SAMPLE 6560	0.9866	0.1407	0.0831	0.0153
+			*CONTROL_ROT_SAMPLE 6720	0.9864	0.1400	0.0857	0.0155
+			*CONTROL_ROT_SAMPLE 6880	0.9863	0.1392	0.0883	0.0156
+			*CONTROL_ROT_SAMPLE 7040	0.9862	0.1383	0.0909	0.0158
+			*CONTROL_ROT_SAMPLE 7200	0.9861	0.1375	0.0935	0.0160
+			*CONTROL_ROT_SAMPLE 7360	0.9859	0.1366	0.0961	0.0161
+			*CONTROL_ROT_SAMPLE 7520	0.9858	0.1357	0.0988	0.0162
+			*CONTROL_ROT_SAMPLE 7680	0.9857	0.1348	0.1014	0.0163
+			*CONTROL_ROT_SAMPLE 7840	0.9855	0.1339	0.1041	0.0165
+			*CONTROL_ROT_SAMPLE 8000	0.9854	0.1329	0.1067	0.0166
+			*CONTROL_ROT_SAMPLE 8160	0.9852	0.1319	0.1094	0.0166
+			*CONTROL_ROT_SAMPLE 8320	0.9850	0.1309	0.1121	0.0167
+			*CONTROL_ROT_SAMPLE 8480	0.9849	0.1299	0.1147	0.0168
+			*CONTROL_ROT_SAMPLE 8640	0.9847	0.1288	0.1174	0.0169
+			*CONTROL_ROT_SAMPLE 8800	0.9845	0.1277	0.1200	0.0169
+			*CONTROL_ROT_SAMPLE 8960	0.9844	0.1265	0.1226	0.0170
+			*CONTROL_ROT_SAMPLE 9120	0.9842	0.1254	0.1252	0.0170
+			*CONTROL_ROT_SAMPLE 9280	0.9840	0.1242	0.1278	0.0170
+			*CONTROL_ROT_SAMPLE 9440	0.9838	0.1229	0.1304	0.0170
+			*CONTROL_ROT_SAMPLE 9600	0.9836	0.1216	0.1330	0.0170
+			*CONTROL_ROT_SAMPLE 9760	0.9834	0.1203	0.1355	0.0170
+			*CONTROL_ROT_SAMPLE 9920	0.9833	0.1189	0.1380	0.0170
+			*CONTROL_ROT_SAMPLE 10080	0.9831	0.1175	0.1405	0.0170
+			*CONTROL_ROT_SAMPLE 10240	0.9829	0.1161	0.1430	0.0170
+			*CONTROL_ROT_SAMPLE 10400	0.9827	0.1146	0.1454	0.0169
+			*CONTROL_ROT_SAMPLE 10560	0.9825	0.1130	0.1478	0.0169
+			*CONTROL_ROT_SAMPLE 10720	0.9824	0.1114	0.1502	0.0168
+			*CONTROL_ROT_SAMPLE 10880	0.9822	0.1097	0.1525	0.0168
+			*CONTROL_ROT_SAMPLE 11040	0.9820	0.1080	0.1548	0.0167
+			*CONTROL_ROT_SAMPLE 11200	0.9819	0.1062	0.1571	0.0166
+			*CONTROL_ROT_SAMPLE 11360	0.9817	0.1043	0.1593	0.0165
+			*CONTROL_ROT_SAMPLE 11520	0.9815	0.1024	0.1615	0.0164
+			*CONTROL_ROT_SAMPLE 11680	0.9814	0.1004	0.1636	0.0163
+			*CONTROL_ROT_SAMPLE 11840	0.9813	0.0983	0.1657	0.0161
+			*CONTROL_ROT_SAMPLE 12000	0.9811	0.0962	0.1678	0.0160
+			*CONTROL_ROT_SAMPLE 12160	0.9810	0.0939	0.1697	0.0158
+			*CONTROL_ROT_SAMPLE 12320	0.9809	0.0915	0.1717	0.0157
+			*CONTROL_ROT_SAMPLE 12480	0.9808	0.0891	0.1736	0.0155
+			*CONTROL_ROT_SAMPLE 12640	0.9807	0.0865	0.1754	0.0154
+			*CONTROL_ROT_SAMPLE 12800	0.9806	0.0838	0.1771	0.0152
+			*CONTROL_ROT_SAMPLE 12960	0.9805	0.0810	0.1789	0.0150
+			*CONTROL_ROT_SAMPLE 13120	0.9805	0.0780	0.1805	0.0148
+			*CONTROL_ROT_SAMPLE 13280	0.9804	0.0749	0.1821	0.0146
+			*CONTROL_ROT_SAMPLE 13440	0.9804	0.0716	0.1836	0.0143
+			*CONTROL_ROT_SAMPLE 13600	0.9804	0.0681	0.1850	0.0141
+			*CONTROL_ROT_SAMPLE 13760	0.9804	0.0644	0.1864	0.0139
+			*CONTROL_ROT_SAMPLE 13920	0.9804	0.0606	0.1877	0.0136
+			*CONTROL_ROT_SAMPLE 14080	0.9804	0.0564	0.1890	0.0134
+			*CONTROL_ROT_SAMPLE 14240	0.9804	0.0521	0.1901	0.0131
+			*CONTROL_ROT_SAMPLE 14400	0.9804	0.0474	0.1912	0.0128
+			*CONTROL_ROT_SAMPLE 14560	0.9804	0.0424	0.1922	0.0125
+			*CONTROL_ROT_SAMPLE 14720	0.9805	0.0371	0.1931	0.0122
+			*CONTROL_ROT_SAMPLE 14880	0.9805	0.0314	0.1939	0.0119
+			*CONTROL_ROT_SAMPLE 15040	0.9805	0.0253	0.1946	0.0116
+			*CONTROL_ROT_SAMPLE 15200	0.9806	0.0187	0.1953	0.0113
+			*CONTROL_ROT_SAMPLE 15360	0.9806	0.0115	0.1958	0.0110
+			*CONTROL_ROT_SAMPLE 15520	0.9805	0.0038	0.1963	0.0106
+			*CONTROL_ROT_SAMPLE 15680	0.9805	-0.0047	0.1966	0.0103
+			*CONTROL_ROT_SAMPLE 15840	0.9803	-0.0140	0.1969	0.0099
+			*CONTROL_ROT_SAMPLE 16000	0.9801	-0.0241	0.1970	0.0095
+			*CONTROL_ROT_SAMPLE 16160	0.9800	-0.0295	0.1970	0.0092
+			*CONTROL_ROT_SAMPLE 16320	0.9800	-0.0295	0.1970	0.0088
+			*CONTROL_ROT_SAMPLE 16480	0.9800	-0.0296	0.1970	0.0084
+			*CONTROL_ROT_SAMPLE 16640	0.9800	-0.0295	0.1970	0.0080
+			*CONTROL_ROT_SAMPLE 16800	0.9800	-0.0295	0.1970	0.0076
+			*CONTROL_ROT_SAMPLE 16960	0.9800	-0.0296	0.1970	0.0072
+			*CONTROL_ROT_SAMPLE 17120	0.9800	-0.0295	0.1970	0.0067
+			*CONTROL_ROT_SAMPLE 17280	0.9800	-0.0295	0.1970	0.0063
+			*CONTROL_ROT_SAMPLE 17440	0.9800	-0.0296	0.1970	0.0058
+			*CONTROL_ROT_SAMPLE 17600	0.9800	-0.0295	0.1970	0.0054
+			*CONTROL_ROT_SAMPLE 17760	0.9800	-0.0295	0.1970	0.0049
+			*CONTROL_ROT_SAMPLE 17920	0.9800	-0.0296	0.1970	0.0044
+			*CONTROL_ROT_SAMPLE 18080	0.9800	-0.0295	0.1970	0.0039
+			*CONTROL_ROT_SAMPLE 18240	0.9800	-0.0295	0.1970	0.0035
+			*CONTROL_ROT_SAMPLE 18400	0.9800	-0.0295	0.1970	0.0029
+			*CONTROL_ROT_SAMPLE 18560	0.9800	-0.0295	0.1970	0.0024
+			*CONTROL_ROT_SAMPLE 18720	0.9800	-0.0295	0.1970	0.0019
+			*CONTROL_ROT_SAMPLE 18880	0.9800	-0.0296	0.1970	0.0014
+			*CONTROL_ROT_SAMPLE 19040	0.9799	-0.0295	0.1971	0.0008
+			*CONTROL_ROT_SAMPLE 19200	0.9800	-0.0297	0.1970	0.0003
+		}
+	}
+	*TM_ANIMATION {
+		*NODE_NAME "Camera01.Target"
+		*CONTROL_POS_TRACK {
+			*CONTROL_POS_SAMPLE 0	-36.5134	-0.2420	25.0386
+			*CONTROL_POS_SAMPLE 160	-36.5134	-0.2420	25.0449
+			*CONTROL_POS_SAMPLE 320	-36.5134	-0.2420	25.0635
+			*CONTROL_POS_SAMPLE 480	-36.5134	-0.2420	25.0943
+			*CONTROL_POS_SAMPLE 640	-36.5134	-0.2420	25.1370
+			*CONTROL_POS_SAMPLE 800	-36.5134	-0.2420	25.1912
+			*CONTROL_POS_SAMPLE 960	-36.5134	-0.2420	25.2568
+			*CONTROL_POS_SAMPLE 1120	-36.5134	-0.2420	25.3336
+			*CONTROL_POS_SAMPLE 1280	-36.5134	-0.2420	25.4212
+			*CONTROL_POS_SAMPLE 1440	-36.5134	-0.2420	25.5194
+			*CONTROL_POS_SAMPLE 1600	-36.5134	-0.2420	25.6279
+			*CONTROL_POS_SAMPLE 1760	-36.5134	-0.2420	25.7466
+			*CONTROL_POS_SAMPLE 1920	-36.5134	-0.2420	25.8751
+			*CONTROL_POS_SAMPLE 2080	-36.5134	-0.2420	26.0132
+			*CONTROL_POS_SAMPLE 2240	-36.5134	-0.2420	26.1606
+			*CONTROL_POS_SAMPLE 2400	-36.5134	-0.2420	26.3172
+			*CONTROL_POS_SAMPLE 2560	-36.5134	-0.2420	26.4826
+			*CONTROL_POS_SAMPLE 2720	-36.5134	-0.2420	26.6565
+			*CONTROL_POS_SAMPLE 2880	-36.5134	-0.2420	26.8388
+			*CONTROL_POS_SAMPLE 3040	-36.5134	-0.2420	27.0292
+			*CONTROL_POS_SAMPLE 3200	-36.5134	-0.2420	27.2274
+			*CONTROL_POS_SAMPLE 3360	-36.5134	-0.2420	27.4332
+			*CONTROL_POS_SAMPLE 3520	-36.5134	-0.2420	27.6463
+			*CONTROL_POS_SAMPLE 3680	-36.5134	-0.2420	27.8665
+			*CONTROL_POS_SAMPLE 3840	-36.5134	-0.2420	28.0935
+			*CONTROL_POS_SAMPLE 4000	-36.5134	-0.2420	28.3271
+			*CONTROL_POS_SAMPLE 4160	-36.5134	-0.2420	28.5670
+			*CONTROL_POS_SAMPLE 4320	-36.5134	-0.2420	28.8129
+			*CONTROL_POS_SAMPLE 4480	-36.5134	-0.2420	29.0646
+			*CONTROL_POS_SAMPLE 4640	-36.5134	-0.2420	29.3220
+			*CONTROL_POS_SAMPLE 4800	-36.5134	-0.2420	29.5846
+			*CONTROL_POS_SAMPLE 4960	-36.5134	-0.2420	29.8522
+			*CONTROL_POS_SAMPLE 5120	-36.5134	-0.2420	30.1247
+			*CONTROL_POS_SAMPLE 5280	-36.5134	-0.2420	30.4017
+			*CONTROL_POS_SAMPLE 5440	-36.5134	-0.2420	30.6830
+			*CONTROL_POS_SAMPLE 5600	-36.5134	-0.2420	30.9683
+			*CONTROL_POS_SAMPLE 5760	-36.5134	-0.2420	31.2575
+			*CONTROL_POS_SAMPLE 5920	-36.5134	-0.2420	31.5501
+			*CONTROL_POS_SAMPLE 6080	-36.5134	-0.2420	31.8461
+			*CONTROL_POS_SAMPLE 6240	-36.5134	-0.2420	32.1451
+			*CONTROL_POS_SAMPLE 6400	-36.5134	-0.2420	32.4468
+			*CONTROL_POS_SAMPLE 6560	-36.5134	-0.2420	32.7511
+			*CONTROL_POS_SAMPLE 6720	-36.5134	-0.2420	33.0577
+			*CONTROL_POS_SAMPLE 6880	-36.5134	-0.2420	33.3662
+			*CONTROL_POS_SAMPLE 7040	-36.5134	-0.2420	33.6766
+			*CONTROL_POS_SAMPLE 7200	-36.5134	-0.2420	33.9884
+			*CONTROL_POS_SAMPLE 7360	-36.5134	-0.2420	34.3016
+			*CONTROL_POS_SAMPLE 7520	-36.5134	-0.2420	34.6157
+			*CONTROL_POS_SAMPLE 7680	-36.5134	-0.2420	34.9306
+			*CONTROL_POS_SAMPLE 7840	-36.5134	-0.2420	35.2460
+			*CONTROL_POS_SAMPLE 8000	-36.5134	-0.2420	35.5616
+			*CONTROL_POS_SAMPLE 8160	-36.5134	-0.2420	35.8773
+			*CONTROL_POS_SAMPLE 8320	-36.5134	-0.2420	36.1927
+			*CONTROL_POS_SAMPLE 8480	-36.5134	-0.2420	36.5076
+			*CONTROL_POS_SAMPLE 8640	-36.5134	-0.2420	36.8217
+			*CONTROL_POS_SAMPLE 8800	-36.5134	-0.2420	37.1348
+			*CONTROL_POS_SAMPLE 8960	-36.5134	-0.2420	37.4467
+			*CONTROL_POS_SAMPLE 9120	-36.5134	-0.2420	37.7570
+			*CONTROL_POS_SAMPLE 9280	-36.5134	-0.2420	38.0656
+			*CONTROL_POS_SAMPLE 9440	-36.5134	-0.2420	38.3722
+			*CONTROL_POS_SAMPLE 9600	-36.5134	-0.2420	38.6764
+			*CONTROL_POS_SAMPLE 9760	-36.5134	-0.2420	38.9782
+			*CONTROL_POS_SAMPLE 9920	-36.5134	-0.2420	39.2772
+			*CONTROL_POS_SAMPLE 10080	-36.5134	-0.2420	39.5731
+			*CONTROL_POS_SAMPLE 10240	-36.5134	-0.2420	39.8658
+			*CONTROL_POS_SAMPLE 10400	-36.5134	-0.2420	40.1549
+			*CONTROL_POS_SAMPLE 10560	-36.5134	-0.2420	40.4403
+			*CONTROL_POS_SAMPLE 10720	-36.5134	-0.2420	40.7216
+			*CONTROL_POS_SAMPLE 10880	-36.5134	-0.2420	40.9986
+			*CONTROL_POS_SAMPLE 11040	-36.5134	-0.2420	41.2710
+			*CONTROL_POS_SAMPLE 11200	-36.5134	-0.2420	41.5387
+			*CONTROL_POS_SAMPLE 11360	-36.5134	-0.2420	41.8013
+			*CONTROL_POS_SAMPLE 11520	-36.5134	-0.2420	42.0586
+			*CONTROL_POS_SAMPLE 11680	-36.5134	-0.2420	42.3104
+			*CONTROL_POS_SAMPLE 11840	-36.5134	-0.2420	42.5563
+			*CONTROL_POS_SAMPLE 12000	-36.5134	-0.2420	42.7962
+			*CONTROL_POS_SAMPLE 12160	-36.5134	-0.2420	43.0298
+			*CONTROL_POS_SAMPLE 12320	-36.5134	-0.2420	43.2568
+			*CONTROL_POS_SAMPLE 12480	-36.5134	-0.2420	43.4769
+			*CONTROL_POS_SAMPLE 12640	-36.5134	-0.2420	43.6901
+			*CONTROL_POS_SAMPLE 12800	-36.5134	-0.2420	43.8958
+			*CONTROL_POS_SAMPLE 12960	-36.5134	-0.2420	44.0941
+			*CONTROL_POS_SAMPLE 13120	-36.5134	-0.2420	44.2844
+			*CONTROL_POS_SAMPLE 13280	-36.5134	-0.2420	44.4667
+			*CONTROL_POS_SAMPLE 13440	-36.5134	-0.2420	44.6407
+			*CONTROL_POS_SAMPLE 13600	-36.5134	-0.2420	44.8061
+			*CONTROL_POS_SAMPLE 13760	-36.5134	-0.2420	44.9626
+			*CONTROL_POS_SAMPLE 13920	-36.5134	-0.2420	45.1101
+			*CONTROL_POS_SAMPLE 14080	-36.5134	-0.2420	45.2482
+			*CONTROL_POS_SAMPLE 14240	-36.5134	-0.2420	45.3767
+			*CONTROL_POS_SAMPLE 14400	-36.5134	-0.2420	45.4953
+			*CONTROL_POS_SAMPLE 14560	-36.5134	-0.2420	45.6039
+			*CONTROL_POS_SAMPLE 14720	-36.5134	-0.2420	45.7021
+			*CONTROL_POS_SAMPLE 14880	-36.5134	-0.2420	45.7897
+			*CONTROL_POS_SAMPLE 15040	-36.5134	-0.2420	45.8664
+			*CONTROL_POS_SAMPLE 15200	-36.5134	-0.2420	45.9320
+			*CONTROL_POS_SAMPLE 15360	-36.5134	-0.2420	45.9863
+			*CONTROL_POS_SAMPLE 15520	-36.5134	-0.2420	46.0289
+			*CONTROL_POS_SAMPLE 15680	-36.5134	-0.2420	46.0597
+			*CONTROL_POS_SAMPLE 15840	-36.5134	-0.2420	46.0784
+			*CONTROL_POS_SAMPLE 16000	-36.5134	-0.2420	46.0846
+		}
+	}
+}
+*GEOMOBJECT {
+	*NODE_NAME "Box02"
+	*NODE_PARENT "Camera01"
+	*NODE_TM {
+		*NODE_NAME "Box02"
+		*INHERIT_POS 0 0 0
+		*INHERIT_ROT 0 0 0
+		*INHERIT_SCL 0 0 0
+		*TM_ROW0 0.9998	0.0000	0.0195
+		*TM_ROW1 -0.0001	1.0000	0.0028
+		*TM_ROW2 -0.0195	-0.0028	0.9998
+		*TM_ROW3 -15.3872	-72.2765	2.4662
+		*TM_POS -15.3872	-72.2765	2.4662
+		*TM_ROTAXIS -0.1436	0.9896	-0.0017
+		*TM_ROTANGLE 0.0197
+		*TM_SCALE 1.0000	1.0000	1.0000
+		*TM_SCALEAXIS 0.1219	-0.6686	0.7336
+		*TM_SCALEAXISANG 0.0099
+	}
+	*MESH {
+		*TIMEVALUE 0
+		*MESH_NUMVERTEX 8
+		*MESH_NUMFACES 12
+		*MESH_VERTEX_LIST {
+			*MESH_VERTEX    0	-54.4096	-97.3479	1.6351
+			*MESH_VERTEX    1	23.6383	-97.3474	3.1556
+			*MESH_VERTEX    2	-54.4126	-47.2056	1.7768
+			*MESH_VERTEX    3	23.6353	-47.2052	3.2974
+			*MESH_VERTEX    4	-54.8868	-97.4171	26.1318
+			*MESH_VERTEX    5	23.1610	-97.4167	27.6523
+			*MESH_VERTEX    6	-54.8899	-47.2749	26.2735
+			*MESH_VERTEX    7	23.1580	-47.2744	27.7940
+		}
+		*MESH_FACE_LIST {
+			*MESH_FACE    0:    A:    0 B:    2 C:    3 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 2 	*MESH_MTLID 1
+			*MESH_FACE    1:    A:    3 B:    1 C:    0 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 2 	*MESH_MTLID 1
+			*MESH_FACE    2:    A:    4 B:    5 C:    7 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 3 	*MESH_MTLID 0
+			*MESH_FACE    3:    A:    7 B:    6 C:    4 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 3 	*MESH_MTLID 0
+			*MESH_FACE    4:    A:    0 B:    1 C:    5 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 4 	*MESH_MTLID 4
+			*MESH_FACE    5:    A:    5 B:    4 C:    0 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 4 	*MESH_MTLID 4
+			*MESH_FACE    6:    A:    1 B:    3 C:    7 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 5 	*MESH_MTLID 3
+			*MESH_FACE    7:    A:    7 B:    5 C:    1 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 5 	*MESH_MTLID 3
+			*MESH_FACE    8:    A:    3 B:    2 C:    6 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 6 	*MESH_MTLID 5
+			*MESH_FACE    9:    A:    6 B:    7 C:    3 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 6 	*MESH_MTLID 5
+			*MESH_FACE   10:    A:    2 B:    0 C:    4 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 7 	*MESH_MTLID 2
+			*MESH_FACE   11:    A:    4 B:    6 C:    2 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 7 	*MESH_MTLID 2
+		}
+		*MESH_NUMTVERTEX 12
+		*MESH_TVERTLIST {
+			*MESH_TVERT 0	0.0000	0.0000	0.0000
+			*MESH_TVERT 1	1.0000	0.0000	0.0000
+			*MESH_TVERT 2	0.0000	1.0000	0.0000
+			*MESH_TVERT 3	1.0000	1.0000	0.0000
+			*MESH_TVERT 4	0.0000	0.0000	0.0000
+			*MESH_TVERT 5	1.0000	0.0000	0.0000
+			*MESH_TVERT 6	0.0000	1.0000	0.0000
+			*MESH_TVERT 7	1.0000	1.0000	0.0000
+			*MESH_TVERT 8	0.0000	0.0000	0.0000
+			*MESH_TVERT 9	1.0000	0.0000	0.0000
+			*MESH_TVERT 10	0.0000	1.0000	0.0000
+			*MESH_TVERT 11	1.0000	1.0000	0.0000
+		}
+		*MESH_NUMTVFACES 12
+		*MESH_TFACELIST {
+			*MESH_TFACE 0	9	11	10
+			*MESH_TFACE 1	10	8	9
+			*MESH_TFACE 2	8	9	11
+			*MESH_TFACE 3	11	10	8
+			*MESH_TFACE 4	4	5	7
+			*MESH_TFACE 5	7	6	4
+			*MESH_TFACE 6	0	1	3
+			*MESH_TFACE 7	3	2	0
+			*MESH_TFACE 8	4	5	7
+			*MESH_TFACE 9	7	6	4
+			*MESH_TFACE 10	0	1	3
+			*MESH_TFACE 11	3	2	0
+		}
+		*MESH_NUMCVERTEX 0
+		*MESH_NORMALS {
+			*MESH_FACENORMAL 0	0.0000	0.0000	-1.0000
+				*MESH_VERTEXNORMAL 0	0.0000	0.0000	-1.0000
+				*MESH_VERTEXNORMAL 2	0.0000	0.0000	-1.0000
+				*MESH_VERTEXNORMAL 3	0.0000	0.0000	-1.0000
+			*MESH_FACENORMAL 1	0.0000	0.0000	-1.0000
+				*MESH_VERTEXNORMAL 3	0.0000	0.0000	-1.0000
+				*MESH_VERTEXNORMAL 1	0.0000	0.0000	-1.0000
+				*MESH_VERTEXNORMAL 0	0.0000	0.0000	-1.0000
+			*MESH_FACENORMAL 2	0.0000	-0.0000	1.0000
+				*MESH_VERTEXNORMAL 4	0.0000	0.0000	1.0000
+				*MESH_VERTEXNORMAL 5	0.0000	-0.0000	1.0000
+				*MESH_VERTEXNORMAL 7	0.0000	0.0000	1.0000
+			*MESH_FACENORMAL 3	-0.0000	0.0000	1.0000
+				*MESH_VERTEXNORMAL 7	0.0000	0.0000	1.0000
+				*MESH_VERTEXNORMAL 6	-0.0000	0.0000	1.0000
+				*MESH_VERTEXNORMAL 4	0.0000	0.0000	1.0000
+			*MESH_FACENORMAL 4	0.0000	-1.0000	0.0000
+				*MESH_VERTEXNORMAL 0	0.0000	-1.0000	0.0000
+				*MESH_VERTEXNORMAL 1	0.0000	-1.0000	0.0000
+				*MESH_VERTEXNORMAL 5	0.0000	-1.0000	0.0000
+			*MESH_FACENORMAL 5	0.0000	-1.0000	0.0000
+				*MESH_VERTEXNORMAL 5	0.0000	-1.0000	0.0000
+				*MESH_VERTEXNORMAL 4	0.0000	-1.0000	0.0000
+				*MESH_VERTEXNORMAL 0	0.0000	-1.0000	0.0000
+			*MESH_FACENORMAL 6	1.0000	0.0000	-0.0000
+				*MESH_VERTEXNORMAL 1	1.0000	0.0000	0.0000
+				*MESH_VERTEXNORMAL 3	1.0000	0.0000	-0.0000
+				*MESH_VERTEXNORMAL 7	1.0000	0.0000	0.0000
+			*MESH_FACENORMAL 7	1.0000	-0.0000	0.0000
+				*MESH_VERTEXNORMAL 7	1.0000	0.0000	0.0000
+				*MESH_VERTEXNORMAL 5	1.0000	-0.0000	0.0000
+				*MESH_VERTEXNORMAL 1	1.0000	0.0000	0.0000
+			*MESH_FACENORMAL 8	0.0000	1.0000	0.0000
+				*MESH_VERTEXNORMAL 3	0.0000	1.0000	0.0000
+				*MESH_VERTEXNORMAL 2	0.0000	1.0000	0.0000
+				*MESH_VERTEXNORMAL 6	0.0000	1.0000	0.0000
+			*MESH_FACENORMAL 9	0.0000	1.0000	0.0000
+				*MESH_VERTEXNORMAL 6	0.0000	1.0000	0.0000
+				*MESH_VERTEXNORMAL 7	0.0000	1.0000	0.0000
+				*MESH_VERTEXNORMAL 3	0.0000	1.0000	0.0000
+			*MESH_FACENORMAL 10	-1.0000	0.0000	0.0000
+				*MESH_VERTEXNORMAL 2	-1.0000	0.0000	0.0000
+				*MESH_VERTEXNORMAL 0	-1.0000	0.0000	0.0000
+				*MESH_VERTEXNORMAL 4	-1.0000	0.0000	0.0000
+			*MESH_FACENORMAL 11	-1.0000	-0.0000	-0.0000
+				*MESH_VERTEXNORMAL 4	-1.0000	0.0000	0.0000
+				*MESH_VERTEXNORMAL 6	-1.0000	-0.0000	-0.0000
+				*MESH_VERTEXNORMAL 2	-1.0000	0.0000	0.0000
+		}
+	}
+	*PROP_MOTIONBLUR 0
+	*PROP_CASTSHADOW 1
+	*PROP_RECVSHADOW 1
+	*TM_ANIMATION {
+		*NODE_NAME "Box02"
+		*CONTROL_POS_TRACK {
+			*CONTROL_POS_SAMPLE 0	71.3652	-23.0196	-138.7689
+			*CONTROL_POS_SAMPLE 160	71.3603	-23.0080	-138.7694
+			*CONTROL_POS_SAMPLE 320	71.3456	-22.9735	-138.7706
+			*CONTROL_POS_SAMPLE 480	71.3213	-22.9167	-138.7726
+			*CONTROL_POS_SAMPLE 640	71.2877	-22.8379	-138.7755
+			*CONTROL_POS_SAMPLE 800	71.2450	-22.7378	-138.7790
+			*CONTROL_POS_SAMPLE 960	71.1935	-22.6169	-138.7834
+			*CONTROL_POS_SAMPLE 1120	71.1332	-22.4756	-138.7884
+			*CONTROL_POS_SAMPLE 1280	71.0645	-22.3144	-138.7942
+			*CONTROL_POS_SAMPLE 1440	70.9875	-22.1339	-138.8007
+			*CONTROL_POS_SAMPLE 1600	70.9025	-21.9345	-138.8078
+			*CONTROL_POS_SAMPLE 1760	70.8096	-21.7167	-138.8156
+			*CONTROL_POS_SAMPLE 1920	70.7091	-21.4812	-138.8241
+			*CONTROL_POS_SAMPLE 2080	70.6013	-21.2282	-138.8331
+			*CONTROL_POS_SAMPLE 2240	70.4863	-20.9584	-138.8428
+			*CONTROL_POS_SAMPLE 2400	70.3642	-20.6723	-138.8530
+			*CONTROL_POS_SAMPLE 2560	70.2355	-20.3704	-138.8639
+			*CONTROL_POS_SAMPLE 2720	70.1002	-20.0531	-138.8752
+			*CONTROL_POS_SAMPLE 2880	69.9585	-19.7210	-138.8871
+			*CONTROL_POS_SAMPLE 3040	69.8108	-19.3745	-138.8996
+			*CONTROL_POS_SAMPLE 3200	69.6572	-19.0143	-138.9124
+			*CONTROL_POS_SAMPLE 3360	69.4979	-18.6407	-138.9259
+			*CONTROL_POS_SAMPLE 3520	69.3331	-18.2543	-138.9397
+			*CONTROL_POS_SAMPLE 3680	69.1630	-17.8555	-138.9540
+			*CONTROL_POS_SAMPLE 3840	68.9880	-17.4450	-138.9687
+			*CONTROL_POS_SAMPLE 4000	68.8081	-17.0231	-138.9838
+			*CONTROL_POS_SAMPLE 4160	68.6235	-16.5905	-138.9993
+			*CONTROL_POS_SAMPLE 4320	68.4346	-16.1475	-139.0152
+			*CONTROL_POS_SAMPLE 4480	68.2416	-15.6947	-139.0314
+			*CONTROL_POS_SAMPLE 4640	68.0445	-15.2326	-139.0480
+			*CONTROL_POS_SAMPLE 4800	67.8437	-14.7618	-139.0648
+			*CONTROL_POS_SAMPLE 4960	67.6394	-14.2826	-139.0820
+			*CONTROL_POS_SAMPLE 5120	67.4317	-13.7956	-139.0994
+			*CONTROL_POS_SAMPLE 5280	67.2209	-13.3014	-139.1172
+			*CONTROL_POS_SAMPLE 5440	67.0073	-12.8003	-139.1351
+			*CONTROL_POS_SAMPLE 5600	66.7909	-12.2930	-139.1533
+			*CONTROL_POS_SAMPLE 5760	66.5721	-11.7799	-139.1717
+			*CONTROL_POS_SAMPLE 5920	66.3511	-11.2615	-139.1903
+			*CONTROL_POS_SAMPLE 6080	66.1280	-10.7383	-139.2090
+			*CONTROL_POS_SAMPLE 6240	65.9031	-10.2109	-139.2279
+			*CONTROL_POS_SAMPLE 6400	65.6765	-9.6797	-139.2469
+			*CONTROL_POS_SAMPLE 6560	65.4486	-9.1452	-139.2661
+			*CONTROL_POS_SAMPLE 6720	65.2195	-8.6080	-139.2854
+			*CONTROL_POS_SAMPLE 6880	64.9894	-8.0685	-139.3047
+			*CONTROL_POS_SAMPLE 7040	64.7586	-7.5272	-139.3241
+			*CONTROL_POS_SAMPLE 7200	64.5273	-6.9847	-139.3435
+			*CONTROL_POS_SAMPLE 7360	64.2956	-6.4414	-139.3630
+			*CONTROL_POS_SAMPLE 7520	64.0638	-5.8979	-139.3825
+			*CONTROL_POS_SAMPLE 7680	63.8321	-5.3546	-139.4019
+			*CONTROL_POS_SAMPLE 7840	63.6008	-4.8121	-139.4214
+			*CONTROL_POS_SAMPLE 8000	63.3700	-4.2708	-139.4408
+			*CONTROL_POS_SAMPLE 8160	63.1399	-3.7313	-139.4601
+			*CONTROL_POS_SAMPLE 8320	62.9108	-3.1941	-139.4794
+			*CONTROL_POS_SAMPLE 8480	62.6829	-2.6596	-139.4986
+			*CONTROL_POS_SAMPLE 8640	62.4564	-2.1284	-139.5176
+			*CONTROL_POS_SAMPLE 8800	62.2314	-1.6009	-139.5365
+			*CONTROL_POS_SAMPLE 8960	62.0083	-1.0778	-139.5552
+			*CONTROL_POS_SAMPLE 9120	61.7873	-0.5594	-139.5738
+			*CONTROL_POS_SAMPLE 9280	61.5685	-0.0463	-139.5922
+			*CONTROL_POS_SAMPLE 9440	61.3521	0.4610	-139.6103
+			*CONTROL_POS_SAMPLE 9600	61.1384	0.9621	-139.6283
+			*CONTROL_POS_SAMPLE 9760	60.9277	1.4563	-139.6460
+			*CONTROL_POS_SAMPLE 9920	60.7200	1.9433	-139.6635
+			*CONTROL_POS_SAMPLE 10080	60.5157	2.4225	-139.6807
+			*CONTROL_POS_SAMPLE 10240	60.3149	2.8934	-139.6975
+			*CONTROL_POS_SAMPLE 10400	60.1178	3.3554	-139.7141
+			*CONTROL_POS_SAMPLE 10560	59.9247	3.8082	-139.7303
+			*CONTROL_POS_SAMPLE 10720	59.7358	4.2512	-139.7462
+			*CONTROL_POS_SAMPLE 10880	59.5513	4.6839	-139.7617
+			*CONTROL_POS_SAMPLE 11040	59.3714	5.1057	-139.7768
+			*CONTROL_POS_SAMPLE 11200	59.1964	5.5163	-139.7915
+			*CONTROL_POS_SAMPLE 11360	59.0263	5.9150	-139.8058
+			*CONTROL_POS_SAMPLE 11520	58.8616	6.3014	-139.8196
+			*CONTROL_POS_SAMPLE 11680	58.7022	6.6750	-139.8330
+			*CONTROL_POS_SAMPLE 11840	58.5486	7.0353	-139.8459
+			*CONTROL_POS_SAMPLE 12000	58.4009	7.3817	-139.8583
+			*CONTROL_POS_SAMPLE 12160	58.2592	7.7138	-139.8703
+			*CONTROL_POS_SAMPLE 12320	58.1239	8.0311	-139.8816
+			*CONTROL_POS_SAMPLE 12480	57.9952	8.3331	-139.8925
+			*CONTROL_POS_SAMPLE 12640	57.8732	8.6192	-139.9027
+			*CONTROL_POS_SAMPLE 12800	57.7581	8.8889	-139.9124
+			*CONTROL_POS_SAMPLE 12960	57.6503	9.1419	-139.9214
+			*CONTROL_POS_SAMPLE 13120	57.5498	9.3775	-139.9299
+			*CONTROL_POS_SAMPLE 13280	57.4570	9.5952	-139.9377
+			*CONTROL_POS_SAMPLE 13440	57.3719	9.7946	-139.9448
+			*CONTROL_POS_SAMPLE 13600	57.2949	9.9751	-139.9513
+			*CONTROL_POS_SAMPLE 13760	57.2262	10.1363	-139.9570
+			*CONTROL_POS_SAMPLE 13920	57.1659	10.2776	-139.9621
+			*CONTROL_POS_SAMPLE 14080	57.1144	10.3986	-139.9665
+			*CONTROL_POS_SAMPLE 14240	57.0717	10.4987	-139.9700
+			*CONTROL_POS_SAMPLE 14400	57.0381	10.5774	-139.9729
+			*CONTROL_POS_SAMPLE 14560	57.0139	10.6342	-139.9749
+			*CONTROL_POS_SAMPLE 14720	56.9991	10.6687	-139.9761
+			*CONTROL_POS_SAMPLE 14880	56.9942	10.6803	-139.9766
+		}
+	}
+	*WIREFRAME_COLOR 0.0235	0.5294	0.0235
+}

+ 333 - 0
test/ASEFiles/RotatingCube.ASE

@@ -0,0 +1,333 @@
+*3DSMAX_ASCIIEXPORT	200
+*COMMENT "AsciiExport Version  2,00 - Sat Nov 08 18:02:21 2008"
+*SCENE {
+	*SCENE_FILENAME "RotatingCube.max"
+	*SCENE_FIRSTFRAME 0
+	*SCENE_LASTFRAME 300
+	*SCENE_FRAMESPEED 30
+	*SCENE_TICKSPERFRAME 160
+	*SCENE_BACKGROUND_STATIC 0.0000	0.0000	0.0000
+	*SCENE_AMBIENT_STATIC 0.0000	0.0000	0.0000
+}
+*MATERIAL_LIST {
+	*MATERIAL_COUNT 0
+}
+*GEOMOBJECT {
+	*NODE_NAME "Box01"
+	*NODE_TM {
+		*NODE_NAME "Box01"
+		*INHERIT_POS 0 0 0
+		*INHERIT_ROT 0 0 0
+		*INHERIT_SCL 0 0 0
+		*TM_ROW0 1.0000	0.0000	0.0000
+		*TM_ROW1 0.0000	0.9899	-0.1420
+		*TM_ROW2 0.0000	0.1420	0.9899
+		*TM_ROW3 3.9886	-5.6980	0.0000
+		*TM_POS 3.9886	-5.6980	0.0000
+		*TM_ROTAXIS 1.0000	-0.0000	-0.0000
+		*TM_ROTANGLE 0.1425
+		*TM_SCALE 1.0000	1.0000	1.0000
+		*TM_SCALEAXIS 0.0000	0.0000	0.0000
+		*TM_SCALEAXISANG 0.0000
+	}
+	*MESH {
+		*TIMEVALUE 0
+		*MESH_NUMVERTEX 8
+		*MESH_NUMFACES 12
+		*MESH_VERTEX_LIST {
+			*MESH_VERTEX    0	-29.3447	-34.1813	4.0862
+			*MESH_VERTEX    1	37.3219	-34.1813	4.0862
+			*MESH_VERTEX    2	-29.3447	22.7853	-4.0862
+			*MESH_VERTEX    3	37.3219	22.7853	-4.0862
+			*MESH_VERTEX    4	-29.3447	-27.7082	49.2083
+			*MESH_VERTEX    5	37.3219	-27.7082	49.2083
+			*MESH_VERTEX    6	-29.3447	29.2585	41.0359
+			*MESH_VERTEX    7	37.3219	29.2585	41.0359
+		}
+		*MESH_FACE_LIST {
+			*MESH_FACE    0:    A:    0 B:    2 C:    3 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 2 	*MESH_MTLID 1
+			*MESH_FACE    1:    A:    3 B:    1 C:    0 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 2 	*MESH_MTLID 1
+			*MESH_FACE    2:    A:    4 B:    5 C:    7 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 3 	*MESH_MTLID 0
+			*MESH_FACE    3:    A:    7 B:    6 C:    4 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 3 	*MESH_MTLID 0
+			*MESH_FACE    4:    A:    0 B:    1 C:    5 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 4 	*MESH_MTLID 4
+			*MESH_FACE    5:    A:    5 B:    4 C:    0 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 4 	*MESH_MTLID 4
+			*MESH_FACE    6:    A:    1 B:    3 C:    7 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 5 	*MESH_MTLID 3
+			*MESH_FACE    7:    A:    7 B:    5 C:    1 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 5 	*MESH_MTLID 3
+			*MESH_FACE    8:    A:    3 B:    2 C:    6 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 6 	*MESH_MTLID 5
+			*MESH_FACE    9:    A:    6 B:    7 C:    3 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 6 	*MESH_MTLID 5
+			*MESH_FACE   10:    A:    2 B:    0 C:    4 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 7 	*MESH_MTLID 2
+			*MESH_FACE   11:    A:    4 B:    6 C:    2 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 7 	*MESH_MTLID 2
+		}
+		*MESH_NUMTVERTEX 12
+		*MESH_TVERTLIST {
+			*MESH_TVERT 0	0.0000	0.0000	0.0000
+			*MESH_TVERT 1	1.0000	0.0000	0.0000
+			*MESH_TVERT 2	0.0000	1.0000	0.0000
+			*MESH_TVERT 3	1.0000	1.0000	0.0000
+			*MESH_TVERT 4	0.0000	0.0000	0.0000
+			*MESH_TVERT 5	1.0000	0.0000	0.0000
+			*MESH_TVERT 6	0.0000	1.0000	0.0000
+			*MESH_TVERT 7	1.0000	1.0000	0.0000
+			*MESH_TVERT 8	0.0000	0.0000	0.0000
+			*MESH_TVERT 9	1.0000	0.0000	0.0000
+			*MESH_TVERT 10	0.0000	1.0000	0.0000
+			*MESH_TVERT 11	1.0000	1.0000	0.0000
+		}
+		*MESH_NUMTVFACES 12
+		*MESH_TFACELIST {
+			*MESH_TFACE 0	9	11	10
+			*MESH_TFACE 1	10	8	9
+			*MESH_TFACE 2	8	9	11
+			*MESH_TFACE 3	11	10	8
+			*MESH_TFACE 4	4	5	7
+			*MESH_TFACE 5	7	6	4
+			*MESH_TFACE 6	0	1	3
+			*MESH_TFACE 7	3	2	0
+			*MESH_TFACE 8	4	5	7
+			*MESH_TFACE 9	7	6	4
+			*MESH_TFACE 10	0	1	3
+			*MESH_TFACE 11	3	2	0
+		}
+		*MESH_NUMCVERTEX 0
+		*MESH_NORMALS {
+			*MESH_FACENORMAL 0	0.0000	0.0000	-1.0000
+				*MESH_VERTEXNORMAL 0	0.0000	0.0000	-1.0000
+				*MESH_VERTEXNORMAL 2	0.0000	0.0000	-1.0000
+				*MESH_VERTEXNORMAL 3	0.0000	0.0000	-1.0000
+			*MESH_FACENORMAL 1	0.0000	0.0000	-1.0000
+				*MESH_VERTEXNORMAL 3	0.0000	0.0000	-1.0000
+				*MESH_VERTEXNORMAL 1	0.0000	0.0000	-1.0000
+				*MESH_VERTEXNORMAL 0	0.0000	0.0000	-1.0000
+			*MESH_FACENORMAL 2	0.0000	-0.0000	1.0000
+				*MESH_VERTEXNORMAL 4	0.0000	0.0000	1.0000
+				*MESH_VERTEXNORMAL 5	0.0000	-0.0000	1.0000
+				*MESH_VERTEXNORMAL 7	0.0000	0.0000	1.0000
+			*MESH_FACENORMAL 3	-0.0000	0.0000	1.0000
+				*MESH_VERTEXNORMAL 7	0.0000	0.0000	1.0000
+				*MESH_VERTEXNORMAL 6	-0.0000	0.0000	1.0000
+				*MESH_VERTEXNORMAL 4	0.0000	0.0000	1.0000
+			*MESH_FACENORMAL 4	0.0000	-1.0000	0.0000
+				*MESH_VERTEXNORMAL 0	0.0000	-1.0000	0.0000
+				*MESH_VERTEXNORMAL 1	0.0000	-1.0000	0.0000
+				*MESH_VERTEXNORMAL 5	0.0000	-1.0000	0.0000
+			*MESH_FACENORMAL 5	0.0000	-1.0000	0.0000
+				*MESH_VERTEXNORMAL 5	0.0000	-1.0000	0.0000
+				*MESH_VERTEXNORMAL 4	0.0000	-1.0000	0.0000
+				*MESH_VERTEXNORMAL 0	0.0000	-1.0000	0.0000
+			*MESH_FACENORMAL 6	1.0000	0.0000	-0.0000
+				*MESH_VERTEXNORMAL 1	1.0000	0.0000	0.0000
+				*MESH_VERTEXNORMAL 3	1.0000	0.0000	-0.0000
+				*MESH_VERTEXNORMAL 7	1.0000	0.0000	0.0000
+			*MESH_FACENORMAL 7	1.0000	-0.0000	0.0000
+				*MESH_VERTEXNORMAL 7	1.0000	0.0000	0.0000
+				*MESH_VERTEXNORMAL 5	1.0000	-0.0000	0.0000
+				*MESH_VERTEXNORMAL 1	1.0000	0.0000	0.0000
+			*MESH_FACENORMAL 8	0.0000	1.0000	0.0000
+				*MESH_VERTEXNORMAL 3	0.0000	1.0000	0.0000
+				*MESH_VERTEXNORMAL 2	0.0000	1.0000	0.0000
+				*MESH_VERTEXNORMAL 6	0.0000	1.0000	0.0000
+			*MESH_FACENORMAL 9	0.0000	1.0000	0.0000
+				*MESH_VERTEXNORMAL 6	0.0000	1.0000	0.0000
+				*MESH_VERTEXNORMAL 7	0.0000	1.0000	0.0000
+				*MESH_VERTEXNORMAL 3	0.0000	1.0000	0.0000
+			*MESH_FACENORMAL 10	-1.0000	0.0000	0.0000
+				*MESH_VERTEXNORMAL 2	-1.0000	0.0000	0.0000
+				*MESH_VERTEXNORMAL 0	-1.0000	0.0000	0.0000
+				*MESH_VERTEXNORMAL 4	-1.0000	0.0000	0.0000
+			*MESH_FACENORMAL 11	-1.0000	-0.0000	-0.0000
+				*MESH_VERTEXNORMAL 4	-1.0000	0.0000	0.0000
+				*MESH_VERTEXNORMAL 6	-1.0000	-0.0000	-0.0000
+				*MESH_VERTEXNORMAL 2	-1.0000	0.0000	0.0000
+		}
+	}
+	*PROP_MOTIONBLUR 0
+	*PROP_CASTSHADOW 1
+	*PROP_RECVSHADOW 1
+	*TM_ANIMATION {
+		*NODE_NAME "Box01"
+		*CONTROL_ROT_TRACK {
+			*CONTROL_ROT_SAMPLE 0	1.0000	-0.0000	0.0000	0.1425
+			*CONTROL_ROT_SAMPLE 160	-1.0000	0.0000	0.0000	0.0008
+			*CONTROL_ROT_SAMPLE 320	-1.0000	0.0000	0.0000	0.0024
+			*CONTROL_ROT_SAMPLE 480	-1.0000	0.0000	0.0000	0.0039
+			*CONTROL_ROT_SAMPLE 640	-1.0000	0.0000	0.0000	0.0053
+			*CONTROL_ROT_SAMPLE 800	-1.0000	0.0000	0.0000	0.0067
+			*CONTROL_ROT_SAMPLE 960	-1.0000	0.0000	0.0000	0.0081
+			*CONTROL_ROT_SAMPLE 1120	-1.0000	0.0000	0.0000	0.0094
+			*CONTROL_ROT_SAMPLE 1280	-1.0000	0.0000	0.0000	0.0106
+			*CONTROL_ROT_SAMPLE 1440	-1.0000	0.0000	0.0000	0.0118
+			*CONTROL_ROT_SAMPLE 1600	-1.0000	0.0000	0.0000	0.0129
+			*CONTROL_ROT_SAMPLE 1760	-1.0000	0.0000	0.0000	0.0140
+			*CONTROL_ROT_SAMPLE 1920	-1.0000	0.0000	0.0000	0.0150
+			*CONTROL_ROT_SAMPLE 2080	-1.0000	0.0000	0.0000	0.0160
+			*CONTROL_ROT_SAMPLE 2240	-1.0000	0.0000	0.0000	0.0169
+			*CONTROL_ROT_SAMPLE 2400	-1.0000	0.0000	0.0000	0.0178
+			*CONTROL_ROT_SAMPLE 2560	-1.0000	0.0000	0.0000	0.0186
+			*CONTROL_ROT_SAMPLE 2720	-1.0000	0.0000	0.0000	0.0193
+			*CONTROL_ROT_SAMPLE 2880	-1.0000	0.0000	0.0000	0.0200
+			*CONTROL_ROT_SAMPLE 3040	-1.0000	0.0000	0.0000	0.0207
+			*CONTROL_ROT_SAMPLE 3200	-1.0000	0.0000	0.0000	0.0213
+			*CONTROL_ROT_SAMPLE 3360	-1.0000	0.0000	0.0000	0.0218
+			*CONTROL_ROT_SAMPLE 3520	-1.0000	0.0000	0.0000	0.0223
+			*CONTROL_ROT_SAMPLE 3680	-1.0000	0.0000	0.0000	0.0227
+			*CONTROL_ROT_SAMPLE 3840	-1.0000	0.0000	0.0000	0.0231
+			*CONTROL_ROT_SAMPLE 4000	-1.0000	0.0000	0.0000	0.0234
+			*CONTROL_ROT_SAMPLE 4160	-1.0000	0.0000	0.0000	0.0237
+			*CONTROL_ROT_SAMPLE 4320	-1.0000	0.0000	0.0000	0.0239
+			*CONTROL_ROT_SAMPLE 4480	-1.0000	0.0000	0.0000	0.0241
+			*CONTROL_ROT_SAMPLE 4640	-1.0000	0.0000	0.0000	0.0242
+			*CONTROL_ROT_SAMPLE 4800	-1.0000	0.0000	0.0000	0.0242
+			*CONTROL_ROT_SAMPLE 4960	-1.0000	0.0000	0.0000	0.0242
+			*CONTROL_ROT_SAMPLE 5120	-1.0000	0.0000	0.0000	0.0242
+			*CONTROL_ROT_SAMPLE 5280	-1.0000	0.0000	0.0000	0.0241
+			*CONTROL_ROT_SAMPLE 5440	-1.0000	0.0000	0.0000	0.0239
+			*CONTROL_ROT_SAMPLE 5600	-1.0000	0.0000	0.0000	0.0237
+			*CONTROL_ROT_SAMPLE 5760	-1.0000	0.0000	0.0000	0.0234
+			*CONTROL_ROT_SAMPLE 5920	-1.0000	0.0000	0.0000	0.0231
+			*CONTROL_ROT_SAMPLE 6080	-1.0000	0.0000	0.0000	0.0227
+			*CONTROL_ROT_SAMPLE 6240	-1.0000	0.0000	0.0000	0.0223
+			*CONTROL_ROT_SAMPLE 6400	-1.0000	0.0000	0.0000	0.0218
+			*CONTROL_ROT_SAMPLE 6560	-1.0000	0.0000	0.0000	0.0213
+			*CONTROL_ROT_SAMPLE 6720	-1.0000	0.0000	0.0000	0.0207
+			*CONTROL_ROT_SAMPLE 6880	-1.0000	0.0000	0.0000	0.0200
+			*CONTROL_ROT_SAMPLE 7040	-1.0000	0.0000	0.0000	0.0193
+			*CONTROL_ROT_SAMPLE 7200	-1.0000	0.0000	0.0000	0.0186
+			*CONTROL_ROT_SAMPLE 7360	-1.0000	0.0000	0.0000	0.0178
+			*CONTROL_ROT_SAMPLE 7520	-1.0000	0.0000	0.0000	0.0169
+			*CONTROL_ROT_SAMPLE 7680	-1.0000	0.0000	0.0000	0.0160
+			*CONTROL_ROT_SAMPLE 7840	-1.0000	0.0000	0.0000	0.0150
+			*CONTROL_ROT_SAMPLE 8000	-1.0000	0.0000	0.0000	0.0140
+			*CONTROL_ROT_SAMPLE 8160	-1.0000	0.0000	0.0000	0.0129
+			*CONTROL_ROT_SAMPLE 8320	-1.0000	0.0000	0.0000	0.0118
+			*CONTROL_ROT_SAMPLE 8480	-1.0000	0.0000	0.0000	0.0106
+			*CONTROL_ROT_SAMPLE 8640	-1.0000	0.0000	0.0000	0.0094
+			*CONTROL_ROT_SAMPLE 8800	-1.0000	0.0000	0.0000	0.0081
+			*CONTROL_ROT_SAMPLE 8960	-1.0000	0.0000	0.0000	0.0067
+			*CONTROL_ROT_SAMPLE 9120	-1.0000	0.0000	0.0000	0.0053
+			*CONTROL_ROT_SAMPLE 9280	-1.0000	0.0000	0.0000	0.0039
+			*CONTROL_ROT_SAMPLE 9440	-1.0000	0.0000	0.0000	0.0024
+			*CONTROL_ROT_SAMPLE 9600	-1.0000	0.0000	0.0000	0.0008
+			*CONTROL_ROT_SAMPLE 9760	0.0000	-1.0000	-0.0000	0.0012
+			*CONTROL_ROT_SAMPLE 9920	0.0000	-1.0000	0.0000	0.0037
+			*CONTROL_ROT_SAMPLE 10080	-0.0000	-1.0000	0.0000	0.0060
+			*CONTROL_ROT_SAMPLE 10240	-0.0000	-1.0000	0.0000	0.0083
+			*CONTROL_ROT_SAMPLE 10400	0.0000	-1.0000	-0.0000	0.0104
+			*CONTROL_ROT_SAMPLE 10560	0.0000	-1.0000	0.0000	0.0125
+			*CONTROL_ROT_SAMPLE 10720	-0.0000	-1.0000	0.0000	0.0145
+			*CONTROL_ROT_SAMPLE 10880	0.0000	-1.0000	0.0000	0.0164
+			*CONTROL_ROT_SAMPLE 11040	0.0000	-1.0000	-0.0000	0.0183
+			*CONTROL_ROT_SAMPLE 11200	0.0000	-1.0000	0.0000	0.0200
+			*CONTROL_ROT_SAMPLE 11360	-0.0000	-1.0000	0.0000	0.0217
+			*CONTROL_ROT_SAMPLE 11520	0.0000	-1.0000	-0.0000	0.0232
+			*CONTROL_ROT_SAMPLE 11680	-0.0000	-1.0000	0.0000	0.0247
+			*CONTROL_ROT_SAMPLE 11840	0.0000	-1.0000	-0.0000	0.0261
+			*CONTROL_ROT_SAMPLE 12000	-0.0000	-1.0000	-0.0000	0.0274
+			*CONTROL_ROT_SAMPLE 12160	-0.0000	-1.0000	0.0000	0.0287
+			*CONTROL_ROT_SAMPLE 12320	0.0000	-1.0000	-0.0000	0.0298
+			*CONTROL_ROT_SAMPLE 12480	-0.0000	-1.0000	0.0000	0.0309
+			*CONTROL_ROT_SAMPLE 12640	0.0000	-1.0000	-0.0000	0.0319
+			*CONTROL_ROT_SAMPLE 12800	-0.0000	-1.0000	0.0000	0.0328
+			*CONTROL_ROT_SAMPLE 12960	0.0000	-1.0000	-0.0000	0.0336
+			*CONTROL_ROT_SAMPLE 13120	-0.0000	-1.0000	0.0000	0.0343
+			*CONTROL_ROT_SAMPLE 13280	-0.0000	-1.0000	0.0000	0.0349
+			*CONTROL_ROT_SAMPLE 13440	0.0000	-1.0000	0.0000	0.0355
+			*CONTROL_ROT_SAMPLE 13600	-0.0000	-1.0000	-0.0000	0.0359
+			*CONTROL_ROT_SAMPLE 13760	0.0000	-1.0000	-0.0000	0.0363
+			*CONTROL_ROT_SAMPLE 13920	0.0000	-1.0000	-0.0000	0.0366
+			*CONTROL_ROT_SAMPLE 14080	-0.0000	-1.0000	0.0000	0.0368
+			*CONTROL_ROT_SAMPLE 14240	-0.0000	-1.0000	0.0000	0.0370
+			*CONTROL_ROT_SAMPLE 14400	0.0000	-1.0000	0.0000	0.0370
+			*CONTROL_ROT_SAMPLE 14560	-0.0000	-1.0000	0.0000	0.0370
+			*CONTROL_ROT_SAMPLE 14720	0.0000	-1.0000	-0.0000	0.0368
+			*CONTROL_ROT_SAMPLE 14880	-0.0000	-1.0000	0.0000	0.0366
+			*CONTROL_ROT_SAMPLE 15040	0.0000	-1.0000	-0.0000	0.0363
+			*CONTROL_ROT_SAMPLE 15200	-0.0000	-1.0000	0.0000	0.0359
+			*CONTROL_ROT_SAMPLE 15360	0.0000	-1.0000	0.0000	0.0355
+			*CONTROL_ROT_SAMPLE 15520	-0.0000	-1.0000	0.0000	0.0349
+			*CONTROL_ROT_SAMPLE 15680	-0.0000	-1.0000	-0.0000	0.0343
+			*CONTROL_ROT_SAMPLE 15840	-0.0000	-1.0000	0.0000	0.0336
+			*CONTROL_ROT_SAMPLE 16000	0.0000	-1.0000	0.0000	0.0328
+			*CONTROL_ROT_SAMPLE 16160	-0.0000	-1.0000	0.0000	0.0319
+			*CONTROL_ROT_SAMPLE 16320	0.0000	-1.0000	-0.0000	0.0309
+			*CONTROL_ROT_SAMPLE 16480	0.0000	-1.0000	0.0000	0.0298
+			*CONTROL_ROT_SAMPLE 16640	-0.0000	-1.0000	-0.0000	0.0287
+			*CONTROL_ROT_SAMPLE 16800	-0.0000	-1.0000	0.0000	0.0274
+			*CONTROL_ROT_SAMPLE 16960	-0.0000	-1.0000	-0.0000	0.0261
+			*CONTROL_ROT_SAMPLE 17120	0.0000	-1.0000	0.0000	0.0247
+			*CONTROL_ROT_SAMPLE 17280	-0.0000	-1.0000	-0.0000	0.0232
+			*CONTROL_ROT_SAMPLE 17440	0.0000	-1.0000	-0.0000	0.0217
+			*CONTROL_ROT_SAMPLE 17600	-0.0000	-1.0000	0.0000	0.0200
+			*CONTROL_ROT_SAMPLE 17760	0.0000	-1.0000	-0.0000	0.0183
+			*CONTROL_ROT_SAMPLE 17920	-0.0000	-1.0000	0.0000	0.0164
+			*CONTROL_ROT_SAMPLE 18080	0.0000	-1.0000	-0.0000	0.0145
+			*CONTROL_ROT_SAMPLE 18240	0.0000	-1.0000	-0.0000	0.0125
+			*CONTROL_ROT_SAMPLE 18400	0.0000	-1.0000	0.0000	0.0104
+			*CONTROL_ROT_SAMPLE 18560	-0.0000	-1.0000	0.0000	0.0083
+			*CONTROL_ROT_SAMPLE 18720	-0.0000	-1.0000	0.0000	0.0060
+			*CONTROL_ROT_SAMPLE 18880	0.0000	-1.0000	-0.0000	0.0037
+			*CONTROL_ROT_SAMPLE 19040	0.0000	-1.0000	0.0000	0.0012
+			*CONTROL_ROT_SAMPLE 19200	-0.0000	0.0000	1.0000	0.0018
+			*CONTROL_ROT_SAMPLE 19360	-0.0000	-0.0000	1.0000	0.0053
+			*CONTROL_ROT_SAMPLE 19520	0.0000	-0.0000	1.0000	0.0086
+			*CONTROL_ROT_SAMPLE 19680	0.0000	-0.0000	1.0000	0.0119
+			*CONTROL_ROT_SAMPLE 19840	0.0000	0.0000	1.0000	0.0150
+			*CONTROL_ROT_SAMPLE 20000	0.0000	-0.0000	1.0000	0.0180
+			*CONTROL_ROT_SAMPLE 20160	0.0000	0.0000	1.0000	0.0209
+			*CONTROL_ROT_SAMPLE 20320	0.0000	-0.0000	1.0000	0.0237
+			*CONTROL_ROT_SAMPLE 20480	0.0000	0.0000	1.0000	0.0263
+			*CONTROL_ROT_SAMPLE 20640	0.0000	-0.0000	1.0000	0.0289
+			*CONTROL_ROT_SAMPLE 20800	0.0000	0.0000	1.0000	0.0313
+			*CONTROL_ROT_SAMPLE 20960	-0.0000	-0.0000	1.0000	0.0336
+			*CONTROL_ROT_SAMPLE 21120	0.0000	0.0000	1.0000	0.0358
+			*CONTROL_ROT_SAMPLE 21280	-0.0000	0.0000	1.0000	0.0379
+			*CONTROL_ROT_SAMPLE 21440	0.0000	0.0000	1.0000	0.0398
+			*CONTROL_ROT_SAMPLE 21600	0.0000	-0.0000	1.0000	0.0416
+			*CONTROL_ROT_SAMPLE 21760	-0.0000	-0.0000	1.0000	0.0434
+			*CONTROL_ROT_SAMPLE 21920	-0.0000	0.0000	1.0000	0.0449
+			*CONTROL_ROT_SAMPLE 22080	0.0000	0.0000	1.0000	0.0464
+			*CONTROL_ROT_SAMPLE 22240	0.0000	0.0000	1.0000	0.0478
+			*CONTROL_ROT_SAMPLE 22400	-0.0000	0.0000	1.0000	0.0490
+			*CONTROL_ROT_SAMPLE 22560	-0.0000	-0.0000	1.0000	0.0501
+			*CONTROL_ROT_SAMPLE 22720	0.0000	0.0000	1.0000	0.0511
+			*CONTROL_ROT_SAMPLE 22880	-0.0000	0.0000	1.0000	0.0520
+			*CONTROL_ROT_SAMPLE 23040	0.0000	-0.0000	1.0000	0.0528
+			*CONTROL_ROT_SAMPLE 23200	-0.0000	-0.0000	1.0000	0.0535
+			*CONTROL_ROT_SAMPLE 23360	0.0000	0.0000	-1.0000	6.2292
+			*CONTROL_ROT_SAMPLE 23520	0.0000	0.0000	1.0000	0.0544
+			*CONTROL_ROT_SAMPLE 23680	-0.0000	0.0000	1.0000	0.0547
+			*CONTROL_ROT_SAMPLE 23840	0.0000	-0.0000	1.0000	0.0549
+			*CONTROL_ROT_SAMPLE 24000	0.0000	0.0000	1.0000	0.0549
+			*CONTROL_ROT_SAMPLE 24160	0.0000	-0.0000	1.0000	0.0549
+			*CONTROL_ROT_SAMPLE 24320	0.0000	0.0000	1.0000	0.0547
+			*CONTROL_ROT_SAMPLE 24480	0.0000	0.0000	1.0000	0.0544
+			*CONTROL_ROT_SAMPLE 24640	-0.0000	0.0000	1.0000	0.0540
+			*CONTROL_ROT_SAMPLE 24800	0.0000	0.0000	1.0000	0.0535
+			*CONTROL_ROT_SAMPLE 24960	-0.0000	-0.0000	1.0000	0.0528
+			*CONTROL_ROT_SAMPLE 25120	-0.0000	0.0000	1.0000	0.0520
+			*CONTROL_ROT_SAMPLE 25280	0.0000	0.0000	1.0000	0.0511
+			*CONTROL_ROT_SAMPLE 25440	-0.0000	-0.0000	1.0000	0.0501
+			*CONTROL_ROT_SAMPLE 25600	0.0000	0.0000	1.0000	0.0490
+			*CONTROL_ROT_SAMPLE 25760	-0.0000	0.0000	1.0000	0.0478
+			*CONTROL_ROT_SAMPLE 25920	-0.0000	-0.0000	1.0000	0.0464
+			*CONTROL_ROT_SAMPLE 26080	0.0000	-0.0000	1.0000	0.0449
+			*CONTROL_ROT_SAMPLE 26240	0.0000	0.0000	1.0000	0.0434
+			*CONTROL_ROT_SAMPLE 26400	0.0000	0.0000	1.0000	0.0416
+			*CONTROL_ROT_SAMPLE 26560	0.0000	-0.0000	1.0000	0.0398
+			*CONTROL_ROT_SAMPLE 26720	-0.0000	0.0000	1.0000	0.0379
+			*CONTROL_ROT_SAMPLE 26880	0.0000	-0.0000	1.0000	0.0358
+			*CONTROL_ROT_SAMPLE 27040	0.0000	0.0000	1.0000	0.0336
+			*CONTROL_ROT_SAMPLE 27200	0.0000	0.0000	1.0000	0.0313
+			*CONTROL_ROT_SAMPLE 27360	-0.0000	0.0000	1.0000	0.0289
+			*CONTROL_ROT_SAMPLE 27520	-0.0000	0.0000	1.0000	0.0263
+			*CONTROL_ROT_SAMPLE 27680	0.0000	-0.0000	1.0000	0.0237
+			*CONTROL_ROT_SAMPLE 27840	-0.0000	0.0000	1.0000	0.0209
+			*CONTROL_ROT_SAMPLE 28000	0.0000	-0.0000	1.0000	0.0180
+			*CONTROL_ROT_SAMPLE 28160	-0.0000	0.0000	1.0000	0.0150
+			*CONTROL_ROT_SAMPLE 28320	0.0000	-0.0000	1.0000	0.0119
+			*CONTROL_ROT_SAMPLE 28480	0.0000	0.0000	1.0000	0.0086
+			*CONTROL_ROT_SAMPLE 28640	-0.0000	-0.0000	1.0000	0.0053
+			*CONTROL_ROT_SAMPLE 28800	0.0000	-0.0001	1.0000	0.0018
+		}
+	}
+	*WIREFRAME_COLOR 0.2235	0.0314	0.5333
+}

+ 385 - 0
test/ASEFiles/TargetCameraAnim.ase

@@ -0,0 +1,385 @@
+*3DSMAX_ASCIIEXPORT	200
+*COMMENT "AsciiExport Version  2,00 - Sat Nov 08 16:24:29 2008"
+*SCENE {
+	*SCENE_FILENAME "TargetCameraAnim.max"
+	*SCENE_FIRSTFRAME 0
+	*SCENE_LASTFRAME 300
+	*SCENE_FRAMESPEED 30
+	*SCENE_TICKSPERFRAME 160
+	*SCENE_BACKGROUND_STATIC 0.0000	0.0000	0.0000
+	*SCENE_AMBIENT_STATIC 0.0000	0.0000	0.0000
+}
+*MATERIAL_LIST {
+	*MATERIAL_COUNT 0
+}
+*GEOMOBJECT {
+	*NODE_NAME "Box01"
+	*NODE_TM {
+		*NODE_NAME "Box01"
+		*INHERIT_POS 0 0 0
+		*INHERIT_ROT 0 0 0
+		*INHERIT_SCL 0 0 0
+		*TM_ROW0 1.0000	0.0000	0.0000
+		*TM_ROW1 0.0000	1.0000	0.0000
+		*TM_ROW2 0.0000	0.0000	1.0000
+		*TM_ROW3 10.5413	-0.8547	0.0000
+		*TM_POS 10.5413	-0.8547	0.0000
+		*TM_ROTAXIS 0.0000	0.0000	0.0000
+		*TM_ROTANGLE 0.0000
+		*TM_SCALE 1.0000	1.0000	1.0000
+		*TM_SCALEAXIS 0.0000	0.0000	0.0000
+		*TM_SCALEAXISANG 0.0000
+	}
+	*MESH {
+		*TIMEVALUE 0
+		*MESH_NUMVERTEX 8
+		*MESH_NUMFACES 12
+		*MESH_VERTEX_LIST {
+			*MESH_VERTEX    0	-24.7863	-24.7863	0.0000
+			*MESH_VERTEX    1	45.8689	-24.7863	0.0000
+			*MESH_VERTEX    2	-24.7863	23.0769	0.0000
+			*MESH_VERTEX    3	45.8689	23.0769	0.0000
+			*MESH_VERTEX    4	-24.7863	-24.7863	38.7464
+			*MESH_VERTEX    5	45.8689	-24.7863	38.7464
+			*MESH_VERTEX    6	-24.7863	23.0769	38.7464
+			*MESH_VERTEX    7	45.8689	23.0769	38.7464
+		}
+		*MESH_FACE_LIST {
+			*MESH_FACE    0:    A:    0 B:    2 C:    3 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 2 	*MESH_MTLID 1
+			*MESH_FACE    1:    A:    3 B:    1 C:    0 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 2 	*MESH_MTLID 1
+			*MESH_FACE    2:    A:    4 B:    5 C:    7 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 3 	*MESH_MTLID 0
+			*MESH_FACE    3:    A:    7 B:    6 C:    4 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 3 	*MESH_MTLID 0
+			*MESH_FACE    4:    A:    0 B:    1 C:    5 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 4 	*MESH_MTLID 4
+			*MESH_FACE    5:    A:    5 B:    4 C:    0 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 4 	*MESH_MTLID 4
+			*MESH_FACE    6:    A:    1 B:    3 C:    7 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 5 	*MESH_MTLID 3
+			*MESH_FACE    7:    A:    7 B:    5 C:    1 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 5 	*MESH_MTLID 3
+			*MESH_FACE    8:    A:    3 B:    2 C:    6 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 6 	*MESH_MTLID 5
+			*MESH_FACE    9:    A:    6 B:    7 C:    3 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 6 	*MESH_MTLID 5
+			*MESH_FACE   10:    A:    2 B:    0 C:    4 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 7 	*MESH_MTLID 2
+			*MESH_FACE   11:    A:    4 B:    6 C:    2 AB:    1 BC:    1 CA:    0	 *MESH_SMOOTHING 7 	*MESH_MTLID 2
+		}
+		*MESH_NUMTVERTEX 12
+		*MESH_TVERTLIST {
+			*MESH_TVERT 0	0.0000	0.0000	0.0000
+			*MESH_TVERT 1	1.0000	0.0000	0.0000
+			*MESH_TVERT 2	0.0000	1.0000	0.0000
+			*MESH_TVERT 3	1.0000	1.0000	0.0000
+			*MESH_TVERT 4	0.0000	0.0000	0.0000
+			*MESH_TVERT 5	1.0000	0.0000	0.0000
+			*MESH_TVERT 6	0.0000	1.0000	0.0000
+			*MESH_TVERT 7	1.0000	1.0000	0.0000
+			*MESH_TVERT 8	0.0000	0.0000	0.0000
+			*MESH_TVERT 9	1.0000	0.0000	0.0000
+			*MESH_TVERT 10	0.0000	1.0000	0.0000
+			*MESH_TVERT 11	1.0000	1.0000	0.0000
+		}
+		*MESH_NUMTVFACES 12
+		*MESH_TFACELIST {
+			*MESH_TFACE 0	9	11	10
+			*MESH_TFACE 1	10	8	9
+			*MESH_TFACE 2	8	9	11
+			*MESH_TFACE 3	11	10	8
+			*MESH_TFACE 4	4	5	7
+			*MESH_TFACE 5	7	6	4
+			*MESH_TFACE 6	0	1	3
+			*MESH_TFACE 7	3	2	0
+			*MESH_TFACE 8	4	5	7
+			*MESH_TFACE 9	7	6	4
+			*MESH_TFACE 10	0	1	3
+			*MESH_TFACE 11	3	2	0
+		}
+		*MESH_NUMCVERTEX 0
+		*MESH_NORMALS {
+			*MESH_FACENORMAL 0	0.0000	0.0000	-1.0000
+				*MESH_VERTEXNORMAL 0	0.0000	0.0000	-1.0000
+				*MESH_VERTEXNORMAL 2	0.0000	0.0000	-1.0000
+				*MESH_VERTEXNORMAL 3	0.0000	0.0000	-1.0000
+			*MESH_FACENORMAL 1	0.0000	0.0000	-1.0000
+				*MESH_VERTEXNORMAL 3	0.0000	0.0000	-1.0000
+				*MESH_VERTEXNORMAL 1	0.0000	0.0000	-1.0000
+				*MESH_VERTEXNORMAL 0	0.0000	0.0000	-1.0000
+			*MESH_FACENORMAL 2	0.0000	-0.0000	1.0000
+				*MESH_VERTEXNORMAL 4	0.0000	0.0000	1.0000
+				*MESH_VERTEXNORMAL 5	0.0000	-0.0000	1.0000
+				*MESH_VERTEXNORMAL 7	0.0000	0.0000	1.0000
+			*MESH_FACENORMAL 3	-0.0000	0.0000	1.0000
+				*MESH_VERTEXNORMAL 7	0.0000	0.0000	1.0000
+				*MESH_VERTEXNORMAL 6	-0.0000	0.0000	1.0000
+				*MESH_VERTEXNORMAL 4	0.0000	0.0000	1.0000
+			*MESH_FACENORMAL 4	0.0000	-1.0000	0.0000
+				*MESH_VERTEXNORMAL 0	0.0000	-1.0000	0.0000
+				*MESH_VERTEXNORMAL 1	0.0000	-1.0000	0.0000
+				*MESH_VERTEXNORMAL 5	0.0000	-1.0000	0.0000
+			*MESH_FACENORMAL 5	0.0000	-1.0000	0.0000
+				*MESH_VERTEXNORMAL 5	0.0000	-1.0000	0.0000
+				*MESH_VERTEXNORMAL 4	0.0000	-1.0000	0.0000
+				*MESH_VERTEXNORMAL 0	0.0000	-1.0000	0.0000
+			*MESH_FACENORMAL 6	1.0000	0.0000	-0.0000
+				*MESH_VERTEXNORMAL 1	1.0000	0.0000	0.0000
+				*MESH_VERTEXNORMAL 3	1.0000	0.0000	-0.0000
+				*MESH_VERTEXNORMAL 7	1.0000	0.0000	0.0000
+			*MESH_FACENORMAL 7	1.0000	-0.0000	0.0000
+				*MESH_VERTEXNORMAL 7	1.0000	0.0000	0.0000
+				*MESH_VERTEXNORMAL 5	1.0000	-0.0000	0.0000
+				*MESH_VERTEXNORMAL 1	1.0000	0.0000	0.0000
+			*MESH_FACENORMAL 8	0.0000	1.0000	0.0000
+				*MESH_VERTEXNORMAL 3	0.0000	1.0000	0.0000
+				*MESH_VERTEXNORMAL 2	0.0000	1.0000	0.0000
+				*MESH_VERTEXNORMAL 6	0.0000	1.0000	0.0000
+			*MESH_FACENORMAL 9	0.0000	1.0000	0.0000
+				*MESH_VERTEXNORMAL 6	0.0000	1.0000	0.0000
+				*MESH_VERTEXNORMAL 7	0.0000	1.0000	0.0000
+				*MESH_VERTEXNORMAL 3	0.0000	1.0000	0.0000
+			*MESH_FACENORMAL 10	-1.0000	0.0000	0.0000
+				*MESH_VERTEXNORMAL 2	-1.0000	0.0000	0.0000
+				*MESH_VERTEXNORMAL 0	-1.0000	0.0000	0.0000
+				*MESH_VERTEXNORMAL 4	-1.0000	0.0000	0.0000
+			*MESH_FACENORMAL 11	-1.0000	-0.0000	-0.0000
+				*MESH_VERTEXNORMAL 4	-1.0000	0.0000	0.0000
+				*MESH_VERTEXNORMAL 6	-1.0000	-0.0000	-0.0000
+				*MESH_VERTEXNORMAL 2	-1.0000	0.0000	0.0000
+		}
+	}
+	*PROP_MOTIONBLUR 0
+	*PROP_CASTSHADOW 1
+	*PROP_RECVSHADOW 1
+	*WIREFRAME_COLOR 0.6941	0.3451	0.1059
+}
+*CAMERAOBJECT {
+	*NODE_NAME "Camera01"
+	*CAMERA_TYPE Target
+	*NODE_TM {
+		*NODE_NAME "Camera01"
+		*INHERIT_POS 0 0 0
+		*INHERIT_ROT 0 0 0
+		*INHERIT_SCL 1 1 1
+		*TM_ROW0 0.1489	-0.9888	0.0000
+		*TM_ROW1 0.0000	0.0000	1.0000
+		*TM_ROW2 -0.9888	-0.1489	0.0000
+		*TM_ROW3 -65.8677	16.1213	0.0000
+		*TM_POS -65.8677	16.1213	0.0000
+		*TM_ROTAXIS -0.6348	0.5464	0.5464
+		*TM_ROTANGLE 2.0103
+		*TM_SCALE 1.0000	1.0000	1.0000
+		*TM_SCALEAXIS 0.0000	0.0000	0.0000
+		*TM_SCALEAXISANG 0.0000
+	}
+	*NODE_TM {
+		*NODE_NAME "Camera01.Target"
+		*INHERIT_POS 0 0 0
+		*INHERIT_ROT 0 0 0
+		*INHERIT_SCL 0 0 0
+		*TM_ROW0 1.0000	0.0000	0.0000
+		*TM_ROW1 0.0000	1.0000	0.0000
+		*TM_ROW2 0.0000	0.0000	1.0000
+		*TM_ROW3 14.4292	28.2159	0.0000
+		*TM_POS 14.4292	28.2159	0.0000
+		*TM_ROTAXIS 0.0000	0.0000	0.0000
+		*TM_ROTANGLE 0.0000
+		*TM_SCALE 1.0000	1.0000	1.0000
+		*TM_SCALEAXIS 0.0000	0.0000	0.0000
+		*TM_SCALEAXISANG 0.0000
+	}
+	*CAMERA_SETTINGS {
+		*TIMEVALUE 0
+		*CAMERA_NEAR 0.0000
+		*CAMERA_FAR 1000.0000
+		*CAMERA_FOV 0.7854
+		*CAMERA_TDIST 166.7347
+	}
+	*TM_ANIMATION {
+		*NODE_NAME "Camera01"
+		*CONTROL_POS_TRACK {
+			*CONTROL_POS_SAMPLE 0	-65.8677	16.1213	0.0000
+			*CONTROL_POS_SAMPLE 160	-65.8677	16.1213	0.2784
+			*CONTROL_POS_SAMPLE 320	-65.8677	16.1213	1.0883
+			*CONTROL_POS_SAMPLE 480	-65.8677	16.1213	2.3917
+			*CONTROL_POS_SAMPLE 640	-65.8677	16.1213	4.1507
+			*CONTROL_POS_SAMPLE 800	-65.8677	16.1213	6.3273
+			*CONTROL_POS_SAMPLE 960	-65.8677	16.1213	8.8835
+			*CONTROL_POS_SAMPLE 1120	-65.8677	16.1213	11.7814
+			*CONTROL_POS_SAMPLE 1280	-65.8677	16.1213	14.9830
+			*CONTROL_POS_SAMPLE 1440	-65.8677	16.1213	18.4503
+			*CONTROL_POS_SAMPLE 1600	-65.8677	16.1213	22.1454
+			*CONTROL_POS_SAMPLE 1760	-65.8677	16.1213	26.0304
+			*CONTROL_POS_SAMPLE 1920	-65.8677	16.1213	30.0672
+			*CONTROL_POS_SAMPLE 2080	-65.8677	16.1213	34.2179
+			*CONTROL_POS_SAMPLE 2240	-65.8677	16.1213	38.4445
+			*CONTROL_POS_SAMPLE 2400	-65.8677	16.1213	42.7091
+			*CONTROL_POS_SAMPLE 2560	-65.8677	16.1213	46.9737
+			*CONTROL_POS_SAMPLE 2720	-65.8677	16.1213	51.2003
+			*CONTROL_POS_SAMPLE 2880	-65.8677	16.1213	55.3510
+			*CONTROL_POS_SAMPLE 3040	-65.8677	16.1213	59.3877
+			*CONTROL_POS_SAMPLE 3200	-65.8677	16.1213	63.2727
+			*CONTROL_POS_SAMPLE 3360	-65.8677	16.1213	66.9678
+			*CONTROL_POS_SAMPLE 3520	-65.8677	16.1213	70.4352
+			*CONTROL_POS_SAMPLE 3680	-65.8677	16.1213	73.6368
+			*CONTROL_POS_SAMPLE 3840	-65.8677	16.1213	76.5347
+			*CONTROL_POS_SAMPLE 4000	-65.8677	16.1213	79.0909
+			*CONTROL_POS_SAMPLE 4160	-65.8677	16.1213	81.2674
+			*CONTROL_POS_SAMPLE 4320	-65.8677	16.1213	83.0264
+			*CONTROL_POS_SAMPLE 4480	-65.8677	16.1213	84.3298
+			*CONTROL_POS_SAMPLE 4640	-65.8677	16.1213	85.1397
+			*CONTROL_POS_SAMPLE 4800	-65.8677	16.1213	85.4181
+			*CONTROL_POS_SAMPLE 9760	-65.8677	16.1213	84.8455
+			*CONTROL_POS_SAMPLE 9920	-65.8677	16.1213	83.1798
+			*CONTROL_POS_SAMPLE 10080	-65.8677	16.1213	80.4989
+			*CONTROL_POS_SAMPLE 10240	-65.8677	16.1213	76.8811
+			*CONTROL_POS_SAMPLE 10400	-65.8677	16.1213	72.4043
+			*CONTROL_POS_SAMPLE 10560	-65.8677	16.1213	67.1468
+			*CONTROL_POS_SAMPLE 10720	-65.8677	16.1213	61.1864
+			*CONTROL_POS_SAMPLE 10880	-65.8677	16.1213	54.6015
+			*CONTROL_POS_SAMPLE 11040	-65.8677	16.1213	47.4699
+			*CONTROL_POS_SAMPLE 11200	-65.8677	16.1213	39.8698
+			*CONTROL_POS_SAMPLE 11360	-65.8677	16.1213	31.8794
+			*CONTROL_POS_SAMPLE 11520	-65.8677	16.1213	23.5766
+			*CONTROL_POS_SAMPLE 11680	-65.8677	16.1213	15.0395
+			*CONTROL_POS_SAMPLE 11840	-65.8677	16.1213	6.3463
+			*CONTROL_POS_SAMPLE 12000	-65.8677	16.1213	-2.4250
+			*CONTROL_POS_SAMPLE 12160	-65.8677	16.1213	-11.1963
+			*CONTROL_POS_SAMPLE 12320	-65.8677	16.1213	-19.8895
+			*CONTROL_POS_SAMPLE 12480	-65.8677	16.1213	-28.4266
+			*CONTROL_POS_SAMPLE 12640	-65.8677	16.1213	-36.7294
+			*CONTROL_POS_SAMPLE 12800	-65.8677	16.1213	-44.7199
+			*CONTROL_POS_SAMPLE 12960	-65.8677	16.1213	-52.3199
+			*CONTROL_POS_SAMPLE 13120	-65.8677	16.1213	-59.4515
+			*CONTROL_POS_SAMPLE 13280	-65.8677	16.1213	-66.0365
+			*CONTROL_POS_SAMPLE 13440	-65.8677	16.1213	-71.9968
+			*CONTROL_POS_SAMPLE 13600	-65.8677	16.1213	-77.2544
+			*CONTROL_POS_SAMPLE 13760	-65.8677	16.1213	-81.7311
+			*CONTROL_POS_SAMPLE 13920	-65.8677	16.1213	-85.3490
+			*CONTROL_POS_SAMPLE 14080	-65.8677	16.1213	-88.0298
+			*CONTROL_POS_SAMPLE 14240	-65.8677	16.1213	-89.6956
+			*CONTROL_POS_SAMPLE 14400	-65.8677	16.1213	-90.2682
+		}
+		*CONTROL_ROT_TRACK {
+			*CONTROL_ROT_SAMPLE 0	-0.6348	0.5464	0.5464	2.0103
+			*CONTROL_ROT_SAMPLE 160	0.1489	-0.9888	0.0000	0.0034
+			*CONTROL_ROT_SAMPLE 320	0.1490	-0.9888	-0.0000	0.0100
+			*CONTROL_ROT_SAMPLE 480	0.1489	-0.9888	0.0000	0.0160
+			*CONTROL_ROT_SAMPLE 640	0.1489	-0.9888	-0.0000	0.0216
+			*CONTROL_ROT_SAMPLE 800	0.1489	-0.9888	0.0000	0.0267
+			*CONTROL_ROT_SAMPLE 960	0.1489	-0.9888	-0.0000	0.0312
+			*CONTROL_ROT_SAMPLE 1120	0.1489	-0.9888	0.0000	0.0351
+			*CONTROL_ROT_SAMPLE 1280	0.1489	-0.9888	0.0000	0.0384
+			*CONTROL_ROT_SAMPLE 1440	0.1489	-0.9888	0.0000	0.0410
+			*CONTROL_ROT_SAMPLE 1600	0.1489	-0.9888	0.0000	0.0428
+			*CONTROL_ROT_SAMPLE 1760	0.1489	-0.9888	-0.0000	0.0440
+			*CONTROL_ROT_SAMPLE 1920	0.1489	-0.9888	0.0000	0.0444
+			*CONTROL_ROT_SAMPLE 2080	0.1489	-0.9888	-0.0000	0.0442
+			*CONTROL_ROT_SAMPLE 2240	0.1489	-0.9888	-0.0000	0.0434
+			*CONTROL_ROT_SAMPLE 2400	0.1489	-0.9888	0.0000	0.0420
+			*CONTROL_ROT_SAMPLE 2560	0.1489	-0.9888	0.0000	0.0402
+			*CONTROL_ROT_SAMPLE 2720	0.1489	-0.9888	-0.0000	0.0381
+			*CONTROL_ROT_SAMPLE 2880	0.1489	-0.9888	0.0000	0.0357
+			*CONTROL_ROT_SAMPLE 3040	0.1489	-0.9888	-0.0000	0.0332
+			*CONTROL_ROT_SAMPLE 3200	0.1489	-0.9888	-0.0000	0.0305
+			*CONTROL_ROT_SAMPLE 3360	0.1489	-0.9888	0.0000	0.0277
+			*CONTROL_ROT_SAMPLE 3520	0.1489	-0.9888	-0.0000	0.0249
+			*CONTROL_ROT_SAMPLE 3680	0.1489	-0.9888	0.0000	0.0221
+			*CONTROL_ROT_SAMPLE 3840	0.1489	-0.9888	-0.0000	0.0192
+			*CONTROL_ROT_SAMPLE 4000	0.1489	-0.9888	0.0000	0.0164
+			*CONTROL_ROT_SAMPLE 4160	0.1489	-0.9888	0.0000	0.0136
+			*CONTROL_ROT_SAMPLE 4320	0.1489	-0.9888	-0.0000	0.0107
+			*CONTROL_ROT_SAMPLE 4480	0.1489	-0.9888	-0.0000	0.0078
+			*CONTROL_ROT_SAMPLE 4640	0.1489	-0.9888	0.0000	0.0048
+			*CONTROL_ROT_SAMPLE 4800	0.1489	-0.9888	0.0000	0.0016
+			*CONTROL_ROT_SAMPLE 4960	-0.1424	0.9469	0.2882	0.0018
+			*CONTROL_ROT_SAMPLE 5120	-0.1415	0.9476	0.2864	0.0051
+			*CONTROL_ROT_SAMPLE 5280	-0.1399	0.9489	0.2829	0.0082
+			*CONTROL_ROT_SAMPLE 5440	-0.1376	0.9507	0.2780	0.0108
+			*CONTROL_ROT_SAMPLE 5600	-0.1347	0.9528	0.2721	0.0130
+			*CONTROL_ROT_SAMPLE 5760	-0.1314	0.9551	0.2654	0.0149
+			*CONTROL_ROT_SAMPLE 5920	-0.1277	0.9576	0.2583	0.0163
+			*CONTROL_ROT_SAMPLE 6080	-0.1239	0.9600	0.2511	0.0174
+			*CONTROL_ROT_SAMPLE 6240	-0.1199	0.9624	0.2438	0.0181
+			*CONTROL_ROT_SAMPLE 6400	-0.1159	0.9646	0.2368	0.0185
+			*CONTROL_ROT_SAMPLE 6560	-0.1119	0.9667	0.2301	0.0186
+			*CONTROL_ROT_SAMPLE 6720	-0.1080	0.9686	0.2239	0.0184
+			*CONTROL_ROT_SAMPLE 6880	-0.1042	0.9704	0.2180	0.0181
+			*CONTROL_ROT_SAMPLE 7040	-0.1006	0.9719	0.2127	0.0175
+			*CONTROL_ROT_SAMPLE 7200	-0.0972	0.9733	0.2077	0.0169
+			*CONTROL_ROT_SAMPLE 7360	-0.0940	0.9746	0.2033	0.0161
+			*CONTROL_ROT_SAMPLE 7520	-0.0910	0.9757	0.1993	0.0152
+			*CONTROL_ROT_SAMPLE 7680	-0.0882	0.9767	0.1957	0.0143
+			*CONTROL_ROT_SAMPLE 7840	-0.0857	0.9776	0.1925	0.0133
+			*CONTROL_ROT_SAMPLE 8000	-0.0833	0.9783	0.1896	0.0123
+			*CONTROL_ROT_SAMPLE 8160	-0.0812	0.9790	0.1871	0.0112
+			*CONTROL_ROT_SAMPLE 8320	-0.0793	0.9795	0.1849	0.0102
+			*CONTROL_ROT_SAMPLE 8480	-0.0776	0.9800	0.1830	0.0091
+			*CONTROL_ROT_SAMPLE 8640	-0.0761	0.9805	0.1814	0.0080
+			*CONTROL_ROT_SAMPLE 8800	-0.0748	0.9808	0.1800	0.0068
+			*CONTROL_ROT_SAMPLE 8960	-0.0737	0.9811	0.1788	0.0057
+			*CONTROL_ROT_SAMPLE 9120	-0.0728	0.9814	0.1779	0.0045
+			*CONTROL_ROT_SAMPLE 9280	-0.0721	0.9815	0.1772	0.0033
+			*CONTROL_ROT_SAMPLE 9440	-0.0717	0.9816	0.1767	0.0020
+			*CONTROL_ROT_SAMPLE 9600	-0.0714	0.9817	0.1765	0.0007
+			*CONTROL_ROT_SAMPLE 9760	-0.0725	0.9974	-0.0000	0.0027
+			*CONTROL_ROT_SAMPLE 9920	-0.0726	0.9974	0.0000	0.0080
+			*CONTROL_ROT_SAMPLE 10080	-0.0725	0.9974	0.0000	0.0130
+			*CONTROL_ROT_SAMPLE 10240	-0.0725	0.9974	-0.0000	0.0177
+			*CONTROL_ROT_SAMPLE 10400	-0.0725	0.9974	0.0000	0.0224
+			*CONTROL_ROT_SAMPLE 10560	-0.0725	0.9974	0.0000	0.0268
+			*CONTROL_ROT_SAMPLE 10720	-0.0725	0.9974	-0.0000	0.0311
+			*CONTROL_ROT_SAMPLE 10880	-0.0725	0.9974	0.0000	0.0352
+			*CONTROL_ROT_SAMPLE 11040	-0.0725	0.9974	-0.0000	0.0391
+			*CONTROL_ROT_SAMPLE 11200	-0.0725	0.9974	0.0000	0.0427
+			*CONTROL_ROT_SAMPLE 11360	-0.0725	0.9974	-0.0000	0.0458
+			*CONTROL_ROT_SAMPLE 11520	-0.0725	0.9974	0.0000	0.0485
+			*CONTROL_ROT_SAMPLE 11680	-0.0725	0.9974	-0.0000	0.0505
+			*CONTROL_ROT_SAMPLE 11840	-0.0725	0.9974	-0.0000	0.0519
+			*CONTROL_ROT_SAMPLE 12000	-0.0725	0.9974	0.0000	0.0526
+			*CONTROL_ROT_SAMPLE 12160	-0.0725	0.9974	-0.0000	0.0525
+			*CONTROL_ROT_SAMPLE 12320	-0.0725	0.9974	0.0000	0.0517
+			*CONTROL_ROT_SAMPLE 12480	-0.0725	0.9974	-0.0000	0.0501
+			*CONTROL_ROT_SAMPLE 12640	-0.0725	0.9974	-0.0000	0.0480
+			*CONTROL_ROT_SAMPLE 12800	-0.0725	0.9974	0.0000	0.0452
+			*CONTROL_ROT_SAMPLE 12960	-0.0725	0.9974	-0.0000	0.0420
+			*CONTROL_ROT_SAMPLE 13120	-0.0725	0.9974	-0.0000	0.0385
+			*CONTROL_ROT_SAMPLE 13280	-0.0725	0.9974	0.0000	0.0346
+			*CONTROL_ROT_SAMPLE 13440	-0.0725	0.9974	0.0000	0.0305
+			*CONTROL_ROT_SAMPLE 13600	-0.0725	0.9974	-0.0000	0.0263
+			*CONTROL_ROT_SAMPLE 13760	-0.0725	0.9974	-0.0000	0.0219
+			*CONTROL_ROT_SAMPLE 13920	-0.0725	0.9974	0.0000	0.0173
+			*CONTROL_ROT_SAMPLE 14080	-0.0725	0.9974	0.0000	0.0127
+			*CONTROL_ROT_SAMPLE 14240	-0.0726	0.9974	0.0000	0.0078
+			*CONTROL_ROT_SAMPLE 14400	-0.0725	0.9974	0.0000	0.0027
+		}
+	}
+	*TM_ANIMATION {
+		*NODE_NAME "Camera01.Target"
+		*CONTROL_POS_TRACK {
+			*CONTROL_POS_SAMPLE 0	14.4292	28.2159	0.0000
+			*CONTROL_POS_SAMPLE 4960	14.7094	28.2159	0.0000
+			*CONTROL_POS_SAMPLE 5120	15.5247	28.2159	0.0000
+			*CONTROL_POS_SAMPLE 5280	16.8367	28.2159	0.0000
+			*CONTROL_POS_SAMPLE 5440	18.6072	28.2159	0.0000
+			*CONTROL_POS_SAMPLE 5600	20.7982	28.2159	0.0000
+			*CONTROL_POS_SAMPLE 5760	23.3712	28.2159	0.0000
+			*CONTROL_POS_SAMPLE 5920	26.2882	28.2159	0.0000
+			*CONTROL_POS_SAMPLE 6080	29.5109	28.2159	0.0000
+			*CONTROL_POS_SAMPLE 6240	33.0011	28.2159	0.0000
+			*CONTROL_POS_SAMPLE 6400	36.7205	28.2159	0.0000
+			*CONTROL_POS_SAMPLE 6560	40.6311	28.2159	0.0000
+			*CONTROL_POS_SAMPLE 6720	44.6945	28.2159	0.0000
+			*CONTROL_POS_SAMPLE 6880	48.8725	28.2159	0.0000
+			*CONTROL_POS_SAMPLE 7040	53.1270	28.2159	0.0000
+			*CONTROL_POS_SAMPLE 7200	57.4196	28.2159	0.0000
+			*CONTROL_POS_SAMPLE 7360	61.7123	28.2159	0.0000
+			*CONTROL_POS_SAMPLE 7520	65.9668	28.2159	0.0000
+			*CONTROL_POS_SAMPLE 7680	70.1448	28.2159	0.0000
+			*CONTROL_POS_SAMPLE 7840	74.2082	28.2159	0.0000
+			*CONTROL_POS_SAMPLE 8000	78.1187	28.2159	0.0000
+			*CONTROL_POS_SAMPLE 8160	81.8382	28.2159	0.0000
+			*CONTROL_POS_SAMPLE 8320	85.3284	28.2159	0.0000
+			*CONTROL_POS_SAMPLE 8480	88.5511	28.2159	0.0000
+			*CONTROL_POS_SAMPLE 8640	91.4680	28.2159	0.0000
+			*CONTROL_POS_SAMPLE 8800	94.0411	28.2159	0.0000
+			*CONTROL_POS_SAMPLE 8960	96.2320	28.2159	0.0000
+			*CONTROL_POS_SAMPLE 9120	98.0026	28.2159	0.0000
+			*CONTROL_POS_SAMPLE 9280	99.3146	28.2159	0.0000
+			*CONTROL_POS_SAMPLE 9440	100.1298	28.2159	0.0000
+			*CONTROL_POS_SAMPLE 9600	100.4100	28.2159	0.0000
+		}
+	}
+}

+ 5 - 1
test/ASEFiles/credits.txt

@@ -1,2 +1,6 @@
 "würfel.ase" - Made by Marius Schröder, free for any purpose
-"MotionCaptureROM.ase" - Recorded at the HTW Aalen using LycosIQ. Free for any purpose
+
+"MotionCaptureROM.ase" - Recorded at the HTW Aalen using LycosIQ.
+
+NOTE: The errors in the middle of the animation are there in the
+original animation track, too. The captured person lost a sensor ...

BIN
test/IRR/multipleAnimators.irr


BIN
test/IRR/skybox.xml


BIN
test/SourceFiles/CameraRollAnim.max


BIN
test/SourceFiles/CameraRollAnimWithChildObject.max


BIN
test/SourceFiles/RotatingCube.max


BIN
test/SourceFiles/TargetCameraAnim.max


+ 1 - 1
tools/assimp_view/LogDisplay.cpp

@@ -122,7 +122,7 @@ void CLogDisplay::OnRender()
 	// if no asset is loaded draw a "no asset loaded" text in the center
 	if (!g_pcAsset)
 		{
-		const char* szText = "No asset loaded\r\nUse [Viewer | Open asset] to load one";
+			const char* szText = "No asset loaded\r\nUse [Viewer | Open asset] to load one";
 
 		// shadow
 		RECT sCopy;

+ 8 - 0
workspaces/vc8/assimp.vcproj

@@ -895,6 +895,14 @@
 				RelativePath="..\..\code\StringComparison.h"
 				>
 			</File>
+			<File
+				RelativePath="..\..\code\TargetAnimation.cpp"
+				>
+			</File>
+			<File
+				RelativePath="..\..\code\TargetAnimation.h"
+				>
+			</File>
 			<File
 				RelativePath="..\..\code\TextureTransform.cpp"
 				>

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません