//
// 3D Studio .ASE loader
//

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

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

#include "Mesh3DC.h"
#include "TCSetC.h"
#include "ScenegraphItemI.h"
#include "MeshRefC.h"
#include "MtlLayerC.h"
#include "ControllerC.h"


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


static
void
TRACE( const char* szFormat, ...  )
{
	char	szMsg[256];
	va_list	rList;
	va_start( rList, szFormat );
	_vsnprintf( szMsg, 255, szFormat, rList );
	va_end( rList );
	OutputDebugString( szMsg );
}



ASELoaderC::ASELoaderC()
{
	m_pFile = 0;
	m_ui32FilePointer = 0;
	m_i32Percent = 0;
}


ASELoaderC::~ASELoaderC()
{
	cleanup();
}


void
ASELoaderC::purge()
{
	cleanup();
}

void
ASELoaderC::cleanup()
{

//	delete [] m_pFile;

	if( m_objects.size() ) {
		for( uint32 i = 0; i < m_objects.size(); i++ ) {
			delete m_objects[i];
		}
		m_objects.clear();
	}

	if( m_materials.size() ) {
		for( uint32 i = 0; i < m_materials.size(); i++ ) {
			delete m_materials[i];
		}
		m_materials.clear();
	}

	if( m_rCameras.size() ) {
		for( uint32 i = 0; i < m_rCameras.size(); i++ ) {
			delete m_rCameras[i];
		}
		m_rCameras.clear();
	}

	if( m_rLights.size() ) {
		for( uint32 i = 0; i < m_rLights.size(); i++ ) {
			delete m_rLights[i];
		}
		m_rLights.clear();
	}

	if( m_rConvLights.size() ) {
		for( uint32 i = 0; i < m_rConvLights.size(); i++ ) {
			delete m_rConvLights[i];
		}
	}

	if( m_rConvCameras.size() ) {
		for( uint32 i = 0; i < m_rConvCameras.size(); i++ ) {
			delete m_rConvCameras[i];
		}
	}

	if( m_rConvMeshes.size() ) {
		for( uint32 i = 0; i < m_rConvMeshes.size(); i++ ) {
			delete m_rConvMeshes[i];
		}
	}

	if( m_rConvMeshRefs.size() ) {
		for( uint32 i = 0; i < m_rConvMeshRefs.size(); i++ ) {
			delete m_rConvMeshRefs[i];
		}
	}

}


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, char cStartDelimit, char cEndDelimit, int* iNRead )
{

	if( !szBuf || !*szBuf )
		return 0;

	static char		szWord[512];

	char*	szSrc = szBuf;
	char*	szDst = szWord;
	int		iReadCount = 0;

	if( cStartDelimit == cEndDelimit ) {

		// find first delimit
		while( *szSrc && *szSrc != cStartDelimit ) {
			szSrc++;
			iReadCount++;
		}

		// step over the first delimiter
		szSrc++;
		iReadCount++;

		// find second delimit and copy the between to szWord
		while( *szSrc && *szSrc != cStartDelimit ) {
			*szDst++ = *szSrc++;
			iReadCount++;
		}

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

	}
	else {

		// find first delimit
		while( *szSrc && *szSrc != cStartDelimit ) {
			szSrc++;
			iReadCount++;
		}

		// step over the first delimiter
		szSrc++;
		iReadCount++;

		// find second delimit and copy the between to szWord keeping indentation (handles nesting)
		int32 iIndent = 1;

		while( *szSrc ) {

			// new scope start found
			if( *szSrc == cStartDelimit )
				iIndent++;
			// scope end found... only exit if the indentation is at base level
			if( *szSrc == cEndDelimit ) {
				iIndent--;
				if( iIndent == 0 )
					break;
			}

			*szDst++ = *szSrc++;
			iReadCount++;
		}

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

	}

	if( iNRead )
		*iNRead = iReadCount;
	
	return szWord;
}


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


void
ASELoaderC::read_row()
{
	if( !m_fp )
		return;
	m_ui32FilePointer += fGets( m_row, 1000, m_fp );
	remove_nl( m_row );

/*	if( m_pCallback ) {
		int32	i32Percent = (int32)((double)m_ui32FilePointer / (double)m_ui32FileSize * 100.0);
		if( m_i32Percent != i32Percent ) {
			m_pCallback->update_progress( i32Percent, m_sProgressMsg );
			m_i32Percent = i32Percent;
		}
	}*/
}

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

bool
ASELoaderC::is_token( const char* token )
{
	sscanf( m_row, "%s", m_word );
	return (strcmp( m_word, token ) == 0 );
}

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

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


uint32
ASELoaderC::parse_dummy()
{
	//TRACE( "parsing dummy: \"%s\"\n", m_row );
	do {
		read_row();
		if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
//		else if( eof() )
//			throw ASEExceptionC( "ASELoader DUMMY: Preliminary EOF." );
	} while( !eof() );
  
	return 0;
}

int32
ASELoaderC::get_ort( const char* szORT )
{
	int32	i32ORT = CONT_ORT_CONSTANT;

	if( strcmp( "ORT_CYCLE", szORT ) == 0 )
		i32ORT = CONT_ORT_REPEAT;
	else if( strcmp( "ORT_LOOP", szORT ) == 0 )
		i32ORT = CONT_ORT_LOOP;

	return i32ORT;
}

