/***************************************************************************
 *
 * playgfx.c
 *
 ***************************************************************************
 *
 * interface between game and 3d engine for player gfx
 *
 ***************************************************************************/

/*
 * includes
 */

#include "ult_64.h"

#include "playgfx.h"
#include "anim.h"
#include "menu.h"
#include "lfptypes.h"
#include "define.h"
#include "ml.h"
#include "memory.h"
#include "file.h"
#include "min.h"


/*
 * defines
 */

/*
 * typedefs
 */

/*
 * structures
 */

typedef struct SModelHeader
{
	char
		Type[4],
		Game[4],
		Platform[4];
	s32
		Version,
		NumNodeTypes,
		NodeTypeList,
		NumMeshTypes,
		MeshTypeList;
} SModelHeader;

typedef struct SNodeType
{
	u8
		Type,
		MaxVerts,
		Count,
		pad;
	vector16
		Base;
	s32
		NodeList;
} SNodeType;


/*
 * globals
 */

#if 1
animDef
	*playerAnims;

animLegDef
	*legAnims;
#endif

vector16
	BoneOffs[MAX_BONES];

const s8
	ParentList[MAX_BONES] =
	{
		-1,				//hips
		NODE_HIPS,		//chest
		NODE_CHEST,		//head
		NODE_CHEST,		//lshoulder
		NODE_LSHOULDER,	//lbicep
		NODE_LBICEP,	//lforarm
		NODE_LFORARM,	//lhand
		NODE_CHEST,		//rshoulder
		NODE_RSHOULDER,	//rbicep
		NODE_RBICEP,	//rforarm
		NODE_RFORARM,	//rhand
		NODE_HIPS,		//lthigh
		NODE_LTHIGH,	//lshin
		NODE_LSHIN,		//lfoot
		NODE_HIPS,		//rthigh
		NODE_RTHIGH,	//rshin
		NODE_RSHIN,		//rfoot
	};


int		ufoLfp=0;
int		bigHeadMode=0;
int		midgetMode=0;
int		giraffeMode=0;

int		ufoFlag=0;


extern u8
	*FaceAnims;


/*
 * function prototypes
 */

int PlayGfxEvalBall(playert *plyr,int *bx,int *by,int *bz);

static void LookAtBall(playert *plyr, int b);


/*
 * code
 */

void SetupBoneOffs()
{
	vector16
		bases[MAX_BONES];
	u8
		*model;
	SModelHeader
		*header;
	SNodeType
		*nodetype;
	s16
		x,
		y,
		z;
	s32
		i,
		p,
		type;

	model = (u8 *)AllocAndLoadFile( "HIGH.MDL", MEM_TMP );

	header = (SModelHeader *)model;

	nodetype = (SNodeType *)&model[header->NodeTypeList];

	for (i=0; i<header->NumNodeTypes; i++)
	{
		type = nodetype->Type;

		bases[type] = nodetype->Base;

		nodetype++;
	}

	MemFree( model );


	*(u64 *)&BoneOffs[NODE_HIPS] = 0;

	for (i=0; i<MAX_BONES; i++)
	{
		p = ParentList[i];

		if (p >= 0)
		{
			BoneOffs[i].x = bases[i].x - bases[p].x;
			BoneOffs[i].y = bases[i].y - bases[p].y;
			BoneOffs[i].z = bases[i].z - bases[p].z;
		}
	}


	if (giraffeMode)
		BoneOffs[NODE_HEAD].y += 500;
}


void GetHeadBases( SPlayerInfo *pi )
{
	SModelHeader
		*header;
	SNodeType
		*nodetype;
	u8
		*model;
	s16
		x,
		y,
		z;
	s32
		i,
		type;

	model = pi->HeadModel;

	header = (SModelHeader *)model;

	nodetype = (SNodeType *)&model[header->NodeTypeList];

	for (i=0; i<header->NumNodeTypes; i++)
	{
		type = nodetype->Type;

		pi->HeadBases[type] = nodetype->Base;

		nodetype++;
	}

	x = pi->HeadBases[0].x;
	y = pi->HeadBases[0].y;
	z = pi->HeadBases[0].z;

	for (i=0; i<HEAD_NODES; i++)
	{
		pi->HeadBases[i].x -= x;
		pi->HeadBases[i].y -= y;
		pi->HeadBases[i].z -= z;
	}
}


