//========= Copyright Valve Corporation, All rights reserved. ============//
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <math.h>
#include "filesystem_tools.h"
#include "cmdlib.h"
#include "scriplib.h"
#include "mathlib/mathlib.h"
#define EXTERN
#include "studio.h"
#include "motionmapper.h"
#include "tier1/strtools.h"
#include "tier0/icommandline.h"
#include "utldict.h"
#include <windows.h>
#include "UtlBuffer.h"
#include "utlsymbol.h"

bool g_quiet = false;
bool g_verbose = false;
char g_outfile[1024];
bool uselogfile = false;

char	g_szFilename[1024];
FILE	*g_fpInput;
char	g_szLine[4096];
int		g_iLinecount;

bool g_bZBrush = false;
bool g_bGaveMissingBoneWarning = false;


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : depth - 
//			*fmt - 
//			... - 
//-----------------------------------------------------------------------------
void vprint( int depth, const char *fmt, ... )
{
	char string[ 8192 ];
	va_list va;
	va_start( va, fmt );
	V_vsprintf_safe( string, fmt, va );
	va_end( va );

	FILE *fp = NULL;

	if ( uselogfile )
	{
		fp = fopen( "log.txt", "ab" );
	}

	while ( depth-- > 0 )
	{
		vprint( 0,  "  " );
		OutputDebugString( "  " );
		if ( fp )
		{
			fprintf( fp, "  " );
		}
	}

	::printf( "%s", string );
	OutputDebugString( string );

	if ( fp )
	{
		char *p = string;
		while ( *p )
		{
			if ( *p == '\n' )
			{
				fputc( '\r', fp );
			}
			fputc( *p, fp );
			p++;
		}
		fclose( fp );
	}
}


int k_memtotal;
void *kalloc( int num, int size )
{
	// vprint( 0,  "calloc( %d, %d )\n", num, size );
	// vprint( 0,  "%d ", num * size );
	k_memtotal += num * size;
	return calloc( num, size );
}

void kmemset( void *ptr, int value, int size )
{
	// vprint( 0,  "kmemset( %x, %d, %d )\n", ptr, value, size );
	memset( ptr, value, size );
	return;
}

static bool g_bFirstWarning = true;

void MdlWarning( const char *fmt, ... )
{
	va_list args;
	static char output[1024];

	if (g_quiet)
	{
		if (g_bFirstWarning)
		{
			vprint( 0, "%s :\n", fullpath );
			g_bFirstWarning = false;
		}
		vprint( 0, "\t");
	}

	vprint( 0, "WARNING: ");
	va_start( args, fmt );
	vprint( 0, fmt, args );
}


void MdlError( char const *fmt, ... )
{
	va_list		args;

	if (g_quiet)
	{
		if (g_bFirstWarning)
		{
			vprint( 0, "%s :\n", fullpath );
			g_bFirstWarning = false;
		}
		vprint( 0, "\t");
	}

	vprint( 0, "ERROR: ");
	va_start( args, fmt );
	vprint( 0, fmt, args );

	exit( -1 );
}

int OpenGlobalFile( char *src )
{
	int		time1;
	char	filename[1024];

	// local copy of string
	strcpy( filename, ExpandPath( src ) );

	// Ummm, path sanity checking
	int pathLength;
	int numBasePaths = CmdLib_GetNumBasePaths();
	// This is kinda gross. . . doing the same work in cmdlib on SafeOpenRead.
	if( CmdLib_HasBasePath( filename, pathLength ) )
	{
		char tmp[1024];
		int i;
		for( i = 0; i < numBasePaths; i++ )
		{
			strcpy( tmp, CmdLib_GetBasePath( i ) );
			strcat( tmp, filename + pathLength );
			
			time1 = FileTime( tmp );
			if( time1 != -1 )
			{
				if ((g_fpInput = fopen(tmp, "r")) == 0) 
				{
					MdlWarning( "reader: could not open file '%s'\n", src );
					return 0;
				}
				else
				{
					return 1;
				}
			}
		}
		return 0;
	}
	else
	{
		time1 = FileTime (filename);
		if (time1 == -1)
			return 0;

		// Whoohooo, FOPEN!
		if ((g_fpInput = fopen(filename, "r")) == 0) 
		{
			MdlWarning( "reader: could not open file '%s'\n", src );
			return 0;
		}

		return 1;
	}
}

bool IsEnd( char const* pLine )
{
	if (strncmp( "end", pLine, 3 ) != 0) 
		return false;
	return (pLine[3] == '\0') || (pLine[3] == '\n');
}


//Wrong name for the use of it.
void scale_vertex( Vector &org )
{
	org[0] = org[0] * g_currentscale;
	org[1] = org[1] * g_currentscale;
	org[2] = org[2] * g_currentscale;
}


void clip_rotations( RadianEuler& rot )
{
	int j;
	// clip everything to : -M_PI <= x < M_PI

	for (j = 0; j < 3; j++) {
		while (rot[j] >= M_PI) 
			rot[j] -= M_PI*2;
		while (rot[j] < -M_PI) 
			rot[j] += M_PI*2;
	}
}


void clip_rotations( Vector& rot )
{
	int j;
	// clip everything to : -180 <= x < 180

	for (j = 0; j < 3; j++) {
		while (rot[j] >= 180) 
			rot[j] -= 180*2;
		while (rot[j] < -180) 
			rot[j] += 180*2;
	}
}


void Build_Reference( s_source_t *psource)
{
	int		i, parent;
	Vector	angle;

	for (i = 0; i < psource->numbones; i++)
	{
		matrix3x4_t m;
		AngleMatrix( psource->rawanim[0][i].rot, m );
		m[0][3] = psource->rawanim[0][i].pos[0];
		m[1][3] = psource->rawanim[0][i].pos[1];
		m[2][3] = psource->rawanim[0][i].pos[2];

		parent = psource->localBone[i].parent;
		if (parent == -1) 
		{
			// scale the done pos.
			// calc rotational matrices
			MatrixCopy( m, psource->boneToPose[i] );
		}
		else 
		{
			// calc compound rotational matrices
			// FIXME : Hey, it's orthogical so inv(A) == transpose(A)
			ConcatTransforms( psource->boneToPose[parent], m, psource->boneToPose[i] );
		}
		// vprint( 0, "%3d %f %f %f\n", i, psource->bonefixup[i].worldorg[0], psource->bonefixup[i].worldorg[1], psource->bonefixup[i].worldorg[2] );
		/*
		AngleMatrix( angle, m );
		vprint( 0, "%8.4f %8.4f %8.4f\n", m[0][0], m[1][0], m[2][0] );
		vprint( 0, "%8.4f %8.4f %8.4f\n", m[0][1], m[1][1], m[2][1] );
		vprint( 0, "%8.4f %8.4f %8.4f\n", m[0][2], m[1][2], m[2][2] );
		*/
	}
}

int Grab_Nodes( s_node_t *pnodes )
{
	//
	// s_node_t structure: index is index!!
	//
	int index;
	char name[1024];
	int parent;
	int numbones = 0;

	// Init parent to none
	for (index = 0; index < MAXSTUDIOSRCBONES; index++)
	{
		pnodes[index].parent = -1;
	}

	// March through nodes lines
	while (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) != NULL) 
	{
		g_iLinecount++;
		// get tokens
		if (sscanf( g_szLine, "%d \"%[^\"]\" %d", &index, name, &parent ) == 3)
		{
			// check for duplicated bones
			/*
			if (strlen(pnodes[index].name) != 0)
			{
				MdlError( "bone \"%s\" exists more than once\n", name );
			}
			*/
			// copy name to struct array
			V_strcpy_safe( pnodes[index].name, name );
			// set parent into struct array
			pnodes[index].parent = parent;
			// increment numbones
			if (index > numbones)
			{
				numbones = index;
			}
		}
		else 
		{
			return numbones + 1;
		}
	}
	MdlError( "Unexpected EOF at line %d\n", g_iLinecount );
	return 0;
}

void Grab_Vertexanimation( s_source_t *psource )
{
	char	cmd[1024];
	int		index;
	Vector	pos;
	Vector	normal;
	int		t = -1;
	int		count = 0;
	static s_vertanim_t	tmpvanim[MAXSTUDIOVERTS*4];

	while (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) != NULL) 
	{
		g_iLinecount++;
		if (sscanf( g_szLine, "%d %f %f %f %f %f %f", &index, &pos[0], &pos[1], &pos[2], &normal[0], &normal[1], &normal[2] ) == 7)
		{
			if (psource->startframe < 0)
			{
				MdlError( "Missing frame start(%d) : %s", g_iLinecount, g_szLine );
			}

			if (t < 0)
			{
				MdlError( "VTA Frame Sync (%d) : %s", g_iLinecount, g_szLine );
			}

			tmpvanim[count].vertex = index;
			VectorCopy( pos, tmpvanim[count].pos );
			VectorCopy( normal, tmpvanim[count].normal );
			count++;

			if (index >= psource->numvertices)
				psource->numvertices = index + 1;
		}
		else
		{
			// flush data

			if (count)
			{
				psource->numvanims[t] = count;

				psource->vanim[t] = (s_vertanim_t *)kalloc( count, sizeof( s_vertanim_t ) );

				memcpy( psource->vanim[t], tmpvanim, count * sizeof( s_vertanim_t ) );
			}
			else if (t > 0)
			{
				psource->numvanims[t] = 0;
			}

			// next command
			if (sscanf( g_szLine, "%1023s %d", cmd, &index ))
			{
				if (strcmp( cmd, "time" ) == 0) 
				{
					t = index;
					count = 0;

					if (t < psource->startframe)
					{
						MdlError( "Frame MdlError(%d) : %s", g_iLinecount, g_szLine );
					}
					if (t > psource->endframe)
					{
						MdlError( "Frame MdlError(%d) : %s", g_iLinecount, g_szLine );
					}

					t -= psource->startframe;
				}
				else if (strcmp( cmd, "end") == 0) 
				{
					psource->numframes = psource->endframe - psource->startframe + 1;
					return;
				}
				else
				{
					MdlError( "MdlError(%d) : %s", g_iLinecount, g_szLine );
				}

			}
			else
			{
				MdlError( "MdlError(%d) : %s", g_iLinecount, g_szLine );
			}
		}
	}
	MdlError( "unexpected EOF: %s\n", psource->filename );
}

void Grab_Animation( s_source_t *psource )
{
	Vector pos;
	RadianEuler rot;
	char cmd[1024];
	int index;
	int	t = -99999999;
	int size;

	// Init startframe
	psource->startframe = -1;

	// size per frame
	size = psource->numbones * sizeof( s_bone_t );

	// march through animation
	while (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) != NULL) 
	{
		// linecount
		g_iLinecount++;
		// split if big enoough
		if (sscanf( g_szLine, "%d %f %f %f %f %f %f", &index, &pos[0], &pos[1], &pos[2], &rot[0], &rot[1], &rot[2] ) == 7)
		{
			// startframe is sanity check for having determined time
			if (psource->startframe < 0)
			{
				MdlError( "Missing frame start(%d) : %s", g_iLinecount, g_szLine );
			}

			// scale if pertinent
			scale_vertex( pos );
			VectorCopy( pos, psource->rawanim[t][index].pos );
			VectorCopy( rot, psource->rawanim[t][index].rot );

			clip_rotations( rot ); // !!!
		}
		else if (sscanf( g_szLine, "%1023s %d", cmd, &index ))
		{
			// get time
			if (strcmp( cmd, "time" ) == 0) 
			{
				// again time IS an index
				t = index;
				if (psource->startframe == -1)
				{
					psource->startframe = t;
				}
				// sanity check time (little funny logic here, see previous IF)
				if (t < psource->startframe)
				{
					MdlError( "Frame MdlError(%d) : %s", g_iLinecount, g_szLine );
				}
				// bump up endframe?
				if (t > psource->endframe)
				{
					psource->endframe = t;
				}
				// make t into pure index
				t -= psource->startframe;

				// check for memory allocation
				if (psource->rawanim[t] == NULL)
				{
					// Allocate 1 frame of full bonecount 
					psource->rawanim[t] = (s_bone_t *)kalloc( 1, size );

					// duplicate previous frames keys?? preventative sanity?
					if (t > 0 && psource->rawanim[t-1])
					{
						for (int j = 0; j < psource->numbones; j++)
						{
							VectorCopy( psource->rawanim[t-1][j].pos, psource->rawanim[t][j].pos );
							VectorCopy( psource->rawanim[t-1][j].rot, psource->rawanim[t][j].rot );
						}
					}
				}
				else
				{
					// MdlError( "%s has duplicated frame %d\n", psource->filename, t );
				}
			}
			else if (strcmp( cmd, "end") == 0) 
			{
				psource->numframes = psource->endframe - psource->startframe + 1;

				for (t = 0; t < psource->numframes; t++)
				{
					if (psource->rawanim[t] == NULL)
					{
						MdlError( "%s is missing frame %d\n", psource->filename, t + psource->startframe );
					}
				}

				Build_Reference( psource );
				return;
			}
			else
			{
				MdlError( "MdlError(%d) : %s", g_iLinecount, g_szLine );
			}
		}
		else
		{
			MdlError( "MdlError(%d) : %s", g_iLinecount, g_szLine );
		}
	}

	MdlError( "unexpected EOF: %s\n", psource->filename );
}

int lookup_index( s_source_t *psource, int material, Vector& vertex, Vector& normal, Vector2D texcoord )
{
	int i;

	for (i = 0; i < numvlist; i++) 
	{
		if (v_listdata[i].m == material
			&& DotProduct( g_normal[i], normal ) > normal_blend
			&& VectorCompare( g_vertex[i], vertex )
			&& g_texcoord[i][0] == texcoord[0]
			&& g_texcoord[i][1] == texcoord[1])
		{
			v_listdata[i].lastref = numvlist;
			return i;
		}
	}
	if (i >= MAXSTUDIOVERTS) {
		MdlError( "too many indices in source: \"%s\"\n", psource->filename);
	}

	VectorCopy( vertex, g_vertex[i] );
	VectorCopy( normal, g_normal[i] );
	Vector2Copy( texcoord, g_texcoord[i] );

	v_listdata[i].v = i;
	v_listdata[i].m = material;
	v_listdata[i].n = i;
	v_listdata[i].t = i;

	v_listdata[i].firstref = numvlist;
	v_listdata[i].lastref = numvlist;

	numvlist = i + 1;
	return i;
}


