/************************************************************************************
	GLOVER PSX	(c) 1998-9 ISL

	object.c:		Poly draw routines for general objects

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

#include "glover.h"

//extern GsOTA *PolyList;
							 

#define LARGESORTDEPTH NO

int object_atlantis_waterflag = 0;   //this should be removed!!!

static ULONG	partcount;

MODELCTRL	modelctrl;

/************************************************************************************/
void Reset_Modelctrl()
{
	modelctrl.depthoveride = 0;
//	modelctrl.specialmode = OFF;
//	modelctrl.onmap = 0;
//	SETRGBC(modelctrl.col.r,128,128,128,YES);
//	modelctrl.scaling = NO;
//	modelctrl.nearclip = 100;
//	modelctrl.shownormals = 0;
//	modelctrl.reducefaces = YES;
}
/************************************************************************************/
MATRIX matrixstack[MAXMATRIXSTACK];
ULONG matrixstackcount;
void objectCalcWorldMatrix(NEWMODEL *actor, NEWOBJECT *world)
{
	//MATRIX	cameraAndGlobalscale;

	if (world->parent == WORLD)
	{
		//cameraAndGlobalscale = GsWSMATRIX;
	   	//ScaleMatrix(&cameraAndGlobalscale, &actor->globalscale);
		matrixstack[matrixstackcount] = GsWSMATRIX;
	   	ScaleMatrix(&matrixstack[matrixstackcount], &actor->globalscale);
	}

	while(world)
	{
	   	world->matrixscale = world->matrix;
	   	ScaleMatrix(&world->matrixscale,&world->scale);
		if (world->parent == WORLD)
		{
			if (actor==pGlovePSA && GloveCtrl.action==HAND_SWISH)
			{
				gte_MulMatrix0(&world->matrix,&GloveCtrl.matrix , &world->matrix);
			}

//			gte_MulMatrix0(&cameraAndGlobalscale, &world->matrix, &world->matrix);
//			gte_MulMatrix0(&cameraAndGlobalscale, &world->matrixscale, &world->matrixscale);
			gte_MulMatrix0(&matrixstack[matrixstackcount], &world->matrix, &world->matrix);
			gte_MulMatrix0(&matrixstack[matrixstackcount], &world->matrixscale, &world->matrixscale);

			gte_SetRotMatrix(&GsWSMATRIX);
			gte_SetTransMatrix(&GsWSMATRIX);
			gte_ldlvl(&world->matrixscale.t);
			gte_rtirtr();
			gte_stlvl(&world->matrixscale.t);
			
	 	}
	   	else
	   	{
			gte_MulMatrix0(&world->parent->matrix, &world->matrix, &world->matrix);
			gte_MulMatrix0(&world->parent->matrix, &world->matrixscale, &world->matrixscale);
			gte_SetRotMatrix(&world->parent->matrixscale);
			gte_SetTransMatrix(&world->parent->matrixscale);
			gte_ldlvl(&world->matrix.t);
			gte_rtirtr();
			gte_stlvl(&world->matrixscale.t);
		}

		if(world->child){
			matrixstackcount++;
			if (matrixstackcount==MAXMATRIXSTACK){DB("MAX matrix stack hit- Objectdraw!\n");CRASH;}
			objectCalcWorldMatrix(actor, world->child);
			matrixstackcount--;
		}
		world = world->next;
	}
}
/**********************************************************************************************************/