void PlayGfx_SetFaceAnim( s32 pnum, EFaceAnim anim, s32 time )
{
	SPlayerInfo
		*pi;
	s32
		*ptr;

	pi = &PlayerInfos[pnum];

	pi->FaceAnim = anim;
	pi->FaceFrame = 0;

	if (time)
	{
		ptr = (s32 *)&FaceAnims[5*4];
		ptr += (pi->FaceAnim * 2);
		pi->FaceRate = (*ptr << 8) / time;
	}
	else
	{
		pi->FaceRate = (1 << 8);
	}
}


/***************************************************************************
 *
 * void PlayGfxOpenAnims(void)
 *
 ***************************************************************************
 *
 * initialise animations
 *
 ***************************************************************************/

//#define LOAD_ANIMS

void PlayGfxOpenAnims(void)
{
	FH
		fh;
	u8
		*data;
	s32
		i,
		len,
		aptr,
		boff,
		size,
		count,
		offset;
	u32
		rom,
		bits;
	DIR_INFO_T
		*info;
	anim
		*anims;

	// init anim cache system
	AnimCacheOpen();


	// Load main defs...

	fh = FileOpen( "MainAnim.DEF", "rb" );

	FileSeek( fh, 4*4, SEEK_SET );
	FileRead( &count, 4, 1, fh );

	size = count * sizeof( animDef );
	playerAnims = MemMalloc( size, MEM_ANY );

	for (i=0; i<count; i++)
	{
		if (!(i & ((32/4)-1)))
			FileRead( &bits, 4, 1, fh );


		playerAnims[i].loopFlag = bits & 1;
		bits >>= 1;

		playerAnims[i].rootFlag = bits & 1;
		bits >>= 1;

		playerAnims[i].fistFlag = bits & 3;
		bits >>= 2;
	}

	FileClose( fh );


	// Load leg defs...

	fh = FileOpen( "LegsAnim.DEF", "rb" );

	FileSeek( fh, 4*4, SEEK_SET );
	FileRead( &count, 4, 1, fh );

	size = count * sizeof( animLegDef );
	legAnims = MemMalloc( size, MEM_ANY );

	for (i=0; i<count; i++)
	{
		if (!(i & 31))
			FileRead( &bits, 4, 1, fh );

		legAnims[i].loopFlag = bits & 1;

		bits >>= 1;
	}

	FileClose( fh );


	// Load main animations...

	info = FileRomInfo( "MainAnim.DAT" );
	rom = info->romOffset;

	fh = FileOpen( "MainAnim.DAT", "rb" );
	FileSeek( fh, 4*4, SEEK_SET );
	FileRead( &count, 4, 1, fh );

	FileRead( &size, 4, 1, fh );
	anims = (anim *)MemMalloc( size * sizeof( anim ), MEM_ANY );
	aptr = 0;

	for (i=0; i<count; i++)
	{
		FileSeek( fh, (6*4)+(i*3*4), SEEK_SET );
		FileRead( &offset, 4, 1, fh );
		FileRead( &size, 4, 1, fh );
		FileRead( &boff, 4, 1, fh );

		FileSeek( fh, offset, SEEK_SET );
		FileRead( &anims[aptr], sizeof( anim ), 1, fh );

		size -= sizeof( anim );
		playerAnims[i].a = &anims[aptr];
		aptr++;

#ifdef LOAD_ANIMS

		if (boff)
			len = boff - sizeof( anim );
		else
			len = size;

		data = MemMalloc( len, MEM_ANY );
		FileRead( data, len, 1, fh );
		playerAnims[i].a->data = data;

#else

		playerAnims[i].a->data = (u8 *)(rom + offset + sizeof( anim ));

#endif

		playerAnims[i].b = NULL;
		if (boff)
		{
			FileSeek( fh, offset + boff, SEEK_SET );
			FileRead( &anims[aptr], sizeof( anim ), 1, fh );
			playerAnims[i].b = &anims[aptr];
			aptr++;

#ifdef LOAD_ANIMS

			size -= boff;
			data = MemMalloc( size, MEM_ANY );
			FileRead( data, size, 1, fh );
			playerAnims[i].b->data = data;

#else

			playerAnims[i].b->data = (u8 *)(rom + offset + boff + sizeof( anim ));

#endif
		}
	}

	FileClose( fh );


	// Load leg animations...

	info = FileRomInfo( "LegsAnim.DAT" );
	rom = info->romOffset;

	fh = FileOpen( "LegsAnim.DAT", "rb" );
	FileSeek( fh, 4*4, SEEK_SET );
	FileRead( &count, 4, 1, fh );

	anims = (anim *)MemMalloc( count * sizeof( anim ), MEM_ANY );

	for (i=0; i<count; i++)
	{
		FileSeek( fh, (5*4)+(i*2*4), SEEK_SET );
		FileRead( &offset, 4, 1, fh );
		FileRead( &size, 4, 1, fh );

		FileSeek( fh, offset, SEEK_SET );
		FileRead( &anims[i], sizeof( anim ), 1, fh );

		size -= sizeof( anim );
		legAnims[i].a = &anims[i];

#ifdef LOAD_ANIMS

		data = MemMalloc( size, MEM_ANY );
		FileRead( data, size, 1, fh );

		legAnims[i].a->data = data;

#else

		legAnims[i].a->data = (u8 *)(rom + offset + sizeof( anim ));

#endif
	}

	FileClose( fh );
}