void ParseFaceData( s_source_t *psource, int material, s_face_t *pFace )
{
	int index[3];
	int i, j;
	Vector p;
	Vector normal;
	Vector2D t;
	int		iCount, bones[MAXSTUDIOSRCBONES];
	float   weights[MAXSTUDIOSRCBONES];
	int bone;

	for (j = 0; j < 3; j++) 
	{
		memset( g_szLine, 0, sizeof( g_szLine ) );

		if (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) == NULL) 
		{
			MdlError("%s: error on g_szLine %d: %s", g_szFilename, g_iLinecount, g_szLine );
		}

		iCount = 0;

		g_iLinecount++;
		i = sscanf( g_szLine, "%d %f %f %f %f %f %f %f %f %d %d %f %d %f %d %f %d %f",
			&bone, 
			&p[0], &p[1], &p[2], 
			&normal[0], &normal[1], &normal[2], 
			&t[0], &t[1],
			&iCount,
			&bones[0], &weights[0], &bones[1], &weights[1], &bones[2], &weights[2], &bones[3], &weights[3] );
			
		if (i < 9) 
			continue;

		if (bone < 0 || bone >= psource->numbones) 
		{
			MdlError("bogus bone index\n%d %s :\n%s", g_iLinecount, g_szFilename, g_szLine );
		}

		//Scale face pos
		scale_vertex( p );
		
		// continue parsing more bones.
		// FIXME: don't we have a built in parser that'll do this?
		if (iCount > 4)
		{
			int k;
			int ctr = 0;
			char *token;
			for (k = 0; k < 18; k++)
			{
				while (g_szLine[ctr] == ' ')
				{
					ctr++;
				}
				token = strtok( &g_szLine[ctr], " " );
				ctr += strlen( token ) + 1;
			}
			for (k = 4; k < iCount && k < MAXSTUDIOSRCBONES; k++)
			{
				while (g_szLine[ctr] == ' ')
				{
					ctr++;
				}
				token = strtok( &g_szLine[ctr], " " );
				ctr += strlen( token ) + 1;

				bones[k] = atoi(token);

				token = strtok( &g_szLine[ctr], " " );
				ctr += strlen( token ) + 1;
			
				weights[k] = atof(token);
			}
			// vprint( 0, "%d ", iCount );

			//vprint( 0, "\n");
			//exit(1);
		}

		// adjust_vertex( p );
		// scale_vertex( p );

		// move vertex position to object space.
		// VectorSubtract( p, psource->bonefixup[bone].worldorg, tmp );
		// VectorTransform(tmp, psource->bonefixup[bone].im, p );

		// move normal to object space.
		// VectorCopy( normal, tmp );
		// VectorTransform(tmp, psource->bonefixup[bone].im, normal );
		// VectorNormalize( normal );

		// invert v
		t[1] = 1.0 - t[1];

		index[j] = lookup_index( psource, material, p, normal, t );

		if (i == 9 || iCount == 0)
		{
			g_bone[index[j]].numbones = 1;
			g_bone[index[j]].bone[0] = bone;
			g_bone[index[j]].weight[0] = 1.0;
		}
		else
		{
			iCount = SortAndBalanceBones( iCount, MAXSTUDIOBONEWEIGHTS, bones, weights );

			g_bone[index[j]].numbones = iCount;
			for (i = 0; i < iCount; i++)
			{
				g_bone[index[j]].bone[i] = bones[i];
				g_bone[index[j]].weight[i] = weights[i];
			}
		}
	}

	// pFace->material = material; // BUG
	pFace->a		= index[0];
	pFace->b		= index[1];
	pFace->c		= index[2];
	Assert( ((pFace->a & 0xF0000000) == 0) && ((pFace->b & 0xF0000000) == 0) && 
		((pFace->c & 0xF0000000) == 0) );

	if (flip_triangles)
	{
		j = pFace->b;  pFace->b  = pFace->c;  pFace->c  = j;
	}
}

int use_texture_as_material( int textureindex )
{
	if (g_texture[textureindex].material == -1)
	{
		// vprint( 0, "%d %d %s\n", textureindex, g_nummaterials, g_texture[textureindex].name );
		g_material[g_nummaterials] = textureindex;
		g_texture[textureindex].material = g_nummaterials++;
	}

	return g_texture[textureindex].material;
}

int material_to_texture( int material )
{
	int i;
	for (i = 0; i < g_numtextures; i++)
	{
		if (g_texture[i].material == material)
		{
			return i;
		}
	}
	return -1;
}

int lookup_texture( char *texturename, int maxlen )
{
	int i;

	Q_StripExtension( texturename, texturename, maxlen );

	for (i = 0; i < g_numtextures; i++) 
	{
		if (stricmp( g_texture[i].name, texturename ) == 0) 
		{
			return i;
		}
	}

	if (i >= MAXSTUDIOSKINS)
		MdlError("Too many materials used, max %d\n", ( int )MAXSTUDIOSKINS );

//	vprint( 0,  "texture %d = %s\n", i, texturename );
	V_strcpy_safe( g_texture[i].name, texturename );

	g_texture[i].material = -1;
	/*
	if (stristr( texturename, "chrome" ) != NULL) {
		texture[i].flags = STUDIO_NF_FLATSHADE | STUDIO_NF_CHROME;
	}
	else {
		texture[i].flags = 0;
	}
	*/
	g_numtextures++;
	return i;
}

int SortAndBalanceBones( int iCount, int iMaxCount, int bones[], float weights[] )
{
	int i;

	// collapse duplicate bone weights
	for (i = 0; i < iCount-1; i++)
	{
		int j;
		for (j = i + 1; j < iCount; j++)
		{
			if (bones[i] == bones[j])
			{
				weights[i] += weights[j];
				weights[j] = 0.0;
			}
		}
	}

	// do sleazy bubble sort
	int bShouldSort;
	do {
		bShouldSort = false;
		for (i = 0; i < iCount-1; i++)
		{
			if (weights[i+1] > weights[i])
			{
				int j = bones[i+1]; bones[i+1] = bones[i]; bones[i] = j;
				float w = weights[i+1]; weights[i+1] = weights[i]; weights[i] = w;
				bShouldSort = true;
			}
		}
	} while (bShouldSort);

	// throw away all weights less than 1/20th
	while (iCount > 1 && weights[iCount-1] < 0.05)
	{
		iCount--;
	}

	// clip to the top iMaxCount bones
	if (iCount > iMaxCount)
	{
		iCount = iMaxCount;
	}

	float t = 0;
	for (i = 0; i < iCount; i++)
	{
		t += weights[i];
	}

	if (t <= 0.0)
	{
		// missing weights?, go ahead and evenly share?
		// FIXME: shouldn't this error out?
		t = 1.0 / iCount;

		for (i = 0; i < iCount; i++)
		{
			weights[i] = t;
		}
	}
	else
	{
		// scale to sum to 1.0
		t = 1.0 / t;

		for (i = 0; i < iCount; i++)
		{
			weights[i] = weights[i] * t;
		}
	}

	return iCount;
}

int vlistCompare( const void *elem1, const void *elem2 )
{
	v_unify_t *u1 = &v_listdata[*(int *)elem1];
	v_unify_t *u2 = &v_listdata[*(int *)elem2];

	// sort by material
	if (u1->m < u2->m)
		return -1;
	if (u1->m > u2->m)
		return 1;

	// sort by last used
	if (u1->lastref < u2->lastref)
		return -1;
	if (u1->lastref > u2->lastref)
		return 1;

	return 0;
}

int faceCompare( const void *elem1, const void *elem2 )
{
	int i1 = *(int *)elem1;
	int i2 = *(int *)elem2;

	// sort by material
	if (g_face[i1].material < g_face[i2].material)
		return -1;
	if (g_face[i1].material > g_face[i2].material)
		return 1;

	// sort by original usage
	if (i1 < i2)
		return -1;
	if (i1 > i2)
		return 1;

	return 0;
}

#define SMALL_FLOAT 1e-12

// NOTE: This routine was taken (and modified) from NVidia's BlinnReflection demo
// Creates basis vectors, based on a vertex and index list.
// See the NVidia white paper 'GDC2K PerPixel Lighting' for a description
// of how this computation works
static void CalcTriangleTangentSpace( s_source_t *pSrc, int v1, int v2, int v3, 
									  Vector &sVect, Vector &tVect )
{
/*
	static bool firstTime = true;
	static FILE *fp = NULL;
	if( firstTime )
	{
		firstTime = false;
		fp = fopen( "crap.out", "w" );
	}
*/
    
	/* Compute the partial derivatives of X, Y, and Z with respect to S and T. */
	Vector2D t0( pSrc->texcoord[v1][0], pSrc->texcoord[v1][1] );
	Vector2D t1( pSrc->texcoord[v2][0], pSrc->texcoord[v2][1] );
	Vector2D t2( pSrc->texcoord[v3][0], pSrc->texcoord[v3][1] );
	Vector p0( pSrc->vertex[v1][0], pSrc->vertex[v1][1], pSrc->vertex[v1][2] );
	Vector p1( pSrc->vertex[v2][0], pSrc->vertex[v2][1], pSrc->vertex[v2][2] );
	Vector p2( pSrc->vertex[v3][0], pSrc->vertex[v3][1], pSrc->vertex[v3][2] );

	sVect.Init( 0.0f, 0.0f, 0.0f );
	tVect.Init( 0.0f, 0.0f, 0.0f );

	// x, s, t
	Vector edge01 = Vector( p1.x - p0.x, t1.x - t0.x, t1.y - t0.y );
	Vector edge02 = Vector( p2.x - p0.x, t2.x - t0.x, t2.y - t0.y );

	Vector cross;
	CrossProduct( edge01, edge02, cross );
	if( fabs( cross.x ) > SMALL_FLOAT )
	{
		sVect.x += -cross.y / cross.x;
		tVect.x += -cross.z / cross.x;
	}

	// y, s, t
	edge01 = Vector( p1.y - p0.y, t1.x - t0.x, t1.y - t0.y );
	edge02 = Vector( p2.y - p0.y, t2.x - t0.x, t2.y - t0.y );

	CrossProduct( edge01, edge02, cross );
	if( fabs( cross.x ) > SMALL_FLOAT )
	{
		sVect.y += -cross.y / cross.x;
		tVect.y += -cross.z / cross.x;
	}
	
	// z, s, t
	edge01 = Vector( p1.z - p0.z, t1.x - t0.x, t1.y - t0.y );
	edge02 = Vector( p2.z - p0.z, t2.x - t0.x, t2.y - t0.y );

	CrossProduct( edge01, edge02, cross );
	if( fabs( cross.x ) > SMALL_FLOAT )
	{
		sVect.z += -cross.y / cross.x;
		tVect.z += -cross.z / cross.x;
	}

	// Normalize sVect and tVect
	VectorNormalize( sVect );
	VectorNormalize( tVect );

/*
	// Calculate flat normal
	Vector flatNormal;
	edge01 = p1 - p0;
	edge02 = p2 - p0;
	CrossProduct( edge02, edge01, flatNormal );
	VectorNormalize( flatNormal );
	
	// Get the average position
	Vector avgPos = ( p0 + p1 + p2 ) / 3.0f;

	// Draw the svect
	Vector endS = avgPos + sVect * .2f;
	fvprint( 0,  fp, "2\n" );
	fvprint( 0,  fp, "%f %f %f 1.0 0.0 0.0\n", endS[0], endS[1], endS[2] );
	fvprint( 0,  fp, "%f %f %f 1.0 0.0 0.0\n", avgPos[0], avgPos[1], avgPos[2] );
	
	// Draw the tvect
	Vector endT = avgPos + tVect * .2f;
	fvprint( 0,  fp, "2\n" );
	fvprint( 0,  fp, "%f %f %f 0.0 1.0 0.0\n", endT[0], endT[1], endT[2] );
	fvprint( 0,  fp, "%f %f %f 0.0 1.0 0.0\n", avgPos[0], avgPos[1], avgPos[2] );
	
	// Draw the normal
	Vector endN = avgPos + flatNormal * .2f;
	fvprint( 0,  fp, "2\n" );
	fvprint( 0,  fp, "%f %f %f 0.0 0.0 1.0\n", endN[0], endN[1], endN[2] );
	fvprint( 0,  fp, "%f %f %f 0.0 0.0 1.0\n", avgPos[0], avgPos[1], avgPos[2] );
	
	// Draw the wireframe of the triangle in white.
	fvprint( 0,  fp, "2\n" );
	fvprint( 0,  fp, "%f %f %f 1.0 1.0 1.0\n", p0[0], p0[1], p0[2] );
	fvprint( 0,  fp, "%f %f %f 1.0 1.0 1.0\n", p1[0], p1[1], p1[2] );
	fvprint( 0,  fp, "2\n" );
	fvprint( 0,  fp, "%f %f %f 1.0 1.0 1.0\n", p1[0], p1[1], p1[2] );
	fvprint( 0,  fp, "%f %f %f 1.0 1.0 1.0\n", p2[0], p2[1], p2[2] );
	fvprint( 0,  fp, "2\n" );
	fvprint( 0,  fp, "%f %f %f 1.0 1.0 1.0\n", p2[0], p2[1], p2[2] );
	fvprint( 0,  fp, "%f %f %f 1.0 1.0 1.0\n", p0[0], p0[1], p0[2] );

	// Draw a slightly shrunken version of the geometry to hide surfaces
	Vector tmp0 = p0 - flatNormal * .1f;
	Vector tmp1 = p1 - flatNormal * .1f;
	Vector tmp2 = p2 - flatNormal * .1f;
	fvprint( 0,  fp, "3\n" );
	fvprint( 0,  fp, "%f %f %f 0.1 0.1 0.1\n", tmp0[0], tmp0[1], tmp0[2] );
	fvprint( 0,  fp, "%f %f %f 0.1 0.1 0.1\n", tmp1[0], tmp1[1], tmp1[2] );
	fvprint( 0,  fp, "%f %f %f 0.1 0.1 0.1\n", tmp2[0], tmp2[1], tmp2[2] );
		
	fflush( fp );
*/
}

