//************************************************************************** 
//* Animout.cpp	- Ascii File Exporter
//* 
//* By Christer Janson
//* Kinetix Development
//*
//* January 20, 1997 CCJ Initial coding
//*
//* This module handles controller key output and controller sampling.
//*
//* Copyright (c) 1997, All Rights Reserved. 
//***************************************************************************

#include "asciiexp.h"

#define ALMOST_ZERO 1.0e-3f
BOOL EqualPoint3(Point3 p1, Point3 p2);

/****************************************************************************

  TM Animation output
  
****************************************************************************/

// Get hold of the transform controllers for the node... 
void AsciiExp::ExportAnimKeys( INode* node ) 
{
	BOOL bPosAnim;
	BOOL bRotAnim;
	BOOL bScaleAnim;
	BOOL bDoKeys = FALSE;

	// We can only export keys if all TM controllers are "known" to us.
	// The reason for that is that some controllers control more than what
	// they should. Consider a path position controller, if you turn on
	// follow and banking, this position controller will also control
	// rotation. If a node that had a path position controller also had a
	// TCB rotation controller, the TCB keys would not describe the whole
	// rotation of the node.
	// For that reason we will only export keys if all controllers
	// position, rotation and scale are linear, hybrid (bezier) or tcb.

/*	if (!GetAlwaysSample())
	{
		Control* pC = node->GetTMController()->GetPositionController();
		Control* rC = node->GetTMController()->GetRotationController();
		Control* sC = node->GetTMController()->GetScaleController();

		if (IsKnownController(pC) && IsKnownController(rC) && IsKnownController(sC))
		{
			bDoKeys = TRUE;
		}
	}
*/
	if (bDoKeys)
	{
		// Only dump the track header if any of the controllers have keys
		if (node->GetTMController()->GetPositionController()->NumKeys() ||
			node->GetTMController()->GetRotationController()->NumKeys() ||
			node->GetTMController()->GetScaleController()->NumKeys())
		{
//			fprintf(pStream,"%s\t%s {\n", indent.data(), ID_TM_ANIMATION); 
//			fprintf(pStream,"%s\t\t%s \"%s\"\n", indent.data(), ID_NODE_NAME, FixupName(node->GetName()));

			DumpPosKeys(node->GetTMController()->GetPositionController());
			DumpRotKeys(node->GetTMController()->GetRotationController());
			DumpScaleKeys(node->GetTMController()->GetScaleController());

//			fprintf(pStream,"%s\t}\n", indent.data());
		}
	}
	else if (CheckForAnimation(node, bPosAnim, bRotAnim, bScaleAnim))
	{
//		fprintf(pStream,"%s\t%s {\n", indent.data(), ID_TM_ANIMATION); 
//		fprintf(pStream,"%s\t\t%s \"%s\"\n", indent.data(), ID_NODE_NAME, FixupName(node->GetName()));

		if (bPosAnim)		DumpPosSample(node);
		if (bRotAnim)		DumpRotSample(node);
		if (bScaleAnim)		DumpScaleSample(node);

//		fprintf(pStream,"%s\t}\n", indent.data());
	}
}


// To really see if a node is animated we can step through the animation range
// and decompose the TM matrix for every frame and examine the components.
// This way we can identify position, rotation and scale animation separately.
// 
// Some controllers makes it problematic to examine the TMContollers instead of
// the actual TMMatrix. For example, a path controller is a position controller, 
// but if you turn on follow and banking, it will also affect the rotation component.
// If we want to, we can examine the position, rotation and scale controllers and
// if they all are Linear, Hybrid (bezier) or TCB, then we could export the actual keys.
// This is not at all difficult, but the importer has to know the exact interpolation
// algorithm in order to use it. The source code to the interpolation routines are available
// to ADN members.
// 
// For an example of how to export actual keys, look at DumpPoint3Keys() below.
// This method will check the actual controller to determine if the controller is known.
// If we know how to work this controller, its actual keys will be exported,
// otherwise the controller will be sampled using the user specified sampling frequency.