void PlayGfxCloseAnims()
{
	MemFree( legAnims[0].a );
	MemFree( playerAnims[0].a );
	MemFree( legAnims );
	MemFree( playerAnims );

	AnimCacheClose();
}


static char rightNodes[]={
 NODE_HIPS, NODE_CHEST, NODE_RSHOULDER, NODE_RBICEP, NODE_RFORARM, NODE_RHAND, -1
};

static char leftNodes[]={
 NODE_HIPS, NODE_CHEST, NODE_LSHOULDER,NODE_LBICEP, NODE_LFORARM, NODE_LHAND, -1
};



/***************************************************************************
 *
 *
 *
 ***************************************************************************
 *
 * Evaluate the ball position at a given frame of the animation
 *
 ***************************************************************************/

int PlayGfxEvalBall(playert *plyr,int *bx,int *by,int *bz)
{
	int	i,j;
	anim	*an,*lan;
	short 	*wptr;
	long	tmp[3];
	vector16 vec1, vec2, vec3;
	matrix	mat1;
	long	frameNo,lfn,w;
	int	worldBlend;

	/*
	 * get ball animation
	 */



	an=playerAnims[abs(plyr->top_anim)].b;
	if(!an) return(1);

	/*
	 * evaluate player angles...
	 */

	EvaluateAngles(plyr,&i,&j, plyr->ikBlend);

	if (plyr->ikBlend != 0)
	{
		/*
		 * Evaluate node positions 
		 */

		if (plyr->top_anim>0)
		{
			GetNodePos(plyr, rightNodes);
			*bx = plyr->nodeVector[NODE_RHAND].x << 10;
			*by = plyr->nodeVector[NODE_RHAND].y << 10;
			*bz = plyr->nodeVector[NODE_RHAND].z << 10;
		}
		else
		{
			GetNodePos(plyr, leftNodes);
			*bx = plyr->nodeVector[NODE_LHAND].x << 10;
			*by = plyr->nodeVector[NODE_LHAND].y << 10;
			*bz = plyr->nodeVector[NODE_LHAND].z << 10;

		}
		return 0;
	}



	/*
	 * clamp frame number
	 */

	frameNo=plyr->top_frame/256;
	if(frameNo >= an->numFrames)
		if (playerAnims[abs(plyr->top_anim)].loopFlag)
			frameNo=frameNo % an->numFrames;
		else
			frameNo=an->numFrames-1;

	/*
	 * get [maybe blended] ball position
	 */

	worldBlend=0;

	w=plyr->top_blend_ctr & 0xFF;

	
	if(w)
	{
		lan=playerAnims[abs(plyr->last_top_anim)].b;

		if(lan)
		{
			lfn=plyr->last_top_frame/256;
			if(lfn >= lan->numFrames)
				if (playerAnims[abs(plyr->last_top_anim)].loopFlag)
					lfn=lfn % lan->numFrames;
				else
					lfn=lan->numFrames-1;

			wptr=AnimCache(lan,lfn);
			vec2.x=wptr[0];
			vec2.y=wptr[1];
			vec2.z=wptr[2];
			if(plyr->last_top_anim<0) vec2.x = -vec2.x;
		}
		else
		{
			// we want to blend from the ball's previous WORLD
			// coordinates - just store current values for now...

			worldBlend=w;
			w=0;
		}

		wptr=AnimCache(an,frameNo);
		vec3.x=wptr[0];
		vec3.y=wptr[1];
		vec3.z=wptr[2];
		if(plyr->top_anim<0) vec3.x = -vec3.x;

		vec1.x=((w * (SI)vec2.x) + ((256-w) * (SI)vec3.x)) / 256;
		vec1.y=((w * (SI)vec2.y) + ((256-w) * (SI)vec3.y)) / 256;
		vec1.z=((w * (SI)vec2.z) + ((256-w) * (SI)vec3.z)) / 256;
	}
	else
	{
		wptr=AnimCache(an,frameNo);
		vec1.x=wptr[0];
		vec1.y=wptr[1];
		vec1.z=wptr[2];
		if(plyr->top_anim<0) vec1.x = -vec1.x;
	}

	/*
	     vec2 =  (chestOff*hipMat)
			    + hipOff		//aFrame
			    + ballOff		//vec1
	 */

	QuatToMatrix(&mat1, &plyr->aFrame.q[NODE_HIPS]);
	Vec16MulMat(&BoneOffs[NODE_CHEST],&mat1,&vec2);
	if (plyr->flags&MOVE_ROOT)
	{
		vec2.x=0;
		vec2.z=0;
	}
	else
	{
		vec2.x += plyr->aFrame.x;
		vec2.z += plyr->aFrame.z;
	}

	vec2.y += plyr->aFrame.y;

	vec2.x += vec1.x;
	vec2.y += vec1.y;
	vec2.z += vec1.z;

	/* 
	 * vec1 = (vec2*rootMat)
	 */
	MakeMatPRH(&mat1,0,(plyr->pitch)<<8,(plyr->angle)+(0x40<<8));
//	MakeMatPRH(&mat1,0,(plyr->pitch)<<8, 0x4000);
	Vec16MulMat(&vec2,&mat1,&vec1);

	/*
	 * convert to Mike's coordinate system
	 * add world offset
	 */
	*bx = ((SI)vec1.x*(plyr->scale<<2)) + plyr->xco;
	*by = ((SI)vec1.y*(plyr->scale<<2)) + plyr->zco;
	*bz = ((SI)vec1.z*(plyr->scale<<2)) + plyr->yco;

	/*
	 * blend from previous world coordinates if required
	 */
	if(worldBlend)
	{
		*bx=(((*bx)>>10)*(256-worldBlend) + (last_bx>>10)*worldBlend) << (10-8);
		*by=(((*by)>>10)*(256-worldBlend) + (last_bz>>10)*worldBlend) << (10-8);
		*bz=(((*bz)>>10)*(256-worldBlend) + (last_by>>10)*worldBlend) << (10-8);
	}

	/*
	 * that'll do nicely, madam
	 */

	return(0);
}