typedef CUtlVector<int> CIntVector;

void CalcModelTangentSpaces( s_source_t *pSrc )
{
	// Build a map from vertex to a list of triangles that share the vert.
	int meshID;
	for( meshID = 0; meshID < pSrc->nummeshes; meshID++ )
	{
		s_mesh_t *pMesh = &pSrc->mesh[pSrc->meshindex[meshID]];
		CUtlVector<CIntVector> vertToTriMap;
		vertToTriMap.AddMultipleToTail( pMesh->numvertices );
		int triID;
		for( triID = 0; triID < pMesh->numfaces; triID++ )
		{
			s_face_t *pFace = &pSrc->face[triID + pMesh->faceoffset];
			vertToTriMap[pFace->a].AddToTail( triID );
			vertToTriMap[pFace->b].AddToTail( triID );
			vertToTriMap[pFace->c].AddToTail( triID );
		}

		// Calculate the tangent space for each triangle.
		CUtlVector<Vector> triSVect;
		CUtlVector<Vector> triTVect;
		triSVect.AddMultipleToTail( pMesh->numfaces );
		triTVect.AddMultipleToTail( pMesh->numfaces );
		for( triID = 0; triID < pMesh->numfaces; triID++ )
		{
			s_face_t *pFace = &pSrc->face[triID + pMesh->faceoffset];
			CalcTriangleTangentSpace( pSrc, 
				pMesh->vertexoffset + pFace->a, 
				pMesh->vertexoffset + pFace->b, 
				pMesh->vertexoffset + pFace->c, 
				triSVect[triID], triTVect[triID] );
		}	

		// calculate an average tangent space for each vertex.
		int vertID;
		for( vertID = 0; vertID < pMesh->numvertices; vertID++ )
		{
			const Vector &normal = pSrc->normal[vertID+pMesh->vertexoffset];
			Vector4D &finalSVect = pSrc->tangentS[vertID+pMesh->vertexoffset];
			Vector sVect, tVect;

			sVect.Init( 0.0f, 0.0f, 0.0f );
			tVect.Init( 0.0f, 0.0f, 0.0f );
			for( triID = 0; triID < vertToTriMap[vertID].Size(); triID++ )
			{
				sVect += triSVect[vertToTriMap[vertID][triID]];
				tVect += triTVect[vertToTriMap[vertID][triID]];
			}

			// In the case of zbrush, everything needs to be treated as smooth.
			if( g_bZBrush )
			{
				int vertID2;
				Vector vertPos1( pSrc->vertex[vertID][0], pSrc->vertex[vertID][1], pSrc->vertex[vertID][2] );
				for( vertID2 = 0; vertID2 < pMesh->numvertices; vertID2++ )
				{
					if( vertID2 == vertID )
					{
						continue;
					}
					Vector vertPos2( pSrc->vertex[vertID2][0], pSrc->vertex[vertID2][1], pSrc->vertex[vertID2][2] );
					if( vertPos1 == vertPos2 )
					{
						int triID2;
						for( triID2 = 0; triID2 < vertToTriMap[vertID2].Size(); triID2++ )
						{
							sVect += triSVect[vertToTriMap[vertID2][triID2]];
							tVect += triTVect[vertToTriMap[vertID2][triID2]];
						}
					}
				}
			}

			// make an orthonormal system.
			// need to check if we are left or right handed.
			Vector tmpVect;
			CrossProduct( sVect, tVect, tmpVect );
			bool leftHanded = DotProduct( tmpVect, normal ) < 0.0f;
			if( !leftHanded )
			{
				CrossProduct( normal, sVect, tVect );
				CrossProduct( tVect, normal, sVect );
				VectorNormalize( sVect );
				VectorNormalize( tVect );
				finalSVect[0] = sVect[0];
				finalSVect[1] = sVect[1];
				finalSVect[2] = sVect[2];
				finalSVect[3] = 1.0f;
			}
			else
			{
				CrossProduct( sVect, normal, tVect );
				CrossProduct( normal, tVect, sVect );
				VectorNormalize( sVect );
				VectorNormalize( tVect );
				finalSVect[0] = sVect[0];
				finalSVect[1] = sVect[1];
				finalSVect[2] = sVect[2];
				finalSVect[3] = -1.0f;
			}
		}
	}
}

void BuildIndividualMeshes( s_source_t *psource )
{
	int i, j, k;
	
	// sort new vertices by materials, last used
	static int v_listsort[MAXSTUDIOVERTS];	// map desired order to vlist entry
	static int v_ilistsort[MAXSTUDIOVERTS]; // map vlist entry to desired order

	for (i = 0; i < numvlist; i++)
	{
		v_listsort[i] = i;
	}
	qsort( v_listsort, numvlist, sizeof( int ), vlistCompare );
	for (i = 0; i < numvlist; i++)
	{
		v_ilistsort[v_listsort[i]] = i;
	}


	// allocate memory
	psource->numvertices = numvlist;
	psource->localBoneweight = (s_boneweight_t *)kalloc( psource->numvertices, sizeof( s_boneweight_t ) );
	psource->globalBoneweight = NULL;
	psource->vertexInfo = (s_vertexinfo_t *)kalloc( psource->numvertices, sizeof( s_vertexinfo_t ) );
	psource->vertex = new Vector[psource->numvertices];
	psource->normal = new Vector[psource->numvertices];
	psource->tangentS = new Vector4D[psource->numvertices];
	psource->texcoord = (Vector2D *)kalloc( psource->numvertices, sizeof( Vector2D ) );

	// create arrays of unique vertexes, normals, texcoords.
	for (i = 0; i < psource->numvertices; i++)
	{
		j = v_listsort[i];

		VectorCopy( g_vertex[v_listdata[j].v], psource->vertex[i] );
		VectorCopy( g_normal[v_listdata[j].n], psource->normal[i] );		
		Vector2Copy( g_texcoord[v_listdata[j].t], psource->texcoord[i] );

		psource->localBoneweight[i].numbones		= g_bone[v_listdata[j].v].numbones;
		int k;
		for( k = 0; k < MAXSTUDIOBONEWEIGHTS; k++ )
		{
			psource->localBoneweight[i].bone[k]		= g_bone[v_listdata[j].v].bone[k];
			psource->localBoneweight[i].weight[k]	= g_bone[v_listdata[j].v].weight[k];
		}

		// store a bunch of other info
		psource->vertexInfo[i].material		= v_listdata[j].m;

		psource->vertexInfo[i].firstref		= v_listdata[j].firstref;
		psource->vertexInfo[i].lastref		= v_listdata[j].lastref;
		// vprint( 0, "%4d : %2d :  %6.2f %6.2f %6.2f\n", i, psource->boneweight[i].bone[0], psource->vertex[i][0], psource->vertex[i][1], psource->vertex[i][2] );
	}

	// sort faces by materials, last used.
	static int facesort[MAXSTUDIOTRIANGLES];	// map desired order to src_face entry
	static int ifacesort[MAXSTUDIOTRIANGLES];	// map src_face entry to desired order
	
	for (i = 0; i < g_numfaces; i++)
	{
		facesort[i] = i;
	}
	qsort( facesort, g_numfaces, sizeof( int ), faceCompare );
	for (i = 0; i < g_numfaces; i++)
	{
		ifacesort[facesort[i]] = i;
	}

	psource->numfaces = g_numfaces;
	// find first occurance for each material
	for (k = 0; k < MAXSTUDIOSKINS; k++)
	{
		psource->mesh[k].numvertices = 0;
		psource->mesh[k].vertexoffset = psource->numvertices;

		psource->mesh[k].numfaces = 0;
		psource->mesh[k].faceoffset = g_numfaces;
	}

	// find first and count of indices per material
	for (i = 0; i < psource->numvertices; i++)
	{
		k = psource->vertexInfo[i].material;
		psource->mesh[k].numvertices++;
		if (psource->mesh[k].vertexoffset > i)
			psource->mesh[k].vertexoffset = i;
	}

	// find first and count of faces per material
	for (i = 0; i < psource->numfaces; i++)
	{
		k = g_face[facesort[i]].material;

		psource->mesh[k].numfaces++;
		if (psource->mesh[k].faceoffset > i)
			psource->mesh[k].faceoffset = i;
	}

	/*
	for (k = 0; k < MAXSTUDIOSKINS; k++)
	{
		vprint( 0, "%d : %d:%d %d:%d\n", k, psource->mesh[k].numvertices, psource->mesh[k].vertexoffset, psource->mesh[k].numfaces, psource->mesh[k].faceoffset );
	}
	*/

	// create remapped faces
	psource->face = (s_face_t *)kalloc( psource->numfaces, sizeof( s_face_t ));
	for (k = 0; k < MAXSTUDIOSKINS; k++)
	{
		if (psource->mesh[k].numfaces)
		{
			psource->meshindex[psource->nummeshes] = k;

			for (i = psource->mesh[k].faceoffset; i < psource->mesh[k].numfaces + psource->mesh[k].faceoffset; i++)
			{
				j = facesort[i];

				psource->face[i].a = v_ilistsort[g_src_uface[j].a] - psource->mesh[k].vertexoffset;
				psource->face[i].b = v_ilistsort[g_src_uface[j].b] - psource->mesh[k].vertexoffset;
				psource->face[i].c = v_ilistsort[g_src_uface[j].c] - psource->mesh[k].vertexoffset;
				Assert( ((psource->face[i].a & 0xF0000000) == 0) && ((psource->face[i].b & 0xF0000000) == 0) && 
					((psource->face[i].c & 0xF0000000) == 0) );
				// vprint( 0, "%3d : %4d %4d %4d\n", i, psource->face[i].a, psource->face[i].b, psource->face[i].c );
			}

			psource->nummeshes++;
		}
	}

	CalcModelTangentSpaces( psource );
}

void Grab_Triangles( s_source_t *psource )
{
	int		i;
	Vector	vmin, vmax;

	vmin[0] = vmin[1] = vmin[2] = 99999;
	vmax[0] = vmax[1] = vmax[2] = -99999;

	g_numfaces = 0;
	numvlist = 0;
 
	//
	// load the base triangles
	//
	int texture;
	int material;
	char texturename[64];

	while (1) 
	{
		if (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) == NULL) 
			break;

		g_iLinecount++;

		// check for end
		if (IsEnd( g_szLine )) 
			break;

		// Look for extra junk that we may want to avoid...
		int nLineLength = strlen( g_szLine );
		if (nLineLength >= 64)
		{
			MdlWarning("Unexpected data at line %d, (need a texture name) ignoring...\n", g_iLinecount );
			continue;
		}

		// strip off trailing smag
		V_strcpy_safe( texturename, g_szLine );
		for (i = strlen( texturename ) - 1; i >= 0 && ! isgraph( texturename[i] ); i--)
		{
		}
		texturename[i + 1] = '\0';

		// funky texture overrides
		for (i = 0; i < numrep; i++)  
		{
			if (sourcetexture[i][0] == '\0') 
			{
				strcpy( texturename, defaulttexture[i] );
				break;
			}
			if (stricmp( texturename, sourcetexture[i]) == 0) 
			{
				strcpy( texturename, defaulttexture[i] );
				break;
			}
		}

		if (texturename[0] == '\0')
		{
			// weird source problem, skip them
			fgets( g_szLine, sizeof( g_szLine ), g_fpInput );
			fgets( g_szLine, sizeof( g_szLine ), g_fpInput );
			fgets( g_szLine, sizeof( g_szLine ), g_fpInput );
			g_iLinecount += 3;
			continue;
		}

		if (stricmp( texturename, "null.bmp") == 0 || stricmp( texturename, "null.tga") == 0)
		{
			// skip all faces with the null texture on them.
			fgets( g_szLine, sizeof( g_szLine ), g_fpInput );
			fgets( g_szLine, sizeof( g_szLine ), g_fpInput );
			fgets( g_szLine, sizeof( g_szLine ), g_fpInput );
			g_iLinecount += 3;
			continue;
		}

		texture = lookup_texture( texturename, sizeof( texturename ) );
		psource->texmap[texture] = texture;	// hack, make it 1:1
		material = use_texture_as_material( texture );

		s_face_t f;
		ParseFaceData( psource, material, &f );
	
		g_src_uface[g_numfaces] = f;
		g_face[g_numfaces].material = material;
		g_numfaces++;
	}

	BuildIndividualMeshes( psource );
}

//--------------------------------------------------------------------
// Load a SMD file
//--------------------------------------------------------------------  
int Load_SMD ( s_source_t *psource )
{
	char	cmd[1024];
	int		option;

	// Open file
	if (!OpenGlobalFile( psource->filename ))
		return 0;

	// verbose
	if( !g_quiet )
	{
		printf ("SMD MODEL %s\n", psource->filename);
	}

	//March through lines
	g_iLinecount = 0;
	while (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) != NULL) 
	{
		g_iLinecount++;
		int numRead = sscanf( g_szLine, "%s %d", cmd, &option );

		// Blank line
		if ((numRead == EOF) || (numRead == 0))
			continue;

		if (strcmp( cmd, "version" ) == 0) 
		{
			if (option != 1) 
			{
				MdlError("bad version\n");
			}
		}
		// Get hierarchy?
		else if (strcmp( cmd, "nodes" ) == 0) 
		{
			psource->numbones = Grab_Nodes( psource->localBone );
		}
		// Get animation??
		else if (strcmp( cmd, "skeleton" ) == 0) 
		{
			Grab_Animation( psource );
		}
		// Geo?
		else if (strcmp( cmd, "triangles" ) == 0) 
		{
			Grab_Triangles( psource );
		}
		// Geo animation
		else if (strcmp( cmd, "vertexanimation" ) == 0) 
		{
			Grab_Vertexanimation( psource );
		}
		else 
		{
			MdlWarning("unknown studio command\n" );
		}
	}
	fclose( g_fpInput );

	is_v1support = true;

	return 1;
}

