//
// 3D Studio .ASE loader
//
// The loader is not complete. The person who designed the ASE file was either drunk,
// or dead tired, because the node TM and vertex transform stuff is plain stupid!
//

#pragma warning( disable : 4786 )		// long names by STL

#include "ASELoaderC.h"
#include <stdio.h>
#include <stdlib.h>

#include "ScenegraphItemI.h"
#include "MeshC.h"
#include "CameraC.h"
#include "LightC.h"
#include "ContVector3C.h"
#include "ContQuatC.h"
#include "ContFloatC.h"
#include "DecomposeAffineC.h"

using namespace PajaTypes;
using namespace std;
using namespace Composition;
using namespace Import;
using namespace PluginClass;
using namespace FileIO;


ASELoaderC::ASELoaderC()
{
}


ASELoaderC::~ASELoaderC()
{
}


void
ASELoaderC::remove_nl( char* buf )
{
	uint32	len = strlen( buf );
	if( buf[len - 1] == '\n' )
		buf[len - 1] = '\0';
}

char*
ASELoaderC::extract_string( char* szBuf )
{
	if( !szBuf || !*szBuf )
		return 0;

	static char		szWord[512];

	char*	szSrc = szBuf;
	char*	szDst = szWord;

	// find first delimit
	while( *szSrc && *szSrc != '\"' )
		szSrc++;

	// step over the first delimiter
	szSrc++;

	// find second delimit and copy the between to szWord
	while( *szSrc && *szSrc != '\"' )
		*szDst++ = *szSrc++;

	// terminate the string
	*szDst = '\0';

	return szWord;
}


static
int
fGets( char* pRow, int n, FILE* pStream )
{
	int		c;
	int		i = 0;
	
	do {
		c = fgetc( pStream );
		pRow[i] = c;
		i++;
		if( c == 0xd )
			break;
		if( i >= n )
			break;
	} while( !feof( pStream ) );
	pRow[i] = '\0';	// null terminate string
	
	return i;
}


void
ASELoaderC::read_row()
{
	if( !m_pStream )
		return;
	fGets( m_szRow, 1000, m_pStream );
	remove_nl( m_szRow );
}

bool
ASELoaderC::eof()
{
	return feof( m_pStream ) != 0;
}

bool
ASELoaderC::is_token( const char* szToken )
{
	sscanf( m_szRow, "%s", m_szWord );
	return (strcmp( m_szWord, szToken ) == 0 );
}

char*
ASELoaderC::get_row()
{
	return m_szRow;
}

bool
ASELoaderC::is_block()
{
	for( uint32 i = 0; i < strlen( m_szRow ); i++ )
		if( m_szRow[i] == '{' )
			return true;
	return false;
}


uint32
ASELoaderC::parse_dummy()
{
	do {
		read_row();
		if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );
  
	return 0;
}