void objectInitLightingForObject(NEWMODEL *actor,NEWOBJECT *world)
{
	MATRIX	lightmat;
	SVECTOR	rotVec;

	MATRIX	lmat2,lmat3;
//	printf("lighting %s\n",world->meshdata->name);
	if (actor==pBallPSA || actor==pPlasterPSA)
	{
		// NOTE :- for some strange reason the lighting uses difference axis to the world 
		quaternionGetMatrix(&actor->world.qRotLight, &lmat2);		
		lmat3=GsIDMATRIX;
		rotVec.vx = 640; // 
		rotVec.vy = -320;
		rotVec.vz = 0;

		if (BallChange.changeOn)
		{
			GsSetAmbient(BallChange.dimAmbient.vx,BallChange.dimAmbient.vy,BallChange.dimAmbient.vz);
			RotMatrixYXZ_gte(&rotVec,&lmat3);
			gte_MulMatrix0(&lmat3,&lmat2,&lmat2);
			ScaleMatrix(&lmat2,&BallChange.dimAmount);
			GsSetLightMatrix(&lmat2);
		}
		else
		{
			RotMatrixYXZ_gte(&rotVec,&lmat3);
			gte_MulMatrix0(&lmat3,&lmat2,&lmat2);
			GsSetLightMatrix(&lmat2);
		}
	}
	else
	{
		lightmat=GsIDMATRIX;
		rotVec.vx = -actor->world.rotate.vx;
		rotVec.vy = -actor->world.rotate.vy;
		rotVec.vz = actor->world.rotate.vz;
		RotMatrixYXZ(&rotVec,&lightmat);
		GsSetLightMatrix(&lightmat);
	}
}
/**************************************************************************
	FUNCTION:	objectDraw()
	PURPOSE:	Draw a generic hierarchical object
	PARAMETERS:	Model structure
	RETURNS:	
**************************************************************************/
VECTOR nullvect = {0,0,0};
void objectDraw(NEWMODEL *actor)
{
	LONG		loop;
	NEWOBJECT	*world;
	LONG		depth,avdepth,depthdif;
	MODELCTRL	*modctrl = &modelctrl;
	LONG objrotate;
	world = &actor->world;
	objrotate=(world->rotate.vy&0xfff);

	TIMER_START(TIMER_DRAWSYNC);

	world->matrix.t[0] += actor->position.vx;
	world->matrix.t[1] += actor->position.vy;
	world->matrix.t[2] += actor->position.vz;

	matrixstackcount=0;
	objectCalcWorldMatrix(actor, world);

 	if(modctrl->lighting==LIGHTING_PARALLEL)
 		objectInitLightingForObject(actor, world);

   	gte_SetRotMatrix(&world->matrixscale);
   	gte_SetTransMatrix(&world->matrixscale);

	gte_ldv0(&nullvect);
	gte_rtps();
	gte_stsz(&avdepth);

	modelctrl.currentmodel = actor;

	modctrl->sorttable=((-objrotate-1024+(4096*4)-calc_angle(CamVars.camera.vpz-actor->position.vz,CamVars.camera.vpx-actor->position.vx))&0xfff)>>10;

	for(loop = 0; loop < actor->numberofsegments; loop++)
	{
		world = actor->segmenttable[loop];
	 
	 	gte_SetRotMatrix(&world->matrixscale);
	   	gte_SetTransMatrix(&world->matrixscale);
		
		gte_ldv0(&world->meshdata->center);
		gte_rtps();
		gte_stsz(&depth);

		depthdif=depth-avdepth;


#if LARGESORTDEPTH==YES
		if(modelctrl.depthoveride!=0)	depth=depthdif+(modelctrl.depthoveride*4);
		else							depth=depthdif+(avdepth/4);
#else
		if(modelctrl.depthoveride!=0)	depth=depthdif/4+(modelctrl.depthoveride);
		else							depth=depthdif/4+(avdepth/4);
#endif

		modctrl->VertTop = world->meshdata->vertop;
		modctrl->NormTop = (VERT *)world->meshdata->nortop;
		modctrl->PrimTop = world->meshdata->primtop;
		
	 	modctrl->SortPtr = world->meshdata->sorttable[modctrl->sorttable];
		modctrl->PrimLeft = world->meshdata->sorttablesize[modctrl->sorttable];

		if(TOOMANYPOLYS(modctrl->PrimLeft * MAXPACKETSIZE,"DrawModel"))	return;

		transformVertexListB((VERT*)(world->meshdata->vertop), world->meshdata->vern, transVerts);

// Fred sez... For some reason this limit needed to be pulled closer
// (Otherwise the OOTW boss sorted behind his back wall when he was beyond max-Z

		// if top bit set, set depth directly.
		if(modelctrl.depthoveride & 0x8000)
			depth=modelctrl.depthoveride & 0x7fff;

		if(depth>MAXOTZ-50)
		{
//			DB("Model was at %d. MAXOTZ = %d\n", depth, MAXOTZ);

			depth=MAXOTZ - 50;
		}
		else if (depth<2)
		{
			if(actor == fadeStar)
				depth = 0;
			else
				depth=2;	// text overlays are at one (yes one - see overlay, spriteprint)
		}
		if (modctrl->lighting==NO)polyDrawPrimitives((GsOTA *)(PolyList->org + depth));
		else polyDrawPrimitivesLite((GsOTA *)(PolyList->org + depth));
	}

	modctrl->lighting=NO;  //always turns of lighting, as off is default

	modctrl->lastdepth=depth & MAXDEPTH;
	TIMER_STOP(TIMER_DRAWSYNC);
}

/**********************************************************************************************************/
// =================================================================
// Position is taken from the NEWMODEL structure. Radii are "small" (not *4096)
// This rou calls "getheightat". If the height of the shadow is known & fixed, call the shadow rou directly
// If the object is within "objradius" of the floor, the shadow will be inserted at the same OT position
// as the object, otherwise it will be independantly sorted
// TBD - Currently, if the object ain't visible neither is the shadow
// (flags (will be) for things like height-dependant shadow radii)
//long objrotat=0;