//-----------------------------------------------------------------------------
// Checks to see if the model source was already loaded
//-----------------------------------------------------------------------------
static s_source_t *FindCachedSource( char const* name, char const* xext )
{
	int i;

	if( xext[0] )
	{
		// we know what extension is necessary. . look for it.
		sprintf (g_szFilename, "%s%s.%s", cddir[numdirs], name, xext );
		for (i = 0; i < g_numsources; i++)
		{
			if (stricmp( g_szFilename, g_source[i]->filename ) == 0)
				return g_source[i];
		}
	}
	else
	{
		// we don't know what extension to use, so look for all of 'em.
		sprintf (g_szFilename, "%s%s.vrm", cddir[numdirs], name );
		for (i = 0; i < g_numsources; i++)
		{
			if (stricmp( g_szFilename, g_source[i]->filename ) == 0)
				return g_source[i];
		}
		sprintf (g_szFilename, "%s%s.smd", cddir[numdirs], name );
		for (i = 0; i < g_numsources; i++)
		{
			if (stricmp( g_szFilename, g_source[i]->filename ) == 0)
				return g_source[i];
		}
		/*
		sprintf (g_szFilename, "%s%s.vta", cddir[numdirs], name );
		for (i = 0; i < g_numsources; i++)
		{
			if (stricmp( g_szFilename, g_source[i]->filename ) == 0)
				return g_source[i];
		}
		*/
	}

	// Not found
	return 0;
}

static void FlipFacing( s_source_t *pSrc )
{
	unsigned short tmp;

	int i, j;
	for( i = 0; i < pSrc->nummeshes; i++ )
	{
		s_mesh_t *pMesh = &pSrc->mesh[i];
		for( j = 0; j < pMesh->numfaces; j++ )
		{
			s_face_t &f = pSrc->face[pMesh->faceoffset + j];
			tmp = f.b;  f.b  = f.c;  f.c  = tmp;
		}
	}
}

//-----------------------------------------------------------------------------
// Loads an animation source
//-----------------------------------------------------------------------------

s_source_t *Load_Source( char const *name, const char *ext, bool reverse, bool isActiveModel )
{
	// Sanity check number of source files
	if ( g_numsources >= MAXSTUDIOSEQUENCES )
		MdlError( "Load_Source( %s ) - overflowed g_numsources.", name );

	// Sanity check file and init
	Assert(name);
	int namelen = strlen(name) + 1;
	char* pTempName = (char*)_alloca( namelen );
	char xext[32];
	int result = false;

	// Local copy of filename
	strcpy( pTempName, name );
	
	// Sanity check file extension?
	Q_ExtractFileExtension( pTempName, xext, sizeof( xext ) );
	if (xext[0] == '\0')
	{
		V_strcpy_safe( xext, ext );
	}
	else
	{
		Q_StripExtension( pTempName, pTempName, namelen );
	}

	// Cached source, ie: already loaded model, legacy
	// s_source_t* pSource = FindCachedSource( pTempName, xext );
	// if (pSource)
	// {
		// if (isActiveModel)
			// pSource->isActiveModel = true;
		// return pSource;
	// }

	// allocate space and whatnot
	g_source[g_numsources] = (s_source_t *)kalloc( 1, sizeof( s_source_t ) );
	V_strcpy_safe( g_source[g_numsources]->filename, g_szFilename );

	// legacy stuff
	if (isActiveModel)
	{
		g_source[g_numsources]->isActiveModel = true;
	}

	// more ext sanity check
	if ( ( !result && xext[0] == '\0' ) || stricmp( xext, "smd" ) == 0)
	{
		Q_snprintf( g_szFilename, sizeof(g_szFilename), "%s%s.smd", cddir[numdirs], pTempName );
		V_strcpy_safe( g_source[g_numsources]->filename, g_szFilename );
 
		// Import part, load smd file
		result = Load_SMD( g_source[g_numsources] );
	}

	/*
	if ( ( !result && xext[0] == '\0' ) || stricmp( xext, "dmx" ) == 0)
	{
		Q_snprintf( g_szFilename, sizeof(g_szFilename), "%s%s.dmx", cddir[numdirs], pTempName );
		V_strcpy_safe( g_source[g_numsources]->filename, g_szFilename );

		// Import part, load smd file
		result = Load_DMX( g_source[g_numsources] );
	}
	*/

	// Oops
	if ( !result)
	{
		MdlError( "could not load file '%s'\n", g_source[g_numsources]->filename );
	}

	// bump up number of sources
	g_numsources++;
	if( reverse )
	{
		FlipFacing( g_source[g_numsources-1] );
	}
	return g_source[g_numsources-1];
}

void SaveNodes( s_source_t *source, CUtlBuffer& buf )
{
	if ( source->numbones <= 0 )
		return;

	buf.Printf( "nodes\n" );

	for ( int i = 0; i < source->numbones; ++i )
	{
		s_node_t *bone = &source->localBone[ i ];

		buf.Printf( "%d \"%s\" %d\n", i, bone->name, bone->parent );
	}

	buf.Printf( "end\n" );
}

// FIXME:  since we don't us a .qc, we could have problems with scaling, etc.???
void descale_vertex( Vector &org )
{
	float invscale = 1.0f / g_currentscale;

	org[0] = org[0] * invscale;
	org[1] = org[1] * invscale;
	org[2] = org[2] * invscale;
}

void SaveAnimation( s_source_t *source, CUtlBuffer& buf )
{
	if ( source->numbones <= 0 )
		return;

	buf.Printf( "skeleton\n" );

	for ( int frame = 0; frame < source->numframes; ++frame )
	{
		buf.Printf( "time %i\n", frame + source->startframe );

		for ( int i = 0; i < source->numbones; ++i )
		{
			s_bone_t *prev = NULL;
			if ( frame > 0 )
			{
				if ( source->rawanim[ frame - 1 ] )
				{
					prev = &source->rawanim[ frame - 1 ][ i ];
				}
			}

			Vector pos = source->rawanim[ frame ][ i ].pos;
			descale_vertex( pos );
			RadianEuler rot = source->rawanim[ frame ][ i ].rot;

// If this is enabled, then we delta this pos vs the prev frame and don't write out a sample if it's the same value...
#if 0
			if ( prev )
			{
				Vector ppos = source->rawanim[ frame -1 ][ i ].pos; 
				descale_vertex( pos );
				RadianEuler prot = source->rawanim[ frame -1 ][ i ].rot;

				// Only output it if there's a delta
				if ( ( ppos != pos ) || 
					Q_memcmp( &prot, &rot, sizeof( prot ) ) )
				{
					buf.Printf
						( "%d %f %f %f %f %f %f\n", 
						i,				// bone index
						pos[ 0 ],
						pos[ 1 ],
						pos[ 2 ],
						rot[ 0 ],
						rot[ 1 ],
						rot[ 2 ]
						);
				}
			}
			else
#endif
			{
				buf.Printf
					( "%d %f %f %f %f %f %f\n", 
					i,				// bone index
					pos[ 0 ],
					pos[ 1 ],
					pos[ 2 ],
					rot[ 0 ],
					rot[ 1 ],
					rot[ 2 ]
					);
			}
		}
	}

	buf.Printf( "end\n" );
}

void Save_SMD( char const *filename, s_source_t *source )
{
	// Text buffer
	CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );

	buf.Printf( "version 1\n" );

	SaveNodes( source, buf );
	SaveAnimation( source, buf );

	FileHandle_t fh = g_pFileSystem->Open( filename, "wb" );
	if ( FILESYSTEM_INVALID_HANDLE != fh )
	{
		g_pFileSystem->Write( buf.Base(), buf.TellPut(), fh );
		g_pFileSystem->Close( fh );
	}
}

//--------------------------------------------------------------------
// mikes right handed row based linear algebra
//--------------------------------------------------------------------
struct M_matrix4x4_t
{
	M_matrix4x4_t() {
	
		m_flMatVal[0][0] = 1.0;	m_flMatVal[0][1] = 0.0; m_flMatVal[0][2] = 0.0; m_flMatVal[0][3] = 0.0;
		m_flMatVal[1][0] = 0.0;	m_flMatVal[1][1] = 1.0; m_flMatVal[1][2] = 0.0;	m_flMatVal[1][3] = 0.0;
		m_flMatVal[2][0] = 0.0;	m_flMatVal[2][1] = 0.0; m_flMatVal[2][2] = 1.0;	m_flMatVal[2][3] = 0.0;
		m_flMatVal[3][0] = 0.0;	m_flMatVal[3][1] = 0.0; m_flMatVal[3][2] = 0.0;	m_flMatVal[3][3] = 1.0;

	}
	// M_matrix3x4_t( 
		// float m00, float m01, float m02, 
		// float m10, float m11, float m12,
		// float m20, float m21, float m22,
		// float m30, float m31, float m32)
	// {
		// m_flMatVal[0][0] = m00;	m_flMatVal[0][1] = m01; m_flMatVal[0][2] = m02;
		// m_flMatVal[1][0] = m10;	m_flMatVal[1][1] = m11; m_flMatVal[1][2] = m12;
		// m_flMatVal[2][0] = m20;	m_flMatVal[2][1] = m21; m_flMatVal[2][2] = m22;
		// m_flMatVal[3][0] = m30;	m_flMatVal[3][1] = m31; m_flMatVal[3][2] = m32;

	// }

	float *operator[]( int i )				{ Assert(( i >= 0 ) && ( i < 4 )); return m_flMatVal[i]; }
	const float *operator[]( int i ) const	{ Assert(( i >= 0 ) && ( i < 4 )); return m_flMatVal[i]; }
	float *Base()							{ return &m_flMatVal[0][0]; }
	const float *Base() const				{ return &m_flMatVal[0][0]; }

	float m_flMatVal[4][4];
};

void M_MatrixAngles( const M_matrix4x4_t& matrix, RadianEuler &angles, Vector &position)
{
	float cX, sX, cY, sY, cZ, sZ;

	sY = -matrix[0][2];
	cY = sqrtf(1.0-(sY*sY));

	if (cY != 0.0)
	{
		sX = matrix[1][2];
		cX = matrix[2][2];
		sZ = matrix[0][1];
		cZ = matrix[0][0];
	}
	else
	{
		sX = -matrix[2][1];
		cX = matrix[1][1];
		sZ = 0.0;
		cZ = 1.0;
	}

	angles[0] = atan2f( sX, cX );
	angles[2] = atan2f( sZ, cZ );

	sX = sinf(angles[0]);
	cX = cosf(angles[0]);

	if (sX > cX)
		cY = matrix[1][2] / sX;
	else
		cY = matrix[2][2] / cX;

	angles[1] = atan2f( sY, cY );
	

	position.x = matrix[3][0];
	position.y = matrix[3][1];
	position.z = matrix[3][2];

}

// void M_MatrixAngles( const M_matrix4x4_t& matrix, RadianEuler &angles, Vector &position)
// { 

	// float cX, sX, cY, sY, cZ, sZ;

	// sY = matrix[2][0];
	// cY = sqrtf(1.0-(sY*sY));

	// if (cY != 0.0)
	// {
		// sX = -matrix[2][1];
		// cX = matrix[2][2];
		// sZ = -matrix[1][0];
		// cZ = matrix[0][0];
	// }
	// else
	// {
		// sX = matrix[0][1];
		// cX = matrix[1][1];
		// sZ = 0.0;
		// cZ = 1.0;
	// }

	// angles[0] = atan2f( sX, cX );
	// angles[2] = atan2f( sZ, cZ );

	// sX = sinf(angles[0]);
	// cX = cosf(angles[0]);

	// if (sX > cX)
		// cY = -matrix[2][1] / sX;
	// else
		// cY = matrix[2][2] / cX;

	// angles[1] = atan2f( sY, cY );
	
	// angles[0] = angles[0];
	// angles[1] = angles[1];
	// angles[2] = angles[2];

	// position.x = matrix[3][0];
	// position.y = matrix[3][1];
	// position.z = matrix[3][2];
// }

void M_MatrixCopy( const M_matrix4x4_t& in, M_matrix4x4_t& out )
{
	// Assert( s_bMathlibInitialized );
	memcpy( out.Base(), in.Base(), sizeof( float ) * 4 * 4 );
}
void M_RotateZMatrix(float radian, M_matrix4x4_t &resultMatrix)
{

	resultMatrix[0][0] = cosf(radian);
	resultMatrix[0][1] = sin(radian);
	resultMatrix[0][2] = 0.0;
	resultMatrix[1][0] =-sin(radian);
	resultMatrix[1][1] =  cos(radian);
	resultMatrix[1][2] = 0.0;
	resultMatrix[2][0] = 0.0;
	resultMatrix[2][1] =  0.0;
	resultMatrix[2][2] = 1.0;
}

// !!! THIS SHIT DOESN'T WORK!! WHY? HAS I EVER?
void M_AngleAboutAxis(Vector &axis, float radianAngle, M_matrix4x4_t &result)
{
	float c = cosf(radianAngle);
	float s = sinf(radianAngle);
	float t = 1.0 - c;
	// axis.normalize();
	
	result[0][0] = t * axis[0] * axis[0] + c;
	result[0][1] = t * axis[0] * axis[1] - s * axis[2];
	result[0][2] = t * axis[0] * axis[2] + s * axis[1];          
	result[1][0] = t * axis[0] * axis[1] + s * axis[2];
	result[1][1] = t * axis[1] * axis[1] + c;
	result[1][2] = t * axis[1] * axis[2] - s * axis[0];
	result[2][0] = t * axis[1] * axis[2] - s;
	result[2][1] = t * axis[1] * axis[2] + s * axis[1];
	result[2][2] = t * axis[2] * axis[2] + c * axis[0];

}