BOOL AsciiExp::CheckForAnimation(INode* node, BOOL& bPos, BOOL& bRot, BOOL& bScale)
{
	TimeValue start = ip->GetAnimRange().Start();
	TimeValue end = ip->GetAnimRange().End();
	TimeValue t;
	int delta = GetTicksPerFrame();
	Matrix3 tm;
	AffineParts ap;
	Point3 firstPos;
	float rotAngle, firstRotAngle;
	Point3 rotAxis;
	Point3 firstScaleFactor;

	bPos = bRot = bScale = FALSE;

	for (t=start; t<=end; t+=delta)
	{
		tm = node->GetNodeTM(t) * Inverse(node->GetParentTM(t));

		decomp_affine(tm, &ap);

		AngAxisFromQ(ap.q, &rotAngle, rotAxis);

		if (t != start)
		{
			// We examine the rotation angle to see if the rotation component
			// has changed.
			// Although not entierly true, it should work.
			// It is rare that the rotation axis is animated without
			// the rotation angle being somewhat affected.
			bPos = TRUE;
			bRot = TRUE;
			bScale = TRUE;
//			if (!EqualPoint3(ap.t, firstPos))					bPos = TRUE;
//			if (fabs(rotAngle - firstRotAngle) > ALMOST_ZERO)	bRot = TRUE;
//			if (!EqualPoint3(ap.k, firstScaleFactor))			bScale = TRUE;
		}
		else
		{
			firstPos = ap.t;
			firstRotAngle = rotAngle;
			firstScaleFactor = ap.k;
		}

		// No need to continue looping if all components are animated
		if (bPos && bRot && bScale)		break;
	}

	return bPos || bRot || bScale;
}


void AsciiExp::DumpPosQuatSample(INode* node)
{	
//	fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_POS_TRACK);

	TimeValue	start = ip->GetAnimRange().Start();
	TimeValue	end = ip->GetAnimRange().End();
	TimeValue	t;
	int			delta = GetTicksPerFrame();
	Matrix3		tm;
	AffineParts	ap;
	Point3		prevPos;
	Quat		prevQ;
	Quat		q;
	char		name[256];


	sprintf( name, "%s", node->GetName() );
	fprintf( tempStream, "OUTPUTTING - %s\n", name );				// DEBUG FILE
//	fwrite( &name, sizeof(char), NAME_LENGTH, boneStream );					// WRITE BONE NAME

	prevQ.Identity();

	for (t=start; t<=end; t+=delta)
	{
		// TRANSLATION
		tm = node->GetNodeTM(t);// * Inverse(node->GetParentTM(t));
		decomp_affine(tm, &ap);

		fwrite( &ap.t.x, sizeof(float), 1, expStream );			// WRITE BONE X-POS
		fwrite( &ap.t.z, sizeof(float), 1, expStream );			// WRITE BONE Y-POS
		fwrite( &ap.t.y, sizeof(float), 1, expStream );			// WRITE BONE Z-POS

		fwrite( &ap.q.x, sizeof(float), 1, expStream );				// WRITE BONE ROT X-AXIS
		fwrite( &ap.q.z, sizeof(float), 1, expStream );				// WRITE BONE ROT Y-AXIS
		fwrite( &ap.q.y, sizeof(float), 1, expStream );				// WRITE BONE ROT Z-AXIS
		fwrite( &ap.q.w, sizeof(float), 1, expStream );				// WRITE BONE ROT W ANGLE
	}
}