void objectDrawWithShadow(NEWMODEL *actor,LONG objradius,LONG shadradius, int flags)
{
	short		loop;
	NEWOBJECT	*world;
	MODELCTRL	*modctrl = &modelctrl;

	LONG		depth,avdepth,depthdif;
	LONG objrotate;

	VALIDATE(actor);

	world = &actor->world;
	objrotate=(world->rotate.vy&0xfff);
	world->matrix.t[0] += actor->position.vx;
	world->matrix.t[1] += actor->position.vy;
	world->matrix.t[2] += actor->position.vz;

	matrixstackcount=0;
	objectCalcWorldMatrix(actor, world);

	if(modctrl->lighting==LIGHTING_PARALLEL)
 		objectInitLightingForObject(actor, world);

   	gte_SetRotMatrix(&world->matrixscale);
   	gte_SetTransMatrix(&world->matrixscale);

	gte_ldv0(&nullvect);
	gte_rtps();
	gte_stsz(&avdepth);

	modelctrl.lighting&=(~LIGHTING_DYNAMIC); // Turn off dynamic lights FTM
	modelctrl.currentmodel = actor;

	for(loop = 0; loop < actor->numberofsegments; loop++)
	{
		world = actor->segmenttable[loop];

		//if((world->pad == 0x00ff) || (world->pad == 0xbac2))	// skip gun if it's hidden
		//	continue;

	 	gte_SetRotMatrix(&world->matrixscale);
	   	gte_SetTransMatrix(&world->matrixscale);

		gte_ldv0(&world->meshdata->center);
		gte_rtps();
		gte_stsz(&depth);

		depthdif=depth-avdepth;
  //		printf("%s %d (%d,%d):",world->meshdata->name,depthdif,depth,avdepth);

#if LARGESORTDEPTH==YES
		if(modelctrl.depthoveride!=0)	depth=depthdif+(modelctrl.depthoveride*4);
		else							depth=depthdif+(avdepth/4);
#else
		if(modelctrl.depthoveride!=0)	depth=depthdif/4+(modelctrl.depthoveride);
		else							depth=depthdif/4+(avdepth/4);
#endif

		modctrl->VertTop = world->meshdata->vertop;
		modctrl->NormTop = (VERT *)world->meshdata->nortop;
		modctrl->PrimTop = world->meshdata->primtop;
		modctrl->sorttable=((-objrotate-1024+(4096*4)-calc_angle(CamVars.camera.vpz-CamVars.camera.vrz,CamVars.camera.vpx-CamVars.camera.vrx))&0xfff)>>10;
	 	modctrl->SortPtr = world->meshdata->sorttable[modctrl->sorttable];		// this needs to point to correct sort table
		modctrl->PrimLeft = world->meshdata->sorttablesize[modctrl->sorttable];

		if(TOOMANYPOLYS(modctrl->PrimLeft * MAXPACKETSIZE,"DrawModel"))
  			return;

		transformVertexListB((VERT*)(world->meshdata->vertop), world->meshdata->vern, transVerts);
		
		// if top bit set, set depth directly.
		if(modelctrl.depthoveride & 0x8000)
			depth=modelctrl.depthoveride & 0x7fff;

		if(depth>MAXOTZ-50)
		{
			depth=MAXOTZ - 50;
		}
		else if (depth<2)depth=2;	// text overlays are at one (yes one - see overlay, spriteprint)
  		modctrl->lastdepth=depth;
		//polyDrawPrimitives((GsOTA *)(PolyList->org + depth));
		if (modctrl->lighting==NO)polyDrawPrimitives((GsOTA *)(PolyList->org + depth));
		else polyDrawPrimitivesLite((GsOTA *)(PolyList->org + depth));


	}
// okay, now let's deal with the shadow...
	{
		VECTOR bigvec;
		int hag;

		bigvec.vx = actor->position.vx << 12;
		bigvec.vy = actor->position.vy << 12;
		bigvec.vz = actor->position.vz << 12;

		hag = getHeightAt(bigvec.vx, bigvec.vy, bigvec.vz);

		if(hag < 0x7fffffff)
		{
			SVECTOR vec;
			vec.vx = bigvec.vx >> 12;
			vec.vy = (bigvec.vy + hag) >> 12;
			vec.vz = bigvec.vz >> 12;

			gte_SetRotMatrix(&GsWSMATRIX);
			gte_SetTransMatrix(&GsWSMATRIX);

			if(hag <= objradius)
			{
				Draw_RawShadow(&vec,shadradius,0x404040,-1);
			}
			else
			{
				Draw_RawShadow(&vec,shadradius,0x404040,depth);
			}
		}
	}
	modctrl->lighting=NO;  //always turns of lighting, as off is default
}
/*-------------------------------------------------------*/
static void objectGetSegments(NEWOBJECT *object)
{
	while(object)
	{
		partcount++;
		if(object->child)
			objectGetSegments(object->child);
		object = object->next;
	}
}
/*-------------------------------------------------------*/
void objectSetSegments(NEWMODEL *model,NEWOBJECT *object)
{
	while(object)
	{
		(UBYTE*)(model->segmenttable[partcount])=object;
		partcount++;
		if(object->child)
			objectSetSegments(model,object->child);
		object = object->next;
	}
}
/**************************************************************************
	FUNCTION:	objectSetupSegmentSort()
	PURPOSE:	Set up object inter-segment sorting
	PARAMETERS:	Model structure
	RETURNS:	
**************************************************************************/
void objectSetupSegmentSort(NEWMODEL *model)
{
	partcount = 0;
	objectGetSegments(&model->world);
	model->numberofsegments = partcount;

// BFF_PSA now has a little "no-of-objects" buffer straight after the object header
	model->segmenttable = ADD2POINTER(model,sizeof(NEWMODEL));
	partcount = 0;
	objectSetSegments(model,&model->world);
}
/*********************************************************************/