void M_MatrixInvert( const M_matrix4x4_t& in, M_matrix4x4_t& out )
{
	// Assert( s_bMathlibInitialized );
	if ( &in == &out )
	{
		M_matrix4x4_t in2;
		M_MatrixCopy( in, in2 );
		M_MatrixInvert( in2, out );
		return;
	}
	float tmp[3];

	// I'm guessing this only works on a 3x4 orthonormal matrix
	out[0][0] = in[0][0];
	out[1][0] = in[0][1];
	out[2][0] = in[0][2];

	out[0][1] = in[1][0];
	out[1][1] = in[1][1];
	out[2][1] = in[1][2];

	out[0][2] = in[2][0];
	out[1][2] = in[2][1];
	out[2][2] = in[2][2];

	tmp[0] = in[3][0];
	tmp[1] = in[3][1];
	tmp[2] = in[3][2];

	float v1[3], v2[3], v3[3];
	v1[0] = out[0][0];
	v1[1] = out[1][0];
	v1[2] = out[2][0];
	v2[0] = out[0][1];
	v2[1] = out[1][1];
	v2[2] = out[2][1];
	v3[0] = out[0][2];
	v3[1] = out[1][2];
	v3[2] = out[2][2];

	out[3][0] = -DotProduct( tmp, v1 );
	out[3][1] = -DotProduct( tmp, v2 );
	out[3][2] = -DotProduct( tmp, v3 );

    // Trivial case
    // if (IS_IDENTITY(matrix))
	// return SbMatrix::identity();

    // // Affine case...
    // // SbMatrix affineAnswer;
    // // if (  affine_inverse( SbMatrix(matrix), affineAnswer ) )
	// // return affineAnswer;

    // int         index[4];
    // float       d, invmat[4][4], temp;
    // SbMatrix	inverse = *this;

    // if(inverse.LUDecomposition(index, d)) {

		// invmat[0][0] = 1.0;
		// invmat[0][1] = 0.0;
		// invmat[0][2] = 0.0;
		// invmat[0][3] = 0.0;
		// inverse.LUBackSubstitution(index, invmat[0]);
		// invmat[1][0] = 0.0;
		// invmat[1][1] = 1.0;
		// invmat[1][2] = 0.0;
		// invmat[1][3] = 0.0;
		// inverse.LUBackSubstitution(index, invmat[1]);
		// invmat[2][0] = 0.0;
		// invmat[2][1] = 0.0;
		// invmat[2][2] = 1.0;
		// invmat[2][3] = 0.0;
		// inverse.LUBackSubstitution(index, invmat[2]);
		// invmat[3][0] = 0.0;
		// invmat[3][1] = 0.0;
		// invmat[3][2] = 0.0;
		// invmat[3][3] = 1.0;
		// inverse.LUBackSubstitution(index, invmat[3]);
		
// #define SWAP(i,j)		 \
		// temp = invmat[i][j];	 \
		// invmat[i][j] = invmat[j][i];			\
		// invmat[j][i] = temp;
		
		// SWAP(1,0);
		
		// SWAP(2,0);
		// SWAP(2,1);
		
		// SWAP(3,0);
		// SWAP(3,1);
		// SWAP(3,2);
// #undef SWAP	
	// }
}

/*
================
M_ConcatTransforms
================
*/
void M_ConcatTransforms (const M_matrix4x4_t &in1, const M_matrix4x4_t &in2, M_matrix4x4_t &out)
{
	
	// Assert( s_bMathlibInitialized );
	// if ( &in1 == &out )
	// {
		// matrix3x4_t in1b;
		// MatrixCopy( in1, in1b );
		// ConcatTransforms( in1b, in2, out );
		// return;
	// }
	// if ( &in2 == &out )
	// {
		// matrix3x4_t in2b;
		// MatrixCopy( in2, in2b );
		// ConcatTransforms( in1, in2b, out );
		// return;
	// }

#define MULT(i,j) (in1[i][0]*in2[0][j] + \
			 in1[i][1]*in2[1][j] + \
			 in1[i][2]*in2[2][j] + \
			 in1[i][3]*in2[3][j])

    out[0][0] = MULT(0,0);
    out[0][1] = MULT(0,1);
    out[0][2] = MULT(0,2);
    out[0][3] = MULT(0,3);
    out[1][0] = MULT(1,0);
    out[1][1] = MULT(1,1);
    out[1][2] = MULT(1,2);
    out[1][3] = MULT(1,3);
    out[2][0] = MULT(2,0);
    out[2][1] = MULT(2,1);
    out[2][2] = MULT(2,2);
    out[2][3] = MULT(2,3);
    out[3][0] = MULT(3,0);
    out[3][1] = MULT(3,1);
    out[3][2] = MULT(3,2);
    out[3][3] = MULT(3,3);

#undef MULT

}

void M_AngleMatrix( RadianEuler const &angles, const Vector &position, M_matrix4x4_t& matrix )
{
	// Assert( s_bMathlibInitialized );
	float		sx, sy, sz, cx, cy, cz;
	

	sx = sinf(angles[0]);
	cx = cosf(angles[0]);
	sy = sinf(angles[1]);
	cy = cosf(angles[1]);
	sz = sinf(angles[2]);
	cz = cosf(angles[2]);
	
	// SinCos( angles[0], &sx, &cx ); // 2
	// SinCos( angles[1], &sy, &cy ); // 1
	// SinCos( angles[2], &sz, &cz ); // 0
	
	M_matrix4x4_t mx, my, mz, temp1;
	
	// rotation about x
	mx[1][1] = cx;
	mx[1][2] = sx;
	mx[2][1] = -sx;
	mx[2][2] = cx;
 
	// rotation about y
	my[0][0] = cy;
	my[0][2] = -sy;
	my[2][0] = sy;
	my[2][2] = cy;
	
    // rotation about z
	mz[0][0] = cz;
	mz[0][1] = sz;
	mz[1][0] = -sz;
	mz[1][1] = cz;

	// z * y * x
	M_ConcatTransforms(mx, my, temp1);
	M_ConcatTransforms(temp1, mz, matrix);

	// put position in
	matrix[3][0] = position.x;
	matrix[3][1] = position.y;
	matrix[3][2] = position.z;

}


//-----------------------------------------------------------------------------
// Motion mapper functions
//-----------------------------------------------------------------------------
#define BONEAXIS 0
#define BONEDIR 0
#define BONESIDE  1	
#define BONEUP   2
#define WORLDUP 2	
#define PRINTMAT(m) \
	printf("\n%f %f %f %f\n", m[0][0], m[0][1], m[0][2], m[0][3]);	\
	printf("%f %f %f %f\n",   m[1][0], m[1][1], m[1][2], m[1][3]);	\
	printf("%f %f %f %f\n",   m[2][0], m[2][1], m[2][2], m[2][3]);	\
	printf("%f %f %f %f\n",   m[3][0], m[3][1], m[3][2], m[3][3]);

struct s_planeConstraint_t
{
	char jointNameString[1024];
	float floor;
	int axis;
	
};

struct s_iksolve_t
{
	char jointNameString[1024];
	int reverseSolve;
	float extremityScale;
	Vector limbRootOffsetScale;
	int doRelativeLock;
	char relativeLockNameString[1024];
	float relativeLockScale;
	
};

struct s_jointScale_t
{
	char jointNameString[1024];
	float scale;
};
	
struct s_template_t
{
	char rootScaleJoint[1024];
	float rootScaleAmount;
	int numIKSolves;
	s_iksolve_t *ikSolves[128];
	int numJointScales;
	s_jointScale_t *jointScales[128];
	int numPlaneConstraints;
	s_planeConstraint_t *planeConstraints[128];
	float toeFloorZ;
	int doSkeletonScale;
	float skeletonScale;

};

	
//-----------------------------------------------------------------------------
// Load a template file into structure
//-----------------------------------------------------------------------------
s_template_t *New_Template()
{
	s_template_t *pTemplate = (s_template_t *)kalloc(1, sizeof(s_template_t));
	pTemplate->rootScaleAmount = 1.0;
	pTemplate->numIKSolves = 0;
	pTemplate->numJointScales = 0;
	pTemplate->toeFloorZ = 2.802277;
	pTemplate->numPlaneConstraints = 0;
	pTemplate->doSkeletonScale = 0;
	pTemplate->skeletonScale = 1.0;
	return pTemplate;
}
s_iksolve_t *New_IKSolve()
{	
	s_iksolve_t *pIKSolve = (s_iksolve_t *)kalloc(1, sizeof(s_iksolve_t));
	pIKSolve->reverseSolve = 0;
	pIKSolve->extremityScale = 1.0;
	pIKSolve->limbRootOffsetScale[0] = pIKSolve->limbRootOffsetScale[1] = pIKSolve->limbRootOffsetScale[2] = 0.0;
	pIKSolve->doRelativeLock = 0;
	pIKSolve->relativeLockScale = 1.0;
	return pIKSolve;
}

s_planeConstraint_t *New_planeConstraint(float floor)
{	
	s_planeConstraint_t *pConstraint = (s_planeConstraint_t *)kalloc(1, sizeof(s_planeConstraint_t));
	pConstraint->floor = floor;
	pConstraint->axis = 2;
	
	return pConstraint;
}

void Set_DefaultTemplate(s_template_t *pTemplate)
{
	pTemplate->numJointScales = 0;
	
	strcpy(pTemplate->rootScaleJoint, "ValveBiped.Bip01_L_Foot");
	pTemplate->rootScaleAmount = 1.0;

	pTemplate->numIKSolves = 4;
	pTemplate->ikSolves[0] = New_IKSolve();
	pTemplate->ikSolves[1] = New_IKSolve();
	pTemplate->ikSolves[2] = New_IKSolve();
	pTemplate->ikSolves[3] = New_IKSolve();
	

	pTemplate->numPlaneConstraints = 2;
	pTemplate->planeConstraints[0] = New_planeConstraint(pTemplate->toeFloorZ);
	strcpy(pTemplate->planeConstraints[0]->jointNameString, "ValveBiped.Bip01_L_Toe0");
	pTemplate->planeConstraints[1] = New_planeConstraint(pTemplate->toeFloorZ);
	strcpy(pTemplate->planeConstraints[1]->jointNameString, "ValveBiped.Bip01_R_Toe0");	

	strcpy(pTemplate->ikSolves[0]->jointNameString, "ValveBiped.Bip01_L_Foot");
	pTemplate->ikSolves[0]->reverseSolve = 0;
	pTemplate->ikSolves[0]->extremityScale = 1.0;
	pTemplate->ikSolves[0]->limbRootOffsetScale[0] = 1.0;
	pTemplate->ikSolves[0]->limbRootOffsetScale[1] = 1.0;
	pTemplate->ikSolves[0]->limbRootOffsetScale[2] = 0.0;

	strcpy(pTemplate->ikSolves[1]->jointNameString, "ValveBiped.Bip01_R_Foot");
	pTemplate->ikSolves[1]->reverseSolve = 0;
	pTemplate->ikSolves[1]->extremityScale = 1.0;
	pTemplate->ikSolves[1]->limbRootOffsetScale[0] = 1.0;
	pTemplate->ikSolves[1]->limbRootOffsetScale[1] = 1.0;
	pTemplate->ikSolves[1]->limbRootOffsetScale[2] = 0.0;

	strcpy(pTemplate->ikSolves[2]->jointNameString, "ValveBiped.Bip01_R_Hand");
	pTemplate->ikSolves[2]->reverseSolve = 1;
	pTemplate->ikSolves[2]->extremityScale = 1.0;
	pTemplate->ikSolves[2]->limbRootOffsetScale[0] = 0.0;
	pTemplate->ikSolves[2]->limbRootOffsetScale[1] = 0.0;
	pTemplate->ikSolves[2]->limbRootOffsetScale[2] = 1.0;

	strcpy(pTemplate->ikSolves[3]->jointNameString, "ValveBiped.Bip01_L_Hand");
	pTemplate->ikSolves[3]->reverseSolve = 1;
	pTemplate->ikSolves[3]->extremityScale = 1.0;
	pTemplate->ikSolves[3]->limbRootOffsetScale[0] = 0.0;
	pTemplate->ikSolves[3]->limbRootOffsetScale[1] = 0.0;
	pTemplate->ikSolves[3]->limbRootOffsetScale[2] = 1.0;
	// pTemplate->ikSolves[3]->doRelativeLock = 1;
	// strcpy(pTemplate->ikSolves[3]->relativeLockNameString, "ValveBiped.Bip01_R_Hand");
	// pTemplate->ikSolves[3]->relativeLockScale = 1.0;

}

void split(char *str, char *sep, char **sp)
{
	char *r = strtok(str, sep);
	while(r != NULL)
	{
		*sp = r;
		sp++;
		r = strtok(NULL, sep);
	}
	*sp = NULL;
}

	
int checkCommand(char *str, char *cmd, int numOptions, int numSplit)
{
	if(strcmp(str, cmd) == 0)
	{
		if(numOptions <= numSplit)
			return 1;
		else
		{
			printf("Error: Number or argument mismatch in template file cmd %s, requires %i, found %i\n", cmd, numOptions, numSplit);
			return 0;
		}
	}
	return 0;
}