void AsciiExp::DumpPosSample(INode* node)
{	
//	fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_POS_TRACK);

	TimeValue	start = ip->GetAnimRange().Start();
	TimeValue	end = ip->GetAnimRange().End();
	TimeValue	t;
	int			delta = GetTicksPerFrame();
	Matrix3		tm;
	AffineParts	ap;
	Point3		prevPos;
	Quat		prevQ;
	Quat		q;
	char		name[256];


	sprintf( name, "%s", node->GetName() );
	fprintf( tempStream, "OUTPUTTING - %s\n", name );				// DEBUG FILE
//	fwrite( &name, sizeof(char), NAME_LENGTH, boneStream );					// WRITE BONE NAME

	prevQ.Identity();

	for (t=start; t<=end; t+=delta)
	{
		// TRANSLATION
		tm = node->GetNodeTM(t);// * Inverse(node->GetParentTM(t));
		decomp_affine(tm, &ap);

		Point3	fpos;
		Point3	pos = ap.t;

		prevPos = pos;

		fpos.x = pos.x;
		fpos.y = pos.z;
		fpos.z = -pos.y;

		fprintf( tempStream, "	POS = %f %f %f\n", pos.x, pos.z, -pos.y );
//		fwrite( &fpos.x, sizeof(float), 1, boneStream );			// WRITE BONE X-POS
//		fwrite( &fpos.y, sizeof(float), 1, boneStream );			// WRITE BONE Y-POS
//		fwrite( &fpos.z, sizeof(float), 1, boneStream );			// WRITE BONE Z-POS


		// ROTATION	
		tm = node->GetNodeTM(t);// * Inverse(node->GetParentTM(t));

		decomp_affine(tm, &ap);

		if (t == start)
		{
			q = ap.q;
			prevQ = ap.q;		// SAVE BASE ROTATION, THEN USE THIS AS ROTATION ORIGIN
		}
		else
		{
			q = ap.q / prevQ;
		}

		fprintf( tempStream, "	QUAT = %f %f %f %f\n", q.x, q.y, q.z, q.w );
//		fwrite( &q.x, sizeof(float), 1, boneStream );				// WRITE BONE ROT X-AXIS
//		fwrite( &q.y, sizeof(float), 1, boneStream );				// WRITE BONE ROT Y-AXIS
//		fwrite( &q.z, sizeof(float), 1, boneStream );				// WRITE BONE ROT Z-AXIS
//		fwrite( &q.w, sizeof(float), 1, boneStream );				// WRITE BONE ROT W ANGLE
	}
}


void AsciiExp::DumpRotSample(INode* node)
{
//	fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_ROT_TRACK);

	TimeValue start = ip->GetAnimRange().Start();
	TimeValue end = ip->GetAnimRange().End();
	TimeValue t;
	int delta = GetTicksPerFrame();
	Matrix3 tm;
	AffineParts ap;
	Quat prevQ;

	prevQ.Identity();

	for (t=start; t<=end; t+=delta)
	{
		tm = node->GetNodeTM(t) * Inverse(node->GetParentTM(t));

		decomp_affine(tm, &ap);

		// Rotation keys should be relative, so we need to convert these
		// absolute samples to relative values.

		Quat q = ap.q / prevQ;
		prevQ = ap.q;

		// No point in exporting null keys...
//		if (q.IsIdentity())		continue;

		// Output the sample
		fprintf( tempStream, "	QUAT = %f %f %f %f\n", q.x, q.y, q.z, q.w );
//		fprintf(pStream, "%s\t\t\t%s %d\t%s\n", indent.data(), ID_ROT_SAMPLE, t, Format(q));
	}

//	fprintf(pStream,"%s\t\t}\n", indent.data());
}


void AsciiExp::DumpScaleSample(INode* node)
{	
//	fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_SCALE_TRACK);

	TimeValue start = ip->GetAnimRange().Start();
	TimeValue end = ip->GetAnimRange().End();
	TimeValue t;
	int delta = GetTicksPerFrame();
	Matrix3 tm;
	AffineParts ap;
	Point3	prevFac;

	for (t=start; t<=end; t+=delta)
	{
		tm = node->GetNodeTM(t) * Inverse(node->GetParentTM(t));
		decomp_affine(tm, &ap);

		// Skip identical keys 
		if (t!= start && EqualPoint3(ap.k, prevFac))	continue;

		prevFac = ap.k;

		// Output the sample
//		fprintf(pStream, "%s\t\t\t%s %d\t%s %s\n", indent.data(), ID_SCALE_SAMPLE, t, Format(ap.k), Format(ap.u));
	}

//	fprintf(pStream,"%s\t\t}\n", indent.data());
}