// used to be a *big* local (& temporary) variable inside a recursive routine. Ouch!
#define OSRKF_RMAT ((MATRIX *)(SCRATCHPAD))

void objectSetRotateKeyFrames(NEWMODEL *actor, ULONG frame)
{//this routine is now NOT RECURSIVE		  
	SQKEYFRAME	*tmprotatekeys,*tmprotatekeyslast;
	long		keystep;
	ULONG 		oldframe = frame;
	long		tempx, tempz;
	ULONG loop;
	NEWOBJECT *world;

	for(loop = 0; loop < actor->numberofsegments; loop++)
	{
		world = actor->segmenttable[loop];

		if (world->numRotateKeys==1)
		{
			ShortquaternionGetMatrix(&world->rotatekeys->vect, &world->matrix);
			RotMatrixYXZ_gte(&world->rotate,OSRKF_RMAT);
			gte_MulMatrix0(OSRKF_RMAT,&world->matrix,&world->matrix);
		}
		else
		{
			tmprotatekeys = world->rotatekeys;
			if(!frame)
			{
				ShortquaternionGetMatrix(&tmprotatekeys->vect, &world->matrix);
			}
			else
			{

// Ian Bird's new binsearch

			keystep = world->numRotateKeys / 2;
			tmprotatekeys = &world->rotatekeys[keystep];
			
			if ( keystep>1 ) keystep /= 2;
			
			while (frame != tmprotatekeys->time)
			{

				if (frame > tmprotatekeys->time)
				{
					if ( frame <= tmprotatekeys[1].time )
					{
						tmprotatekeys++;
						break;
					}
					else
					{
						tmprotatekeys += keystep;
// If this assert fails, then the animation needs to have a keyframe at the end
// to find what animation it is, printing "world->meshdata->name" might be handy
//						ASSERT(
//							tmprotatekeys-world->rotatekeys < world->numRotateKeys-1
//							|| (tmprotatekeys-world->rotatekeys == world->numRotateKeys-1 && tmprotatekeys->time == frame))

						if(
							tmprotatekeys-world->rotatekeys < world->numRotateKeys-1
							|| (tmprotatekeys-world->rotatekeys == world->numRotateKeys-1 && tmprotatekeys->time == frame))
						{
						}
						else
						{
#if (GOLDCD==NO) && (RELEASE==NO)
							DB("Object has no terminating rotate keyframe %s\n",world->meshdata->name);
#endif
							tmprotatekeys = &world->rotatekeys[world->numRotateKeys-1];
							frame = tmprotatekeys->time;
							break;
						}
					}
				}
				else
				{
					ASSERT(tmprotatekeys >= 0);
					tmprotatekeys -= keystep;
				}

				if ( keystep>1 ) keystep /= 2;
			}
//end of binary search	
	
/**************/
			
				if (tmprotatekeys->time == frame) // it's on the keyframe 
				{
					ShortquaternionGetMatrix(&tmprotatekeys->vect, &world->matrix);
				}
				else // work out the differences 
				{
					tmprotatekeyslast = tmprotatekeys - 1;

					if((tmprotatekeys->vect.x == tmprotatekeyslast->vect.x) && (tmprotatekeys->vect.y == tmprotatekeyslast->vect.y) && (tmprotatekeys->vect.z == tmprotatekeyslast->vect.z) && (tmprotatekeys->vect.w == tmprotatekeyslast->vect.w))
					{
						ShortquaternionGetMatrix(&tmprotatekeys->vect, &world->matrix);
					}
					else
					{
						ShortquaternionSlerpMatrix(&tmprotatekeyslast->vect,
										&tmprotatekeys->vect,
										((frame - tmprotatekeyslast->time) << 12) / (tmprotatekeys->time - tmprotatekeyslast->time),
										&world->matrix);
					}
				}
			}
	   
			RotMatrixYXZ_gte(&world->rotate,OSRKF_RMAT);
			gte_MulMatrix0(OSRKF_RMAT,&world->matrix,&world->matrix);
		}

		if(!world->parent)
		{
			tempx = rcos(world->rotate.vy);
			tempz = rsin(world->rotate.vy);
			world->matrix.t[0] = ( (tempx * world->matrix.t[0]) + (tempz * world->matrix.t[2]) ) >> 13;
			world->matrix.t[2] = ( -(tempz * world->matrix.t[0]) + (tempx * world->matrix.t[2]) ) >> 13;
		}
		frame=oldframe;
	}

}
/************************************************************************************/