s_template_t *Load_Template(char *name )
{

	// Sanity check file and init
	Assert(name);

	s_template_t *pTemplate = New_Template();
	

	// Open file
	if (!OpenGlobalFile( name ))
		return 0;


	//March through lines
	g_iLinecount = 0;
	while(fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) != NULL) 
	{
		g_iLinecount++;
		if(g_szLine[0] == '#')
			continue;
		
		char *endP = strrchr(g_szLine, '\n');
		if(endP != NULL)
			*endP = '\0';
		

		char *sp[128];
		char **spp = sp;
		
		char sep[] = " ";
		split(g_szLine, sep, sp);
		int numSplit = 0;
		
		while(*spp != NULL)
		{
			spp++;
			numSplit++;
			
		}
		if(numSplit < 1 ||
			*sp[0] == '\n')
			continue;


		// int numRead = sscanf( g_szLine, "%s %s %s", cmd, &option, &option2 );

		// // Blank line
		// if ((numRead == EOF) || (numRead == 0))
			// continue;

		// commands
		char	*cmd;
		int numOptions = numSplit - 1;
		
		cmd = sp[0];
		if(checkCommand(cmd, "twoJointIKSolve", 1, numOptions))
		{
			printf("\nCreating two joint IK solve %s\n", sp[1]);
			pTemplate->ikSolves[pTemplate->numIKSolves] = New_IKSolve();
			strcpy(pTemplate->ikSolves[pTemplate->numIKSolves]->jointNameString, sp[1]);
			pTemplate->numIKSolves++;
			
		}
		else if(checkCommand(cmd, "oneJointPlaneConstraint", 1, numOptions))
		{
			printf("\nCreating one joint plane constraint %s\n", sp[1]);
			pTemplate->planeConstraints[pTemplate->numPlaneConstraints] = New_planeConstraint(pTemplate->toeFloorZ);
			strcpy(pTemplate->planeConstraints[pTemplate->numPlaneConstraints]->jointNameString, sp[1]);
			pTemplate->numPlaneConstraints++;

		}
		else if(checkCommand(cmd, "reverseSolve", 1, numOptions)) 
		{
			printf("reverseSolve: %s\n", sp[1]);
			pTemplate->ikSolves[pTemplate->numIKSolves - 1]->reverseSolve = atoi(sp[1]);
		}
		else if(checkCommand(cmd, "extremityScale", 1, numOptions)) 
		{
			printf("extremityScale: %s\n", sp[1]);
			pTemplate->ikSolves[pTemplate->numIKSolves - 1]->extremityScale = atof(sp[1]);
		}
		else if(checkCommand(cmd, "limbRootOffsetScale", 3, numOptions)) 
		{
			printf("limbRootOffsetScale: %s %s %s\n", sp[1], sp[2], sp[3]);
			pTemplate->ikSolves[pTemplate->numIKSolves - 1]->limbRootOffsetScale[0] = atof(sp[1]);
			pTemplate->ikSolves[pTemplate->numIKSolves - 1]->limbRootOffsetScale[1] = atof(sp[2]);
			pTemplate->ikSolves[pTemplate->numIKSolves - 1]->limbRootOffsetScale[2] = atof(sp[3]);
		}
		else if(checkCommand(cmd, "toeFloorZ", 1, numOptions))
		{
			printf("toeFloorZ: %s\n", sp[1]);
			pTemplate->toeFloorZ = atof(sp[1]);
		}
		else if(checkCommand(cmd, "relativeLock", 2, numOptions)) 
		{
			printf("relativeLock: %s\n", sp[1]);
			pTemplate->ikSolves[pTemplate->numIKSolves - 1]->doRelativeLock = 1;
			strcpy(pTemplate->ikSolves[pTemplate->numIKSolves - 1]->relativeLockNameString, sp[1]);
			pTemplate->ikSolves[pTemplate->numIKSolves - 1]->relativeLockScale = atof(sp[2]);

		}
		else if(checkCommand(cmd, "rootScaleJoint", 1, numOptions)) 
		{
			printf("\nrootScaleJoint: %s\n", sp[1]);
			strcpy(pTemplate->rootScaleJoint, sp[1]);
		}
		else if(checkCommand(cmd, "rootScaleAmount", 1, numOptions)) 
		{
			printf("rootScaleAmount: %s\n", sp[1]);
			pTemplate->rootScaleAmount = atof(sp[1]);
		}
		else if(checkCommand(cmd, "jointScale", 2, numOptions))
		{
			printf("\nCreating joint scale %s of %s\n", sp[1], sp[2]);
			pTemplate->jointScales[pTemplate->numJointScales] = (s_jointScale_t *)kalloc(1, sizeof(s_jointScale_t));
			strcpy(pTemplate->jointScales[pTemplate->numJointScales]->jointNameString, sp[1]);
			pTemplate->jointScales[pTemplate->numJointScales]->scale = atof(sp[2]);
			pTemplate->numJointScales++;
		}
		else if(checkCommand(cmd, "skeletonScale", 2, numOptions))
		{
			printf("\nCreating skeleton scale of %s\n", sp[1]);
			pTemplate->doSkeletonScale = 1;
			pTemplate->skeletonScale = atof(sp[1]);
		}
		else 
		{
			MdlWarning("unknown studio command\n" );
		}
	}
	fclose( g_fpInput );
	return pTemplate;
}

//-----------------------------------------------------------------------------
// get node index from node string name
//-----------------------------------------------------------------------------
int GetNodeIndex(s_source_t *psource, char *nodeName)
{
	for(int i = 0; i < psource->numbones; i++)
	{
		if(strcmp(nodeName, psource->localBone[i].name) == 0)
		{
			return i;
		}	
	}
	return -1;
}

//-----------------------------------------------------------------------------
// get node index from node string name
//-----------------------------------------------------------------------------
void GetNodePath(s_source_t *psource,  int startIndex, int endIndex, int *path)
{
	*path = endIndex;
	
	s_node_t *nodes;
	nodes = psource->localBone;
	while(*path != startIndex)
	{	
		int parent = nodes[*path].parent;
		path++;
		*path = parent;
	}
	path++;
	*path = -1;
}

void SumBonePathTranslations(int *indexPath, s_bone_t *boneArray, Vector &resultVector, int rootOffset = 0)
{

	// walk the path
	int *pathPtr = indexPath;
	// M_matrix4x4_t matrixCum;

	// find length of path
	int length = 0;
	while(*pathPtr != -1)
	{
		length++;
		pathPtr++;
	}

	int l = length - (1 + rootOffset);

	resultVector[0] = 0.0;
	resultVector[1] = 0.0;
	resultVector[2] = 0.0;
	
	for(int i = l; i > -1; i--)
	{		
		s_bone_t *thisBone = boneArray + indexPath[i];
		resultVector += thisBone->pos;
	}
}

void CatBonePath(int *indexPath, s_bone_t *boneArray, M_matrix4x4_t &resultMatrix, int rootOffset = 0)
{

	// walk the path
	int *pathPtr = indexPath;
	// M_matrix4x4_t matrixCum;

	// find length of path
	int length = 0;
	while(*pathPtr != -1)
	{
		length++;
		pathPtr++;
	}

	int l = length - (1 + rootOffset);

	for(int i = l; i > -1; i--)
	{		
		s_bone_t *thisBone = boneArray + indexPath[i];
		// printf("bone index: %i  %i\n", i, indexPath[i]);
		// printf("pos: %f %f %f, rot: %f %f %f\n", thisBone->pos.x, thisBone->pos.y, thisBone->pos.z, thisBone->rot.x, thisBone->rot.y, thisBone->rot.z);
		M_matrix4x4_t thisMatrix;		
		M_AngleMatrix(thisBone->rot, thisBone->pos, thisMatrix);
		// PRINTMAT(thisMatrix)
		M_matrix4x4_t tempCum;
		M_MatrixCopy(resultMatrix, tempCum);
		M_ConcatTransforms(thisMatrix, tempCum, resultMatrix);
	}
	// PRINTMAT(matrixCum);	
	// M_MatrixAngles(matrixCum, resultBone.rot, resultBone.pos);
	
	// printf("pos: %f %f %f, rot: %f %f %f\n", resultBone.pos.x,resultBone.pos.y, resultBone.pos.z, RAD2DEG(resultBone.rot.x),RAD2DEG(resultBone.rot.y),RAD2DEG(resultBone.rot.z));
	
}
// int ConformSources(s_source_t *pSource, s_source_t *pTarget)
// {
	// if(pSource->numbones != *pTarget->numbones)
	// {
		// printf("ERROR: The number of bones in the target file must match the source file.");
		// return 1;
	// }
	// if(pSource->numframes != pTarget->numframes)
	// {
		// printf("Note: Source and target frame lengths do not match");
		// for(int t = 0; t < pTarget->numframes; t++)
		// {
			// free(pTarget->rawanim[t]);
		// }
		// pTarget->numframes = pSource->numframes;
		// int size = pTarget->numbones * sizeof( s_bone_t ); 
		// for(t = 0; t < pTarget->numframes; t++)
		// {
			// pTarget->rawanim[t] = (s_bone_t *) kalloc(1, size);
			// memcpy((void *) pSource->rawanim[t], (void *) pTarget->rawanim[t], size
		// }	
	// }			
	// pTarget->startframe = pSource->startframe;
	// pTarget->endframe = pSource->endframe;




void ScaleJointsFrame(s_source_t *pSkeleton, s_jointScale_t *jointScale, int t)
{
	int numBones = pSkeleton->numbones;

	for(int i = 0; i < numBones; i++)
	{
		s_node_t pNode = pSkeleton->localBone[i];
		s_bone_t *pSkelBone = &pSkeleton->rawanim[t][i];
		if(strcmp(jointScale->jointNameString, pNode.name) == 0)
		{
			// printf("Scaling joint %s\n", pNode.name);			
			pSkelBone->pos = pSkelBone->pos * jointScale->scale;
		}
		
	}
}
void ScaleJoints(s_source_t *pSkeleton, s_jointScale_t *jointScale)
{
	int numFrames = pSkeleton->numframes;
	for(int t = 0; t < numFrames; t++)
	{
		ScaleJointsFrame(pSkeleton, jointScale, t);
	}
}

void ScaleSkeletonFrame(s_source_t *pSkeleton, float scale, int t)
{
	int numBones = pSkeleton->numbones;

	for(int i = 0; i < numBones; i++)
	{
		s_bone_t *pSkelBone = &pSkeleton->rawanim[t][i];
		pSkelBone->pos = pSkelBone->pos * scale;
		
	}
}
void ScaleSkeleton(s_source_t *pSkeleton, float scale)
{
	int numFrames = pSkeleton->numframes;
	for(int t = 0; t < numFrames; t++)
	{
		ScaleSkeletonFrame(pSkeleton, scale, t);
	}
}

void CombineSkeletonAnimationFrame(s_source_t *pSkeleton, s_source_t *pAnimation, s_bone_t **ppAnim, int t)
{
	int numBones = pAnimation->numbones;
	int size = numBones * sizeof( s_bone_t );
	ppAnim[t] = (s_bone_t *) kalloc(1, size);
	for(int i = 0; i < numBones; i++)
	{
		s_node_t pNode = pAnimation->localBone[i];
		s_bone_t pAnimBone = pAnimation->rawanim[t][i];
		
		if(pNode.parent > -1)
		{
			if ( i < pSkeleton->numbones )
			{
				s_bone_t pSkelBone = pSkeleton->rawanim[0][i];
				ppAnim[t][i].pos = pSkelBone.pos;
			}
			else
			{
				if ( !g_bGaveMissingBoneWarning )
				{
					g_bGaveMissingBoneWarning = true;
					Warning( "Warning: Target skeleton has less bones than source animation. Reverting to source data for extra bones.\n" );
				}
				
				ppAnim[t][i].pos = pAnimBone.pos;
			}
		}
		else
		{
			ppAnim[t][i].pos = pAnimBone.pos;
		}
		
		ppAnim[t][i].rot = pAnimBone.rot;	
	}
}
void CombineSkeletonAnimation(s_source_t *pSkeleton, s_source_t *pAnimation, s_bone_t **ppAnim)
{
	int numFrames = pAnimation->numframes;
	for(int t = 0; t < numFrames; t++)
	{
		CombineSkeletonAnimationFrame(pSkeleton, pAnimation, ppAnim, t);
	}
}