// Output point3 keys if this is a known point3 controller that
// supports key operations. Otherwise we will sample the controller 
// once for each frame to get the value.
// Point3 controllers can control, for example, color.
void AsciiExp::DumpPoint3Keys(Control* cont)
{
	// Bug out if no controller.
	if (!cont)	return;

	int i;
	IKeyControl *ikc = NULL;

	// If the user wants us to always sample, we will ignore the KeyControlInterface
//	if (!GetAlwaysSample())		ikc = GetKeyControlInterface(cont);

	// TCB point3
	if (ikc && cont->ClassID() == Class_ID(TCBINTERP_POINT3_CLASS_ID, 0))
	{
//		fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_CONTROL_POINT3_TCB); 
		for (i=0; i<ikc->GetNumKeys(); i++)
		{
			ITCBPoint3Key key;
			ikc->GetKey(i, &key);
			fprintf( tempStream, "	KEYPOS = %f %f %f\n", key.val.x, key.val.y, key.val.z );
//			fprintf(pStream, "%s\t\t\t%s %d\t%s", indent.data(), ID_TCB_POINT3_KEY, key.time, Format(key.val));
			// Add TCB specific data
//			fprintf(pStream, "\t%s\t%s\t%s\t%s\t%s\n", Format(key.tens), Format(key.cont), Format(key.bias), Format(key.easeIn), Format(key.easeOut));
		}
//		fprintf(pStream,"%s\t\t}\n", indent.data());
	}
	// Bezier point3
	else if (ikc && cont->ClassID() == Class_ID(HYBRIDINTERP_POINT3_CLASS_ID, 0))
	{
//		fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_CONTROL_POINT3_BEZIER);
		for (i=0; i<ikc->GetNumKeys(); i++)
		{
			IBezPoint3Key key;
			ikc->GetKey(i, &key);
			fprintf( tempStream, "	KEYPOS = %f %f %f\n", key.val.x, key.val.y, key.val.z );
//			fprintf(pStream, "%s\t\t\t%s %d\t%s", indent.data(), ID_BEZIER_POINT3_KEY, key.time, Format(key.val));
//			fprintf(pStream, "\t%s\t%s\t%d\n", Format(key.intan), Format(key.outtan), key.flags);
		}
//		fprintf(pStream,"%s\t\t}\n", indent.data());
	}
	// Bezier color
	else if (ikc && cont->ClassID() == Class_ID(HYBRIDINTERP_COLOR_CLASS_ID, 0))
	{
//		fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_CONTROL_COLOR_BEZIER); 
		for (i=0; i<ikc->GetNumKeys(); i++)
		{
			IBezPoint3Key key;
			ikc->GetKey(i, &key);
			fprintf( tempStream, "	KEYPOS = %f %f %f\n", key.val.x, key.val.y, key.val.z );
//			fprintf(pStream, "%s\t\t\t%s %d\t%s", indent.data(), ID_BEZIER_POINT3_KEY, key.time, Format(key.val));
//			fprintf(pStream, "\t%s\t%s\t%d\n", Format(key.intan), Format(key.outtan), key.flags);
		}
//		fprintf(pStream,"%s\t\t}\n", indent.data());
	}
	else
	{
		// Unknown controller, no key interface or sample on demand -
		// This might be a procedural controller or something else we
		// don't know about. The last resort is to get the value from the 
		// controller at every n frames.

		TSTR name;
		cont->GetClassName(name);
//		fprintf(pStream,"%s\t\t%s \"%s\" {\n", indent.data(), ID_CONTROL_POINT3_SAMPLE, FixupName(name));

		// If it is animated at all...
		if (cont->IsAnimated())
		{
			// Get the range of the controller animation 
			Interval range; 
			// Get range of full animation
			Interval animRange = ip->GetAnimRange(); 
			TimeValue t = cont->GetTimeRange(TIMERANGE_ALL).Start();
			Point3 value;

			// While we are inside the animation... 
			while (animRange.InInterval(t))
			{
				// Sample the controller
				range = FOREVER;
				cont->GetValue(t, &value, range);

				// Set time to start of controller validity interval 
				t = range.Start();

				// Output the sample
				fprintf( tempStream, "	KEYPOS = %f %f %f\n", value.x, value.y, value.z );
//				fprintf(pStream, "%s\t\t\t%s %d\t%s\n", indent.data(), ID_POINT3_KEY, t, Format(value));

				// If the end of the controller validity is beyond the 
				// range of the animation
				if (range.End() > cont->GetTimeRange(TIMERANGE_ALL).End())
				{
					break;
				}
				else
				{
					t = (range.End()/GetTicksPerFrame()+1) * GetTicksPerFrame();
				}
			}
		}
//		fprintf(pStream,"%s\t\t}\n", indent.data());
	}
}