static void objectSetScaleKeyFrames(NEWMODEL *actor, ULONG frame)
{//this is a now NOT RECURSIVE routine
	SVKEYFRAME	*tmpscalekeys,*tmpscalekeyslast;
	long		keystep;
	USHORT		oldframe=frame;
	LONG		t;
	ULONG loop;
	NEWOBJECT *world;
	for(loop = 0; loop < actor->numberofsegments; loop++)
	{
		world = actor->segmenttable[loop];

		tmpscalekeys = world->scalekeys;//+frame;

		// First find correct key frames

		// current key frame should be >= frame 
		// position is between this keyframe and this keyframe-1
		// note check for thery first keyframe
		if(!frame || world->numScaleKeys==1)
		{
			world->scale.vx = (tmpscalekeys->vect.x)<<2;
			world->scale.vy = (tmpscalekeys->vect.y)<<2;
			world->scale.vz = (tmpscalekeys->vect.z)<<2;
		}
		else
		{
// Ian Bird's new binsearch
			keystep = world->numScaleKeys / 2;
			tmpscalekeys = &world->scalekeys[keystep];
			if ( keystep>1 ) keystep /= 2;
			
			while (frame != tmpscalekeys->time)
			{
				if (frame > tmpscalekeys->time)
				{
					if ( frame <= tmpscalekeys[1].time )
					{
						tmpscalekeys++;
						break;
					}
					else
					{
						tmpscalekeys += keystep;
// If this assert fails, then the animation needs to have a keyframe at the end
// to find what animation it is, printing "world->meshdata->name" might be handy
//						ASSERT(
//							tmpscalekeys-world->scalekeys < world->numScaleKeys-1
//							|| (tmpscalekeys-world->scalekeys == world->numScaleKeys-1 && tmpscalekeys->time == frame))


						if(
							tmpscalekeys-world->scalekeys < world->numScaleKeys-1
							|| (tmpscalekeys-world->scalekeys == world->numScaleKeys-1 && tmpscalekeys->time == frame))
						{
						}
						else
						{
#if (GOLDCD==NO) && (RELEASE==NO)
							DB("Object has no terminating scale keyframe %s\n",world->meshdata->name);
#endif
							tmpscalekeys = &world->scalekeys[world->numScaleKeys-1];
							frame = tmpscalekeys->time;
							break;
						}
					}
				}
				else
				{
					tmpscalekeys -= keystep;
					ASSERT(tmpscalekeys >= 0);
				}
				if ( keystep>1 ) keystep /= 2;
			}
//end of binary search

			if(tmpscalekeys->time == frame)	// it's on the keyframe 
			{
				world->scale.vx = (tmpscalekeys->vect.x)<<2;
				world->scale.vy = (tmpscalekeys->vect.y)<<2;
				world->scale.vz = (tmpscalekeys->vect.z)<<2;
			}
			else // work out the differences 
			{
				tmpscalekeyslast = tmpscalekeys - 1;

				if((tmpscalekeys->vect.x == tmpscalekeyslast->vect.x) && (tmpscalekeys->vect.y == tmpscalekeyslast->vect.y) && (tmpscalekeys->vect.z == tmpscalekeyslast->vect.z))
				{
					world->scale.vx = (tmpscalekeys->vect.x)<<2;
					world->scale.vy = (tmpscalekeys->vect.y)<<2;
					world->scale.vz = (tmpscalekeys->vect.z)<<2;
				}
				else
				{
					VECTOR temp1;
					VECTOR temp2;
					temp1.vx =((int)(tmpscalekeyslast->vect.x))<<2;
					temp1.vy =((int)(tmpscalekeyslast->vect.y))<<2;
					temp1.vz =((int)(tmpscalekeyslast->vect.z))<<2;
					temp2.vx =((int)(tmpscalekeys->vect.x))<<2;
					temp2.vy =((int)(tmpscalekeys->vect.y))<<2;
					temp2.vz =((int)(tmpscalekeys->vect.z))<<2;

					t = ((frame - tmpscalekeyslast->time) << 12) / (tmpscalekeys->time - tmpscalekeyslast->time);

					gte_lddp(t);							// load interpolant
					gte_ldlvl(&temp1);						// load source
					gte_ldfc(&temp2);						// load dest
					gte_intpl();							// interpolate (8 cycles)
					gte_stlvnl(&world->scale);				// store interpolated vector
				}
			}
		}	
		frame=oldframe;
	}
}
/******************************************************************************************************/
static void objectSetMoveKeyFrames(NEWMODEL *actor, ULONG frame)
{//this routine is now NOT RECURSIVE
	SVKEYFRAME	*tmpmovekeys,*tmpmovekeyslast;
	long		keystep;
	USHORT		oldframe=frame;
	LONG		t;
	VECTOR		source, dest;
	ULONG loop;
	NEWOBJECT *world;

	for(loop = 0; loop < actor->numberofsegments; loop++)
	{
		world = actor->segmenttable[loop];

		tmpmovekeys = world->movekeys;//+frame;
		// First find correct key frames

		// current key frame should be >= frame 
		// position is between this keyframe and this keyframe-1
		// note check for very first keyframe

		if(!frame || world->numMoveKeys==1)
		{
			world->matrix.t[0] = (tmpmovekeys->vect.x);
			world->matrix.t[1] = -(tmpmovekeys->vect.y);
			world->matrix.t[2] = (tmpmovekeys->vect.z);
		}
		else
		{
/*********************************/
// Ian Bird's new binsearch

			keystep = world->numMoveKeys / 2;

			tmpmovekeys = &world->movekeys[keystep];
			
			if ( keystep>1 ) keystep /= 2;
			
			while (frame != tmpmovekeys->time)
			{

				if (frame > tmpmovekeys->time)
				{
					if ( frame <= tmpmovekeys[1].time )
					{
						tmpmovekeys++;
						break;
					}
					else
					{
						tmpmovekeys += keystep;
// If this assert fails, then the animation needs to have a keyframe at the end
// to find what animation it is, printing "world->meshdata->name" might be handy

//						ASSERT(
//							tmpmovekeys-world->movekeys < world->numMoveKeys-1
//							|| (tmpmovekeys-world->movekeys == world->numMoveKeys-1 && tmpmovekeys->time == frame))

						if(
							tmpmovekeys-world->movekeys < world->numMoveKeys-1
							|| (tmpmovekeys-world->movekeys == world->numMoveKeys-1 && tmpmovekeys->time == frame))
						{
						}
						else
						{
#if (GOLDCD==NO) && (RELEASE==NO)
							DB("Object has no terminating move keyframe %s\n",world->meshdata->name);
#endif
							tmpmovekeys = &world->movekeys[world->numMoveKeys-1];
							frame = tmpmovekeys->time;
							break;
						}
					}
				}
				else
				{
					tmpmovekeys -= keystep;
					ASSERT(tmpmovekeys >= 0);
				}

				if ( keystep>1 ) keystep /= 2;
			}
//end binary search


			if(tmpmovekeys->time == frame)	// it's on the keyframe 
			{
				world->matrix.t[0] = (tmpmovekeys->vect.x);
				world->matrix.t[1] = -(tmpmovekeys->vect.y);
				world->matrix.t[2] = (tmpmovekeys->vect.z);
			}
			else // work out the differences 
			{
				tmpmovekeyslast = tmpmovekeys - 1;

				if((tmpmovekeys->vect.x == tmpmovekeyslast->vect.x) && (tmpmovekeys->vect.y == tmpmovekeyslast->vect.y) && (tmpmovekeys->vect.z == tmpmovekeyslast->vect.z))
				{
					world->matrix.t[0] = (tmpmovekeys->vect.x);
					world->matrix.t[1] = -(tmpmovekeys->vect.y);
					world->matrix.t[2] = (tmpmovekeys->vect.z);
				}
				else
				{
					t = ((frame - tmpmovekeyslast->time) << 12) / (tmpmovekeys->time - tmpmovekeyslast->time);
					
					source.vx = tmpmovekeyslast->vect.x;
					source.vy = -tmpmovekeyslast->vect.y;
					source.vz = tmpmovekeyslast->vect.z;

					dest.vx = tmpmovekeys->vect.x;
					dest.vy = -tmpmovekeys->vect.y;
					dest.vz = tmpmovekeys->vect.z;

					gte_lddp(t);							// load interpolant
					gte_ldlvl(&source);						// load source
					gte_ldfc(&dest);						// load dest
					gte_intpl();							// interpolate (8 cycles)
					gte_stlvnl(&world->matrix.t);			// store interpolated vector
				}
			}
		}	
		frame=oldframe;
	}
}