/***************************************************************************
 *
 *
 *
 ***************************************************************************
 *
 *
 *
 ***************************************************************************/

void EvaluateAngles(playert *plyr,int *lf,int *rf, int ik)
{
	int rootFlag,animNo,frameNo;
	int legBlendVal,topBlendVal;
	int i;


	legBlendVal = plyr->leg_blend_ctr;
	if (legBlendVal > 0x100)	legBlendVal &= 0xff;

	topBlendVal = plyr->top_blend_ctr;
	if (topBlendVal > 0x100)	topBlendVal &= 0xff;


	rootFlag = FALSE;

	if (topBlendVal)
	{
		//reconstruct last top
		animNo  = plyr->last_top_anim;
		frameNo = plyr->last_top_frame;
		if (animNo < 0)
		{
			rootFlag = playerAnims[-animNo].rootFlag;
			AnimEvaluateMirror(playerAnims[-animNo].a,rootFlag,frameNo, &plyr->aFrame);
		}
		else
		{
			rootFlag = playerAnims[animNo].rootFlag;
			AnimEvaluate(playerAnims[animNo].a,rootFlag,frameNo, &plyr->aFrame);
		}
	}

	if (legBlendVal)
	{
		//reconstruct last legs
		animNo = plyr->last_leg_anim;
		if (animNo)
		{
			frameNo = plyr->last_leg_frame;
			if (animNo < 0)
			{
				AnimEvaluateMirror(legAnims[-animNo].a,1,frameNo, &plyr->aFrame);
			}
			else
			{
				AnimEvaluate(legAnims[animNo].a,1,frameNo, &plyr->aFrame);
			}
		}
		else
		{
			//use last top
			if ((!rootFlag) && (playerAnims[abs(animNo)].rootFlag))
			{
				frameNo = plyr->last_top_frame;
				if (animNo < 0)
				{
					AnimEvaluateMirror(playerAnims[-animNo].a,1,frameNo, &plyr->aFrame);
				}
				else
				{
					AnimEvaluate(playerAnims[animNo].a,1,frameNo, &plyr->aFrame);
				}
			}
			else
			{
				legBlendVal = 0;
			}
		}
	}

	animNo  = plyr->top_anim;
	frameNo = plyr->top_frame;

	if(animNo>0)
	{
		*lf=(playerAnims[animNo].fistFlag & 2) ? HAND_FIST : HAND_OPEN;
		*rf=(playerAnims[animNo].fistFlag & 1) ? HAND_FIST : HAND_OPEN;
	}
	else
	{
		*lf=(playerAnims[-animNo].fistFlag & 1) ? HAND_FIST : HAND_OPEN;
		*rf=(playerAnims[-animNo].fistFlag & 2) ? HAND_FIST : HAND_OPEN;
	}

	if (topBlendVal == 0)
	{
		//just use this top
		if (animNo < 0)
		{
			rootFlag = playerAnims[-animNo].rootFlag;
			AnimEvaluateMirror(playerAnims[-animNo].a, rootFlag, frameNo, &plyr->aFrame);
		}
		else
		{
			rootFlag = playerAnims[animNo].rootFlag;
			AnimEvaluate(playerAnims[animNo].a, rootFlag, frameNo, &plyr->aFrame);
		}
	}
	else
	{
		//blend last top on
		if (animNo < 0)
		{
			rootFlag = playerAnims[-animNo].rootFlag;
			AnimEvaluateBlendMirror(playerAnims[-animNo].a, rootFlag, frameNo, &plyr->aFrame, 0x100-topBlendVal);
		}
		else
		{
			rootFlag = playerAnims[animNo].rootFlag;
			AnimEvaluateBlend(playerAnims[animNo].a, rootFlag, frameNo, &plyr->aFrame, 0x100-topBlendVal);
		}
	}

	if (!rootFlag)
	{
		animNo  = plyr->leg_anim;
		if (animNo)
		{
			frameNo = plyr->leg_frame;

			if (legBlendVal == 0)
			{
				//just use this legs
				if (animNo < 0)
				{
					AnimEvaluateMirror(legAnims[-animNo].a, 1,frameNo, &plyr->aFrame);
				}
				else
				{
					AnimEvaluate(legAnims[animNo].a, 1, frameNo, &plyr->aFrame);
				}
			}
			else
			{
				//blend legs on top of last legs
				if (animNo < 0)
				{
					AnimEvaluateBlendMirror(legAnims[-animNo].a, 1, frameNo, &plyr->aFrame, 0x100-legBlendVal);
				}
				else
				{
					AnimEvaluateBlend(legAnims[animNo].a, 1, frameNo, &plyr->aFrame, 0x100-legBlendVal);
				}
			}
		}
	}


		/*
		for (i=0; i<MAX_BONES; i++)
		{
			plyr->aFrame.q[i].w = 0x4000;
			plyr->aFrame.q[i].x = 0;
			plyr->aFrame.q[i].y = 0;
			plyr->aFrame.q[i].z = 0;
		}
		*/



	if (ik)
	{
		ikFrameT 	*ikfp;
		int ballIk;

		ikfp = ikFrames+plyr->ikFrameNo;

		for (i=0; i<5; i++)
		{
			int	node;

			node = ikfp->node;
			if (node)
			{
				InterpolateQuat(&plyr->aFrame.q[node], &ikfp->q, ik, &plyr->aFrame.q[node]);

//				plyr->aFrame.q[node] = ikfp->q;
			}

			ikfp++;
		}

	  if (ik>0x80)	ballIk = 0x100;
		else				  ballIk = ik*2;

		LookAtBall(plyr, ballIk);
	}
}