//--------------------------------------------------------------------
// MotionMap
//--------------------------------------------------------------------  
s_source_t *MotionMap( s_source_t *pSource, s_source_t *pTarget, s_template_t *pTemplate )
{

	// scale skeleton
	if(pTemplate->doSkeletonScale)
	{
		ScaleSkeleton(pTarget, pTemplate->skeletonScale);
	}

	// scale joints
	for(int j = 0; j < pTemplate->numJointScales; j++)
	{
		s_jointScale_t *pJointScale = pTemplate->jointScales[j];
		ScaleJoints(pTarget, pJointScale);
	}
	

	// root stuff
	char rootString[128] = "ValveBiped.Bip01";

	// !!! PARAMETER
	int rootIndex  = GetNodeIndex(pSource, rootString);
	int rootScaleIndex = GetNodeIndex(pSource, pTemplate->rootScaleJoint);
	int rootScalePath[512];
	if(rootScaleIndex > -1)
	{
		GetNodePath(pSource, rootIndex, rootScaleIndex, rootScalePath);
	}
	else
	{
		printf("Error: Can't find node\n");
		exit(0);
	}
	float rootScaleLengthSrc = pSource->rawanim[0][rootScaleIndex].pos[BONEDIR];
	float rootScaleParentLengthSrc = pSource->rawanim[0][rootScalePath[1]].pos[BONEDIR];
	float rootScaleSrc = rootScaleLengthSrc + rootScaleParentLengthSrc;
	float rootScaleLengthTgt = pTarget->rawanim[0][rootScaleIndex].pos[BONEDIR];
	float rootScaleParentLengthTgt = pTarget->rawanim[0][rootScalePath[1]].pos[BONEDIR];
	float rootScaleTgt = rootScaleLengthTgt + rootScaleParentLengthTgt;
	float rootScaleFactor = rootScaleTgt / rootScaleSrc;

	if(g_verbose)
		printf("Root Scale Factor: %f\n", rootScaleFactor);
	

	// root scale origin
	float toeFloorZ = pTemplate->toeFloorZ;
	Vector rootScaleOrigin = pSource->rawanim[0][rootIndex].pos;
	rootScaleOrigin[2] = toeFloorZ;


	// setup workspace
	s_bone_t *combinedRefAnimation[MAXSTUDIOANIMFRAMES];
	s_bone_t *combinedAnimation[MAXSTUDIOANIMFRAMES];
	s_bone_t *sourceAnimation[MAXSTUDIOANIMFRAMES];
	CombineSkeletonAnimation(pTarget, pSource, combinedAnimation);
	CombineSkeletonAnimation(pTarget, pSource, combinedRefAnimation);
	

	// do source and target sanity checking
	int sourceNumFrames = pSource->numframes;


	// iterate through limb solves
	for(int t = 0; t < sourceNumFrames; t++)
	{
		// setup pTarget for skeleton comparison
		pTarget->rawanim[t] = combinedRefAnimation[t];

		printf("Note: Processing frame: %i\n", t);
		for(int ii = 0; ii < pTemplate->numIKSolves; ii++)
		{
			s_iksolve_t *thisSolve = pTemplate->ikSolves[ii];
			
			char *thisJointNameString = thisSolve->jointNameString;
			int thisJointIndex = GetNodeIndex(pSource, thisJointNameString);
			
			// init paths to feet
			int thisJointPathInRoot[512];
			
			// get paths to feet
			if(thisJointIndex > -1)
			{
				GetNodePath(pSource, rootIndex, thisJointIndex, thisJointPathInRoot);
			}
			else
			{
				printf("Error: Can't find node: %s\n" , thisJointNameString);
				exit(0);
			}
			
			// leg "root" or thigh pointers
			//int gParentIndex = thisJointPathInRoot[2];
			int *gParentPath = thisJointPathInRoot + 2;
			
			//----------------------------------------------------------------
			// get limb lengths
			//----------------------------------------------------------------  
			float thisJointLengthSrc = pSource->rawanim[0][thisJointIndex].pos[BONEDIR];
			float parentJointLengthSrc = pSource->rawanim[0][thisJointPathInRoot[1]].pos[BONEDIR];
			
			float thisLimbLengthSrc = thisJointLengthSrc + parentJointLengthSrc;
			
			float thisJointLengthTgt = pTarget->rawanim[0][thisJointIndex].pos[BONEDIR];
			float parentJointLengthTgt = pTarget->rawanim[0][thisJointPathInRoot[1]].pos[BONEDIR];
			
			float thisLimbLengthTgt = thisJointLengthTgt + parentJointLengthTgt;
			
			// Factor leg length delta
			float thisLimbLength = thisLimbLengthSrc - thisLimbLengthTgt;
			float thisLimbLengthFactor = thisLimbLengthTgt / thisLimbLengthSrc;
			
			if(g_verbose)
				printf("limb length %s: %i: %f, factor %f\n", thisJointNameString, thisJointIndex, thisLimbLength, thisLimbLengthFactor);
			
			// calculate joint grandparent offset
			// Note: because there's no reference pose this doesn't take rotation into account.
			// This only works because of the assumption that joint translations aren't animated.
			M_matrix4x4_t gParentGlobalMatSrc, gParentGlobalMatTgt;
			Vector gParentGlobalSrc, gParentGlobalTgt;
			
			// SumBonePathTranslations(gParentPath, pSource->rawanim[t], gParentGlobalSrc, 1);			
			// SumBonePathTranslations(gParentPath, pTarget->rawanim[t], gParentGlobalTgt, 1);

			// get root path to source parent
			CatBonePath(gParentPath, pSource->rawanim[t], gParentGlobalMatSrc, 1);
			// check against reference animation
			CatBonePath(gParentPath, pTarget->rawanim[t], gParentGlobalMatTgt, 1);

			gParentGlobalSrc[0] = gParentGlobalMatSrc[3][0];
			gParentGlobalSrc[1] = gParentGlobalMatSrc[3][1];
			gParentGlobalSrc[2] = gParentGlobalMatSrc[3][2];
			
			gParentGlobalTgt[0] = gParentGlobalMatTgt[3][0];
			gParentGlobalTgt[1] = gParentGlobalMatTgt[3][1];
			gParentGlobalTgt[2] = gParentGlobalMatTgt[3][2];
			

			Vector gParentDelta(gParentGlobalTgt - gParentGlobalSrc);
			
			if(g_verbose)
				printf("Grand parent delta: %f %f %f\n", gParentDelta[0], gParentDelta[1], gParentDelta[2]);

			gParentDelta *= thisSolve->limbRootOffsetScale;
			

			//----------------------------------------------------------------
			// time takes effect here
			// above waste is unavoidable?
			//----------------------------------------------------------------
			M_matrix4x4_t rootMat;
			M_AngleMatrix(pSource->rawanim[t][rootIndex].rot, pSource->rawanim[t][rootIndex].pos, rootMat);
			
			
			// OK, time to get it together
			// 1) scale foot by legLengthFactor in the non-translated thigh space
			// 2) translate foot by legRootDelta in the space of the root
			// do we leave everything in the space of the root then? PROBABLY!!
			
			M_matrix4x4_t thisJointMat, parentJointMat, thisJointInGParentMat;
			M_AngleMatrix(pSource->rawanim[t][thisJointPathInRoot[0]].rot, pSource->rawanim[t][thisJointPathInRoot[0]].pos, thisJointMat);
			M_AngleMatrix(pSource->rawanim[t][thisJointPathInRoot[1]].rot, pSource->rawanim[t][thisJointPathInRoot[1]].pos, parentJointMat);
			M_ConcatTransforms(thisJointMat, parentJointMat, thisJointInGParentMat);
				
			if(!thisSolve->doRelativeLock)
			{
				// scale around grand parent
				float effectiveScaleFactor = ((thisLimbLengthFactor - 1.0) * thisSolve->extremityScale ) + 1.0;
				thisJointInGParentMat[3][0] *= effectiveScaleFactor;
				thisJointInGParentMat[3][1] *= effectiveScaleFactor;
				thisJointInGParentMat[3][2] *= effectiveScaleFactor;
			}
			
			// adjust into source root space
			M_matrix4x4_t gParentInRootMat, thisJointInRootMat;
			CatBonePath(gParentPath, pSource->rawanim[t], gParentInRootMat, 1);
			M_ConcatTransforms(thisJointInGParentMat, gParentInRootMat, thisJointInRootMat);			
				
			if(!thisSolve->doRelativeLock)
			{
				// adjust by difference of local root
				thisJointInRootMat[3][0] += gParentDelta[0];
				thisJointInRootMat[3][1] += gParentDelta[1];
				thisJointInRootMat[3][2] += gParentDelta[2];
			}
			else
			{
				char *relativeJointNameString = thisSolve->relativeLockNameString;
				int relativeJointIndex = GetNodeIndex(pSource, relativeJointNameString);
				
				// init paths to feet
				int relativeJointPathInRoot[512];
				
				// get paths to feet
				if(relativeJointIndex > -1)
				{
					GetNodePath(pSource, rootIndex, relativeJointIndex, relativeJointPathInRoot);
				}
				else
				{
					printf("Error: Can't find node: %s\n" , relativeJointNameString);
					exit(0);
				}
				// get the source relative joint
				M_matrix4x4_t relativeJointInRootMatSrc, relativeJointInRootMatSrcInverse, thisJointInRelativeSrcMat;
				CatBonePath(relativeJointPathInRoot, pSource->rawanim[t], relativeJointInRootMatSrc, 1);
				M_MatrixInvert(relativeJointInRootMatSrc, relativeJointInRootMatSrcInverse);
				M_ConcatTransforms(thisJointInRootMat, relativeJointInRootMatSrcInverse, thisJointInRelativeSrcMat);
				if(thisSolve->relativeLockScale != 1.0)
				{
					thisJointInRelativeSrcMat[3][0] *= thisSolve->relativeLockScale;
					thisJointInRelativeSrcMat[3][1] *= thisSolve->relativeLockScale;
					thisJointInRelativeSrcMat[3][2] *= thisSolve->relativeLockScale;
				}
				
				// swap momentarily to get new destination
				// NOTE: the relative lock must have already been solved
				sourceAnimation[t] = pSource->rawanim[t];
				pSource->rawanim[t] = combinedAnimation[t];

				// get new relative location
				M_matrix4x4_t relativeJointInRootMatTgt;
				CatBonePath(relativeJointPathInRoot, pSource->rawanim[t], relativeJointInRootMatTgt, 1);
				M_ConcatTransforms(thisJointInRelativeSrcMat, relativeJointInRootMatTgt, thisJointInRootMat);

				// swap back just for cleanliness
				// a little overkill as it's just swapped
				// just leaving it here for clarity
				combinedAnimation[t] = pSource->rawanim[t];
				pSource->rawanim[t] = sourceAnimation[t];

			}
			
			//----------------------------------------------------------------
			// swap animation
			//----------------------------------------------------------------
			sourceAnimation[t] = pSource->rawanim[t];
			pSource->rawanim[t] = combinedAnimation[t];
			
			//----------------------------------------------------------------
			// make thigh data global based on new skeleton
			//----------------------------------------------------------------  
			// get thigh in global space
			M_matrix4x4_t gParentInTgtRootMat, ggParentInTgtRootMat;
			// int *gParentPath = thisJointPathInRoot + 2;
			CatBonePath(gParentPath, pSource->rawanim[t], gParentInTgtRootMat, 1);
			CatBonePath(gParentPath+1, pSource->rawanim[t], ggParentInTgtRootMat, 1);
			
			
			//----------------------------------------------------------------
			// Calculate IK for legs
			//----------------------------------------------------------------
			float parentJointLength = pSource->rawanim[t][*(thisJointPathInRoot + 1)].pos[BONEDIR];
			float thisJointLength = pSource->rawanim[t][thisJointIndex].pos[BONEDIR];
			
			Vector thisLimbHypot;
			thisLimbHypot[0] = thisJointInRootMat[3][0] - gParentInTgtRootMat[3][0];
			thisLimbHypot[1] = thisJointInRootMat[3][1] - gParentInTgtRootMat[3][1];
			thisLimbHypot[2] = thisJointInRootMat[3][2] - gParentInTgtRootMat[3][2];
			
			float thisLimbHypotLength = thisLimbHypot.Length();
			
			// law of cosines!
			float gParentCos = (thisLimbHypotLength*thisLimbHypotLength + parentJointLength*parentJointLength - thisJointLength*thisJointLength) / (2*parentJointLength*thisLimbHypotLength);
			float parentCos = (parentJointLength*parentJointLength + thisJointLength*thisJointLength - thisLimbHypotLength*thisLimbHypotLength) / (2*parentJointLength*thisJointLength);
			
			VectorNormalize(thisLimbHypot);
			
			Vector thisLimbHypotUnit = thisLimbHypot;
			
			M_matrix4x4_t gParentJointIKMat;
			Vector gParentJointIKRot, gParentJointIKOrth;
			
			gParentJointIKRot[0] = gParentInTgtRootMat[BONEUP][0];
			gParentJointIKRot[1] = gParentInTgtRootMat[BONEUP][1];
			gParentJointIKRot[2] = gParentInTgtRootMat[BONEUP][2];
			
			VectorNormalize(gParentJointIKRot);
			gParentJointIKOrth = gParentJointIKRot.Cross(thisLimbHypotUnit);
			VectorNormalize(gParentJointIKOrth);
			gParentJointIKRot = thisLimbHypotUnit.Cross(gParentJointIKOrth);
			VectorNormalize(gParentJointIKRot);
			
			M_MatrixCopy(gParentInTgtRootMat, gParentJointIKMat);
			
			gParentJointIKMat[0][0] = thisLimbHypotUnit[0];
			gParentJointIKMat[0][1] = thisLimbHypotUnit[1];
			gParentJointIKMat[0][2] = thisLimbHypotUnit[2];
			
			gParentJointIKMat[1][0] = gParentJointIKOrth[0];
			gParentJointIKMat[1][1] = gParentJointIKOrth[1];
			gParentJointIKMat[1][2] = gParentJointIKOrth[2];
			
			gParentJointIKMat[2][0] = gParentJointIKRot[0];
			gParentJointIKMat[2][1] = gParentJointIKRot[1];
			gParentJointIKMat[2][2] = gParentJointIKRot[2];
			
			
			M_matrix4x4_t gParentJointIKRotMat, gParentJointResultMat;
			float gParentDeg;
			if(thisSolve->reverseSolve)
			{
				gParentDeg = acos(gParentCos);
			}
			else
			{
				gParentDeg = -acos(gParentCos);
			}

			// sanity check limb length
			if(thisLimbHypotLength < thisLimbLengthTgt)
			{	
				M_RotateZMatrix(gParentDeg, gParentJointIKRotMat);
			}
			
			M_ConcatTransforms(gParentJointIKRotMat, gParentJointIKMat, gParentJointResultMat);
			
			M_matrix4x4_t parentJointIKRotMat;
			//!!! shouldn't need the 180 degree  addition, something in the law of cosines!!!
			float parentDeg;
			if(thisSolve->reverseSolve)
			{
				parentDeg = acos(parentCos)+M_PI;
			}
			else
			{
				parentDeg = -acos(parentCos)+M_PI;
			}
			
			// sanity check limb length
			if(thisLimbHypotLength < thisLimbLengthTgt)
			{	
				M_RotateZMatrix(parentDeg, parentJointIKRotMat);
			}

		
			// Thighs
			M_matrix4x4_t ggParentInTgtRootMatInverse, gParentJointLocalMat;
			M_MatrixInvert(ggParentInTgtRootMat, ggParentInTgtRootMatInverse);
			M_ConcatTransforms(gParentJointResultMat, ggParentInTgtRootMatInverse, gParentJointLocalMat);
			
			s_bone_t resultBone;
			
			// temp test stuff
			// M_MatrixAngles(thisJointInRootMat, resultBone.rot, resultBone.pos);
			// pSource->rawanim[t][thisJointIndex].rot = resultBone.rot;
			// pSource->rawanim[t][thisJointIndex].pos = resultBone.pos;
			
			// M_MatrixAngles(gParentInTgtRootMat, resultBone.rot, resultBone.pos);
			// pSource->rawanim[t][gParentIndex].rot = resultBone.rot;
			// pSource->rawanim[t][gParentIndex].pos = resultBone.pos;
			
			
			M_MatrixAngles(gParentJointLocalMat, resultBone.rot, resultBone.pos);
			pSource->rawanim[t][*gParentPath].pos = resultBone.pos;
			pSource->rawanim[t][*gParentPath].rot = resultBone.rot;
			
			M_MatrixAngles(parentJointIKRotMat, resultBone.rot, resultBone.pos);
			pSource->rawanim[t][*(thisJointPathInRoot+1)].rot = resultBone.rot;
			
			M_matrix4x4_t parentJointGlobalMat, parentJointGlobalMatInverse, thisJointLocalMat;
			CatBonePath(thisJointPathInRoot+1, pSource->rawanim[t], parentJointGlobalMat, 1);
			
			
			M_MatrixInvert(parentJointGlobalMat, parentJointGlobalMatInverse);
			M_ConcatTransforms(thisJointInRootMat, parentJointGlobalMatInverse, thisJointLocalMat);
			
			M_MatrixAngles(thisJointLocalMat, resultBone.rot, resultBone.pos);
			pSource->rawanim[t][thisJointIndex].rot = resultBone.rot;


			// swap animation back for next solve
			combinedAnimation[t] = pSource->rawanim[t];
			pSource->rawanim[t] = sourceAnimation[t];

		}
		// swap animation
		sourceAnimation[t] = pSource->rawanim[t];
		pSource->rawanim[t] = combinedAnimation[t];

		//----------------------------------------------------------------
		// adjust root
		//----------------------------------------------------------------  
		Vector originBonePos = pSource->rawanim[t][rootIndex].pos;
		Vector rootInScaleOrigin = originBonePos - rootScaleOrigin;
		float effectiveRootScale = ((rootScaleFactor - 1.0) * pTemplate->rootScaleAmount) + 1.0;
		Vector scaledRoot = rootInScaleOrigin * effectiveRootScale;
		pSource->rawanim[t][rootIndex].pos = rootScaleOrigin + scaledRoot;

		//------------------------------------------------------------
        // plane constraints
        //------------------------------------------------------------
		for(int ii = 0; ii < pTemplate->numPlaneConstraints; ii++)
		{
			s_planeConstraint_t *thisSolve = pTemplate->planeConstraints[ii];
			
			char *thisJointNameString = thisSolve->jointNameString;
			if(g_verbose)
				printf("Executing plane constraint: %s\n", thisJointNameString);
			
			int thisJointIndex = GetNodeIndex(pSource, thisJointNameString);
			
			// init paths to feet
			int thisJointPath[512];
			
			// get paths to feet
			if(thisJointIndex > -1)
			{
				GetNodePath(pSource, -1, thisJointIndex, thisJointPath);
			}
			else
			{
				printf("Error: Can't find node: %s\n" , thisJointNameString);
				exit(0);
			}
			int parentIndex = thisJointPath[1];
			int *parentPath = thisJointPath + 1;
			
			M_matrix4x4_t thisJointGlobalMat, parentJointGlobalMat, gParentJointGlobalMat, gParentJointGlobalMatInverse;
			CatBonePath(thisJointPath, pSource->rawanim[t], thisJointGlobalMat, 0);
			CatBonePath(parentPath, pSource->rawanim[t], parentJointGlobalMat, 0);
			CatBonePath(parentPath+1, pSource->rawanim[t], gParentJointGlobalMat, 0);
			M_MatrixInvert(gParentJointGlobalMat, gParentJointGlobalMatInverse);

			if(thisJointGlobalMat[3][thisSolve->axis] < thisSolve->floor)
			{
				// printf("-- broken plane: %f\n", thisJointGlobalMat[3][thisSolve->axis]);
				if(parentJointGlobalMat[3][thisSolve->axis] < thisSolve->floor)
				{
					printf("Error: Constraint parent has broken the plane, this frame's plane constraint unsolvable!\n");
				}
				else
				{
					Vector parentJointAtPlane(parentJointGlobalMat[3][0], parentJointGlobalMat[3][1], parentJointGlobalMat[3][2]);
					Vector parentPos(parentJointGlobalMat[3][0], parentJointGlobalMat[3][1], parentJointGlobalMat[3][2]);
					Vector thisJointAtPlane(thisJointGlobalMat[3][0], thisJointGlobalMat[3][1], thisJointGlobalMat[3][2]);
					Vector thisJointPos(thisJointGlobalMat[3][0], thisJointGlobalMat[3][1], thisJointGlobalMat[3][2]);

					thisJointAtPlane[thisSolve->axis] = thisSolve->floor;
					parentJointAtPlane[thisSolve->axis] = thisSolve->floor;

					float thisJointLength = pSource->rawanim[t][thisJointIndex].pos[BONEAXIS];
					float parentLengthToPlane = parentPos[thisSolve->axis] - thisSolve->floor;
					float adjacent = sqrtf((thisJointLength * thisJointLength) - (parentLengthToPlane * parentLengthToPlane));
					Vector parentDirection = thisJointAtPlane - parentJointAtPlane;
					VectorNormalize(parentDirection);
					
					Vector newJointPos = parentJointAtPlane + (parentDirection * adjacent);

					Vector newParentDir = newJointPos - parentPos;
					Vector parentUp(parentJointGlobalMat[BONEUP][0], parentJointGlobalMat[BONEUP][1], parentJointGlobalMat[BONEUP][2]);
					
					VectorNormalize(newParentDir);
					VectorNormalize(parentUp);
					// Vector parentSide = newParentDir.Cross(parentUp);
					Vector parentSide = parentUp.Cross(newParentDir);
					VectorNormalize(parentSide);
					parentUp = newParentDir.Cross(parentSide);
					// parentUp = parentSide.Cross(newParentDir);
					VectorNormalize(parentUp);
					parentJointGlobalMat[BONEDIR][0] = newParentDir[0];
					parentJointGlobalMat[BONEDIR][1] = newParentDir[1];
					parentJointGlobalMat[BONEDIR][2] = newParentDir[2];
					parentJointGlobalMat[BONEUP][0] = parentUp[0];
					parentJointGlobalMat[BONEUP][1] = parentUp[1];
					parentJointGlobalMat[BONEUP][2] = parentUp[2];
					parentJointGlobalMat[BONESIDE][0] = parentSide[0];
					parentJointGlobalMat[BONESIDE][1] = parentSide[1];
					parentJointGlobalMat[BONESIDE][2] = parentSide[2];
					
					
					M_matrix4x4_t newParentJointMat;
					
					M_ConcatTransforms(parentJointGlobalMat, gParentJointGlobalMatInverse, newParentJointMat);
					
					s_bone_t resultBone;
					M_MatrixAngles(newParentJointMat, resultBone.rot, resultBone.pos);
					pSource->rawanim[t][parentIndex].rot = resultBone.rot;
				}
			}
		}

		// swap animation back for next solve
		combinedAnimation[t] = pSource->rawanim[t];
		pSource->rawanim[t] = sourceAnimation[t];
	}
	for(int t = 0; t < sourceNumFrames; t++)
	{
		pTarget->rawanim[t] = combinedAnimation[t];
	}
	pTarget->numframes = sourceNumFrames;
	


	

#if 0
	// Process motion mapping into out and return that
	s_source_t *out = new s_source_t;

	return out;
#else
	// Just returns the start animation, to test the Save_SMD API.
	return pTarget;
#endif
}