/**************************************************************************
	FUNCTION:	objectSetAnimation()
	PURPOSE:	Set keyframe information for current object frame
	PARAMETERS:	Model structure, frame number
	RETURNS:	
**************************************************************************/

void objectSetAnimation(NEWMODEL *actor, ULONG frame)
{
	NEWOBJECT *world;
	world = &actor->world;
//	objectSetMoveKeyFrames(world, frame);
	objectSetMoveKeyFrames(actor, frame);

	actor->world.matrix.t[0] = (actor->world.matrix.t[0] * actor->globalscale.vx) >> 12;
	actor->world.matrix.t[1] = (actor->world.matrix.t[1] * actor->globalscale.vy) >> 12;
	actor->world.matrix.t[2] = (actor->world.matrix.t[2] * actor->globalscale.vz) >> 12;

	objectSetScaleKeyFrames(actor, frame);
	objectSetRotateKeyFrames(actor, frame);
}

/**************************************************************************
	FUNCTION:	objectLoadSeg()
	PURPOSE:	Load animation segments for object
	PARAMETERS:	Seg info
	RETURNS:	
**************************************************************************/

objectSegDataType *objectLoadSeg(char *name)
{
	char temp[9];
	int i,d;
	NEWMODEL *mdl;
	objectSegDataType *seg;

	{
		for(i = strlen(name); i>= 0; i--)
		{
			if(name[i] == '\\')
			{
				i++;
				break;
			}
		}
		if(i<0) i = 0;
		for(d = 0; name[i] != '.'; d++,i++)
		{
			temp[d] = name[i];
		}
		temp[d] = 0;
		mdl = BFF_IsModelLoaded(temp,&seg);
		if(mdl)
		{
//			printf("seg %s found in BFF\n",temp);
			return seg;
		}
		else
		{
			DB("seg %s not found in BFF\n",temp);
			return NULL;

		}
	}
}