void LookAtBall(playert *plyr, int blend)
{
vector v_bh;
matrix m;
SW     a;
int    s,c,i;
Quat   qRot;

	GetNodePos(plyr, headNodes);
	
	//vector to ball from head
	v_bh.x = (ball.xco>>10) - plyr->nodeVector[NODE_HEAD].x;		
	v_bh.y = (ball.zco>>10) - plyr->nodeVector[NODE_HEAD].y;
	v_bh.z = (ball.yco>>10) - plyr->nodeVector[NODE_HEAD].z;

	v_bh.y = v_bh.y/2;

	//get vector in head co's
	VecMulIMat(&v_bh, plyr->skelMatrix+NODE_HEAD, &v_bh);

	SlowNormalise(&v_bh);

	//axis of head rotation is (0,0,-1) x (v_bh) = (v_bh.y, -v_bh.x, 0)
	//cos(angle) = (0,0,-1).(v_bh) = -v_bh.z
	a   = LFPAcos(-v_bh.z);
	a   = ((blend*(int)(a))>>8);

	s = LFPSin(a/4);
	c = LFPCos(a/4);

	v_bh.z = 0;
	SlowNormalise(&v_bh);

	qRot.w = c;
	qRot.x = (v_bh.y * s)/0x4000;
	qRot.y = -(v_bh.x * s)/0x4000;
	qRot.z = 0;
 	QuatMul(plyr->aFrame.q+NODE_HEAD, &qRot, plyr->aFrame.q+NODE_HEAD);
}