void
ASELoaderC::parse_pos_track( ASEVector3ContC* pCont )
{
	float32		f32X, f32Y, f32Z;
	float32		f32Tens, f32Cont, f32Bias, f32EaseIn, f32EaseOut;
	char		szStartORT[64], szEndORT[64];

//	OutputDebugString( "vector3:\n" );
	
	do {
		read_row();

		if( is_token( "*CONTROL_TCB_POS_KEY" ) ) {

			ASEVector3KeyC	rKey;

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

			rKey.m_rVal[0] = f32X;
			rKey.m_rVal[1] = f32Z;
			rKey.m_rVal[2] = -f32Y;
			rKey.m_f32Tens = f32Tens;
			rKey.m_f32Cont = f32Cont;
			rKey.m_f32Bias = f32Bias;
			rKey.m_f32EaseIn = f32EaseIn;
			rKey.m_f32EaseOut = f32EaseOut;

//			char szMsg[256];
//			_snprintf( szMsg, 255, "      %05d  %9.4f %9.4f %9.4f\n", rKey.m_i32Time, rKey.m_f32Val[0], rKey.m_f32Val[1], rKey.m_f32Val[2] );
//			OutputDebugString( szMsg );

			pCont->m_rKeys.push_back( rKey );

			pCont->m_i32Type = ASE_TCB;
		}
		else if( is_token( "*ORT" ) ) {
			sscanf( get_row(), "%*s %s %s", szStartORT, szEndORT );
			pCont->m_i32StartORT = get_ort( szStartORT );
			pCont->m_i32EndORT = get_ort( szEndORT );
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );
}


void
ASELoaderC::parse_vector3_track( ASEVector3ContC* pCont )
{
	int32		i32Time;
	float32		f32X, f32Y, f32Z;
	float32		f32Tens, f32Cont, f32Bias, f32EaseIn, f32EaseOut;
	char		szStartORT[64], szEndORT[64];

//	OutputDebugString( "vector3:\n" );
	
	do {
		read_row();

		if( is_token( "*CONTROL_TCB_POINT3_KEY" ) ) {

			ASEVector3KeyC	rKey;

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

			rKey.m_i32Time = i32Time;
			rKey.m_rVal[0] = f32X;
			rKey.m_rVal[1] = f32Y;
			rKey.m_rVal[2] = f32Z;
			rKey.m_f32Tens = f32Tens;
			rKey.m_f32Cont = f32Cont;
			rKey.m_f32Bias = f32Bias;
			rKey.m_f32EaseIn = f32EaseIn;
			rKey.m_f32EaseOut = f32EaseOut;

//			char szMsg[256];
//			_snprintf( szMsg, 255, "      %05d  %9.4f %9.4f %9.4f\n", rKey.m_i32Time, rKey.m_f32Val[0], rKey.m_f32Val[1], rKey.m_f32Val[2] );
//			OutputDebugString( szMsg );

			pCont->m_rKeys.push_back( rKey );

			pCont->m_i32Type = ASE_TCB;
		}
		else if( is_token( "*ORT" ) ) {
			sscanf( get_row(), "%*s %s %s", szStartORT, szEndORT );
			pCont->m_i32StartORT = get_ort( szStartORT );
			pCont->m_i32EndORT = get_ort( szEndORT );
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );
}


void
ASELoaderC::parse_float_track( ASEFloatContC* pCont )
{
	int32		i32Time;
	float32		f32Val;
	float32		f32Tens, f32Cont, f32Bias, f32EaseIn, f32EaseOut;
	char		szStartORT[64], szEndORT[64];

//	OutputDebugString( "float:\n" );
	
	do {
		read_row();

		if( is_token( "*CONTROL_TCB_FLOAT_KEY" ) ) {

			ASEFloatKeyC	rKey;

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

			rKey.m_i32Time = i32Time;
			rKey.m_f32Val = f32Val;
			rKey.m_f32Tens = f32Tens;
			rKey.m_f32Cont = f32Cont;
			rKey.m_f32Bias = f32Bias;
			rKey.m_f32EaseIn = f32EaseIn;
			rKey.m_f32EaseOut = f32EaseOut;

			pCont->m_rKeys.push_back( rKey );

//			char szMsg[256];
//			_snprintf( szMsg, 255, "      %05d  %9.4f\n", rKey.m_i32Time, rKey.m_f32Val );
//			OutputDebugString( szMsg );

			pCont->m_i32Type = ASE_TCB;

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

			ASEFloatKeyC	rKey;

			sscanf( get_row(), "%*s %d %f",
				&rKey.m_i32Time, &rKey.m_f32Val );

			rKey.m_f32Tens = rKey.m_f32Cont = rKey.m_f32Bias = 0;
			rKey.m_f32EaseIn = rKey.m_f32EaseOut = 0;

			pCont->m_rKeys.push_back( rKey );

//			char szMsg[256];
//			_snprintf( szMsg, 255, "vis:      %05d  %9.4f\n", rKey.m_i32Time, rKey.m_f32Val );
//			OutputDebugString( szMsg );

			pCont->m_i32Type = ASE_LINEAR;
		}
		else if( is_token( "*ORT" ) ) {
			sscanf( get_row(), "%*s %s %s", szStartORT, szEndORT );
			pCont->m_i32StartORT = get_ort( szStartORT );
			pCont->m_i32EndORT = get_ort( szEndORT );
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );
}

void
ASELoaderC::parse_rot_track( ASEQuatContC* pCont )
{
	int32		i32Time;
	float32		f32X, f32Y, f32Z, f32W;
	float32		f32Tens, f32Cont, f32Bias, f32EaseIn, f32EaseOut;
	char		szStartORT[64], szEndORT[64];
	QuatC		rPrevRot( 0, 0, 0, 1 );

//	OutputDebugString( "quat:\n" );
	
	do {
		read_row();

		if( is_token( "*CONTROL_TCB_ROT_KEY" ) ) {

			ASEQuatKeyC	rKey;

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

/*			QuatC	rRot;
			rRot.from_axis_angle( f32X, f32Y, f32Z, f32W );
			rRot = rPrevRot * rRot;
			rPrevRot = rRot;
*/

			rKey.m_rAxis[0] = f32X;
			rKey.m_rAxis[1] = f32Z;
			rKey.m_rAxis[2] = -f32Y;
			rKey.m_f32Angle = f32W;
			rKey.m_i32Time = i32Time;
			rKey.m_f32Tens = f32Tens;
			rKey.m_f32Cont = f32Cont;
			rKey.m_f32Bias = f32Bias;
			rKey.m_f32EaseIn = f32EaseIn;
			rKey.m_f32EaseOut = f32EaseOut;


//			char szMsg[256];
//			_snprintf( szMsg, 255, "      %05d  %9.4f %9.4f %9.4f %9.4f\n", rKey.m_i32Time, rKey.m_f32Val[0], rKey.m_f32Val[1], rKey.m_f32Val[2], rKey.m_f32Val[3] );
//			_snprintf( szMsg, 255, "      %05d  %9.4f %9.4f %9.4f %9.4f\n", rKey.m_i32Time, f32X, f32Y, f32Z, f32W );
//			_snprintf( szMsg, 255, "      %05d  cont: %9.4f\n", rKey.m_i32Time, rKey.m_f32Cont );
//			OutputDebugString( szMsg );


			pCont->m_rKeys.push_back( rKey );

			pCont->m_i32Type = ASE_TCB;
		}
		if( is_token( "*ORT" ) ) {
			sscanf( get_row(), "%*s %s %s", szStartORT, szEndORT );
			pCont->m_i32StartORT = get_ort( szStartORT );
			pCont->m_i32EndORT = get_ort( szEndORT );
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );
}

void
ASELoaderC::parse_scale_track( ASEVector3ContC* pContScale, ASEQuatContC* pContRot )
{
	int32		i32Time;
	float32		f32SX, f32SY, f32SZ, f32X, f32Y, f32Z, f32W;
	float32		f32Tens, f32Cont, f32Bias, f32EaseIn, f32EaseOut;
	char		szStartORT[64], szEndORT[64];
	QuatC		rPrevRot( 0, 0, 0, 1 );

//	OutputDebugString( "scale:\n" );
	
	do {
		read_row();

		if( is_token( "*CONTROL_TCB_SCALE_KEY" ) ) {

			ASEQuatKeyC		rRotKey;
			ASEVector3KeyC	rScaleKey;

			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 );

			rScaleKey.m_i32Time = i32Time;
			rScaleKey.m_rVal[0] = f32SX;
			rScaleKey.m_rVal[1] = f32SZ;
			rScaleKey.m_rVal[2] = f32SY;
			rScaleKey.m_f32Tens = f32Tens;
			rScaleKey.m_f32Cont = f32Cont;
			rScaleKey.m_f32Bias = f32Bias;
			rScaleKey.m_f32EaseIn = f32EaseIn;
			rScaleKey.m_f32EaseOut = f32EaseOut;

/*			QuatC	rRot;
			rRot.from_axis_angle( f32X, f32Y, f32Z, f32W );
			rRot = rPrevRot * rRot;
			rPrevRot = rRot;*/

			rRotKey.m_i32Time = i32Time;
			rRotKey.m_rAxis[0] = f32X;
			rRotKey.m_rAxis[1] = f32Z;
			rRotKey.m_rAxis[2] = -f32Y;
			rRotKey.m_f32Angle = f32W;
			rRotKey.m_f32Tens = f32Tens;
			rRotKey.m_f32Cont = f32Cont;
			rRotKey.m_f32Bias = f32Bias;
			rRotKey.m_f32EaseIn = f32EaseIn;
			rRotKey.m_f32EaseOut = f32EaseOut;


//			char szMsg[256];
//			_snprintf( szMsg, 255, "      %05d  %9.4f %9.4f %9.4f %9.4f %9.4f %9.4f %9.4f\n", rRotKey.m_i32Time, rScaleKey.m_f32Val[0], rScaleKey.m_f32Val[1], rScaleKey.m_f32Val[2], rRotKey.m_f32Val[0], rRotKey.m_f32Val[1], rRotKey.m_f32Val[2], rRotKey.m_f32Val[3] );
//			OutputDebugString( szMsg );

			pContScale->m_rKeys.push_back( rScaleKey );
			pContRot->m_rKeys.push_back( rRotKey );

			pContRot->m_i32Type = ASE_TCB;
			pContScale->m_i32Type = ASE_TCB;
		}
		if( is_token( "*ORT" ) ) {
			sscanf( get_row(), "%*s %s %s", szStartORT, szEndORT );
			int32	i32Start, i32End;
			i32Start = get_ort( szStartORT );
			i32End = get_ort( szEndORT );
			pContScale->m_i32StartORT = i32Start;
			pContScale->m_i32EndORT = i32End;
			pContRot->m_i32StartORT = i32Start;
			pContRot->m_i32EndORT = i32End;
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );
}

bool
ASELoaderC::parse_look_at_node_tm( Vector3C& rPos, float32& f32OutRoll )
{
	float32		f32X, f32Y, f32Z, f32Roll;
	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_token( "*ROLL_ANGLE" ) ) {
			sscanf( get_row(), "%*s %f", &f32Roll );
			f32OutRoll = f32Roll;
		}
/*
    } else if( strcmp( "*ROLL_ANGLE", word ) == 0 ) {
      if( type == Target ) {
        sscanf( row, "%s %f", word, &val );
        printf( "  - roll angle: %9.6f\n", val );
      }
*/
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );

	return bIsTarget;
}


//
// light
//


void
ASELoaderC::parse_light_settings( ColorC& rColor, float32& f32Multiplier, float32& f32DecayStart, bool& bDoDecay )
{
	float32	f32Val, f32R, f32G, f32B;

	bDoDecay = false;

	do {
		read_row();
		if( is_token( "*LIGHT_COLOR" ) ) {
			sscanf( get_row(), "%*s %f %f %f", &f32R, &f32G, &f32B );
			rColor[0] = f32R;
			rColor[1] = f32G;
			rColor[2] = f32B;
		}
		else if( is_token( "*LIGHT_INTENS" ) ) {
			sscanf( get_row(), "%*s %f", &f32Val );
			f32Multiplier = f32Val;
		}
		else if( is_token( "*LIGHT_DECAY" ) ) {
			sscanf( get_row(), "%*s %f", &f32Val );
			f32DecayStart = f32Val;
		}
		else if( is_token( "*LIGHT_DECAY_TYPE" ) ) {
			char	szType[64];
			sscanf( get_row(), "%*s %s", szType );
			if( strcmp( szType, "INVERSE" ) == 0 )
				bDoDecay = true;
			else if( strcmp( szType, "INVERSE_SQR" ) == 0 )
				bDoDecay = true;
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );
}

void
ASELoaderC::parse_light_tm_anim( ASELightC* 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 )
				parse_pos_track( &pLight->m_rPosCont );
			else
				parse_pos_track( &pLight->m_rTgtCont );
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );
}


void
ASELoaderC::parse_light_param_anim( ASELightC* pLight )
{
	ASEFloatContC*	pCont = 0;

	do {
		read_row();

		if( is_token( "*PARAM_TYPE" ) ) {
			char*	szType = extract_string( get_row() );

//			OutputDebugString( szType );

			if( strcmp( "Color", szType ) == 0 ) {
				pCont = (ASEFloatContC*)&pLight->m_rColorCont;
			}
			else if( strcmp( "Multiplier", szType ) == 0 ) {
				pCont = &pLight->m_rMultCont;
			}
			else if( strcmp( "Decay Falloff", szType ) == 0 ) {
				pCont = &pLight->m_rDecayStartCont;
			}
		}
		else if( is_token( "*CONTROL_FLOAT_TCB" ) ) {
			if( pCont ) {
				parse_float_track( pCont );
				pCont = 0;
			}
			else
				parse_dummy();
		}
		else if( is_token( "*CONTROL_POINT3_TCB" ) ) {
			if( pCont ) {
				parse_vector3_track( (ASEVector3ContC*)pCont );
				pCont = 0;
			}
			else
				parse_dummy();
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );
}

void
ASELoaderC::parse_light_settings_anim( ASELightC* pLight )
{
	do {
		read_row();

		if( is_token( "*PARAMETER" ) ) {
			parse_light_param_anim( pLight );
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );
}


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

	Vector3C	rPos;
	float32		f32Roll;

	do {
		read_row();

		if( is_token( "*NODE_TM" ) ) {
			if( !parse_look_at_node_tm( rPos, f32Roll ) )
				pLight->m_rPos = rPos;
		}
		else if( is_token( "*LIGHT_TYPE" ) ) {
			if( strstr( get_row(), "Omni" ) )
				pLight->m_bIsSpot = false;
			else
				pLight->m_bIsSpot = true;
		}
		else if( is_token( "*LIGHT_SETTINGS" ) ) {
			parse_light_settings( pLight->m_rColor, pLight->m_f32Multiplier, pLight->m_f32DecayStart, pLight->m_bDoDecay );
		}
		else if( is_token( "*LIGHT_ANIMATION" ) ) {
			parse_light_settings_anim( 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( float32& f32FOV, float32& f32NearPlane, float32& f32FarPlane )
{
	float32	f32Val;

	do {
		read_row();
		if( is_token( "*CAMERA_NEAR" ) ) {
			sscanf( get_row(), "%*s %f", &f32Val );
			f32NearPlane = f32Val;
		}
		else if( is_token( "*CAMERA_FAR" ) ) {
			sscanf( get_row(), "%*s %f", &f32Val );
			f32FarPlane = f32Val;
		}
		else if( is_token( "*CAMERA_FOV" ) ) {
			sscanf( get_row(), "%*s %f", &f32Val );
			f32FOV = f32Val;
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );
}

void
ASELoaderC::parse_camera_tm_anim( ASECameraC* 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 )
				parse_pos_track( &pCam->m_rTgtCont );
			else
				parse_pos_track( &pCam->m_rPosCont );
		}
		else if( is_token( "*CONTROL_FLOAT_TCB" ) ) {
			if( bIsTarget )
				parse_dummy();
			else {
//				OutputDebugString( "roll " );
				parse_float_track( &pCam->m_rRollCont );
			}
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );
}


void
ASELoaderC::parse_camera_param_anim( ASECameraC* pCam )
{
	ASEFloatContC*	pCont = 0;

	do {
		read_row();

		if( is_token( "*PARAM_TYPE" ) ) {
			char*	szType = extract_string( get_row() );

//			OutputDebugString( szType );

			if( strcmp( "FOV", szType ) == 0 ) {
//				OutputDebugString( " jep\n" );
				pCont = &pCam->m_rFOVCont;
			}
		}
		else if( is_token( "*CONTROL_FLOAT_TCB" ) ) {
			if( pCont ) {
				parse_float_track( pCont );
				pCont = 0;
			}
			else
				parse_dummy();
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );
}

void
ASELoaderC::parse_camera_settings_anim( ASECameraC* pCam )
{
	do {
		read_row();

		if( is_token( "*PARAMETER" ) ) {
			parse_camera_param_anim( pCam );
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );
}


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

	Vector3C	rPos;
	float32		f32Roll;

	do {
		read_row();

		if( is_token( "*NODE_NAME" ) ) {
			pCam->m_rName = extract_string( get_row() );
		}
		else if( is_token( "*NODE_TM" ) ) {
			if( parse_look_at_node_tm( rPos, f32Roll ) )
				pCam->m_rTgt = rPos;
			else {
				pCam->m_rPos = rPos;
				pCam->m_f32Roll = f32Roll;
			}
		}
		else if( is_token( "*CAMERA_SETTINGS" ) ) {
			parse_camera_settings( pCam->m_f32FOV, pCam->m_f32NearPlane, pCam->m_f32FarPlane );
		}
		else if( is_token( "*CAMERA_ANIMATION" ) ) {
			parse_camera_settings_anim( 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_tcset( ASETCSetC* set )
{
	uint32		tvertices = 0, tfaces = 0;
	float32		x, y, z;
	uint32		a, b, c;

	//TRACE( "parsing: %s\n", get_row() );

	do {
		read_row();

		if( is_token( "*MESH_NUMTVERTEX" ) ) {
			sscanf( get_row(), "%*s %d", &tvertices );
			set->m_coords.resize( tvertices );
		}
		else if( is_token( "*MESH_TVERTLIST" ) ) {
			//TRACE( "    - %d texture vertices:\n", tvertices );
			for( uint32 i = 0; i < tvertices; i++ ) {
				read_row();
				if( eof() )
					return;
//					throw ASEExceptionC( "ASELoader TCSET: Preliminary EOF." );
				sscanf( get_row(), "%*s %*d %f %f %f", &x, &y, &z );
				//TRACE( "        %03d  %9.4f %9.4f\n", i, x, y );
				set->m_coords[i][0] = x;
				set->m_coords[i][1] = 1 - y;
				set->m_coords[i][2] = 0;
			}
			read_row();	// "}"  (end of block)
			if( eof() )
				return;
//				throw ASEExceptionC( "ASELoader TCSET: Preliminary EOF." );
		}
		else if( is_token( "*MESH_NUMTVFACES" ) ) {
			sscanf( get_row(), "%*s %d", &tfaces );
			set->m_indices.resize( tfaces * 3 );
		}
		else if( is_token( "*MESH_TFACELIST" ) ) {
			//TRACE( "    - %d texture indices:\n", tfaces );
			for( uint32 i = 0; i < tfaces; i++ ) {
				read_row();
				if( eof() )
					return;
//					throw ASEExceptionC( "ASELoader TCSET: Preliminary EOF." );
				sscanf( get_row(), "%*s %*d %d %d %d", &a, &b, &c );
				//TRACE( "        %03d  %3d %3d %3d\n", i, a, b, c );
				set->m_indices[i * 3 + 0] = a;
				set->m_indices[i * 3 + 1] = b;
				set->m_indices[i * 3 + 2] = c;
			}
			read_row();	// "}"  (end of block)
			if( eof() )
				return;
//				throw ASEExceptionC( "ASELoader TCSET: Preliminary EOF." );
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
//		else if( eof() )
//			throw ASEExceptionC( "ASELoader TCSET: Preliminary EOF." );

	} while( !eof() );

}

void
ASELoaderC::parse_mesh( ASEGeomObjectC* mesh )
{
	uint32		vertices = 0, faces = 0;
	uint32		tvertices = 0, tfaces = 0;
	uint32		cvertices = 0, cfaces = 0;
	float32		x, y, z;
	uint32		a, b, c, smooth, mtl;
	ASETCSetC*	set;
	Matrix3C	nMat;

	nMat = mesh->m_tm;
	nMat[3] = Vector3C( 0, 0, 0 );	// kill translation

	do {
		read_row();

		if( is_token( "*MESH_NUMVERTEX" ) ) {
			sscanf( get_row(), "%*s %d", &vertices );
		}
		else if( is_token( "*MESH_NUMFACES" ) ) {
			sscanf( get_row(), "%*s %d", &faces );
		}
		else if( is_token( "*MESH_VERTEX_LIST" ) ) {
			//TRACE( "    - %d vertices:\n", vertices );
			mesh->m_vertices.resize( vertices );
			for( uint32 i = 0; i < vertices; i++ ) {

				read_row();
				if( eof() )
					return;
//					throw ASEExceptionC( "ASELoader MESH: Preliminary EOF." );
				sscanf( get_row(), "%*s %*d %f %f %f", &x, &y, &z );
				mesh->m_vertices[i][0] = x;
				mesh->m_vertices[i][1] = z;
				mesh->m_vertices[i][2] = -y;

			}
			read_row();	// "}"  (end of block)
			if( eof() )
				return;
//				throw ASEExceptionC( "ASELoader MESH: Preliminary EOF." );
		}
		else if( is_token( "*MESH_FACE_LIST" ) ) {
			//TRACE( "    - %d faces:\n", faces );
			mesh->m_faces.resize( faces );
			for( uint32 i = 0; i < faces; i++ ) {
				read_row();
				if( eof() )
					return;
//					throw ASEExceptionC( "ASELoader MESH: Preliminary EOF." );

				char*  sPtr;
       
				a = b = c = smooth = mtl = 0;
        
				sPtr = strstr( get_row(), "A:" );
				if( sPtr ) sscanf( sPtr, "%*s %d", &a );

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

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

				sPtr = strstr( get_row(), "ING" );
				if( sPtr ) sscanf( sPtr, "%*s %x", &smooth );

				sPtr = strstr( get_row(), "ID" );
				if( sPtr ) sscanf( sPtr, "%*s %d", &mtl );

				mesh->m_faces[i].m_verts[0] = a;
				mesh->m_faces[i].m_verts[1] = b;
				mesh->m_faces[i].m_verts[2] = c;
				mesh->m_faces[i].m_mtlRef = mtl;
				mesh->m_faces[i].m_smthGrp = smooth;

			}
			read_row();	// "}"  (end of block)
			if( eof() )
				return;
//				throw ASEExceptionC( "ASELoader MESH: Preliminary EOF." );
		}
		else if( is_token( "*MESH_NUMCVERTEX" ) ) {
			sscanf( get_row(), "%*s %d", &cvertices );
			mesh->m_vertColors.resize( cvertices );
		}
		else if( is_token( "*MESH_CVERTLIST" ) ) {
			//TRACE( "    - %d color vertices:\n", cvertices );
			for( uint32 i = 0; i < cvertices; i++ ) {
				read_row();
				if( eof() )
					return;
//					throw ASEExceptionC( "ASELoader MESH: Preliminary EOF." );
				sscanf( get_row(), "%*s %*d %f %f %f", &x, &y, &z );
				//TRACE( "        %03d  %9.4f %9.4f\n", i, x, y );

				mesh->m_vertColors[i][0] = x;
				mesh->m_vertColors[i][1] = y;
				mesh->m_vertColors[i][2] = z;

			}
			read_row();	// "}"  (end of block)
			if( eof() )
				return;
//				throw ASEExceptionC( "ASELoader MESH: Preliminary EOF." );
		}
		else if( is_token( "*MESH_NUMCVFACES" ) ) {
			sscanf( get_row(), "%*s %d", &cfaces );
		}
		else if( is_token( "*MESH_CFACELIST" ) ) {
			//TRACE( "    - %d color indices:\n", cfaces );
			for( uint32 i = 0; i < cfaces; i++ ) {
				read_row();
				if( eof() )
					return;
//					throw ASEExceptionC( "ASELoader MESH: Preliminary EOF." );
				sscanf( get_row(), "%*s %*d %d %d %d", &a, &b, &c );
				mesh->m_faces[i].m_colors[0] = a;
				mesh->m_faces[i].m_colors[1] = b;
				mesh->m_faces[i].m_colors[2] = c;

			}
			read_row();	// "}"  (end of block)
			if( eof() )
				return;
//				throw ASEExceptionC( "ASELoader MESH: Preliminary EOF." );
		}

		else if( is_token( "*MESH_NUMTVERTEX" ) ) {
			sscanf( get_row(), "%*s %d", &tvertices );
			if( tvertices ) {
				set = new ASETCSetC;
				if( !set )
					return;
//					throw ASEExceptionC( "ASELoader MESH: Out of Memory." );
				set->m_coords.resize( tvertices );
			}
		}
		else if( is_token( "*MESH_TVERTLIST" ) ) {
			//TRACE( "    - %d texture vertices:\n", tvertices );
			for( uint32 i = 0; i < tvertices; i++ ) {
				read_row();
				if( eof() )
					return;
//					throw ASEExceptionC( "ASELoader MESH: Preliminary EOF." );
				sscanf( get_row(), "%*s %*d %f %f", &x, &y, &z );
				set->m_coords[i][0] = x;
				set->m_coords[i][1] = 1 - y;
				set->m_coords[i][2] = 0;

			}
			read_row();	// "}"  (end of block)
			if( eof() )
				return;
//				throw ASEExceptionC( "ASELoader MESH: Preliminary EOF." );
		}
		else if( is_token( "*MESH_NUMTVFACES" ) ) {
			sscanf( get_row(), "%*s %d", &tfaces );
			set->m_indices.resize( tfaces * 3 );
		}
		else if( is_token( "*MESH_TFACELIST" ) ) {
			//TRACE( "    - %d texture indices:\n", tfaces );
			for( uint32 i = 0; i < tfaces; i++ ) {
				read_row();
				if( eof() )
					return;
//					throw ASEExceptionC( "ASELoader MESH: Preliminary EOF." );
				sscanf( get_row(), "%*s %*d %d %d %d", &a, &b, &c );
				set->m_indices[i * 3 + 0] = a;
				set->m_indices[i * 3 + 1] = b;
				set->m_indices[i * 3 + 2] = c;

			}
			read_row();	// "}"  (end of block)
			if( eof() )
				return;
//				throw ASEExceptionC( "ASELoader MESH: Preliminary EOF." );
			mesh->m_TCsets.push_back( set );
		}
		else if( is_token( "*MESH_MAPPINGCHANNEL" ) ) {
			set = new ASETCSetC;
			if( !set )
				return;
//				throw ASEExceptionC( "ASELoader MESH: Out of Memory." );
			parse_tcset( set );
			mesh->m_TCsets.push_back( set );
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
//		else if( eof() )
//			throw ASEExceptionC( "ASELoader MESH: Preliminary EOF." );

	} while( !eof() );

}

void
ASELoaderC::parse_mesh_node_tm( Matrix3C& mat, Matrix3C& offset, PajaTypes::Vector3C& rOutPos, PajaTypes::QuatC& rOutRot, PajaTypes::Vector3C& rOutScale, PajaTypes::QuatC& rOutScaleRot )
{
	float32		x, y, z, a;

	Vector3C	rRotAxis;
	float32		f32RotAngle;

	Vector3C	rPos;

	Vector3C	rScale;
	Vector3C	rScaleAxis;
	float32		f32ScaleAngle;

	// pivot
	Vector3C	rPivotRotAxis;
	float32		f32PivotRotAngle;

	Vector3C	rPivotPos;

	Vector3C	rPivotScale;
	Vector3C	rPivotScaleAxis;
	float32		f32PivotScaleAngle;


	do {
		read_row();

		if( is_token( "*TM_POS" ) ) {
			sscanf( get_row(), "%*s %f %f %f", &x, &y, &z );
			rPos[0] = x;
			rPos[1] = z;
			rPos[2] = -y;
		}
		else if( is_token( "*TM_ROTAXIS" ) ) {
			sscanf( get_row(), "%*s %f %f %f", &x, &y, &z );
			rRotAxis[0] = x;
			rRotAxis[1] = z;
			rRotAxis[2] = -y;
		}
		else if( is_token( "*TM_ROTANGLE" ) ) {
			sscanf( get_row(), "%*s %f", &a );
			f32RotAngle = a;
		}
		else if( is_token( "*TM_SCALE" ) ) {
			sscanf( get_row(), "%*s %f %f %f", &x, &y, &z );
			rScale[0] = x;
			rScale[1] = z;
			rScale[2] = y;
		}
		else if( is_token( "*TM_SCALEAXIS" ) ) {
			sscanf( get_row(), "%*s %f %f %f", &x, &y, &z );
			rScaleAxis[0] = x;
			rScaleAxis[1] = z;
			rScaleAxis[2] = -y;
		}
		else if( is_token( "*TM_SCALEAXISANG" ) ) {
			sscanf( get_row(), "%*s %f", &a );
			f32ScaleAngle = a;
		}
		else if( is_token( "*TM_PIVOTPOS" ) ) {
			sscanf( get_row(), "%*s %f %f %f", &x, &y, &z );
			rPivotPos[0] = x;
			rPivotPos[1] = z;
			rPivotPos[2] = -y;
		}
		else if( is_token( "*TM_PIVOTROTAXIS" ) ) {
			sscanf( get_row(), "%*s %f %f %f", &x, &y, &z );
			rPivotRotAxis[0] = x;
			rPivotRotAxis[1] = z;
			rPivotRotAxis[2] = -y;
		}
		else if( is_token( "*TM_PIVOTROTANGLE" ) ) {
			sscanf( get_row(), "%*s %f", &a );
			f32PivotRotAngle = a;
		}
		else if( is_token( "*TM_PIVOTSCALE" ) ) {
			sscanf( get_row(), "%*s %f %f %f", &x, &y, &z );
			rPivotScale[0] = x;
			rPivotScale[1] = z;
			rPivotScale[2] = y;
		}
		else if( is_token( "*TM_PIVOTSCALEAXIS" ) ) {
			sscanf( get_row(), "%*s %f %f %f", &x, &y, &z );
			rPivotScaleAxis[0] = x;
			rPivotScaleAxis[1] = z;
			rPivotScaleAxis[2] = -y;
		}
		else if( is_token( "*TM_PIVOTSCALEAXISANG" ) ) {
			sscanf( get_row(), "%*s %f", &a );
			f32PivotScaleAngle = a;
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
//		else if( eof() )
//			throw ASEExceptionC( "ASELoader NODE_TM: Preliminary EOF." );

	} while( !eof() );


	//
	// compute it!
	//

	Matrix3C	rPivotScaleMat;
	rPivotScaleMat.set_scale( rPivotScale );

	QuatC		rPivotScaleRot;
	rPivotScaleRot.from_axis_angle( rPivotScaleAxis, f32PivotScaleAngle );
	Matrix3C	rPivotScaleRotMat;
	rPivotScaleRotMat.set_rot( rPivotScaleRot );
	Matrix3C	rInvPivotScaleRotMat;
	rInvPivotScaleRotMat = rPivotScaleRotMat.inverse();

	QuatC		rPivotRot;
	rPivotRot.from_axis_angle( rPivotRotAxis, f32PivotRotAngle );
	Matrix3C	rPivotRotMat;
	rPivotRotMat.set_rot( rPivotRot );

	Matrix3C	rPivotPosMat;
	rPivotPosMat.set_trans( rPivotPos );

	offset =  rPivotScaleRotMat * rPivotScaleMat * rInvPivotScaleRotMat * rPivotRotMat * rPivotPosMat;

		
	Matrix3C	rScaleMat;
	rScaleMat.set_scale( rScale );

	QuatC		rScaleRot;
	rScaleRot.from_axis_angle( rScaleAxis, f32ScaleAngle );
	Matrix3C	rScaleRotMat;
	rScaleRotMat.set_rot( rScaleRot );
	Matrix3C	rInvScaleRotMat;
	rInvScaleRotMat = rScaleRotMat.inverse();

	QuatC		rRot;
	rRot.from_axis_angle( rRotAxis[0], rRotAxis[1], rRotAxis[2], f32RotAngle );
	Matrix3C	rRotMat;
	rRotMat.set_rot( rRot );

	Matrix3C	rPosMat;
	rPosMat.set_trans( rPos );


	mat = rScaleRotMat * rScaleMat * rInvScaleRotMat * rRotMat * rPosMat;


	rOutPos = rPos;
	rOutRot = rRot;
	rOutScale = rScale;
	rOutScaleRot = rScaleRot;
}


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

		if( is_token( "*CONTROL_POS_TCB" ) ) {
			parse_pos_track( &pObj->m_rPosCont );
		}
		else if( is_token( "*CONTROL_ROT_TCB" ) ) {
			parse_rot_track( &pObj->m_rRotCont );
		}
		else if( is_token( "*CONTROL_SCALE_TCB" ) ) {
			parse_scale_track( &pObj->m_rScaleCont, &pObj->m_rScaleRotCont );
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );
}

void
ASELoaderC::parse_object_vis_track( ASEObjectC* pObj )
{

	do {
		read_row();

		if( is_token( "*CONTROL_FLOAT_LINEAR" ) ) {
			parse_float_track( &pObj->m_rVisibility );
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );
}

/*
void
ASELoaderC::parse_morph_targets( ASEMorphObjectC* pObj )
{
	do {
		read_row();

		if( is_token( "*MESH" ) ) {
			ASEGeomObjectC*	pTgt = new ASEGeomObjectC;
			parse_mesh( pTgt );
			pObj->m_rTargets.push_back( pTgt );
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );
}

void
ASELoaderC::parse_morph_keys( ASEMorphObjectC* pObj )
{
	do {
		read_row();

		if( is_token( "*MORPH_KEY" ) ) {
			ASEMorphKeyC	rKey;
			sscanf( get_row(), "%*s %d %d %f %f %f", &rKey.m_i32Time, &rKey.m_i32Target, &rKey.m_f32Tens, &rKey.m_f32Cont, &rKey.m_f32Bias );
			pObj->m_rMorphKeys.push_back( rKey );
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );
}

void
ASELoaderC::parse_morph( ASEMorphObjectC* mesh )
{
	do {
		read_row();

		if( is_token( "*MORPH_KEYS" ) ) {
			parse_pos_track( &pObj->m_rPosCont );
		}
		else if( is_token( "*TARGETS" ) ) {
			parse_rot_track( &pObj->m_rRotCont );
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
	} while( !eof() );
}
*/

ASEObjectC*
ASELoaderC::parse_geomobject()
{
	uint32			mtl;
	ASEGeomObjectC*		pObj = new ASEGeomObjectC;
	if( !pObj )
		return 0;

//		throw ASEExceptionC( "ASELoader GEOMOBJ: Out of Memory." );

	//TRACE( "\n* geom object\n" );


	do {
		read_row();

		if( is_token( "*NODE_NAME" ) ) {
			//TRACE( "  - name: '%s'\n", extract_string( get_row() ) );
			pObj->m_name = extract_string( get_row() );
		}
		else if( is_token( "*NODE_PARENT" ) ) {
			//TRACE( "  - parent: '%s'\n", extract_string( get_row() ) );
			pObj->m_parentName = extract_string( get_row() );
		}
		else if( is_token( "*NODE_TM" ) ) {
			parse_mesh_node_tm( pObj->m_tm, pObj->m_offset, pObj->m_rPos, pObj->m_rRot, pObj->m_rScale, pObj->m_rScaleRot );
		}
		else if( is_token( "*MESH" ) ) {
			//TRACE( "  * mesh\n" );
			parse_mesh( pObj );
		}
		else if( is_token( "*MATERIAL_REF" ) ) {
			sscanf( get_row(), "%*s %d", &mtl );
			//TRACE( "  - material: %d\n", mtl );
			pObj->m_mtlRef = mtl;
		}
		else if( is_token( "*WIREFRAME_COLOR" ) ) {
			float32	r, g, b;
			sscanf( get_row(), "%*s %f %f %f", &r, &g, &b );
			pObj->m_wireColor = ColorC( r, g, b );
			//TRACE( "  - color:  %4.2f %4.2f %4.2f\n", r, g, b );
      	}
		else if( is_token( "*NODE_VISIBILITY_TRACK" ) ) {
			parse_object_vis_track( pObj );
		}
		else if( is_token( "*TM_ANIMATION" ) ) {
			assert( pObj );
			parse_object_tm_anim( pObj );
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
//		else if( eof() )
//			throw ASEExceptionC( "ASELoader GEOMOBJ: Preliminary EOF." );

	} while( !eof() );

	return (ASEObjectC*)pObj;
}



ASEObjectC*
ASELoaderC::parse_helperobject( string& groupName )
{
//	ASEHelperObjectC*	obj = new ASEHelperObjectC;

	bool				bIsGroupObject;
	ASEGroupObjectC*	pObj = new ASEGroupObjectC;
	if( !pObj )
		return 0;


	//TRACE( "\n* helper object\n" );
  
	do {
		read_row();

		if( is_token( "*NODE_NAME" ) ) {
			//TRACE( "  - name: '%s'\n", extract_string( get_row() ) );
			pObj->m_name = extract_string( get_row() );

			if( pObj->m_name.compare( groupName ) == 0 )
				bIsGroupObject = true;

//			m_sProgressMsg = sName;
		}
		else if( is_token( "*NODE_PARENT" ) ) {
			//TRACE( "  - parent: '%s'\n", extract_string( get_row() ) );
			pObj->m_parentName = extract_string( get_row() );
		}
		else if( is_token( "*NODE_TM" ) ) {
			parse_mesh_node_tm( pObj->m_tm, pObj->m_offset, pObj->m_rPos, pObj->m_rRot, pObj->m_rScale, pObj->m_rScaleRot );
		}
		else if( is_token( "*NODE_VISIBILITY_TRACK" ) ) {
			parse_object_vis_track( pObj );
		}
		else if( is_token( "*TM_ANIMATION" ) ) {
			assert( pObj );
			parse_object_tm_anim( pObj );
		}
//		else if( is_token( "*NODE_USERPROP" ) ) {
//			sUserProp = extract_string( get_row() );
//			TRACE( "  user prop: >%s<\n", extract_string( get_row() ) );
//		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
//		else if( eof() )
//			throw ASEExceptionC( "ASELoader HELPER: Preliminary EOF." );

	} while( !eof() );


	return pObj;
}


void
ASELoaderC::parse_map( ASEMapC& rMap )
{
	static char		name[256];
	uint16			channel;
	float32			amt;


	do {
		read_row();
    
		if( is_token( "*BITMAP" ) ) {
			//TRACE( "'%s'\n", extract_string( get_row() ) );
			rMap.m_name = extract_string( get_row() );
//			m_sProgressMsg = rMap.m_name;
		}
		else if ( is_token( "*MAP_GENERIC" ) ) {
			if( rMap.m_mapType == ASEMapC::MAP_RGB_MULTIPLY ) {
				//TRACE( "RGB Multiply map %d\n", map.m_genMaps.size() );
				ASEMapC	gMap;
				parse_map( gMap );
				rMap.m_genMaps.push_back( gMap );
			} else
				parse_dummy();
		}
		else if ( is_token( "*MAP_CLASS" ) ) {
			char*	className = extract_string( get_row() );

			if( strcmp( className, "Bitmap" ) == 0 ) {
				rMap.m_mapType = ASEMapC::MAP_BITMAP;
				//TRACE( "Bitmap\n" );
			}
			else if( strcmp( className, "RGB Multiply" ) == 0 ) {
				rMap.m_mapType = ASEMapC::MAP_RGB_MULTIPLY;
				//TRACE( "RGB Multiply map\n" );
			}
			else if( strcmp( className, "Vertex Color" ) == 0 ) {
				rMap.m_mapType = ASEMapC::MAP_VERTEX_COLOR;
				//TRACE( "Vertex Color map\n" );
			}
		}
		else if ( is_token( "*MAP_TYPE" ) ) {
			if( strstr( get_row(), "Explicit" ) != 0 ) {
				sscanf( get_row(), "%*s %*s %d", &channel );
				channel--;	// max has base 1
				rMap.m_mapping = ASEMapC::MAPPING_EXPLICIT;
				rMap.m_texChannel = channel;
			}
			else if( strstr( get_row(), "ObjXYZ" ) != 0 ) {
					rMap.m_mapping = ASEMapC::MAPPING_OBJ_XYZ;
			}
			else if( strstr( get_row(), "WorldXYZ" ) != 0 ) {
					rMap.m_mapping = ASEMapC::MAPPING_WORLD_XYZ;
			}
			else if( strstr( get_row(), "Vertexcolors" ) != 0 ) {
					rMap.m_mapping = ASEMapC::MAPPING_VERTEX_COLORS;
			}
			else if( strstr( get_row(), "Spherical" ) != 0 ) {
					rMap.m_mapping = ASEMapC::MAPPING_SPHERICAL_ENV;
			}
			else if( strstr( get_row(), "Cylindrical" ) != 0 ) {
					rMap.m_mapping = ASEMapC::MAPPING_CYLINDRICAL_ENV;
			}
			else if( strstr( get_row(), "Shrinkwrap" ) != 0 ) {
					rMap.m_mapping = ASEMapC::MAPPING_SHRINK_WRAP_ENV;
			}
			else if( strstr( get_row(), "Screen" ) != 0 ) {
				if( m_version == 200 ) {
					// old ase exporter has a bug
					rMap.m_mapping = ASEMapC::MAPPING_EXPLICIT;
					rMap.m_texChannel = 0;
				} else {
					rMap.m_mapping = ASEMapC::MAPPING_SCREEN;
				}
			}
		}
		else if ( is_token( "*MAPPING_AXIS" ) ) {
			if( strstr( get_row(), "UV" ) != 0 ) {
				rMap.m_axis = ASEMapC::AXIS_UV;
			}
			else if( strstr( get_row(), "VW" ) != 0 ) {
				rMap.m_axis = ASEMapC::AXIS_VW;
			}
			else if( strstr( get_row(), "WU" ) != 0 ) {
				rMap.m_axis = ASEMapC::AXIS_WU;
			}
			else if( strstr( get_row(), "XY" ) != 0 ) {
				rMap.m_axis = ASEMapC::AXIS_UV;
			}
			else if( strstr( get_row(), "YZ" ) != 0 ) {
				rMap.m_axis = ASEMapC::AXIS_VW;
			}
			else if( strstr( get_row(), "ZX" ) != 0 ) {
				rMap.m_axis = ASEMapC::AXIS_WU;
			}
		}
		else if ( is_token( "*MAP_AMOUNT" ) ) {
			sscanf( get_row(), "%*s %f", &amt );
			rMap.m_amount = amt;
		}
		else if ( is_token( "*MAPPING_AXIS" ) ) {
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
//		else if( eof() )
//			throw ASEExceptionC( "ASELoader MAP: Preliminary EOF." );
    
	} while( !eof() );

}


void
ASELoaderC::parse_material( ASEMaterialC* mtl )
{
	float32	r, g, b, val;
	uint32	numMaterials;
	uint32	mtlNum;

	read_row();
	sscanf( get_row(), "%*s %d", &mtlNum );
  
	if( is_token( "*MATERIAL") ) {
		//TRACE( "- material %d\n", mtlNum );
	}
	else {
		//TRACE( "- submaterial %d\n", mtlNum );
	}

	do {
		read_row();

		if( is_token( "*MATERIAL_NAME" ) ) {
			//TRACE( "  - name: '%s'\n", extract_string( get_row() ) );
			mtl->m_name = extract_string( get_row() );
//			m_sProgressMsg = mtl->m_name;
		}
		else if( is_token( "*MAP_DIFFUSE" ) ) {
			//TRACE( "  - diffuse map: " );
			parse_map( mtl->m_mapDiff );
		}
		else if( is_token( "*MAP_REFLECT" ) ) {
			//TRACE( "  - reflect map: " );
			parse_map( mtl->m_mapRefl );
		}
		else if( is_token( "*MATERIAL_SHINE" ) ) {
			sscanf( get_row(), "%*s %f", &val );
			//TRACE( "  - shine: %f\n", val );
			mtl->m_shine = val;
		}
		else if( is_token( "*MATERIAL_SELFILLUM" ) ) {
			sscanf( get_row(), "%*s %f", &val );
			//TRACE( "  - shine: %f\n", val );
			mtl->m_selfillum = val;
		}
		else if( is_token( "*MATERIAL_SHINESTRENGTH" ) ) {
			sscanf( get_row(), "%*s %f", &val );
			//TRACE( "  - shine strength: %f\n", val );
			if( val > 1.0 )
				val = 1.0;
			if( val < 0 )
				val = 0;
			mtl->m_spec *= val;
		}
		else if( is_token( "*MATERIAL_TRANSPARENCY" ) ) {
			sscanf( get_row(), "%*s %f", &val );
			//TRACE( "  - transparency: %f\n", val );
			mtl->m_opacity = val;
		}
		else if( is_token( "*MATERIAL_AMBIENT" ) ) {
			sscanf( get_row(), "%*s %f %f %f", &r, &g, &b );
			mtl->m_amb = ColorC( r, g, b );
			//TRACE( "  - ambient:  %4.2f %4.2f %4.2f\n", r, g, b );
		}
		else if( is_token( "*MATERIAL_DIFFUSE" ) ) {
			sscanf( get_row(), "%*s %f %f %f", &r, &g, &b );
			mtl->m_diff = ColorC( r, g, b );
			//TRACE( "  - diffuse:  %4.2f %4.2f %4.2f\n", r, g, b );
		}
		else if( is_token( "*MATERIAL_SPECULAR" ) ) {
			sscanf( get_row(), "%*s %f %f %f", &r, &g, &b );
			mtl->m_spec = ColorC( r, g, b );
			//TRACE( "  - specular:  %4.2f %4.2f %4.2f\n", r, g, b );
		}
		else if( is_token( "*MATERIAL_TWOSIDED" ) ) {
			mtl->m_twoSided = true;
			//TRACE( "  - specular:  %4.2f %4.2f %4.2f\n", r, g, b );
		}
		else if( is_token( "*NUMSUBMTLS" ) ) {
			sscanf( get_row(), "%*s %d", &numMaterials );
			//TRACE( "  - %d materials\n", numMaterials );
			for( uint32 i = 0; i < numMaterials; i++ ) {
				ASEMaterialC* smtl = new ASEMaterialC;
				if( !smtl )
					return;
//					throw ASEExceptionC( "ASELoader MATERIAL: Out of Memory." );
				parse_material( smtl );
				mtl->m_submaterials.push_back( smtl );
			}

		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
//		else if( eof() )
//			throw ASEExceptionC( "ASELoader MATERIAL: Preliminary EOF." );

	} while( !eof() );

}

void
ASELoaderC::parse_materials()
{
	uint32	numMaterials;

	//TRACE( "\n* materials\n" );
  
	do {
		read_row();

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

			//TRACE( "  - %d materials\n", numMaterials );
			for( uint32 i = 0; i < numMaterials; i++ ) {
				ASEMaterialC* mtl = new ASEMaterialC;
				if( !mtl )
					return;
//					throw ASEExceptionC( "ASELoader MATERIAL: Out of Memory." );
				parse_material( mtl );
				m_materials.push_back( mtl );
			}
		}
		else if( is_block() )
			parse_dummy();
		else if( is_token( "}" ) )
			break;
//		else if( eof() )
//			throw ASEExceptionC( "ASELoader GEOMOBJ: Preliminary EOF." );

	} 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() );
}


ASEObjectC*
ASELoaderC::find_object( const char* szName, ASEObjectC* pParent )
{
	if( !pParent ) {
		for( uint32 i = 0; i < m_objects.size(); i++ ) {
			ASEObjectC*	pObj = m_objects[i];
//			TRACE( "comparing >%s< against >%s<\n", szName, pObj->m_name.c_str() );
			if( pObj->m_name.compare( szName ) == 0 )
				return pObj;
			ASEObjectC*	pRetObj = find_object( szName, pObj );
			if( pRetObj )
				return pRetObj;
		}
	}
	else {

		if( pParent->m_name.compare( szName ) == 0 )
			return pParent;

		for( uint32 i = 0; i < pParent->m_childs.size(); i++ ) {
			ASEObjectC*	pObj = pParent->m_childs[i];
//			TRACE( "comparing >%s< against >%s<\n", szName, pObj->m_name.c_str() );
			if( pObj->m_name.compare( szName ) == 0 )
				return pObj;
			ASEObjectC*	pRetObj = find_object( szName, pObj );
			if( pRetObj )
				return pRetObj;
		}

	}

	return 0;
}


ASEObjectC*
ASELoaderC::parse_group( string& groupName, int32 i32Indent )
{

	ASEGroupObjectC*		pGroup = 0;

	//TRACE( "on parse group\n" );

//	TRACE( "%*sBEGIN {\n", i32Indent, "" );

	do {
		read_row();

//		OutputDebugString( get_row() );

//		TRACE( "%s\n", get_row() );

		if( is_token( "*3DSMAX_ASCIIEXPORT" ) ) {
			sscanf( get_row(), "%*s %d", &m_version );
//			TRACE( "version %d\n", m_version );
		}
		else if( is_token( "*SCENE" ) ) {
			parse_scene();
		}
		else if( is_token( "*MATERIAL_LIST" ) ) {
//			TRACE( "  *materials\n" );
			// read material list
			parse_materials();
		}
		else if( is_token( "*CAMERAOBJECT" ) ) {
			ASECameraC*	pCam = parse_camera();
			if( pCam )
				m_rCameras.push_back( pCam );
		}
		else if( is_token( "*LIGHTOBJECT" ) ) {
			ASELightC*	pLight = parse_light();
			if( pLight )
				m_rLights.push_back( pLight );
		}
		else if( is_token( "*GEOMOBJECT" ) ) {
//			TRACE( "%*s  *geom obj\n", i32Indent, "" );
			// read geomObject
			ASEObjectC*	obj = parse_geomobject();
			if( obj ) {

				ASEObjectC*	parent = 0;
				if( !obj->m_parentName.empty() ) {
					if( pGroup && pGroup->m_name.compare( obj->m_parentName.c_str() ) == 0 ) {
//						OutputDebugString( "group is parent\n" );
						parent = pGroup;
					}
					else {
//						OutputDebugString( "find parent\n" );
						parent = find_object( obj->m_parentName.c_str() );
					}
				}

				if( parent ) {
//					OutputDebugString( "found parent\n" );
					parent->m_childs.push_back( obj );
				}
				else {
//					OutputDebugString( "pushback\n" );
					m_objects.push_back( obj );
				}
			}	
//			if( pGroup && obj )
//				pGroup->m_childs.push_back( obj );
//			else if( obj )
//				m_objects.push_back( obj );		// add object to list

		}
		else if( is_token( "*GROUP" ) ) {
//			TRACE( "*group\n" );
			string	name = extract_string( get_row() );
			ASEObjectC*	obj = parse_group( name, i32Indent + 1 );


			if( obj ) {
				ASEObjectC*	parent = 0;
				if( pGroup && pGroup->m_name.compare( obj->m_parentName.c_str() ) == 0 )
					parent = pGroup;
				else
					parent = find_object( obj->m_parentName.c_str() );

				if( parent ) {
//					OutputDebugString( "found parent\n" );
					parent->m_childs.push_back( obj );
				}
				else
					m_objects.push_back( obj );
			}

		}
		else if( is_token( "*HELPEROBJECT" ) ) {
//			TRACE( "*helper\n" );
			// get origo of this pGroup
			ASEObjectC*	obj = parse_helperobject( groupName );
			if( obj ) {
					
				if( obj->m_type == ASE_GROUP_OBJECT )
					pGroup = (ASEGroupObjectC*)obj;
				else {
//					TRACE( " unknown\n" );
					delete obj;	// i just dont know what to do with it...
				}
			}
		}
		else if( is_block() ) {
			parse_dummy();
		}
		else if( is_token( "}" ) )
			break;
//		else if( eof() )
//			throw ASEExceptionC( "ASELoader pGroup: Preliminary EOF." );

	} while( !eof() );

	//TRACE( "on exit parse pGroup\n \"%s\"\n", m_row );


	return pGroup;
}


int32
get_time()
{
	LARGE_INTEGER	d;
	double			i, j;
	QueryPerformanceCounter( &d );
	i = (double)d.QuadPart;
	QueryPerformanceFrequency( &d );
	j = (double)d.QuadPart;
	return (int32)((i / j) * 1000.0);
}


bool
ASELoaderC::load( const char* szName )
{

	int32	i32StartTime = get_time();

	cleanup();


	//
	// find base dir (for texture load)
	//
	char	szDrive[_MAX_DRIVE];
	char	szPath[_MAX_DIR];
	_splitpath( szName, szDrive, szPath, NULL, NULL );
	m_sBaseDir = szDrive;
	m_sBaseDir = szPath;


//	if( (m_fp = fopen( name, "rb" )) == 0 )
	if( (m_fp = fopen( szName, "rb" )) == 0 )
		return false;

	fseek( m_fp, 0, SEEK_END );
	m_ui32FileSize = ftell( m_fp );
	fseek( m_fp, 0, SEEK_SET );
//		m_pFile = new char[m_ui32FileSize];
//		fread( m_pFile, m_ui32FileSize, 1, m_fp );
//		fclose( m_fp );
	m_ui32FilePointer = 0;
	m_i32Percent = 0;

	string	dummy( "" );


//	TRACE( "parsing...\n" );
	parse_group( dummy, 0 );


//		TRACE( "converting...\n" );
//		convert();

//		delete [] m_pFile;
	m_pFile = 0;
	m_ui32FileSize = 0;
	m_ui32FilePointer = 0;
	fclose( m_fp );


	int32	i32EndTime = get_time();


	TRACE( "ASE Load time: %d ms (%f s)\n", i32EndTime - i32StartTime, (float64)(i32EndTime - i32StartTime) / 1000.0 );


	return true;
}


static
bool
file_exists( const char* szName )
{
	FILE*	pStream;
	if( (pStream = fopen( szName, "rb" )) == NULL )
		return false;
	fclose( pStream );
	return true;
}


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

}

void
ASELoaderC::convert_map( ASEMapC& rMap, MtlLayerC* pLayer, ASEGeomObjectC* obj )
{
	pLayer->set_texture( get_texture( rMap.m_name ) );

	switch( rMap.m_mapping ) {

	case ASEMapC::MAPPING_EXPLICIT:
		//TRACE( "- %d TCsets. i want to use %d\n", obj->m_TCsets.size(), rMap.m_texChannel );
		if( rMap.m_texChannel < 0 || rMap.m_texChannel >= obj->m_TCsets.size() )
			return;
		pLayer->set_mapping_type( MtlLayerC::MAPPING_COORDS );
		break;

	case ASEMapC::MAPPING_OBJ_XYZ:
		pLayer->set_mapping_type( MtlLayerC::MAPPING_OBJ_XYZ );
		break;

	case ASEMapC::MAPPING_WORLD_XYZ:
		pLayer->set_mapping_type( MtlLayerC::MAPPING_OBJ_XYZ );
		break;

	case ASEMapC::MAPPING_SPHERICAL_ENV:
		pLayer->set_mapping_type( MtlLayerC::MAPPING_SPHERICAL_ENV );
		break;

	case ASEMapC::MAPPING_CYLINDRICAL_ENV:
		pLayer->set_mapping_type( MtlLayerC::MAPPING_CYLINDRICAL_ENV );
		break;

	case ASEMapC::MAPPING_SHRINK_WRAP_ENV:
		pLayer->set_mapping_type( MtlLayerC::MAPPING_SHRINKWRAP_ENV );
		break;

	case ASEMapC::MAPPING_SCREEN:
		pLayer->set_mapping_type( MtlLayerC::MAPPING_SCREEN );
		break;
	}

	switch( rMap.m_axis ) {
	case ASEMapC::AXIS_UV:
		pLayer->set_mapping_axis( MtlLayerC::MAPPING_AXIS_UV );
		break;
	case ASEMapC::AXIS_VW:
		pLayer->set_mapping_axis( MtlLayerC::MAPPING_AXIS_VW );
		break;
	case ASEMapC::AXIS_WU:
		pLayer->set_mapping_axis( MtlLayerC::MAPPING_AXIS_WU );
		break;
	}

}

void
ASELoaderC::convert_geom_object( ASEGeomObjectC* obj, Mesh3DC* pDest, Matrix3C& rOrigTM )
{
	uint32	i, j, k;

	Matrix3C	rNormTM;
	Matrix3C	rTM = rOrigTM;

	//
	// transform vertices
	//
	if( !obj->m_transformed ) {

		for( j = 0; j < obj->m_vertices.size(); j++ ) {
			obj->m_vertices[j] = obj->m_vertices[j] * rTM;
		}

		//
		// calculate normals from stracth
		//

		obj->m_baseNormals.resize( obj->m_vertices.size() );

		for( i = 0; i < obj->m_faces.size(); i++ ) {

			Vector3C	a, b, c, d;
			a = obj->m_vertices[obj->m_faces[i].m_verts[1]] - obj->m_vertices[obj->m_faces[i].m_verts[0]];
			b = obj->m_vertices[obj->m_faces[i].m_verts[2]] - obj->m_vertices[obj->m_faces[i].m_verts[0]];
			c = a.cross( b );
			d = c.normalize();

			for( j = 0; j < 3; j++ ) {
				uint32	ui32Idx = obj->m_faces[i].m_verts[j];
				obj->m_faces[i].m_norms[j] = obj->m_baseNormals.at( ui32Idx ).add_norm( d, obj->m_faces[i].m_smthGrp );
			}
		}


		//
		// build correct normal array
		//
		uint32	nNorm = 0;
		for( i = 0; i < obj->m_baseNormals.size(); i++ ) {
			obj->m_baseNormals[i].m_baseIdx = nNorm;
			nNorm += obj->m_baseNormals[i].m_norms.size();
			for( j = 0; j < obj->m_baseNormals[i].m_norms.size(); j++ ) {
				obj->m_normals.push_back( obj->m_baseNormals[i].m_norms[j] );
			}
		}

		//
		// remap normal indices
		//
		for( i = 0; i < obj->m_faces.size(); i++ ) {
			for( j = 0; j < 3; j++ ) {
				// A face normal index at this point is index to m_baseNormal-list's Nth member
				// all "baseNormals" are now unrolled to m_normals array, so:
				// normalIndex = vert[n].basenormalindex + facenormaindex
				// got it?
				uint16	newIdx = obj->m_baseNormals[obj->m_faces[i].m_verts[j]].m_baseIdx + obj->m_faces[i].m_norms[j];
				obj->m_faces[i].m_norms[j] = newIdx;
			}
		}

		//
		// normalize normals
		//
		
		for( i = 0; i < obj->m_normals.size(); i++ ) {
			obj->m_normals[i] = obj->m_normals[i].normalize();
		}

		obj->m_transformed = true;
	}

	//
	// build face lists based on materials
	//
	// NOTE:	if some one is smart enough to use multimaterial as one member multimaterial
	//			let him be smart... i'm not.

	if( obj->m_mtlRef != -1 ) {

		ASEMaterialC*	mtl = m_materials[obj->m_mtlRef];
		uint32			mtlCount;
		bool			multiMtl = false;

		if( mtl->m_submaterials.size() ) {
			// this is multimaterial
			multiMtl = true;
			mtlCount = mtl->m_submaterials.size();
		}
		else {
			mtlCount = 1;
		}

		for( i = 0; i < mtlCount; i++ ) {

			// If we have multimaterial them we are doing ingy-wingy-exciting
			// conversion and stupid remaapping of stuff, just to get rid
			// of data multiplication and stuff...

			ASEMaterialC*						pSmtl = 0;
			bool								bHasColors = false;
			std::vector<uint16>					rRemapIndices;
			std::vector<uint16>					rRemapNormalIndices;
			std::vector<uint16>					rRemapColorIndices;
			std::vector<ColorC>					rColors;
			std::vector<uint16>					rUsedTexChannels;
			std::vector<std::vector<uint16> >	rUsedTcSets;
			std::vector<std::vector<uint16> >	rTcSetIndices;
			std::vector<Vector3C>				rVertices;
			std::vector<Vector3C>				rNormals;

			// Check for multi/single material
			if( multiMtl )
				pSmtl = mtl->m_submaterials[i];
			else
				pSmtl = mtl;

			//
			// Find texture channel count
			//
			rUsedTcSets.resize( obj->m_TCsets.size() );
			rTcSetIndices.resize( obj->m_TCsets.size() );

			rUsedTexChannels.resize( obj->m_TCsets.size() );
			for( j = 0; j < rUsedTexChannels.size(); j++ )
				rUsedTexChannels[j] = 0;

			//
			// loop thru all materials and see which texture channels are in use
			//
			if( obj->m_vertColors.size() && pSmtl->m_mapDiff.m_mapType == ASEMapC::MAP_VERTEX_COLOR ) {
				// If we have colors and this material needs 'em -> export colors
				bHasColors = true;
			} 
			else if( pSmtl->m_mapDiff.m_mapType == ASEMapC::MAP_BITMAP ) {
				// diffuse texture
				if( pSmtl->m_mapDiff.m_mapping == ASEMapC::MAPPING_EXPLICIT ) {
					if( pSmtl->m_mapDiff.m_texChannel >= 0 &&
						pSmtl->m_mapDiff.m_texChannel < rUsedTexChannels.size() ) {
						rUsedTexChannels[pSmtl->m_mapDiff.m_texChannel]++;	// use this channel
					}
					else {
						pSmtl->m_mapDiff.m_mapping = ASEMapC::MAPPING_NONE;
					}
				}
			} 
			else if( pSmtl->m_mapDiff.m_mapType == ASEMapC::MAP_RGB_MULTIPLY ) {
				// RGB-multiplymap as diffuse texture
				for( j = 0; j < pSmtl->m_mapDiff.m_genMaps.size(); j++ ) {
					if( pSmtl->m_mapDiff.m_genMaps[j].m_mapping == ASEMapC::MAPPING_EXPLICIT ) {
						if( pSmtl->m_mapDiff.m_genMaps[j].m_texChannel >= 0 &&
							pSmtl->m_mapDiff.m_genMaps[j].m_texChannel < rUsedTexChannels.size() ) {
							rUsedTexChannels[pSmtl->m_mapDiff.m_genMaps[j].m_texChannel]++;	// use this channel
						}
						else {
							// set mapping to none if there's problems
							pSmtl->m_mapDiff.m_genMaps[j].m_mapping = ASEMapC::MAPPING_NONE;
						}
					}
				}
			} 

			// alloc size in arrays
			rRemapIndices.resize( obj->m_vertices.size() );
			rRemapNormalIndices.resize( obj->m_normals.size() );
			if( bHasColors )
				rRemapColorIndices.resize( obj->m_vertColors.size() );
			for( j = 0; j < rUsedTcSets.size(); j++ )
				rUsedTcSets[j].resize( obj->m_TCsets[j]->m_coords.size() );

			// reset arrays
			for( k = 0; k < rRemapIndices.size(); k++ )
				rRemapIndices[k] = 0;

			for( k = 0; k < rRemapNormalIndices.size(); k++ )
				rRemapNormalIndices[k] = 0;

			if( bHasColors )
				for( k = 0; k < rRemapColorIndices.size(); k++ )
					rRemapColorIndices[k] = 0;

			for( j = 0; j < rUsedTcSets.size(); j++ )
				for( k = 0; k < rUsedTcSets[j].size(); k++ )
					rUsedTcSets[j][k] = 0;


			//
			// Traverse face list and mark used indices.
			//
			for( j = 0; j < obj->m_faces.size(); j++ ) {
				if( multiMtl && (obj->m_faces[j].m_mtlRef % mtlCount) != i )
					continue;
				for( k = 0; k < 3; k++ ) {
					rRemapIndices[obj->m_faces[j].m_verts[k]]++;
					rRemapNormalIndices[obj->m_faces[j].m_norms[k]]++;

					if( bHasColors ) {
						rRemapColorIndices[obj->m_faces[j].m_colors[k]]++;
					}

					for( uint32 t = 0; t < rUsedTexChannels.size(); t++ ) {
						if( rUsedTexChannels[t] ) {
							rUsedTcSets[t][obj->m_TCsets[t]->m_indices[j * 3 + k]]++;
						}
					}
				}
			}


			int32		i32ListIndex = pDest->get_geomlist_count();
			GeomListC*	pGeomList = pDest->add_geomlist();

			std::vector<uint16>		rIndices;
			std::vector<uint16>		rNormalIndices;
			std::vector<uint16>		rColorIndices;


			//
			// Remap Vertex indices
			// and collect used vertices
			//
			uint16 ui16VertCount = 0;
			for( j = 0; j < rRemapIndices.size(); j++ ) {
				if( rRemapIndices[j] ) {
					// Vertex j is used
					rVertices.push_back( obj->m_vertices[j] );
					rRemapIndices[j] = ui16VertCount++;
				}
			}

			// reserve memory for vertices and put them in.
			pGeomList->reserve_vertices( rVertices.size() );
			for( j = 0; j < rVertices.size(); j++ ) {
				pGeomList->set_vertex( j, rVertices[j] );
			}

			//
			// Remap Normal Indices
			// and collect used normals
			//
			uint16 ui16NormCount = 0;
			for( j = 0; j < rRemapNormalIndices.size(); j++ ) {
				if( rRemapNormalIndices[j] ) {
					// Vertex j is used
					rNormals.push_back( obj->m_normals[j] );
					rRemapNormalIndices[j] = ui16NormCount++;
				}
			}

			// reserve memory for normals and put them in.
			pGeomList->reserve_normals( rNormals.size() );
			for( j = 0; j < rNormals.size(); j++ )
				pGeomList->set_normal( j, rNormals[j] );


			//
			// Remap Color indices
			// and collect used colors
			//
			if( bHasColors ) {
				uint16 ui16ColorCount = 0;
				for( j = 0; j < rRemapColorIndices.size(); j++ ) {
					if( rRemapColorIndices[j] ) {
						// Vertex j is used
						ColorC	rCol;
						rCol[0] = obj->m_vertColors[j][0];
						rCol[1] = obj->m_vertColors[j][1];
						rCol[2] = obj->m_vertColors[j][2];
						rCol[3] = 1;
						rColors.push_back( rCol );
						rRemapColorIndices[j] = ui16ColorCount++;
					}
				}

				// reserve memory for colors and put them in.
				pGeomList->reserve_colors( rColors.size() );
				for( j = 0; j < rColors.size(); j++ )
					pGeomList->set_color( j, rColors[j] );
			}


			//
			// Remap texture sets
			//
			uint16	ui16TexChannelCount = 0;
			for( j = 0; j < rUsedTcSets.size(); j++ ) {				// Loop thru all texture channels
				
				if( rUsedTexChannels[j] && rUsedTcSets[j].size() ) {		// If this channel is used...
					rUsedTexChannels[j] = ui16TexChannelCount++;			// assign it to new index (local index inside geomlist)

					//
					// collect texture coords for this channel
					//

					std::vector<TextureCoordC>	rTexCoords;

					uint16	ui16TVertCount = 0;
					for( k = 0; k < rUsedTcSets[j].size(); k++ ) {	// Loop thru all texture indices
						if( rUsedTcSets[j][k] ) {					// If this index is used...
							rUsedTcSets[j][k] = ui16TVertCount++;		// Remap index to local index space
							TextureCoordC	rTexCoord;
							Vector3C&		rTexVert = obj->m_TCsets[j]->m_coords[k];
							rTexCoord[0] = rTexVert[0];
							rTexCoord[1] = rTexVert[1];
							rTexCoord[2] = rTexVert[2];
							rTexCoords.push_back( rTexCoord );
						}
					}

					// Create new texture channel and put collected coords in.
					TCSetC*	pTcSet = pGeomList->add_texture_channel();
					pTcSet->reserve_coords( rTexCoords.size() );
					for( k = 0; k < rTexCoords.size(); k++ )
						pTcSet->set_coord( k, rTexCoords[k] );
				}
				else {
					rUsedTexChannels[j] = 0xffff;					// mark this channels not-in-use.
				}

			}

			//
			// Traverse face list again and collect remapped indices
			//
			for( j = 0; j < obj->m_faces.size(); j++ ) {
				if( multiMtl && (obj->m_faces[j].m_mtlRef % mtlCount) != i )
					continue;
				for( k = 0; k < 3; k++ ) {
					// Add vertex index
					rIndices.push_back( rRemapIndices[obj->m_faces[j].m_verts[k]] );
					// and normal
					rNormalIndices.push_back( rRemapNormalIndices[ obj->m_faces[j].m_norms[k]] );

					// and color if enabled.
					if( bHasColors )
						rColorIndices.push_back( rRemapColorIndices[ obj->m_faces[j].m_colors[k]] );

					// and texture indices
					for( uint32 t = 0; t < rUsedTexChannels.size(); t++ ) {				
						// in all channels.
						if( rUsedTexChannels[t] != 0xffff )
							rTcSetIndices[t].push_back( rUsedTcSets[t][obj->m_TCsets[t]->m_indices[j * 3 + k]] );
					}
				}
			}


			//
			// put collected indices to geomlist
			//
			pGeomList->reserve_indices( rIndices.size() );
			for( j = 0; j < rIndices.size(); j++ )
				pGeomList->set_index( j, rIndices[j] );

			pGeomList->reserve_normal_indices( rNormalIndices.size() );
			for( j = 0; j < rNormalIndices.size(); j++ )
				pGeomList->set_normal_index( j, rNormalIndices[j] );

			if( bHasColors ) {
				pGeomList->reserve_color_indices( rColorIndices.size() );
				for( j = 0; j < rColorIndices.size(); j++ )
					pGeomList->set_color_index( j, rColorIndices[j] );
			}

			for( uint32 t = 0; t < rUsedTexChannels.size(); t++ ) {
				if( rUsedTexChannels[t] != 0xffff ) {
					TCSetC*	pTcSet = pGeomList->get_texture_channel( rUsedTexChannels[t] );
					assert( pTcSet );
					uint32	ui32Indices = rTcSetIndices[t].size();
					if( !ui32Indices ) {
					}
					pTcSet->reserve_indices( rTcSetIndices[t].size() );
					for( k = 0; k < rTcSetIndices[t].size(); k++ )
						pTcSet->set_index( k, rTcSetIndices[t][k] );
				}
			}


			//
			// Make material layers
			//

			int32	i32Default = MtlLayerC::LAYER_BLEND_REPLACE;

			if( pSmtl->m_opacity > 0 )
				i32Default = MtlLayerC::LAYER_BLEND_ADD;

			if( pSmtl->m_twoSided )
				i32Default |= MtlLayerC::LAYER_TWO_SIDED;

			// Diffuse layer
			if( pSmtl->m_mapDiff.m_mapType == ASEMapC::MAP_NO_MAP ) {
				// No Diffuse map. Put solid color layer
				MtlLayerC*	pLayer = pGeomList->add_layer();
				pLayer->set_flags( MtlLayerC::LAYER_LIGHTING_AMBIENT | MtlLayerC::LAYER_LIGHTING_DIFFUSE |
					MtlLayerC::LAYER_LIGHTING_SPECULAR | i32Default );
				pLayer->set_ambient( pSmtl->m_amb );
				pLayer->set_diffuse( pSmtl->m_diff );
				pLayer->set_specular( pSmtl->m_spec );
				pLayer->set_shininess( pSmtl->m_shine * 128.0f );
				pLayer->set_self_illumination( pSmtl->m_selfillum );
			}
			else if( pSmtl->m_mapDiff.m_mapType == ASEMapC::MAP_VERTEX_COLOR ) {
				// Vertex colors
				MtlLayerC*	pLayer = pGeomList->add_layer();
				pLayer->set_flags( MtlLayerC::LAYER_LIGHTING_AMBIENT | MtlLayerC::LAYER_LIGHTING_DIFFUSE |
										MtlLayerC::LAYER_LIGHTING_SPECULAR | MtlLayerC::LAYER_VERTEX_COLOR | i32Default );
				pLayer->set_ambient( pSmtl->m_amb );
				pLayer->set_diffuse( pSmtl->m_diff );
				pLayer->set_specular( pSmtl->m_spec );
				pLayer->set_shininess( pSmtl->m_shine * 128.0f );
				pLayer->set_self_illumination( pSmtl->m_selfillum );
			}
			else if( pSmtl->m_mapDiff.m_mapType == ASEMapC::MAP_BITMAP ) {
				// Texture
				MtlLayerC*	pLayer = pGeomList->add_layer();
				pLayer->set_flags( MtlLayerC::LAYER_LIGHTING_AMBIENT | MtlLayerC::LAYER_LIGHTING_DIFFUSE |
										MtlLayerC::LAYER_LIGHTING_SPECULAR | MtlLayerC::LAYER_TEXTURE | i32Default );
				pLayer->set_ambient( pSmtl->m_amb );
				pLayer->set_diffuse( pSmtl->m_diff );
				pLayer->set_specular( pSmtl->m_spec );
				pLayer->set_shininess( pSmtl->m_shine * 128.0f );
				pLayer->set_self_illumination( pSmtl->m_selfillum );

				convert_map( pSmtl->m_mapDiff, pLayer , obj );	// Sets texture ID, and mapping parameters.
				if( pSmtl->m_mapDiff.m_texChannel != 0xffff )
					pLayer->set_mapping_channel( rUsedTexChannels[pSmtl->m_mapDiff.m_texChannel] );
			}
			else if( pSmtl->m_mapDiff.m_mapType == ASEMapC::MAP_RGB_MULTIPLY ) {
				// Multi texture
				if( pSmtl->m_mapDiff.m_genMaps.size() == 2 ) {

					// add decal texture layer
					MtlLayerC*	pLayer1 = pGeomList->add_layer();
					pLayer1->set_flags( MtlLayerC::LAYER_TEXTURE | i32Default );
					convert_map( pSmtl->m_mapDiff.m_genMaps[0], pLayer1, obj );	// Sets texture ID, and mapping parameters.
					if( pSmtl->m_mapDiff.m_genMaps[0].m_texChannel != 0xffff )
						pLayer1->set_mapping_channel( rUsedTexChannels[pSmtl->m_mapDiff.m_genMaps[0].m_texChannel] );

					// add texturelayer with lighting which is multiplied on the previous layer
					MtlLayerC*	pLayer2 = pGeomList->add_layer();
					pLayer2->set_flags( MtlLayerC::LAYER_LIGHTING_AMBIENT | MtlLayerC::LAYER_LIGHTING_DIFFUSE |
											MtlLayerC::LAYER_LIGHTING_SPECULAR | MtlLayerC::LAYER_TEXTURE |
											MtlLayerC::LAYER_BLEND_MULTIPLY );
					pLayer2->set_ambient( pSmtl->m_amb );
					pLayer2->set_diffuse( pSmtl->m_diff );
					pLayer2->set_specular( pSmtl->m_spec );
					pLayer2->set_shininess( pSmtl->m_shine * 128.0f );
					pLayer2->set_self_illumination( pSmtl->m_selfillum );

					convert_map( pSmtl->m_mapDiff.m_genMaps[0], pLayer2, obj );	// Sets texture ID, and mapping parameters.
					if( pSmtl->m_mapDiff.m_genMaps[0].m_texChannel != 0xffff )
						pLayer2->set_mapping_channel( rUsedTexChannels[pSmtl->m_mapDiff.m_genMaps[0].m_texChannel] );

				} else {
					//TRACE( "multimaterial error... %d gemaps\n", pSmtl->m_mapDiff.m_genMaps.size() );
				}
			}

			// Reflection layer
			if( pSmtl->m_mapRefl.m_mapType == ASEMapC::MAP_NO_MAP ) {
				// No Diffuse map. Do nothing
			}
			else if( pSmtl->m_mapRefl.m_mapType == ASEMapC::MAP_VERTEX_COLOR ) {
				// Vertex colors
				MtlLayerC*	pLayer = pGeomList->add_layer();
				pLayer->set_flags( MtlLayerC::LAYER_VERTEX_COLOR | MtlLayerC::LAYER_BLEND_ADD );
			}
			else if( pSmtl->m_mapRefl.m_mapType == ASEMapC::MAP_BITMAP ) {
				// Texture
				MtlLayerC*	pLayer = pGeomList->add_layer();
				pLayer->set_flags( MtlLayerC::LAYER_TEXTURE | MtlLayerC::LAYER_BLEND_ADD );
				convert_map( pSmtl->m_mapRefl, pLayer, obj );	// Sets texture ID, and mapping parameters.
				if( pSmtl->m_mapRefl.m_texChannel != 0xffff )
					pLayer->set_mapping_channel( rUsedTexChannels[pSmtl->m_mapRefl.m_texChannel] );
			}


			if( pGeomList->get_vertex_count() == 0 ) {
				pDest->erase_geomlist( i32ListIndex );
			}

		}

	}
	else {

		// There's no material attached to this object
		// use wire frame color as material color.
		// use only one geomlist

		bool		bHasColors;
		GeomListC*	pGeomList = pDest->add_geomlist();

		std::vector<ColorC>	rColors;

		if( obj->m_vertColors.size() ) {
			ColorC	col;
			for( j = 0; j < obj->m_vertColors.size(); j++ ) {
				col[0] = obj->m_vertColors[j][0];
				col[1] = obj->m_vertColors[j][1];
				col[2] = obj->m_vertColors[j][2];
				col[3] = 1;
				rColors.push_back( col );
			}
			bHasColors = true;
		}


		pGeomList->reserve_indices( obj->m_faces.size() * 3 );
		pGeomList->reserve_vertices( obj->m_vertices.size() );

		pGeomList->reserve_normal_indices( obj->m_faces.size() * 3 );
		pGeomList->reserve_normals( obj->m_normals.size() );

		if( bHasColors ) {
			pGeomList->reserve_color_indices( obj->m_faces.size() * 3 );
			pGeomList->reserve_colors( rColors.size() );
		}

		for( j = 0; j < obj->m_faces.size(); j++ ) {
			for( k = 0; k < 3; k++ ) {
				pGeomList->set_index( j * 3 + k, obj->m_faces[j].m_verts[k] );
				pGeomList->set_normal_index( j * 3 + k, obj->m_faces[j].m_norms[k] );
				if( bHasColors )
					pGeomList->set_color_index( j * 3 + k, obj->m_faces[j].m_colors[k] );
			}
		}

		// Copy vertex list and vertex index list.
		for( j = 0; j < obj->m_vertices.size(); j++ )
			pGeomList->set_vertex( j, obj->m_vertices[j] );
		for( j = 0; j < obj->m_normals.size(); j++ )
			pGeomList->set_normal( j, obj->m_normals[j] );

		if( bHasColors ) {
			for( j = 0; j < rColors.size(); j++ )
				pGeomList->set_color( j, rColors[j] );
		}


		// Plain color stuff
		uint16	flags = MtlLayerC::LAYER_LIGHTING_DIFFUSE | MtlLayerC::LAYER_BLEND_REPLACE;
		MtlLayerC*	pLayer = pGeomList->add_layer();
		pLayer->set_flags( bHasColors ? flags | MtlLayerC::LAYER_VERTEX_COLOR : flags );
		pLayer->set_diffuse( obj->m_wireColor );
		pLayer->set_shininess( 10 );

	}
}



static
void
print_object( ASEObjectC* pObj, int32 i32Indent )
{
	uint32	i;

	if( pObj->m_type == ASE_GEOM_OBJECT ) {
//		TRACE( "%*sGEOMOBJ: %s (%d)\n", i32Indent * 2, "", pObj->m_name.c_str(), pObj->m_childs.size() );
		for( i = 0; i < pObj->m_childs.size(); i++ )
			print_object( pObj->m_childs[i], i32Indent + 1 );
	}
	else if( pObj->m_type == ASE_GROUP_OBJECT ) {
//		TRACE( "%*sGROUP: %s (%d)\n", i32Indent * 2, "", pObj->m_name.c_str(), pObj->m_childs.size() );
		for( i = 0; i < pObj->m_childs.size(); i++ )
			print_object( pObj->m_childs[i], i32Indent + 1 );
	}
}



void
ASELoaderC::convert_object( ASEObjectC* pObj, ScenegraphItemI* pParent )
{

	MeshRefC*	pRef = new MeshRefC;

	if( !pRef )
		return;

	if( pObj->m_type == ASE_GEOM_OBJECT ) {
		ASEGeomObjectC*	pGeom = (ASEGeomObjectC*)pObj;
		Mesh3DC*	pMesh = new Mesh3DC;
		convert_geom_object( pGeom, pMesh, pGeom->m_offset );
		m_rConvMeshes.push_back( pMesh );
		pRef->set_mesh( pMesh );
	}

	pRef->set_position( pObj->m_rPos );
	pRef->set_rotation( pObj->m_rRot );
	pRef->set_scale( pObj->m_rScale );
	pRef->set_scale_rotation( pObj->m_rScaleRot );

	// pos
	if( pObj->m_rPosCont.m_rKeys.size() ) {
		ContVector3C*	pCont = new ContVector3C( KEY_SMOOTH );

		pCont->set_start_ort( pObj->m_rPosCont.m_i32StartORT );
		pCont->set_end_ort( pObj->m_rPosCont.m_i32EndORT );

		for( uint32 i = 0; i < pObj->m_rPosCont.m_rKeys.size(); i++ ) {
			KeyVector3C*	pKey = pCont->add_key();
			pKey->set_time( pObj->m_rPosCont.m_rKeys[i].m_i32Time );
			pKey->set_value( pObj->m_rPosCont.m_rKeys[i].m_rVal );
			pKey->set_tens( pObj->m_rPosCont.m_rKeys[i].m_f32Tens );
			pKey->set_cont( pObj->m_rPosCont.m_rKeys[i].m_f32Cont );
			pKey->set_bias( pObj->m_rPosCont.m_rKeys[i].m_f32Bias );
			pKey->set_ease_in( pObj->m_rPosCont.m_rKeys[i].m_f32EaseIn );
			pKey->set_ease_out( pObj->m_rPosCont.m_rKeys[i].m_f32EaseOut );
		}
		
		pCont->sort_keys();
		pCont->prepare();
		
		pRef->set_position_controller( pCont );
	}

	// rot
	if( pObj->m_rRotCont.m_rKeys.size() ) {
		ContQuatC*	pCont = new ContQuatC( KEY_SMOOTH );

		pCont->set_start_ort( pObj->m_rRotCont.m_i32StartORT );
		pCont->set_end_ort( pObj->m_rRotCont.m_i32EndORT );

		for( uint32 i = 0; i < pObj->m_rRotCont.m_rKeys.size(); i++ ) {
			KeyQuatC*	pKey = pCont->add_key();
			pKey->set_time( pObj->m_rRotCont.m_rKeys[i].m_i32Time );
			pKey->set_axis( pObj->m_rRotCont.m_rKeys[i].m_rAxis );
			pKey->set_angle( pObj->m_rRotCont.m_rKeys[i].m_f32Angle );
			pKey->set_tens( pObj->m_rRotCont.m_rKeys[i].m_f32Tens );
			pKey->set_cont( pObj->m_rRotCont.m_rKeys[i].m_f32Cont );
			pKey->set_bias( pObj->m_rRotCont.m_rKeys[i].m_f32Bias );
			pKey->set_ease_in( pObj->m_rRotCont.m_rKeys[i].m_f32EaseIn );
			pKey->set_ease_out( pObj->m_rRotCont.m_rKeys[i].m_f32EaseOut );
		}
		
		pCont->sort_keys();
		pCont->prepare();
		
		pRef->set_rotation_controller( pCont );
	}

	// scale
	if( pObj->m_rScaleCont.m_rKeys.size() ) {
		ContVector3C*	pCont = new ContVector3C( KEY_SMOOTH );

		pCont->set_start_ort( pObj->m_rScaleCont.m_i32StartORT );
		pCont->set_end_ort( pObj->m_rScaleCont.m_i32EndORT );

		for( uint32 i = 0; i < pObj->m_rScaleCont.m_rKeys.size(); i++ ) {
			KeyVector3C*	pKey = pCont->add_key();
			pKey->set_time( pObj->m_rScaleCont.m_rKeys[i].m_i32Time );
			pKey->set_value( pObj->m_rScaleCont.m_rKeys[i].m_rVal );
			pKey->set_tens( pObj->m_rScaleCont.m_rKeys[i].m_f32Tens );
			pKey->set_cont( pObj->m_rScaleCont.m_rKeys[i].m_f32Cont );
			pKey->set_bias( pObj->m_rScaleCont.m_rKeys[i].m_f32Bias );
			pKey->set_ease_in( pObj->m_rScaleCont.m_rKeys[i].m_f32EaseIn );
			pKey->set_ease_out( pObj->m_rScaleCont.m_rKeys[i].m_f32EaseOut );
		}

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

	// scale rot
	if( pObj->m_rScaleRotCont.m_rKeys.size() ) {
		ContQuatC*	pCont = new ContQuatC( KEY_SMOOTH );

		pCont->set_start_ort( pObj->m_rScaleRotCont.m_i32StartORT );
		pCont->set_end_ort( pObj->m_rScaleRotCont.m_i32EndORT );

		for( uint32 i = 0; i < pObj->m_rScaleRotCont.m_rKeys.size(); i++ ) {
			KeyQuatC*	pKey = pCont->add_key();
			pKey->set_time( pObj->m_rScaleRotCont.m_rKeys[i].m_i32Time );
			pKey->set_axis( pObj->m_rScaleRotCont.m_rKeys[i].m_rAxis );
			pKey->set_angle( pObj->m_rScaleRotCont.m_rKeys[i].m_f32Angle );
			pKey->set_tens( pObj->m_rScaleRotCont.m_rKeys[i].m_f32Tens );
			pKey->set_cont( pObj->m_rScaleRotCont.m_rKeys[i].m_f32Cont );
			pKey->set_bias( pObj->m_rScaleRotCont.m_rKeys[i].m_f32Bias );
			pKey->set_ease_in( pObj->m_rScaleRotCont.m_rKeys[i].m_f32EaseIn );
			pKey->set_ease_out( pObj->m_rScaleRotCont.m_rKeys[i].m_f32EaseOut );
		}

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

		pRef->set_scale_rotation_controller( pCont );
	}

	// visibility
	if( pObj->m_rVisibility.m_rKeys.size() ) {
		ContFloatC*	pCont = new ContFloatC( KEY_LINEAR );

		pCont->set_start_ort( pObj->m_rVisibility.m_i32StartORT );
		pCont->set_end_ort( pObj->m_rVisibility.m_i32EndORT );

		for( uint32 i = 0; i < pObj->m_rVisibility.m_rKeys.size(); i++ ) {
			KeyFloatC*	pKey = pCont->add_key();
			pKey->set_time( pObj->m_rVisibility.m_rKeys[i].m_i32Time );
			pKey->set_value( pObj->m_rVisibility.m_rKeys[i].m_f32Val );
			pKey->set_tens( pObj->m_rVisibility.m_rKeys[i].m_f32Tens );
			pKey->set_cont( pObj->m_rVisibility.m_rKeys[i].m_f32Cont );
			pKey->set_bias( pObj->m_rVisibility.m_rKeys[i].m_f32Bias );
			pKey->set_ease_in( pObj->m_rVisibility.m_rKeys[i].m_f32EaseIn );
			pKey->set_ease_out( pObj->m_rVisibility.m_rKeys[i].m_f32EaseOut );
		}

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

		pRef->set_visibility_controller( pCont );
	}


	if( pParent )
		pParent->add_child( pRef );
	else
		m_rConvMeshRefs.push_back( pRef );


	for( uint32 i = 0; i < pObj->m_childs.size(); i++ ) {
		convert_object( pObj->m_childs[i], pRef );
	}
}


void
ASELoaderC::convert_light( ASELightC* pASELight )
{
	LightC*	pLight = new LightC;

	pLight->set_position( pASELight->m_rPos );
	pLight->set_color( pASELight->m_rColor );
	pLight->set_multiplier( pASELight->m_f32Multiplier );
	pLight->set_decay_start( pASELight->m_f32DecayStart );
	pLight->set_do_decay( pASELight->m_bDoDecay );

	// pos
	if( pASELight->m_rPosCont.m_rKeys.size() ) {
		ContVector3C*	pCont = new ContVector3C( KEY_SMOOTH );

		pCont->set_start_ort( pASELight->m_rPosCont.m_i32StartORT );
		pCont->set_end_ort( pASELight->m_rPosCont.m_i32EndORT );

		for( uint32 i = 0; i < pASELight->m_rPosCont.m_rKeys.size(); i++ ) {
			KeyVector3C*	pKey = pCont->add_key();
			pKey->set_time( pASELight->m_rPosCont.m_rKeys[i].m_i32Time );
			pKey->set_value( pASELight->m_rPosCont.m_rKeys[i].m_rVal );
			pKey->set_tens( pASELight->m_rPosCont.m_rKeys[i].m_f32Tens );
			pKey->set_cont( pASELight->m_rPosCont.m_rKeys[i].m_f32Cont );
			pKey->set_bias( pASELight->m_rPosCont.m_rKeys[i].m_f32Bias );
			pKey->set_ease_in( pASELight->m_rPosCont.m_rKeys[i].m_f32EaseIn );
			pKey->set_ease_out( pASELight->m_rPosCont.m_rKeys[i].m_f32EaseOut );
		}

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

		pLight->set_position_controller( pCont );
	}

	// color
	if( pASELight->m_rColorCont.m_rKeys.size() ) {
		ContVector3C*	pCont = new ContVector3C( KEY_SMOOTH );

		pCont->set_start_ort( pASELight->m_rColorCont.m_i32StartORT );
		pCont->set_end_ort( pASELight->m_rColorCont.m_i32EndORT );

		for( uint32 i = 0; i < pASELight->m_rColorCont.m_rKeys.size(); i++ ) {
			KeyVector3C*	pKey = pCont->add_key();
			pKey->set_time( pASELight->m_rColorCont.m_rKeys[i].m_i32Time );
			pKey->set_value( pASELight->m_rColorCont.m_rKeys[i].m_rVal );
			pKey->set_tens( pASELight->m_rColorCont.m_rKeys[i].m_f32Tens );
			pKey->set_cont( pASELight->m_rColorCont.m_rKeys[i].m_f32Cont );
			pKey->set_bias( pASELight->m_rColorCont.m_rKeys[i].m_f32Bias );
			pKey->set_ease_in( pASELight->m_rColorCont.m_rKeys[i].m_f32EaseIn );
			pKey->set_ease_out( pASELight->m_rColorCont.m_rKeys[i].m_f32EaseOut );
		}

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

		pLight->set_color_controller( pCont );
	}

	// multiplier
	if( pASELight->m_rMultCont.m_rKeys.size() ) {
		ContFloatC*	pCont = new ContFloatC( pASELight->m_rMultCont.m_i32Type == KEY_SMOOTH ? KEY_SMOOTH : KEY_LINEAR );

		pCont->set_start_ort( pASELight->m_rMultCont.m_i32StartORT );
		pCont->set_end_ort( pASELight->m_rMultCont.m_i32EndORT );

		for( uint32 i = 0; i < pASELight->m_rMultCont.m_rKeys.size(); i++ ) {
			KeyFloatC*	pKey = pCont->add_key();
			pKey->set_time( pASELight->m_rMultCont.m_rKeys[i].m_i32Time );
			pKey->set_value( pASELight->m_rMultCont.m_rKeys[i].m_f32Val );
			pKey->set_tens( pASELight->m_rMultCont.m_rKeys[i].m_f32Tens );
			pKey->set_cont( pASELight->m_rMultCont.m_rKeys[i].m_f32Cont );
			pKey->set_bias( pASELight->m_rMultCont.m_rKeys[i].m_f32Bias );
			pKey->set_ease_in( pASELight->m_rMultCont.m_rKeys[i].m_f32EaseIn );
			pKey->set_ease_out( pASELight->m_rMultCont.m_rKeys[i].m_f32EaseOut );
		}

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

		pLight->set_multiplier_controller( pCont );
	}

	// attn start
	if( pASELight->m_rDecayStartCont.m_rKeys.size() ) {
		ContFloatC*	pCont = new ContFloatC( pASELight->m_rDecayStartCont.m_i32Type == KEY_SMOOTH ? KEY_SMOOTH : KEY_LINEAR );

		pCont->set_start_ort( pASELight->m_rDecayStartCont.m_i32StartORT );
		pCont->set_end_ort( pASELight->m_rDecayStartCont.m_i32EndORT );

		for( uint32 i = 0; i < pASELight->m_rDecayStartCont.m_rKeys.size(); i++ ) {
			KeyFloatC*	pKey = pCont->add_key();
			pKey->set_time( pASELight->m_rDecayStartCont.m_rKeys[i].m_i32Time );
			pKey->set_value( pASELight->m_rDecayStartCont.m_rKeys[i].m_f32Val );
			pKey->set_tens( pASELight->m_rDecayStartCont.m_rKeys[i].m_f32Tens );
			pKey->set_cont( pASELight->m_rDecayStartCont.m_rKeys[i].m_f32Cont );
			pKey->set_bias( pASELight->m_rDecayStartCont.m_rKeys[i].m_f32Bias );
			pKey->set_ease_in( pASELight->m_rDecayStartCont.m_rKeys[i].m_f32EaseIn );
			pKey->set_ease_out( pASELight->m_rDecayStartCont.m_rKeys[i].m_f32EaseOut );
		}

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

		pLight->set_decay_start_controller( pCont );
	}

	m_rConvLights.push_back( pLight );
}

void
ASELoaderC::convert_camera( ASECameraC* pASECam )
{
	CameraC*	pCam = new CameraC;

	pCam->set_name( pASECam->m_rName.c_str() );
	pCam->set_position( pASECam->m_rPos );
	pCam->set_target_position( pASECam->m_rTgt );
	pCam->set_roll( pASECam->m_f32Roll );
	pCam->set_fov( pASECam->m_f32FOV );
	pCam->set_near_plane( pASECam->m_f32NearPlane );
	pCam->set_far_plane( pASECam->m_f32FarPlane );

	// pos
	if( pASECam->m_rPosCont.m_rKeys.size() ) {
		ContVector3C*	pCont = new ContVector3C( pASECam->m_rPosCont.m_i32Type == KEY_SMOOTH ? KEY_SMOOTH : KEY_LINEAR );

		pCont->set_start_ort( pASECam->m_rPosCont.m_i32StartORT );
		pCont->set_end_ort( pASECam->m_rPosCont.m_i32EndORT );

		for( uint32 i = 0; i < pASECam->m_rPosCont.m_rKeys.size(); i++ ) {
			KeyVector3C*	pKey = pCont->add_key();
			pKey->set_time( pASECam->m_rPosCont.m_rKeys[i].m_i32Time );
			pKey->set_value( pASECam->m_rPosCont.m_rKeys[i].m_rVal );
			pKey->set_tens( pASECam->m_rPosCont.m_rKeys[i].m_f32Tens );
			pKey->set_cont( pASECam->m_rPosCont.m_rKeys[i].m_f32Cont );
			pKey->set_bias( pASECam->m_rPosCont.m_rKeys[i].m_f32Bias );
			pKey->set_ease_in( pASECam->m_rPosCont.m_rKeys[i].m_f32EaseIn );
			pKey->set_ease_out( pASECam->m_rPosCont.m_rKeys[i].m_f32EaseOut );
		}

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

		pCam->set_position_controller( pCont );
	}

	// target pos
	if( pASECam->m_rTgtCont.m_rKeys.size() ) {
		ContVector3C*	pCont = new ContVector3C( pASECam->m_rTgtCont.m_i32Type == KEY_SMOOTH ? KEY_SMOOTH : KEY_LINEAR );

		pCont->set_start_ort( pASECam->m_rTgtCont.m_i32StartORT );
		pCont->set_end_ort( pASECam->m_rTgtCont.m_i32EndORT );

		for( uint32 i = 0; i < pASECam->m_rTgtCont.m_rKeys.size(); i++ ) {
			KeyVector3C*	pKey = pCont->add_key();
			pKey->set_time( pASECam->m_rTgtCont.m_rKeys[i].m_i32Time );
			pKey->set_value( pASECam->m_rTgtCont.m_rKeys[i].m_rVal );
			pKey->set_tens( pASECam->m_rTgtCont.m_rKeys[i].m_f32Tens );
			pKey->set_cont( pASECam->m_rTgtCont.m_rKeys[i].m_f32Cont );
			pKey->set_bias( pASECam->m_rTgtCont.m_rKeys[i].m_f32Bias );
			pKey->set_ease_in( pASECam->m_rTgtCont.m_rKeys[i].m_f32EaseIn );
			pKey->set_ease_out( pASECam->m_rTgtCont.m_rKeys[i].m_f32EaseOut );
		}

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

		pCam->set_target_position_controller( pCont );
	}

	// roll
	if( pASECam->m_rRollCont.m_rKeys.size() ) {
		ContFloatC*	pCont = new ContFloatC( pASECam->m_rRollCont.m_i32Type == KEY_SMOOTH ? KEY_SMOOTH : KEY_LINEAR );

		pCont->set_start_ort( pASECam->m_rRollCont.m_i32StartORT );
		pCont->set_end_ort( pASECam->m_rRollCont.m_i32EndORT );

		for( uint32 i = 0; i < pASECam->m_rRollCont.m_rKeys.size(); i++ ) {
			KeyFloatC*	pKey = pCont->add_key();
			pKey->set_time( pASECam->m_rRollCont.m_rKeys[i].m_i32Time );
			pKey->set_value( pASECam->m_rRollCont.m_rKeys[i].m_f32Val );
			pKey->set_tens( pASECam->m_rRollCont.m_rKeys[i].m_f32Tens );
			pKey->set_cont( pASECam->m_rRollCont.m_rKeys[i].m_f32Cont );
			pKey->set_bias( pASECam->m_rRollCont.m_rKeys[i].m_f32Bias );
			pKey->set_ease_in( pASECam->m_rRollCont.m_rKeys[i].m_f32EaseIn );
			pKey->set_ease_out( pASECam->m_rRollCont.m_rKeys[i].m_f32EaseOut );
		}

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

		pCam->set_roll_controller( pCont );
	}

	// FOV
	if( pASECam->m_rFOVCont.m_rKeys.size() ) {
		ContFloatC*	pCont = new ContFloatC( pASECam->m_rFOVCont.m_i32Type == KEY_SMOOTH ? KEY_SMOOTH : KEY_LINEAR );

		pCont->set_start_ort( pASECam->m_rFOVCont.m_i32StartORT );
		pCont->set_end_ort( pASECam->m_rFOVCont.m_i32EndORT );

		for( uint32 i = 0; i < pASECam->m_rFOVCont.m_rKeys.size(); i++ ) {
			KeyFloatC*	pKey = pCont->add_key();
			pKey->set_time( pASECam->m_rRollCont.m_rKeys[i].m_i32Time );
			pKey->set_value( pASECam->m_rFOVCont.m_rKeys[i].m_f32Val );
			pKey->set_tens( pASECam->m_rFOVCont.m_rKeys[i].m_f32Tens );
			pKey->set_cont( pASECam->m_rFOVCont.m_rKeys[i].m_f32Cont );
			pKey->set_bias( pASECam->m_rFOVCont.m_rKeys[i].m_f32Bias );
			pKey->set_ease_in( pASECam->m_rFOVCont.m_rKeys[i].m_f32EaseIn );
			pKey->set_ease_out( pASECam->m_rFOVCont.m_rKeys[i].m_f32EaseOut );
		}

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

		pCam->set_fov_controller( pCont );
	}

	m_rConvCameras.push_back( pCam );
}

void
ASELoaderC::convert( std::vector<ScenegraphItemI*>& rMeshRefs, std::vector<Mesh3DC*>& rMeshes, std::vector<CameraC*>& rCameras, std::vector<LightC*>& rLights, ImportInterfaceC* pImpInterface )
{
	uint32	i;


//	for( i = 0; i < m_objects.size(); i++ ) {
//		print_object( m_objects[i], 0 );
//	}

	m_pImpInterface = pImpInterface;

	// convert
	for( i = 0; i < m_objects.size(); i++ ) {
		convert_object( m_objects[i], 0 );
	}

	for( i = 0; i < m_rCameras.size(); i++ ) {
		convert_camera( m_rCameras[i] );
	}

	for( i = 0; i < m_rLights.size(); i++ ) {
		convert_light( m_rLights[i] );
	}


	rCameras = m_rConvCameras;
	rLights = m_rConvLights;
	rMeshes = m_rConvMeshes;
	rMeshRefs = m_rConvMeshRefs;

	m_rConvMeshRefs.clear();
	m_rConvMeshes.clear();
	m_rConvCameras.clear();
	m_rConvLights.clear();

}