/**************************************************************************
	FUNCTION:	polyDrawLine()
	PURPOSE:	Add line to packet draw list
	PARAMETERS:	Depth value
	RETURNS:	
**************************************************************************/

LINE_F2	polyLine;
void polyDrawLine(ULONG depth)
{
	LINE_F2	*si;
	GsOTA	*ot;
	depth/=2;
	//DB("depth = %d\n", depth);
	
	si = (LINE_F2 *)GsOUT_PACKET_P; 

  	*(ULONG*)&si->r0 = *(ULONG*)&polyLine.r0;
  	*(ULONG*)&si->x0 = *(ULONG*)&polyLine.x0;
  	*(ULONG*)&si->x1 = *(ULONG*)&polyLine.x1;
	si->code = 0x40|modelctrl.semitrans;
	if(depth>MAXDEPTH) depth=MAXDEPTH;
	if(depth<0) depth=0;
  	ot = (GsOTA*)(PolyList->org+depth);
 	PUTPACKETINTABLE(si, ot, LINEF2_LEN);
	GsOUT_PACKET_P += sizeof(LINE_F2);
}


//- OUTCODE MACROS -------------------------------------------

enum {OUTCODE_LEFT, OUTCODE_RIGHT, OUTCODE_TOP, OUTCODE_BOTTOM};

#define OUTCODEF_LEFT	(1<<OUTCODE_LEFT)
#define OUTCODEF_RIGHT	(1<<OUTCODE_RIGHT)
#define OUTCODEF_TOP	(1<<OUTCODE_TOP)
#define OUTCODEF_BOTTOM	(1<<OUTCODE_BOTTOM)

#define CALC_OUTCODE(x,y,x0,y0,x1,y1)	((((y)>(y1))<<OUTCODE_BOTTOM)| \
										 (((y)<(y0))<<OUTCODE_TOP)| \
										 (((x)>(x1))<<OUTCODE_RIGHT)| \
										 (((x)<(x0))<<OUTCODE_LEFT))
#define LINECLIPX0	(-319)
#define LINECLIPY0	(-(119+8*PALMODE))
#define LINECLIPX1	(319)
#define LINECLIPY1	(119+(8*PALMODE))

#define CALCOUTCODE_F(x,y) (CALC_OUTCODE(x,y, LINECLIPX0*4096,LINECLIPY0*4096,LINECLIPX1*4096,LINECLIPY1*4096))