char templates[] = 
"\n\
#\n\
# default template file is analogus to not specifying a template file at all\n\
#\n\
\n\
rootScaleJoint ValveBiped.Bip01_L_Foot\n\
rootScaleAmount 1.0\n\
toeFloorZ 2.7777\n\
\n\
twoJointIKSolve ValveBiped.Bip01_L_Foot\n\
reverseSolve 0\n\
extremityScale 1.0\n\
limbRootOffsetScale 1.0 1.0 0.0\n\
\n\
twoJointIKSolve ValveBiped.Bip01_R_Foot\n\
reverseSolve 0\n\
extremityScale 1.0\n\
limbRootOffsetScale 1.0 1.0 0.0\n\
\n\
oneJointPlaneConstraint ValveBiped.Bip01_L_Toe0\n\
\n\
oneJointPlaneConstraint ValveBiped.Bip01_R_Toe0\n\
\n\
twoJointIKSolve ValveBiped.Bip01_R_Hand\n\
reverseSolve 1\n\
extremityScale 1.0\n\
limbRootOffsetScale 0.0 0.0 1.0\n\
\n\
twoJointIKSolve ValveBiped.Bip01_L_Hand\n\
reverseSolve 1\n\
extremityScale 1.0\n\
limbRootOffsetScale 0.0 0.0 1.0\n\
\n\
";


void UsageAndExit()
{
	MdlError( "usage: motionmapper [-quiet] [-verbose] [-templateFile filename] [-printTemplates] sourceanim.smd targetskeleton.smd output.smd\n\
\tsourceanim:  should contain ref pose and animation data\n\
\ttargetsekeleton:  should contain new ref pose, animation data ignored/can be absent\n\
\toutput:  animation from source mapped onto target skeleton (contains new ref pose)\n\
\t-templateFile filename : specifies a template file for guiding the mapping of motion\n\
\t-printTemplate: Causes motionmapper to output the contents of an example template file, which can be used in conjunction with the -templateFile argument to create various motion effects.\n\
\n");
}

void PrintHeader()
{
	vprint( 0, "Valve Software - motionmapper.exe ((c) Valve Coroporation %s)\n", __DATE__ );
	vprint( 0, "--- Maps motion from one animation/skeleton onto another skeleton ---\n" );
}



/*
==============
main
==============
*/
int main (int argc, char **argv)
{
	int		i;
	
	int useTemplate = 0;
	char templateFileName[1024];
	
	// Header
	PrintHeader();

	// Init command line stuff
	CommandLine()->CreateCmdLine( argc, argv );
	InstallSpewFunction();

	// init math stuff
	MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f, false, false, false, false );
	g_currentscale = g_defaultscale = 1.0;
	g_defaultrotation = RadianEuler( 0, 0, M_PI / 2 );

	// No args?
	if (argc == 1)
	{
		UsageAndExit();
	}
	
	// Init variable
	g_quiet = false;	
	
	// list template hooey
	CUtlVector< CUtlSymbol > filenames;

	// Get args
	for (i = 1; i < argc; i++) 
	{
		// Switches
		if (argv[i][0] == '-') 
		{
			if (!stricmp(argv[i], "-allowdebug"))
			{
				// Ignore, used by interface system to catch debug builds checked into release tree
				continue;
			}

			if (!stricmp(argv[i], "-quiet"))
			{
				g_quiet = true;
				g_verbose = false;
				continue;
			}

			if (!stricmp(argv[i], "-verbose"))
			{
				g_quiet = false;
				g_verbose = true;
				continue;
			}
			if (!stricmp(argv[i], "-printTemplate"))
			{
				printf("%s\n", templates);
				exit(0);
				
			}
			if (!stricmp(argv[i], "-templateFile"))
			{
				if(i + 1 < argc)
				{
					strcpy( templateFileName, argv[i+1]);
					useTemplate = 1;
					printf("Note: %s passed as template file", templateFileName);
				}
				else
				{
					printf("Error: -templateFile requires an argument, none found!");
					UsageAndExit();
					
				}
				i++;
				continue;
			}
		}
		else
		{
			// more template stuff
			CUtlSymbol sym = argv[ i ];
			filenames.AddToTail( sym );
		}
	}	

	// Enough file args?
	if ( filenames.Count() != 3 )
	{
		// misformed arguments
		// otherwise generating unintended results
		printf("Error: 3 file arguments required, %i found!", filenames.Count());
		UsageAndExit();
	}

	// Filename arg indexes
	int sourceanim = 0;
	int targetskel = 1;
	int outputanim = 2;

	// Copy arg string to global variable
	strcpy( g_outfile, filenames[ outputanim ].String() );

	// Init filesystem hooey
	CmdLib_InitFileSystem( g_outfile );
	// ??
	Q_FileBase( g_outfile, g_outfile, sizeof( g_outfile ) );

	// Verbose stuff
	if (!g_quiet)
	{
		vprint( 0, "%s, %s, %s, path %s\n", qdir, gamedir, g_outfile );
	}
	// ??
	Q_DefaultExtension(g_outfile, ".smd", sizeof( g_outfile ) );
	
	// Verbose stuff
	if (!g_quiet)
	{
		vprint( 0, "Source animation:  %s\n", filenames[ sourceanim ].String() );
		vprint( 0, "Target skeleton:  %s\n", filenames[ targetskel ].String() );

		vprint( 0, "Creating on \"%s\"\n", g_outfile);
	}
	// fullpath = EXTERNAL GLOBAL!!!???
	strcpy( fullpath, g_outfile );
	strcpy( fullpath, ExpandPath( fullpath ) );
	strcpy( fullpath, ExpandArg( fullpath ) );
	
	// Load source and target data
	s_source_t *pSource = Load_Source( filenames[sourceanim].String(), "smd", false, false );
	s_source_t *pTarget = Load_Source( filenames[targetskel].String(), "smd", false, false );


	//
	s_template_t *pTemplate = NULL;
	if(useTemplate)
	{
		pTemplate = Load_Template(templateFileName);
	}
	else
	{
		printf("Note: No template file specified, using defaults settings.\n");
		
		pTemplate = New_Template();
		Set_DefaultTemplate(pTemplate);
	}
	

	// Process skeleton
	s_source_t *pMappedAnimation = MotionMap( pSource, pTarget, pTemplate );

	
	// Save output (ref skeleton & animation data);
	Save_SMD( fullpath, pMappedAnimation );

	Q_StripExtension( filenames[outputanim].String(), outname, sizeof( outname ) );

	// Verbose stuff
	if (!g_quiet)
	{
		vprint( 0, "\nCompleted \"%s\"\n", g_outfile);
	}

	return 0;
}