// Output float keys if this is a known float controller that
// supports key operations. Otherwise we will sample the controller 
// once for each frame to get the value.
void AsciiExp::DumpFloatKeys(Control* cont)
{
	if (!cont)	return;

	int i;
	IKeyControl *ikc = NULL;

	// If the user wants us to always sample, we will ignore the KeyControlInterface
//	if (!GetAlwaysSample())		ikc = GetKeyControlInterface(cont);
	
	// TCB float
	if (ikc && cont->ClassID() == Class_ID(TCBINTERP_FLOAT_CLASS_ID, 0))
	{
//		fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_CONTROL_FLOAT_TCB); 
		for (i=0; i<ikc->GetNumKeys(); i++)
		{
			ITCBFloatKey key;
			ikc->GetKey(i, &key);
//			fprintf(pStream, "%s\t\t\t%s %d\t%s", indent.data(), ID_TCB_FLOAT_KEY, key.time, Format(key.val));
//			fprintf(pStream, "\t%s\t%s\t%s\t%s\t%s\n", Format(key.tens), Format(key.cont), Format(key.bias), Format(key.easeIn), Format(key.easeOut));
		}
//		fprintf(pStream,"%s\t\t}\n", indent.data());
	}
	// Bezier float
	else if (ikc && cont->ClassID() == Class_ID(HYBRIDINTERP_FLOAT_CLASS_ID, 0))
	{
//		fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_CONTROL_FLOAT_BEZIER); 
		for (i=0; i<ikc->GetNumKeys(); i++)
		{
			IBezFloatKey key;
			ikc->GetKey(i, &key);
//			fprintf(pStream, "%s\t\t\t%s %d\t%s", indent.data(), ID_BEZIER_FLOAT_KEY, key.time, Format(key.val));
//			fprintf(pStream, "\t%s\t%s\t%d\n", Format(key.intan), Format(key.outtan), key.flags);
		}
//		fprintf(pStream,"%s\t\t}\n", indent.data());
	}
	else if (ikc && cont->ClassID() == Class_ID(LININTERP_FLOAT_CLASS_ID, 0))
	{
//		fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_CONTROL_FLOAT_LINEAR); 
		for (i=0; i<ikc->GetNumKeys(); i++)
		{
			ILinFloatKey key;
			ikc->GetKey(i, &key);
//			fprintf(pStream, "%s\t\t\t%s %d\t%s\n", indent.data(), ID_FLOAT_KEY, key.time, Format(key.val));
		}
//		fprintf(pStream,"%s\t\t}\n", indent.data());
	}
	else
	{
		// Unknown controller, no key interface or sample on demand -
		// This might be a procedural controller or something else we
		// don't know about. The last resort is to get the value from the 
		// controller at every n frames.
		
		TSTR name;
		cont->GetClassName(name);
//		fprintf(pStream,"%s\t\t%s \"%s\" {\n", indent.data(), ID_CONTROL_FLOAT_SAMPLE, FixupName(name));
		
		// If it is animated at all...
		if (cont->IsAnimated())
		{
			// Get the range of the controller animation 
			Interval range; 
			// Get range of full animation
			Interval animRange = ip->GetAnimRange(); 
			TimeValue t = cont->GetTimeRange(TIMERANGE_ALL).Start();
			float value;
			
			// While we are inside the animation... 
			while (animRange.InInterval(t))
			{
				// Sample the controller
				range = FOREVER;
				cont->GetValue(t, &value, range);
				
				// Set time to start of controller validity interval 
				t = range.Start();
				
				// Output the sample
//				fprintf(pStream, "%s\t\t\t%s %d\t%s\n", indent.data(), ID_FLOAT_KEY, t, Format(value));
				
				// If the end of the controller validity is beyond the 
				// range of the animation
				if (range.End() > cont->GetTimeRange(TIMERANGE_ALL).End())
				{
					break;
				}
				else
				{
					t = (range.End()/GetTicksPerFrame()+1) * GetTicksPerFrame();
				}
			}
		}
//		fprintf(pStream,"%s\t\t}\n", indent.data());
	}
}