/**************************************************************************
	FUNCTION:	polyDrawLineClipped()
	PURPOSE:	Clip line + add line to packet draw list
	PARAMETERS:	x0,y0,x1,y1, r,g,b, Depth value
	RETURNS:	
**************************************************************************/

void polyDrawLineClipped(int x0, int y0, int x1, int y1, char r, char g, char b, int depth)
{
	int		lx0=x0*4096, ly0=y0*4096, lx1=x1*4096,ly1=y1*4096, *xPtr,*yPtr;
	int		out0,out1, *outPtr;

	if (x0==x1)							// Vertical lines: simple clip
	{
		if ((x0<LINECLIPX0)||(x0>LINECLIPX1)||(MAX(y0,y1)<LINECLIPY0)||(MIN(y0,y1)>LINECLIPY1))
			return;
		if (y0<LINECLIPY0)
			y0 = (int)LINECLIPY0;
		if (y1<LINECLIPY0)
			y1 = (int)LINECLIPY0;
		if (y0>LINECLIPY1)
			y0 = (int)LINECLIPY1;
		if (y1>LINECLIPY1)
			y1 = (int)LINECLIPY1;
		polyLine.r0 = r;
		polyLine.g0 = g;
		polyLine.b0 = b;
		polyLine.x0 = x0;
		polyLine.y0 = y0;
		polyLine.x1 = x1;
		polyLine.y1 = y1;
		polyDrawLine(depth);
		return;
	}
	if (y0==y1)							// Horizontal lines: simple clip
	{
		if ((y0<LINECLIPY0)||(y0>LINECLIPY1)||(MAX(x0,x1)<LINECLIPX0)||(MIN(x0,x1)>LINECLIPX1))
			return;
		if (x0<LINECLIPX0)
			x0 = (int)LINECLIPX0;
		if (x1<LINECLIPX0)
			x1 = (int)LINECLIPX0;
		if (x0>LINECLIPX1)
			x0 = (int)LINECLIPX1;
		if (x1>LINECLIPX1)
			x1 = (int)LINECLIPX1;
		polyLine.r0 = r;
		polyLine.g0 = g;
		polyLine.b0 = b;
		polyLine.x0 = x0;
		polyLine.y0 = y0;
		polyLine.x1 = x1;
		polyLine.y1 = y1;
		polyDrawLine(depth);
		return;
	}
	out0 = CALCOUTCODE_F(x0,y0);			// General lines: Cohen-Sutherland clip
	out1 = CALCOUTCODE_F(x1,y1);
	while(1)
	{
		if ((out0 == 0) && (out1 == 0))		// Trivial accept
			break;
		if (out0 & out1)				// Trivial reject
			break;

		if (out0)						// Choose an out end
		{
			xPtr = &lx0;
			yPtr = &ly0;
			outPtr = &out0;
		}
		else
		{
			xPtr = &lx1;
			yPtr = &ly1;
			outPtr = &out1;
		}
		if (*outPtr & 8)
		{
			*xPtr = lx0 + (lx1 - lx0) * (LINECLIPY1 - ly0) / (ly1 - ly0);
			*yPtr = LINECLIPY1;
			*outPtr = CALCOUTCODE_F((int)*xPtr,(int)*yPtr);
			continue;
		}
		if (*outPtr & 4)
		{
			*xPtr = lx0 + (lx1 - lx0) * (LINECLIPY0 - ly0) / (ly1 - ly0);
			*yPtr = LINECLIPY0;
			*outPtr = CALCOUTCODE_F((int)*xPtr,(int)*yPtr);
			continue;
		}
		if (*outPtr & 2)
		{
			*yPtr = ly0 + (ly1 - ly0) * (LINECLIPX1 - lx0) / (lx1 - lx0);
			*xPtr = LINECLIPX1;
			*outPtr = CALCOUTCODE_F((int)*xPtr,(int)*yPtr);
			continue;
		}
		if (*outPtr & 1)
		{
			*yPtr = ly0 + (ly1 - ly0) * (LINECLIPX0 - lx0) / (lx1 - lx0);
			*xPtr = LINECLIPX0;
			*outPtr = CALCOUTCODE_F((int)*xPtr,(int)*yPtr);
			continue;
		}
	}
	if ((out0 | out1)==0)
	{
		polyLine.r0 = r;
		polyLine.g0 = g;
		polyLine.b0 = b;
		polyLine.x0 = lx0 / 4096;
		polyLine.y0 = ly0 / 4096;
		polyLine.x1 = lx1 / 4096;
		polyLine.y1 = ly1 / 4096;
		polyDrawLine(depth);
	}
}