void
ASELoaderC::parse_pos_track( ContVector3C* pCont )
{
	int32		i32Time;
	float32		f32X, f32Y, f32Z;
	float32		f32Tens, f32Cont, f32Bias, f32EaseIn, f32EaseOut;

	do {
		read_row();

		if( is_token( "*CONTROL_TCB_POS_KEY" ) ) {

			KeyVector3C*	pKey = pCont->add_key();

			sscanf( get_row(), "%*s %d %f %f %f %f %f %f %f %f",
				&i32Time, &f32X, &f32Y, &f32Z,
				&f32Tens, &f32Cont, &f32Bias,
				&f32EaseIn, &f32EaseOut );

			pKey->set_time( i32Time );
			pKey->set_value( Vector3C( f32X, f32Z, -f32Y ) );
			pKey->set_tens( f32Tens );
			pKey->set_cont( f32Cont );
			pKey->set_bias( f32Bias);
			pKey->set_ease_in( f32EaseIn );
			pKey->set_ease_out( f32EaseOut );
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );

	pCont->sort_keys();
	pCont->prepare();
}


void
ASELoaderC::parse_vector3_track( ContVector3C* pCont )
{
	int32		i32Time;
	float32		f32X, f32Y, f32Z;
	float32		f32Tens, f32Cont, f32Bias, f32EaseIn, f32EaseOut;
	
	do {
		read_row();

		if( is_token( "*CONTROL_TCB_POINT3_KEY" ) ) {

			KeyVector3C*	pKey = pCont->add_key();

			sscanf( get_row(), "%*s %d %f %f %f %f %f %f %f %f",
				&i32Time, &f32X, &f32Y, &f32Z,
				&f32Tens, &f32Cont, &f32Bias,
				&f32EaseIn, &f32EaseOut );

			pKey->set_time( i32Time );
			pKey->set_value( Vector3C( f32X, f32Y, f32Z ) );
			pKey->set_tens( f32Tens );
			pKey->set_cont( f32Cont );
			pKey->set_bias( f32Bias);
			pKey->set_ease_in( f32EaseIn );
			pKey->set_ease_out( f32EaseOut );
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );

	pCont->sort_keys();
	pCont->prepare();
}


void
ASELoaderC::parse_float_track( ContFloatC* pCont )
{
	int32		i32Time;
	float32		f32Val;
	float32		f32Tens, f32Cont, f32Bias, f32EaseIn, f32EaseOut;
	
	do {
		read_row();

		if( is_token( "*CONTROL_TCB_FLOAT_KEY" ) ) {

			KeyFloatC*	pKey = pCont->add_key();

			sscanf( get_row(), "%*s %d %f %f %f %f %f %f",
				&i32Time, &f32Val,
				&f32Tens, &f32Cont, &f32Bias,
				&f32EaseIn, &f32EaseOut );

			pKey->set_time( i32Time );
			pKey->set_value( f32Val );
			pKey->set_tens( f32Tens );
			pKey->set_cont( f32Cont );
			pKey->set_bias( f32Bias);
			pKey->set_ease_in( f32EaseIn );
			pKey->set_ease_out( f32EaseOut );

		}
		else if( is_token( "*CONTROL_FLOAT_KEY" ) ) {

			KeyFloatC*	pKey = pCont->add_key();

			sscanf( get_row(), "%*s %d %f",
				&i32Time, &f32Val );

			pKey->set_time( i32Time );
			pKey->set_value( f32Val );
			pKey->set_tens( 0 );
			pKey->set_cont( 0 );
			pKey->set_bias( 0);
			pKey->set_ease_in( 0 );
			pKey->set_ease_out( 0 );

			pCont->set_type( KEY_LINEAR );
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );

	pCont->sort_keys();
	pCont->prepare();
}

void
ASELoaderC::parse_rot_track( ContQuatC* pCont )
{
	int32		i32Time;
	float32		f32X, f32Y, f32Z, f32W;
	float32		f32Tens, f32Cont, f32Bias, f32EaseIn, f32EaseOut;
	
	do {
		read_row();

		if( is_token( "*CONTROL_TCB_ROT_KEY" ) ) {

			KeyQuatC*	pKey = pCont->add_key();

			sscanf( get_row(), "%*s %d %f %f %f %f %f %f %f %f %f",
				&i32Time, &f32X, &f32Y, &f32Z, &f32W,
				&f32Tens, &f32Cont, &f32Bias,
				&f32EaseIn, &f32EaseOut );

			pKey->set_time( i32Time );
			pKey->set_axis( Vector3C( f32X, f32Z, -f32Y ) );
			pKey->set_angle( f32W );
			pKey->set_tens( f32Tens );
			pKey->set_cont( f32Cont );
			pKey->set_bias( f32Bias);
			pKey->set_ease_in( f32EaseIn );
			pKey->set_ease_out( f32EaseOut );
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );

	pCont->sort_keys();
	pCont->prepare();
}

void
ASELoaderC::parse_scale_track( ContVector3C* pContScale, ContQuatC* pContRot )
{
	int32		i32Time;
	float32		f32SX, f32SY, f32SZ, f32X, f32Y, f32Z, f32W;
	float32		f32Tens, f32Cont, f32Bias, f32EaseIn, f32EaseOut;
	
	do {
		read_row();

		if( is_token( "*CONTROL_TCB_SCALE_KEY" ) ) {

			KeyQuatC*		pRotKey = pContRot->add_key();
			KeyVector3C*	pScaleKey = pContScale->add_key();

			sscanf( get_row(), "%*s %d %f %f %f %f %f %f %f %f %f %f %f %f",
				&i32Time, &f32SX, &f32SY, &f32SZ,
				&f32X, &f32Y, &f32Z, &f32W,
				&f32Tens, &f32Cont, &f32Bias,
				&f32EaseIn, &f32EaseOut );

			pScaleKey->set_time( i32Time );
			pScaleKey->set_value( Vector3C( f32SX, f32SZ, f32SY ) );
			pScaleKey->set_tens( f32Tens );
			pScaleKey->set_cont( f32Cont );
			pScaleKey->set_bias( f32Bias);
			pScaleKey->set_ease_in( f32EaseIn );
			pScaleKey->set_ease_out( f32EaseOut );

			pRotKey->set_time( i32Time );
			pRotKey->set_axis( Vector3C( f32X, f32Z, -f32Y ) );
			pRotKey->set_angle( f32W );
			pRotKey->set_tens( f32Tens );
			pRotKey->set_cont( f32Cont );
			pRotKey->set_bias( f32Bias);
			pRotKey->set_ease_in( f32EaseIn );
			pRotKey->set_ease_out( f32EaseOut );
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );

	pContScale->sort_keys();
	pContScale->prepare();
	pContRot->sort_keys();
	pContRot->prepare();
}

bool
ASELoaderC::parse_look_at_node_tm( Vector3C& rPos )
{
	float32		f32X, f32Y, f32Z;
	bool		bIsTarget = false;

	do {
		read_row();

		if( is_token( "*TM_POS" ) ) {
			sscanf( get_row(), "%*s %f %f %f", &f32X, &f32Y, &f32Z );
			rPos[0] = f32X;
			rPos[1] = f32Z;
			rPos[2] = -f32Y;
		}
		else if( is_token( "*NODE_NAME" ) ) {
			if( strstr( get_row(), ".Target" ) )
				bIsTarget = true;
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );

	return bIsTarget;
}


//
// light
//


void
ASELoaderC::parse_light_settings( LightC* pLight )
{
	float32	f32Val, f32R, f32G, f32B;

	do {
		read_row();
		if( is_token( "*LIGHT_COLOR" ) ) {
			sscanf( get_row(), "%*s %f %f %f", &f32R, &f32G, &f32B );
			pLight->set_color( ColorC( f32R, f32G, f32B ) );
		}
		else if( is_token( "*LIGHT_INTENS" ) ) {
			sscanf( get_row(), "%*s %f", &f32Val );
			pLight->set_multiplier( f32Val );
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );
}

void
ASELoaderC::parse_light_tm_anim( LightC* pLight )
{
	bool		bIsTarget = false;

	do {
		read_row();

		if( is_token( "*NODE_NAME" ) ) {
			if( strstr( get_row(), ".Target" ) )
				bIsTarget = true;
		}
		else if( is_token( "*CONTROL_POS_TCB" ) ) {
			if( !bIsTarget ) {
				ContVector3C*	pCont = new ContVector3C;
				parse_pos_track( pCont );
				pLight->set_position_controller( pCont );
			}
			else
				parse_dummy();
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );
}


LightC*
ASELoaderC::parse_light()
{
	LightC*	pLight = new LightC;
	if( !pLight )
		return 0;

	Vector3C	rPos;

	do {
		read_row();

		if( is_token( "*NODE_NAME" ) ) {
			pLight->set_name( extract_string( get_row() ) );
		}
		else if( is_token( "*NODE_TM" ) ) {
			if( !parse_look_at_node_tm( rPos ) )
				pLight->set_position( rPos );
		}
		else if( is_token( "*LIGHT_SETTINGS" ) ) {
			parse_light_settings( pLight );
		}
		else if( is_token( "*TM_ANIMATION" ) ) {
			parse_light_tm_anim( pLight );
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );

	return pLight;
}


//
// camera
//

void
ASELoaderC::parse_camera_settings( CameraC* pCam )
{
	float32	f32Val;

	do {
		read_row();
		if( is_token( "*CAMERA_NEAR" ) ) {
			sscanf( get_row(), "%*s %f", &f32Val );
			pCam->set_near_plane( f32Val );
		}
		else if( is_token( "*CAMERA_FAR" ) ) {
			sscanf( get_row(), "%*s %f", &f32Val );
			pCam->set_far_plane( f32Val );
		}
		else if( is_token( "*CAMERA_FOV" ) ) {
			sscanf( get_row(), "%*s %f", &f32Val );
			pCam->set_fov( f32Val );
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );
}

void
ASELoaderC::parse_camera_tm_anim( CameraC* pCam )
{
	bool		bIsTarget = false;

	do {
		read_row();

		if( is_token( "*NODE_NAME" ) ) {
			if( strstr( get_row(), ".Target" ) )
				bIsTarget = true;
		}
		else if( is_token( "*CONTROL_POS_TCB" ) ) {
			if( bIsTarget ) {
				ContVector3C*	pCont = new ContVector3C;
				parse_pos_track( pCont );
				pCam->set_target_position_controller( pCont );
			}
			else {
				ContVector3C*	pCont = new ContVector3C;
				parse_pos_track( pCont );
				pCam->set_position_controller( pCont );
			}
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );
}


CameraC*
ASELoaderC::parse_camera()
{
	CameraC*	pCam = new CameraC;
	if( !pCam )
		return 0;

	Vector3C	rPos;

	do {
		read_row();

		if( is_token( "*NODE_NAME" ) ) {
			pCam->set_name( extract_string( get_row() ) );
		}
		else if( is_token( "*NODE_TM" ) ) {
			if( parse_look_at_node_tm( rPos ) )
				pCam->set_target_position( rPos );
			else
				pCam->set_position( rPos );
		}
		else if( is_token( "*CAMERA_SETTINGS" ) ) {
			parse_camera_settings( pCam );
		}
		else if( is_token( "*TM_ANIMATION" ) ) {
			parse_camera_tm_anim( pCam );
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );

	return pCam;
}


void
ASELoaderC::parse_mesh( MeshC* pMesh )
{
	uint32		ui32Vertices = 0, ui32Faces = 0;
	uint32		ui32TexVertices = 0, ui32TexFaces = 0;
	float32		f32X, f32Y, f32Z;
	uint32		ui32A, ui32B, ui32C;
	Matrix3C	rInvTM;

	rInvTM = pMesh->get_tm().inverse();

	do {
		read_row();

		if( is_token( "*MESH_NUMVERTEX" ) ) {
			sscanf( get_row(), "%*s %d", &ui32Vertices );
		}
		else if( is_token( "*MESH_NUMFACES" ) ) {
			sscanf( get_row(), "%*s %d", &ui32Faces );
		}
		else if( is_token( "*MESH_VERTEX_LIST" ) ) {
			for( uint32 i = 0; i < ui32Vertices; i++ ) {
				read_row();
				if( eof() )
					return;
				sscanf( get_row(), "%*s %*d %f %f %f", &f32X, &f32Y, &f32Z );
				Vector3C	rVert( f32X, f32Z, -f32Y );
				pMesh->add_vert( rInvTM * rVert );

			}
			read_row();	// "}"  (end of block)
			if( eof() )
				return;
		}
		else if( is_token( "*MESH_FACE_LIST" ) ) {
			for( uint32 i = 0; i < ui32Faces; i++ ) {
				read_row();
				if( eof() )
					return;

				char*  sPtr;
       
				ui32A = ui32B = ui32C = 0;
        
				sPtr = strstr( get_row(), "A:" );
				if( sPtr ) sscanf( sPtr, "%*s %d", &ui32A );

				sPtr = strstr( get_row(), "B:" );
				if( sPtr ) sscanf( sPtr, "%*s %d", &ui32B );

				sPtr = strstr( get_row(), "C:" );
				if( sPtr ) sscanf( sPtr, "%*s %d", &ui32C );

				pMesh->add_index( ui32A );
				pMesh->add_index( ui32B );
				pMesh->add_index( ui32C );
			}
			read_row();	// "}"  (end of block)
			if( eof() )
				return;
		}
		else if( is_token( "*MESH_NUMTVERTEX" ) ) {
			sscanf( get_row(), "%*s %d", &ui32TexVertices );
		}
		else if( is_token( "*MESH_TVERTLIST" ) ) {
			for( uint32 i = 0; i < ui32TexVertices; i++ ) {
				read_row();
				if( eof() )
					return;
				sscanf( get_row(), "%*s %*d %f %f", &f32X, &f32Y);
				pMesh->add_texcoord( Vector2C( f32X, 1.0f - f32Y ) );
			}
			read_row();	// "}"  (end of block)
			if( eof() )
				return;
		}
		else if( is_token( "*MESH_NUMTVFACES" ) ) {
			sscanf( get_row(), "%*s %d", &ui32TexFaces );
		}
		else if( is_token( "*MESH_TFACELIST" ) ) {
			for( uint32 i = 0; i < ui32TexFaces; i++ ) {
				read_row();
				if( eof() )
					return;
				sscanf( get_row(), "%*s %*d %d %d %d", &ui32A, &ui32B, &ui32C );
				pMesh->add_texcoord_index( ui32A );
				pMesh->add_texcoord_index( ui32B );
				pMesh->add_texcoord_index( ui32C );

			}
			read_row();	// "}"  (end of block)
			if( eof() )
				return;
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );


	// calculate normals
	std::vector<Vector3C>	rNormals;
	uint32					i;

	rNormals.resize( pMesh->get_vert_count() );

	for( i = 0; i < pMesh->get_index_count(); i += 3 ) {
		uint32	ui32A = pMesh->get_index( i );
		uint32	ui32B = pMesh->get_index( i + 1 );
		uint32	ui32C = pMesh->get_index( i + 2 );

		Vector3C	rA, rB, rNorm;

		rA = pMesh->get_vert( ui32B ) - pMesh->get_vert( ui32A );
		rB = pMesh->get_vert( ui32C ) - pMesh->get_vert( ui32A );
		rNorm = rA.cross( rB );
		rNorm = rNorm.normalize();

		rNormals[ui32A] += rNorm;
		rNormals[ui32B] += rNorm;
		rNormals[ui32C] += rNorm;
	}

	for( i = 0; i < pMesh->get_vert_count(); i++ ) {
		Vector3C	rNorm = rNormals[i];
		rNorm = rNorm.normalize();
		pMesh->add_norm( rNorm );
	}
}

void
ASELoaderC::parse_mesh_node_tm( MeshC* pMesh )
{
	float32		f32X, f32Y, f32Z, f32A;

	Vector3C	rRotAxis;
	float32		f32RotAngle;
	Vector3C	rPos;
	Vector3C	rScale;
	Vector3C	rScaleAxis;
	float32		f32ScaleAngle;


	do {
		read_row();

		if( is_token( "*TM_POS" ) ) {
			sscanf( get_row(), "%*s %f %f %f", &f32X, &f32Y, &f32Z );
			rPos[0] = f32X;
			rPos[1] = f32Z;
			rPos[2] = -f32Y;
		}
		else if( is_token( "*TM_ROTAXIS" ) ) {
			sscanf( get_row(), "%*s %f %f %f", &f32X, &f32Y, &f32Z );
			rRotAxis[0] = f32X;
			rRotAxis[1] = f32Z;
			rRotAxis[2] = -f32Y;
		}
		else if( is_token( "*TM_ROTANGLE" ) ) {
			sscanf( get_row(), "%*s %f", &f32A );
			f32RotAngle = f32A;
		}
		else if( is_token( "*TM_SCALE" ) ) {
			sscanf( get_row(), "%*s %f %f %f", &f32X, &f32Y, &f32Z );
			rScale[0] = f32X;
			rScale[1] = f32Z;
			rScale[2] = f32Y;
		}
		else if( is_token( "*TM_SCALEAXIS" ) ) {
			sscanf( get_row(), "%*s %f %f %f", &f32X, &f32Y, &f32Z );
			rScaleAxis[0] = f32X;
			rScaleAxis[1] = f32Z;
			rScaleAxis[2] = -f32Y;
		}
		else if( is_token( "*TM_SCALEAXISANG" ) ) {
			sscanf( get_row(), "%*s %f", &f32A );
			f32ScaleAngle = f32A;
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;

	} while( !eof() );


	QuatC		rScaleRot;
	QuatC		rInvScaleRot;
	QuatC		rRot;

	rRot.from_axis_angle( rRotAxis, f32RotAngle );
	rScaleRot.from_axis_angle( rScaleAxis, f32ScaleAngle );

	pMesh->set_position( rPos );
	pMesh->set_rotation( rRot );
	pMesh->set_scale( rScale );
	pMesh->set_scale_rotation( rScaleRot );

	Matrix3C	rScaleMat;
	Matrix3C	rScaleRotMat;
	Matrix3C	rInvScaleRotMat;
	Matrix3C	rRotMat;
	Matrix3C	rPosMat;
	Matrix3C	rTM;

	// Strangle kludge. The animated rotations doesnt work correctly
	// if the angle is not negated when the quat is set to OpenGL.
	rRot[3] = -rRot[3];
	rScaleRot[3] = -rScaleRot[3];

	// scale
	rScaleMat.set_scale( rScale );
	// scale rot
	rScaleRotMat.set_rot( rScaleRot );
	// inv scale rot
	rInvScaleRotMat = rScaleRotMat.inverse();
	// rot
	rRotMat.set_rot( rRot );
	// pos
	rPosMat.set_trans( rPos );

	// Compose matrix
	rTM = rInvScaleRotMat * rScaleMat * rScaleRotMat * rRotMat * rPosMat;

	pMesh->set_tm( rTM );
}


void
ASELoaderC::parse_object_tm_anim( MeshC* pObj )
{
	do {
		read_row();

		if( is_token( "*CONTROL_POS_TCB" ) ) {
			ContVector3C*	pCont = new ContVector3C;
			parse_pos_track( pCont );
			pObj->set_position_controller( pCont );
		}
		else if( is_token( "*CONTROL_ROT_TCB" ) ) {
			ContQuatC*	pCont = new ContQuatC;
			parse_rot_track( pCont );
			pObj->set_rotation_controller( pCont );
		}
		else if( is_token( "*CONTROL_SCALE_TCB" ) ) {
			ContVector3C*	pScaleCont = new ContVector3C;
			ContQuatC*		pRotCont = new ContQuatC;
			parse_scale_track( pScaleCont, pRotCont );
			pObj->set_scale_controller( pScaleCont );
			pObj->set_scale_rotation_controller( pRotCont );
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );
}


MeshC*
ASELoaderC::parse_geomobject()
{
	uint32		ui32Mtl;
	MeshC*		pMesh = new MeshC;
	if( !pMesh )
		return 0;

	bool		bHasMtl = false;
	Matrix3C	rTM;

	do {
		read_row();

		if( is_token( "*NODE_NAME" ) ) {
			pMesh->set_name( extract_string( get_row() ) );
		}
		else if( is_token( "*NODE_PARENT" ) ) {
			pMesh->set_parent_name( extract_string( get_row() ) );
		}
		else if( is_token( "*NODE_TM" ) ) {
			parse_mesh_node_tm( pMesh );
		}
		else if( is_token( "*MESH" ) ) {
			parse_mesh( pMesh );
		}
		else if( is_token( "*MATERIAL_REF" ) ) {
			sscanf( get_row(), "%*s %d", &ui32Mtl );
			if( ui32Mtl < m_rMaterials.size() ) {
				pMesh->set_ambient( m_rMaterials[ui32Mtl].m_rAmbient );
				pMesh->set_diffuse( m_rMaterials[ui32Mtl].m_rDiffuse );
				pMesh->set_specular( m_rMaterials[ui32Mtl].m_rSpecular );
				pMesh->set_shininess( m_rMaterials[ui32Mtl].m_f32Shininess );
				pMesh->set_texture( get_texture( m_rMaterials[ui32Mtl].m_sTextureName ) );
				bHasMtl = true;
			}
		}
		else if( is_token( "*WIREFRAME_COLOR" ) ) {
			if( !bHasMtl ) {
				float32	f32R, f32G, f32B;
				sscanf( get_row(), "%*s %f %f %f", &f32R, &f32G, &f32B );
				pMesh->set_diffuse( ColorC( f32R, f32G, f32B ) );
			}
      	}
		else if( is_token( "*TM_ANIMATION" ) ) {
			parse_object_tm_anim( pMesh );
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;

	} while( !eof() );

	return pMesh;
}



MeshC*
ASELoaderC::parse_helperobject( string& sGroupName, bool& bIsGroup )
{
	Matrix3C	rTM;
	MeshC*		pMesh = new MeshC;

	if( !pMesh )
		return 0;

	do {
		read_row();

		if( is_token( "*NODE_NAME" ) ) {
			pMesh->set_name( extract_string( get_row() ) );
			if( sGroupName.compare( pMesh->get_name() ) == 0 )
				bIsGroup = true;
		}
		else if( is_token( "*NODE_PARENT" ) ) {
			pMesh->set_parent_name( extract_string( get_row() ) );
		}
		else if( is_token( "*NODE_TM" ) ) {
			parse_mesh_node_tm( pMesh );
		}
		else if( is_token( "*TM_ANIMATION" ) ) {
			parse_object_tm_anim( pMesh );
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );

	return pMesh;
}


void
ASELoaderC::parse_map( string& sName )
{
	do {
		read_row();
    
		if( is_token( "*BITMAP" ) ) {
			sName = extract_string( get_row() );
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );
}


void
ASELoaderC::parse_material( ASEMaterialS* pMtl )
{
	float32	f32R, f32G, f32B, f32Val;

	read_row();
  
	do {
		read_row();

		if( is_token( "*MAP_DIFFUSE" ) ) {
			parse_map( pMtl->m_sTextureName );
		}
		else if( is_token( "*MATERIAL_SHINE" ) ) {
			sscanf( get_row(), "%*s %f", &f32Val );
			pMtl->m_f32Shininess = f32Val;
		}
		else if( is_token( "*MATERIAL_SHINESTRENGTH" ) ) {
			sscanf( get_row(), "%*s %f", &f32Val );
			if( f32Val > 1.0 )
				f32Val = 1.0;
			if( f32Val < 0 )
				f32Val = 0;
			pMtl->m_rSpecular *= f32Val;
		}
		else if( is_token( "*MATERIAL_AMBIENT" ) ) {
			sscanf( get_row(), "%*s %f %f %f", &f32R, &f32G, &f32B );
			pMtl->m_rAmbient = ColorC( f32R, f32G, f32B );
		}
		else if( is_token( "*MATERIAL_DIFFUSE" ) ) {
			sscanf( get_row(), "%*s %f %f %f", &f32R, &f32G, &f32B );
			pMtl->m_rDiffuse = ColorC( f32R, f32G, f32B );
		}
		else if( is_token( "*MATERIAL_SPECULAR" ) ) {
			sscanf( get_row(), "%*s %f %f %f", &f32R, &f32G, &f32B );
			pMtl->m_rSpecular = ColorC( f32R, f32G, f32B );
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;

	} while( !eof() );

}

void
ASELoaderC::parse_materials()
{
	uint32	ui32NumMaterials;

	do {
		read_row();

		if( is_token( "*MATERIAL_COUNT" ) ) {
			sscanf( get_row(), "%*s %d", &ui32NumMaterials );

			for( uint32 i = 0; i < ui32NumMaterials; i++ ) {
				ASEMaterialS rMtl;
				parse_material( &rMtl );
				m_rMaterials.push_back( rMtl );
			}
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );

}



void
ASELoaderC::get_time_parameters( int32& i32FPS, int32& i32TicksPerFrame, int32& i32FirstFrame, int32& i32LastFrame )
{
	i32FPS = m_i32FPS;
	i32TicksPerFrame = m_i32TicksPerFrame;
	i32FirstFrame = m_i32FirstFrame;
	i32LastFrame = m_i32LastFrame;
}


void
ASELoaderC::parse_scene()
{
	do {
		read_row();
		if( is_token( "*SCENE_FIRSTFRAME" ) ) {
			sscanf( get_row(), "%*s %d", &m_i32FirstFrame );
		}
		else if( is_token( "*SCENE_LASTFRAME" ) ) {
			sscanf( get_row(), "%*s %d", &m_i32LastFrame );
		}
		else if( is_token( "*SCENE_FRAMESPEED" ) ) {
			sscanf( get_row(), "%*s %d", &m_i32FPS );
		}
		else if( is_token( "*SCENE_TICKSPERFRAME" ) ) {
			sscanf( get_row(), "%*s %d", &m_i32TicksPerFrame );
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );
}


MeshC*
ASELoaderC::find_object( const string& sName, MeshC* pParent )
{
	if( !pParent ) {
		for( uint32 i = 0; i < m_rScenegraph.size(); i++ ) {
			MeshC*	pRetItem = find_object( sName, m_rScenegraph[i] );
			if( pRetItem )
				return pRetItem;
		}
	}
	else {
		if( sName.compare( pParent->get_name() ) == 0 )
			return pParent;
		for( uint32 i = 0; i < pParent->get_child_count(); i++ ) {
			MeshC*	pItem = (MeshC*)pParent->get_child( i );
			if( sName.compare( pItem->get_name() ) == 0 )
				return pItem;
			MeshC*	pRetItem = find_object( sName, pItem );
			if( pRetItem )
				return pRetItem;
		}
	}

	return 0;
}


void
ASELoaderC::fix_tm( MeshC* pMesh, MeshC* pParent )
{
	Matrix3C	rTM, rParentTM;

	rTM = pMesh->get_tm() * pParent->get_tm().inverse();

	DecomposeAffineC	rDecomp( rTM );

	QuatC	rRot = rDecomp.get_rotation();
	QuatC	rScaleRot = rDecomp.get_scale_rotation();

	// Strangle kludge. The animated rotations doesnt work correctly
	// if the angle is not negated when the quat is set to OpenGL.
	rRot[3] = -rRot[3];
	rScaleRot[3] = -rScaleRot[3];

	pMesh->set_position( rDecomp.get_translation() );
	pMesh->set_rotation( rRot );
	pMesh->set_scale( rDecomp.get_scale() );
	pMesh->set_scale_rotation( rScaleRot );
}

void
ASELoaderC::add_scenegraphitem( MeshC* pMesh, MeshC* pCurGroup )
{
	if( pMesh ) {
		MeshC*	pParent = 0;
		if( strlen( pMesh->get_parent_name() ) ) {
			if( pCurGroup && strcmp( pMesh->get_parent_name(), pCurGroup->get_name() ) == 0 )
				pParent = pCurGroup;
			else
				pParent = find_object( pMesh->get_parent_name() );
		}

		if( pParent ) {
			fix_tm( pMesh, pParent );
			pParent->add_child( pMesh );
		}
		else
			m_rScenegraph.push_back( pMesh );
	}	
}

MeshC*
ASELoaderC::parse_group( string& sGroupName, int32 i32Indent )
{

	MeshC*	pGroup = 0;

	do {
		read_row();

		if( is_token( "*3DSMAX_ASCIIEXPORT" ) ) {
			sscanf( get_row(), "%*s %d", &m_ui32Version );
		}
		else if( is_token( "*SCENE" ) ) {
			parse_scene();
		}
		else if( is_token( "*MATERIAL_LIST" ) ) {
			parse_materials();
		}
		else if( is_token( "*CAMERAOBJECT" ) ) {
			// Don't store cameras to scenegraph.
			CameraC*	pCam = parse_camera();
			if( pCam )
				m_rCameras.push_back( pCam );
		}
		else if( is_token( "*LIGHTOBJECT" ) ) {
			// Don't store lights to scenegraph.
			LightC*		pLight = parse_light();
			if( pLight )
				m_rLights.push_back( pLight );
		}
		else if( is_token( "*GEOMOBJECT" ) ) {
			MeshC*	pMesh = parse_geomobject();
			add_scenegraphitem( pMesh, pGroup );
		}
		else if( is_token( "*GROUP" ) ) {
			string	sName = extract_string( get_row() );
			MeshC*	pMesh = parse_group( sName, i32Indent + 1 );
			add_scenegraphitem( pMesh, pGroup );
		}
		else if( is_token( "*HELPEROBJECT" ) ) {
			// Kludge.
			// The group is actually a dummy (helper) object.
			// We check if the helper name is same as the group name.
			// If they are we use the helper as group.
			// Other helpers may affect the scenegraph, so we add them
			// to the scenegraph.
			bool	bIsGroup = false;
			MeshC*	pMesh = parse_helperobject( sGroupName, bIsGroup );
			if( pMesh ) {
				if( bIsGroup )
					pGroup = pMesh;
				else
					add_scenegraphitem( pMesh, pGroup );
			}
		}
		else if( is_block() ) {
			parse_dummy();
		}
		else if( is_token( "}" ) )
			break;

	} while( !eof() );

	return pGroup;
}


bool
ASELoaderC::load( const char* szName, Import::ImportInterfaceC* pImpInterface )
{
	m_pImpInterface = pImpInterface;

	if( (m_pStream = fopen( szName, "rb" )) == 0 )
		return false;

	string	dummy( "" );
	parse_group( dummy, 0 );

	fclose( m_pStream );

	return true;
}

FileHandleC*
ASELoaderC::get_texture( string& sOrigName )
{
	FileHandleC*	pHandle = m_pImpInterface->request_import( sOrigName.c_str(), SUPERCLASS_IMAGE, NULL_CLASSID );
	if( !pHandle )
		OutputDebugString( "NO texture HANDLE!!!\n" );
	return pHandle;

}

void
ASELoaderC::get_scenegraph( std::vector<MeshC*>& rScenegraph )
{
	rScenegraph = m_rScenegraph;
}

void
ASELoaderC::get_lights( std::vector<LightC*>& rLights )
{
	rLights = m_rLights;
}

void
ASELoaderC::get_cameras( std::vector<CameraC*>& rCameras )
{
	rCameras = m_rCameras;
}