void AsciiExp::DumpPosKeys(Control* cont) 
{
	if (!cont) return;
	
	int i;
	IKeyControl *ikc = GetKeyControlInterface(cont);
	
	// TCB position
	if (ikc && cont->ClassID() == Class_ID(TCBINTERP_POSITION_CLASS_ID, 0))
	{
		int numKeys;
		if (numKeys = ikc->GetNumKeys())
		{
//			fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_CONTROL_POS_TCB); 
			for (i=0; i<numKeys; i++)
			{
				ITCBPoint3Key key;
				ikc->GetKey(i, &key);
//				fprintf(pStream, "%s\t\t\t%s %d\t%s", indent.data(), ID_TCB_POS_KEY, key.time, Format(key.val));
//				fprintf(pStream, "\t%s\t%s\t%s\t%s\t%s\n", Format(key.tens), Format(key.cont), Format(key.bias), Format(key.easeIn), Format(key.easeOut));
			}
//			fprintf(pStream,"%s\t\t}\n", indent.data());
		}
	}
	// Bezier position
	else if (ikc && cont->ClassID() == Class_ID(HYBRIDINTERP_POSITION_CLASS_ID, 0))
	{
		int numKeys;
		if(numKeys = ikc->GetNumKeys())
		{
//			fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_CONTROL_POS_BEZIER); 
			for (i=0; i<numKeys; i++)
			{
				IBezPoint3Key key;
				ikc->GetKey(i, &key);
//				fprintf(pStream, "%s\t\t\t%s %d\t%s", indent.data(), ID_BEZIER_POS_KEY, key.time, Format(key.val));
//				fprintf(pStream, "\t%s\t%s\t%d\n", Format(key.intan), Format(key.outtan), key.flags);
			}
//			fprintf(pStream,"%s\t\t}\n", indent.data());
		}
	}
	// Linear position
	else if (ikc && cont->ClassID() == Class_ID(LININTERP_POSITION_CLASS_ID, 0))
	{
		int numKeys;
		if(numKeys = ikc->GetNumKeys())
		{
//			fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_CONTROL_POS_LINEAR); 
			for (i=0; i<numKeys; i++)
			{
				ILinPoint3Key key;
				ikc->GetKey(i, &key);
//				fprintf(pStream, "%s\t\t\t%s %d\t%s\n", indent.data(), ID_POS_KEY, key.time, Format(key.val));
			}
//			fprintf(pStream,"%s\t\t}\n", indent.data());
		}
	}
}


void AsciiExp::DumpRotKeys(Control* cont)
{
	if (!cont)	return;
	
	int i;
	IKeyControl *ikc = GetKeyControlInterface(cont);
	
	if (ikc && cont->ClassID() == Class_ID(TCBINTERP_ROTATION_CLASS_ID, 0))
	{
		int numKeys;
		if (numKeys = ikc->GetNumKeys())
		{
//			fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_CONTROL_ROT_TCB); 
			for (i=0; i<numKeys; i++)
			{
				ITCBRotKey key;
				ikc->GetKey(i, &key);
//				fprintf(pStream, "%s\t\t\t%s %d\t%s", indent.data(), ID_TCB_ROT_KEY, key.time, Format(key.val));
//				fprintf(pStream, "\t%s\t%s\t%s\t%s\t%s\n", Format(key.tens), Format(key.cont), Format(key.bias), Format(key.easeIn), Format(key.easeOut));
			}
//			fprintf(pStream,"%s\t\t}\n", indent.data());
		}
	}
	else if (ikc && cont->ClassID() == Class_ID(HYBRIDINTERP_ROTATION_CLASS_ID, 0))
	{
		int numKeys;
		if (numKeys = ikc->GetNumKeys())
		{
//			fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_CONTROL_ROT_BEZIER); 
			for (i=0; i<numKeys; i++)
			{
				IBezQuatKey key;
				ikc->GetKey(i, &key);
				 // Quaternions are converted to AngAxis when written to file
				// There is no intan/outtan for Quat Rotations
//				fprintf(pStream, "%s\t\t\t%s %d\t%s\n", indent.data(), ID_ROT_KEY, key.time, Format(key.val));
			}
//			fprintf(pStream,"%s\t\t}\n", indent.data());
		}
	}
	else if (ikc && cont->ClassID() == Class_ID(LININTERP_ROTATION_CLASS_ID, 0))
	{
		int numKeys;
		if (numKeys = ikc->GetNumKeys())
		{
//			fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_CONTROL_ROT_LINEAR); 
			for (i=0; i<numKeys; i++)
			{
				ILinRotKey key;
				ikc->GetKey(i, &key);
				 // Quaternions are converted to AngAxis when written to file
//				fprintf(pStream, "%s\t\t\t%s %d\t%s\n", indent.data(), ID_ROT_KEY, key.time, Format(key.val));
			}
//			fprintf(pStream,"%s\t\t}\n", indent.data());
		}
	}
}

void AsciiExp::DumpScaleKeys(Control* cont) 
{
	if (!cont)	return;
	
	int i;
	IKeyControl *ikc = GetKeyControlInterface(cont);
	
	if (ikc && cont->ClassID() == Class_ID(TCBINTERP_SCALE_CLASS_ID, 0))
	{
		int numKeys;
		if (numKeys = ikc->GetNumKeys())
		{
//			fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_CONTROL_SCALE_TCB); 
			for (i=0; i<numKeys; i++)
			{
				ITCBScaleKey key;
				ikc->GetKey(i, &key);
//				fprintf(pStream, "%s\t\t\t%s %d\t%s", indent.data(), ID_TCB_SCALE_KEY, key.time, Format(key.val));
//				fprintf(pStream, "\t%s\t%s\t%s\t%s\t%s\n", Format(key.tens), Format(key.cont), Format(key.bias), Format(key.easeIn), Format(key.easeOut));
			}
//			fprintf(pStream,"%s\t\t}\n", indent.data());
		}
	}
	else if (ikc && cont->ClassID() == Class_ID(HYBRIDINTERP_SCALE_CLASS_ID, 0))
	{
		int numKeys;
		if (numKeys = ikc->GetNumKeys())
		{
//			fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_CONTROL_SCALE_BEZIER); 
			for (i=0; i<numKeys; i++)
			{
				IBezScaleKey key;
				ikc->GetKey(i, &key);
//				fprintf(pStream, "%s\t\t\t%s %d\t%s", indent.data(), ID_BEZIER_SCALE_KEY, key.time, Format(key.val));
//				fprintf(pStream, "\t%s\t%s\t%d\n", Format(key.intan), Format(key.outtan), key.flags);
			}
//			fprintf(pStream,"%s\t\t}\n", indent.data());
		}
	}
	else if (ikc && cont->ClassID() == Class_ID(LININTERP_SCALE_CLASS_ID, 0))
	{
		int numKeys;
		if (numKeys = ikc->GetNumKeys())
		{
//			fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_CONTROL_SCALE_LINEAR); 
			for (i=0; i<numKeys; i++)
			{
				ILinScaleKey key;
				ikc->GetKey(i, &key);
//				fprintf(pStream, "%s\t\t\t%s %d\t%s\n", indent.data(), ID_SCALE_KEY, key.time, Format(key.val));
			}
//			fprintf(pStream,"%s\t\t}\n", indent.data());
		}
	}
}


// Not truly the correct way to compare floats of arbitary magnitude...
BOOL EqualPoint3(Point3 p1, Point3 p2)
{
	if (fabs(p1.x - p2.x) > ALMOST_ZERO)	return FALSE;
	if (fabs(p1.y - p2.y) > ALMOST_ZERO)	return FALSE;
	if (fabs(p1.z - p2.z) > ALMOST_ZERO)	return FALSE;
	return TRUE;
}


// Determine if a TM controller is known by the system.
AsciiExp::IsKnownController(Control* cont)
{
	ulong partA, partB;

	if (!cont)	return FALSE;

	partA = cont->ClassID().PartA();
	partB = cont->ClassID().PartB();

	if (partB != 0x00)	return FALSE;

	switch (partA)
	{
		case TCBINTERP_POSITION_CLASS_ID:
		case TCBINTERP_ROTATION_CLASS_ID:
		case TCBINTERP_SCALE_CLASS_ID:
		case HYBRIDINTERP_POSITION_CLASS_ID:
		case HYBRIDINTERP_ROTATION_CLASS_ID:
		case HYBRIDINTERP_SCALE_CLASS_ID:
		case LININTERP_POSITION_CLASS_ID:
		case LININTERP_ROTATION_CLASS_ID:
		case LININTERP_SCALE_CLASS_ID:
			return TRUE;
	}

	return FALSE;
}