//gameframe.cpp
//Calculate a single physics frame

#include <stdio.h>
#include <math.h>
#include "gametime.h"
#include "entities.h"
#include "carphysics.h"
#include "gameframe.h"
#include "controls.h"
#include "gamemem.h"
#include "particles.h"
#include "renderframe.h"
#include "collision.h"
#include "networkphysics.h"
#include "gamesound.h"
#include "gameinitexit.h"
#include "config.h"
#include "parser.h"
#include "roads.h"
#include "gamesystem.h"
#include "interfaceutil.h"
#include "text.h"
#include "random.h"
#include "log.h"
#include "sky.h"
#include "tracker.h"
#include "environment.h"

#define kGraphFrameTimeCount 10 //number of frames used to calculate average FPS

int gFrameCount;		//number of physics frames calculated
int gGraphFrameCount;	//number of graphical frames actually drawn
int gPaused=false;
int gNetPauseTime=0;
int gDisabledRestart=0;
float gStartPauseTime;
float gStartTime;		//time at the start of the game (in seconds)
float gTimeStretch=1.0;

//Used for Time Trial
int gCurrentLapStart;
int gBestLapStart;
int gLastLapTime;
int gBestLapTime=0;
int gWorldRecord=0,gLocalRecord=0;
char gRecordName[255];

float gAccelSignDisplayIntensity,gAccelSignDisplayCorner;

//the times when the last graphics frames have been draw
float gLastGraphFrameTime[kGraphFrameTimeCount]; 

int gGameEnd;		//flag to signal end of game
int gDisqualified;
int gRaceFinished;	//flag to signal that at least one player has finished all laps.
float gFPS;

//Calculate physics for a solid entity 
//(basically any object in the game which is not a car)
void SolidPhysicsEntity(tGameEntity *entity)
{
	//get soldid entity definition
	tSolidEntityPhysics *ent=(tSolidEntityPhysics*)FileGetParsedDataPtr(entity->physicsData,kParserTypeSolidEntityDesc,sizeof(tSolidEntityPhysics));

	//is this entity freely movable?
	if(ent->movable)	
		//gravitational acceleration
		entity->velo.y-=gEnvironment->gravity*kFrameTime;

	float dampfactor=0;
	float v=~entity->velo;
	if(v<7)dampfactor=(7-v)/7;
	*MatrixGetXVector(entity->rVelo)=Vector(1,0,0)+(1-dampfactor*kFrameTime)*(*MatrixGetXVector(entity->rVelo)-Vector(1,0,0));
	*MatrixGetYVector(entity->rVelo)=Vector(0,1,0)+(1-dampfactor*kFrameTime)*(*MatrixGetYVector(entity->rVelo)-Vector(0,1,0));
	*MatrixGetZVector(entity->rVelo)=Vector(0,0,1)+(1-dampfactor*kFrameTime)*(*MatrixGetZVector(entity->rVelo)-Vector(0,0,1));
	entity->velo=(1-dampfactor*kFrameTime)*entity->velo;
	MatrixReAdjust(entity->rVelo);

	//does this entity follow a constant path?
	if(ent->followPath)
		if(ent->pathType==kPathTypeCircular)
		{	
			MatrixRotY(entity->dir,ent->pathVelo*kFrameTime);
			entity->velo=*MatrixGetZVector(entity->dir)*ent->pathVelo*ent->pathSize;
		}
		else
		{	
			entity->velo=Vector(
				3*sin(gFrameCount*kFrameTime*ent->pathVelo*0.2+1)*ent->pathVelo*0.2,
				ent->pathSize*sin(gFrameCount*kFrameTime*ent->pathVelo)*ent->pathVelo,
				2*sin(gFrameCount*kFrameTime*ent->pathVelo*0.3-2)*ent->pathVelo*0.3
			);
			entity->lastActivity=gFrameCount;
		}
		
}


//Calculate one physics frame for all game entities
void PhysicsFrame()
{
	//do this for every entity in the game
	tGameEntity *entity=(tGameEntity*)gFirstEntity->next;
	while(entity!=gFirstEntity)
	{
		if(entity->id>=0)
		{
			//is this entity handled by this machine?
			if(entity->physicsMachine==kPhysicsLocal)
			{
				//this check is used to prevent entitys which are just standing still
				//from eating up CPU cycles
				if(entity->lastActivity+kFPS>gFrameCount)
				{
				
					//handle entity physics
					switch(entity->physicsType)
					{
						case kPhysicsTypeCar:
							CarPhysicsEntity(entity);
							break;
						case kPhysicsTypeSolid:
							SolidPhysicsEntity(entity);
							break;
					}
					
					//Matrices tend to 'drift' apart du to inaccurate
					//floating-point operations. these routines test matrices
					//and re-adjust them if necessary. 
					if(!MatrixVerify(entity->dir))
						MatrixReAdjust(entity->dir);
					if(!MatrixVerify(entity->rVelo))
						MatrixReAdjust(entity->rVelo);
				}
			}
			else if(entity->physicsType==kPhysicsTypeCar)
				CarPhysicsEntity(entity);
		}		
		//proceed to next entity.
		entity=(tGameEntity*)entity->next;
	}
}

//Move around all the entities.
void MotionFrame()
{
	//do this for every entity in the game
	tGameEntity *entity=(tGameEntity*)gFirstEntity->next;
	while(entity!=gFirstEntity)
	{
		entity->oldPos=entity->pos;
		MatrixCopy(entity->dir,entity->oldDir);
		
		//this check is used to prevent entitys which are just standing still
		//from eating up CPU cycles
		if(entity->lastActivity+kFPS>gFrameCount)
		{
			//move entity
			switch(entity->physicsType)
			{
				case kPhysicsTypeCar:
				case kPhysicsTypeGhost:
					CarMotionEntity(entity);
					break;
				case kPhysicsTypeSolid:
					entity->pos=entity->pos+entity->velo*kFrameTime;
					MatrixMult(entity->dir,entity->rVelo,entity->dir);
					break;
			}
		}
		
		//Check if the entity is moving, and update lastActivity
		if(entity->velo*entity->velo>kMinActivityVelo*kMinActivityVelo)
			entity->lastActivity=gFrameCount;
		
		//proceed to next entity.
		entity=(tGameEntity*)entity->next;
	}
}



//checks whether there is time to draw a graphics frame
//and calculates frame rate
int CheckFrameTime()
{
	int optFrameCount;
	int retval=false;
	float curTime;
	
	//get current time
	curTime=(TimeGetSeconds()-gStartTime)*gTimeStretch;
	
	//calculate number of frames we should have calculated in that time
	optFrameCount=curTime*kFPS;
	
	//if we have calculated more frames than required, we have
	//time to draw a screen update.
	if(gFrameCount>optFrameCount)
	{
		//calculate frame rate.
		if(curTime-gLastGraphFrameTime[0]>0)
			gFPS=((float)kGraphFrameTimeCount)/(curTime-gLastGraphFrameTime[0]);
		else
			gFPS=0;
		MemoryMove(gLastGraphFrameTime,gLastGraphFrameTime+1,sizeof(float)*(kGraphFrameTimeCount-1));
		gLastGraphFrameTime[kGraphFrameTimeCount-1]=curTime;
		gGraphFrameCount++;
		retval=true;
	}
	if(gFPS<10)
		gConfig->gfxDynamics-=kFrameTime;
	else if(gFPS<15)
		gConfig->gfxDynamics-=kFrameTime*0.3;
	if(gFPS>35)
		gConfig->gfxDynamics+=kFrameTime;
	else if(gFPS>25)
		gConfig->gfxDynamics+=kFrameTime*0.3;
	if(gConfig->gfxDynamics<0.0)gConfig->gfxDynamics=0;
	if(gConfig->gfxDynamics>1.0)gConfig->gfxDynamics=1;
	return retval;
}

//after drawing a graphics frame, check whether there is still time
//left. in that case wait, so we aren't running too fast.
void CheckTimeSkip()
{
	int optFrameCount;
	float curTime;
	
	curTime=(TimeGetSeconds()-gStartTime)*gTimeStretch;
	optFrameCount=curTime*kFPS;
	while(gFrameCount>optFrameCount+1)
	{
		curTime=(TimeGetSeconds()-gStartTime)*gTimeStretch;
		optFrameCount=curTime*kFPS;
	}	
}

//start the frame counters
void StartFrameCount()
{
	gStartTime=TimeGetSeconds();
	gFrameCount=0;
	gGraphFrameCount=0;
	gCurrentLapStart=0;
	gBestLapStart=0;
	gLastLapTime=0;
	gBestLapTime=0;
	gWorldRecord=-1;
	if(gGameInfo->numLaps==-1||gGameInfo->maxTime!=0)
		TrackerFetchRecord(gGameInfo->map,gGameInfo->playerCars[0],gGameInfo->arcade,gGameInfo->reverse^gMapInfo->reverse);
}

void PauseGame()
{	
	if(!gPaused)
	{
		gPaused=true;
		gStartPauseTime=TimeGetSeconds();
		SoundPause();
		FFBStop();	
	}
}

void UnPauseGame()
{
	if(gPaused)
	{
		gPaused=false;
		gStartTime+=TimeGetSeconds()-gStartPauseTime;
		SoundReInit();
	}
}

void ResetRocks()
{
	int id=0;
	for(int i=0;i<gMapInfo->numObjs;i++)
	{
		if(gMapInfo->obj[i].envFlags==0||(gMapInfo->obj[i].envFlags&gEnvironment->envFlags))
		{
			char *extension=FileGetExtension(gMapInfo->obj[i].model);			
			if(extension)
				if(!_stricmp(extension,kFileTypeSolidEntity))
				{
					tGameEntity *entity=(tGameEntity*)gFirstEntity->next;
					
					//see if we find the entity it belongs to
					while(entity!=gFirstEntity)
					{
						//is this the entity the message refers to?
						if(entity->id==id)
						{
							entity->pos=gMapInfo->obj[i].pos;
							EulerAnglesToMatrix(gMapInfo->obj[i].dir*kDegreeRadians,entity->dir);
							entity->velo=Vector(0,0,0);
							MatrixIdentity(entity->rVelo);
							entity->lastActivity=-1000;
						}
						entity=(tGameEntity*)entity->next;
					}
				}
			id++;
		}
	}
}

//tests if entity has passed the next check-point
//(check-points are invisible, and only used to calculate
//lead and trail delays).
void CheckPointPass(tGameEntity *entity)
{
	tCarPhysics *phys=(tCarPhysics*)entity->physics;
	if(phys->lap==-1)
		return;
	if(gGameInfo->numLaps!=-1)
	{
		if(!gCheckPoints[phys->checkPoint].passTimes[phys->lap].setBy)
		{
			gCheckPoints[phys->checkPoint].passTimes[phys->lap].time=gFrameCount*kFrameTime;
			gCheckPoints[phys->checkPoint].passTimes[phys->lap].setBy=entity;
		}
		else
		{
			phys->lead=gCheckPoints[phys->checkPoint].passTimes[phys->lap].time-gFrameCount*kFrameTime;
			if(!gCheckPoints[phys->checkPoint].passTimes[phys->lap].hit)
			{
				tCarPhysics *phys2=(tCarPhysics*)gCheckPoints[phys->checkPoint].passTimes[phys->lap].setBy->physics;
				phys2->lead=gFrameCount*kFrameTime-gCheckPoints[phys->checkPoint].passTimes[phys->lap].time;
				gCheckPoints[phys->checkPoint].passTimes[phys->lap].hit=true;
			}
		}
	}
	else
	{
		gCheckPoints[phys->checkPoint].passTimes[0].time=(gFrameCount-gCurrentLapStart)*kFrameTime;
		if(phys->lapCount>1)			
			phys->lead=gCheckPoints[phys->checkPoint].passTimes[1].time-gCheckPoints[phys->checkPoint].passTimes[0].time;
	}

	if(phys->checkPoint==0||(!gMapInfo->loop&&phys->checkPoint==kNumCheckPoints-1))
	{
		if(!gReplay&&entity==gViewedEntity)
			if(phys->lapCount&&(phys->lapCount<gGameInfo->numLaps||gGameInfo->numLaps==-1))
			{
				float lastLapTime;
				if(gGameInfo->numLaps!=-1)
					lastLapTime=gFrameCount-phys->lapTimes[phys->lapCount-1];
				else	
					lastLapTime=gFrameCount-gCurrentLapStart;
				lastLapTime*=kFrameTime;
				TextPrintfToBufferFormatedFading(Vector(0,0.4),0.04,kTextAlignMiddle,0.6,1.2,"Time Taken: %d:%02d'%02d",((int)lastLapTime)/60,((int)lastLapTime)%60,((int)(lastLapTime*100))%100);
			}
		
		if(gGameInfo->numLaps!=-1)
			phys->lapTimes[phys->lapCount]=gFrameCount;
		else if(!gReplay){
			if(phys->lapCount>=1)
			{
				gLastLapTime=gFrameCount-gCurrentLapStart;
				if(!gDisqualified)
				{
					TrackerRegisterLapTime(gGameInfo->map,gGameInfo->playerCars[0],gLastLapTime,gGameInfo->arcade,gGameInfo->reverse^gMapInfo->reverse);
					if((gLastLapTime<gBestLapTime||gBestLapTime==0))
					{
						gBestLapTime=gLastLapTime;
						gBestLapStart=gCurrentLapStart;
						if(gBestLapTime<gLocalRecord||gLocalRecord==0)
						{
							gLocalRecord=gBestLapTime;
							int found=false;
							for(int i=0;i<gConfig->numPersonalRecords;i++)
								if(gConfig->records[i].map==gGameInfo->map&&gConfig->records[i].car==gGameInfo->playerCars[0]
									&&gConfig->records[i].mode==gGameInfo->arcade&&gConfig->records[i].direction==gGameInfo->reverse)
								{
									found=true;
									if(gConfig->records[i].time>gLocalRecord)
										gConfig->records[i].time=gLocalRecord;
								}
							if(!found&&gConfig->numPersonalRecords<kMaxPersonalRecords)
							{
								gConfig->records[gConfig->numPersonalRecords].time=gLocalRecord;
								gConfig->records[gConfig->numPersonalRecords].car=gGameInfo->playerCars[0];
								gConfig->records[gConfig->numPersonalRecords].map=gGameInfo->map;
								gConfig->records[gConfig->numPersonalRecords].mode=gGameInfo->arcade;
								gConfig->records[gConfig->numPersonalRecords].direction=gGameInfo->reverse;
								gConfig->numPersonalRecords++;
							}
						}
						LogToGhostLog();
						for(int i=0;i<kNumCheckPoints;i++)
							gCheckPoints[i].passTimes[1].time=gCheckPoints[i].passTimes[0].time;
					}
				}
			}
			LogReset();
			gReplayIndex=0;
			gCurrentLapStart=gFrameCount;
		}
		if(!gReplay&&gGameInfo->maxTime!=0&&!gDisqualified)
			if(phys->lapCount>=1)
				TrackerRegisterLapTime(gGameInfo->map,gGameInfo->playerCars[0],phys->lapTimes[phys->lapCount]-kStartGameDelaySeconds*kFPS,gGameInfo->arcade,gGameInfo->reverse^gMapInfo->reverse);
		
		phys->lapCount++;			

		if(!gReplay&&entity==gViewedEntity&&gMapInfo->loop)
			if(phys->lapCount<gGameInfo->numLaps||gGameInfo->numLaps==-1)
				TextPrintfToBufferFormatedFading(Vector(kFadingMessageXPos,kFadingMessageYPos),kFadingMessageSize,kTextAlignMiddle,0.9,1.8,"Lap %d",phys->lapCount);
			else if(phys->lapCount==gGameInfo->numLaps)
				TextPrintfToBufferFormatedFading(Vector(kFadingMessageXPos,kFadingMessageYPos),kFadingMessageSize,kTextAlignMiddle,0.9,1.8,"Final Lap");
				
		if(gGameInfo->numLaps==-1)
			ResetRocks();
			
		if(phys->lapCount>gGameInfo->numLaps&&gGameInfo->numLaps!=-1)
		{
			if(!phys->finishTime)
				phys->finishTime=phys->lapTimes[gGameInfo->numLaps];	
			
			if(gGameInfo->network)
				if(gGameInfo->playerID==0)
					for(int i=0;i<gGameInfo->numPlayers;i++)
						if(gCarEntities[i]==entity)
						{
							tFinishTimeMessage m;
							m.time=phys->finishTime;
							m.player=i;
							S32Swap(m.time);
							S32Swap(m.player);
							NetworkSendPacket(kMessageTypeFinishTime,&m,sizeof(tFinishTimeMessage),kMessagePriorityHigh,kMessageSendToAllButSelf);
						}
						
			if(!gRaceFinished)
			{
				gRaceFinished=true;
				if(entity!=gViewedEntity&&!gReplay)
					if(gGameInfo->network)
					{
						for(int i=0;i<gGameInfo->numPlayers;i++)
							if(gCarEntities[i]==entity)
								TextPrintfToBufferFormatedFading(Vector(0,1),0.06,kTextAlignMiddle,2,3,"%s won the race!",gGameInfo->playerNames[i]);
					}
					else
						TextPrintfToBufferFormatedFading(Vector(0,1),0.06,kTextAlignMiddle,2,3,"%s won the race!",phys->car.carName);
			}
		}
	}
		
	phys->checkPoint=(phys->checkPoint+1)%kNumCheckPoints;
}

void LapCheck()
{		
	if(gGameInfo->demolition)
	{
		if(gFrameCount>=gDisabledRestart+kFPS*10&&gDisabledRestart)
		{
			tCarPhysics *phys=(tCarPhysics*)gCarEntities[gGameInfo->playerID]->physics;
			phys->damage=0;
			RoadCenterCar(gCarEntities[gGameInfo->playerID]);
			gDisabledRestart=0;
		}	
		int numDisabled=0;
		for(int i=0;i<gGameInfo->numPlayers;i++)
		{
			if(gCarEntities[i]->id>=0)
			{
				tCarPhysics *phys=(tCarPhysics*)gCarEntities[i]->physics;
				if(phys->damage>=kFatalDamage)
					numDisabled++;
			}
		}
		if(numDisabled>0&&numDisabled>=gGameInfo->numPlayers-1)
			if(!gDisabledRestart)
				gDisabledRestart=gFrameCount;
	}
	else
	{
		if(gFrameCount*kFrameTime>=3)
			for(int i=0;i<gGameInfo->numPlayers;i++)
				{
					tCarPhysics *phys=(tCarPhysics*)gCarEntities[i]->physics;
					if(!(gMapInfo->needsToStop&&(sqr(gCarEntities[i]->velo)>0.5)&&(phys->checkPoint==kNumCheckPoints-1)))
					{
						if(phys->lap<=gGameInfo->numLaps||(phys->checkPoint==0&&phys->lap==gGameInfo->numLaps+1)||gGameInfo->numLaps==-1)
						{
							//this doesn't all make sense but works for now.
							if(gGameInfo->reverse){
								if(gCheckPoints[(phys->checkPoint+1)%kNumCheckPoints].index>gCheckPoints[phys->checkPoint].index){
									if(phys->position<gCheckPoints[phys->checkPoint].index||(phys->position>gCheckPoints[(phys->checkPoint+1)%kNumCheckPoints].index&&gMapInfo->loop))
										CheckPointPass(gCarEntities[i]);
								}else if(phys->position<gCheckPoints[phys->checkPoint].index&&phys->position>gCheckPoints[(phys->checkPoint+1)%kNumCheckPoints].index)
									CheckPointPass(gCarEntities[i]);
							}
							else{
								if(gCheckPoints[(phys->checkPoint+10)%kNumCheckPoints].index<gCheckPoints[phys->checkPoint].index){
									if(phys->position>gCheckPoints[phys->checkPoint].index)
										CheckPointPass(gCarEntities[i]);
								}else if(phys->position>gCheckPoints[phys->checkPoint].index&&phys->position<gCheckPoints[(phys->checkPoint+10)%kNumCheckPoints].index)
									CheckPointPass(gCarEntities[i]);
							}
						}
					}
					
					for(int j=0;j<gGameInfo->numPlayers;j++)
					{
						tCarPhysics *jPhys=(tCarPhysics*)gCarEntities[j]->physics;
						if(((gGameInfo->reverse&&phys->position<jPhys->position)||(!gGameInfo->reverse&&phys->position>jPhys->position))&&phys->lap>jPhys->lap+phys->lappedPlayers[j])
						{
							phys->lappedPlayers[j]++;
							if(gCarEntities[i]==gViewedEntity&&!gReplay&&gGameInfo->netID[j]!=-1&&gCarEntities[j]->id!=-1)
								TextPrintfToBufferFormatedFading(Vector(kFadingMessageXPos,kFadingMessageYPos),kFadingMessageSize,kTextAlignMiddle,2.0,3.0,"You lapped %s!!",gGameInfo->network?gGameInfo->playerNames[j]:jPhys->car.carName);
							else if(gCarEntities[j]==gViewedEntity&&!gReplay)
								TextPrintfToBufferFormatedFading(Vector(kFadingMessageXPos,kFadingMessageYPos),kFadingMessageSize,kTextAlignMiddle,2.0,3.0,"You were lapped by %s!!",gGameInfo->network?gGameInfo->playerNames[i]:phys->car.carName);
						}
					}
				}
			
		
		if(gGameInfo->numLaps==-1||gGameInfo->maxTime!=0)
			TrackerWaitForLapTimeRegistry();
			
		gAccelSignDisplayIntensity-=kFrameTime*0.5;
	}
}


#define kMinSteerResistance 0.3
#define kNormalSteerAlignment 0.65

#define kSteerResistanceFactor 0.4
#define kSteerAlignmentFactor 1.5

//creates force-feedback effects when the the players car is sliding
void FFBResponse()
{
	tCarPhysics *phys=(tCarPhysics*)gViewedEntity->physics;
	tCarDefinition *car=&(phys->car);

	float slideVelo=0;
	for(int i=0;i<car->numWheels;i++)
		slideVelo+=gSurfaceTypes->types[phys->wheels[i].surfaceType].soundEnable?phys->wheels[i].slipVelo:0;
	slideVelo/=car->numWheels;
	slideVelo*=0.02;
	if(slideVelo>0.5)slideVelo=0.5;
	FFBiShockDirect(slideVelo,slideVelo);
	
	float velo=~gViewedEntity->velo;
	float bumpHeight=(gSurfaceTypes->types[phys->wheels[0].surfaceType].bumpHeight+gSurfaceTypes->types[phys->wheels[1].surfaceType].bumpHeight)*0.5;
	FFBSetGoundRumble(velo,bumpHeight*10);

	float wheelsSlipFactor=1-(fabs(phys->wheels[0].slipAngle)+fabs(phys->wheels[1].slipAngle))*5-(fabs(phys->wheels[0].slip)+fabs(phys->wheels[1].slip))*0.5;
	if(wheelsSlipFactor<0)wheelsSlipFactor=0;
	
	int wheelsOnGround=0;
	for(int i=0;i<2;i++)
		if(phys->wheels[i].suspension<car->wheels[i].maxSuspension)
			wheelsOnGround++;
	
	wheelsSlipFactor*=wheelsOnGround/2.0;

	float steerResistance=(1-kMinSteerResistance)*(1/(velo*0.2+1))+kMinSteerResistance;
	float steerAlignment=velo<10?velo/10*kNormalSteerAlignment:kNormalSteerAlignment+((velo-10)/70)*(1-kNormalSteerAlignment);

	FFBSetSteerResistance(kSteerResistanceFactor*steerResistance*wheelsSlipFactor,kSteerAlignmentFactor*steerAlignment*wheelsSlipFactor);	
}


tVector3 gCameraViewPos;

void GetNewTripod(tGameEntity *cameraViewEntity)
{
	float side=RandomProb(0.5)?-1:1;
	float speed;
	gCameraEntity->pos=RoadGetNextWaypoint(cameraViewEntity->pos,&cameraViewEntity->lastRoadIndex,&side,&speed,((gCameraEntity->lastRoadIndex<cameraViewEntity->lastRoadIndex)^gGameInfo->reverse)?100:-100)+Vector(0,RandomFl(4,13),0);
}



void CalcCamera()
{/*
	gCameraEntity->pos=gViewedEntity->pos+Vector(0,2000,0);
	MatrixIdentity(gCameraEntity->dir);
	MatrixRotX(gCameraEntity->dir,PI/2);
return;
*/

	tGameEntity *cameraViewEntity=gCarEntities[gReplayViewedEntityID];
	gCameraEntity->velo=cameraViewEntity->velo;
	tVector3 veloFlat,posFlat;
	tVector3 cameraPos=gCameraEntity->pos;
	switch(gCameraMode)
	{
		case kCameraFree:
			if(GetInterfaceKey(kInterfaceKeyLeft))
				MatrixRotY(gCameraEntity->dir,kFrameTime*3);
			if(GetInterfaceKey(kInterfaceKeyRight))
				MatrixRotY(gCameraEntity->dir,-kFrameTime*3);
			if(GetInterfaceKey(kInterfaceKeyUp))
			{
				tMatrix3 m;
				RotationVectorToMatrix(-kFrameTime*2**MatrixGetXVector(gCameraEntity->dir),m);
				MatrixMult(gCameraEntity->dir,m,gCameraEntity->dir);
			}
			if(GetInterfaceKey(kInterfaceKeyDown))
			{
				tMatrix3 m;
				RotationVectorToMatrix(kFrameTime*2**MatrixGetXVector(gCameraEntity->dir),m);
				MatrixMult(gCameraEntity->dir,m,gCameraEntity->dir);
			}
			if(GetInterfaceKey(kInterfaceKeySpace)&&!gInputChatMode)
				cameraPos=cameraPos+*MatrixGetZVector(gCameraEntity->dir)*kFrameTime*25;
			gCameraEntity->velo=Vector(0,0,0);
			break;
		case kCameraChase:
			veloFlat=Vector(cameraViewEntity->velo.x,0,cameraViewEntity->velo.z);
			posFlat=Vector(cameraViewEntity->pos.x,0,cameraViewEntity->pos.z);
			cameraPos=posFlat-(sqr(veloFlat)<sqr(1.5)?*MatrixGetZVector(cameraViewEntity->dir):!veloFlat)*(gCameraReverse?-9:9);
			cameraPos.y=cameraViewEntity->pos.y+2.7;
			break;
		case kCameraChaseClose:
			veloFlat=Vector(cameraViewEntity->velo.x,0,cameraViewEntity->velo.z);
			posFlat=Vector(cameraViewEntity->pos.x,0,cameraViewEntity->pos.z);
			cameraPos=posFlat-(sqr(veloFlat)<sqr(1.5)?*MatrixGetZVector(cameraViewEntity->dir):!veloFlat)*(gCameraReverse?-6:6);
			cameraPos.y=cameraViewEntity->pos.y+1.85;
			break;
		case kCameraLeftSide:
			cameraPos=cameraViewEntity->pos-*MatrixGetXVector(cameraViewEntity->dir)*(gCameraReverse?-10:10);
			cameraPos.y=cameraViewEntity->pos.y+2;
			break;
		case kCameraTripod:
			if(sqr(gCameraEntity->pos-cameraViewEntity->pos)>sqr(120))
				GetNewTripod(cameraViewEntity);
			cameraPos=gCameraEntity->pos;
			gCameraEntity->velo=Vector(0,0,0);
			break;
		case kCameraTop:
			{
				float heigth=7+~cameraViewEntity->velo;
				if(heigth>40)
					heigth=40;
				cameraPos=cameraViewEntity->pos+Vector(0,heigth,1);
			}
			break;
		case kCameraCockpit:
		case kCameraCockpitCarHidden:
			{
				tCarPhysics *phys=(tCarPhysics*)cameraViewEntity->physics;
				tCarDefinition *car=&(phys->car);
				cameraPos=cameraViewEntity->pos
					+((gCameraMode==kCameraCockpit)?(*MatrixGetXVector(cameraViewEntity->dir)*car->driverPos.x):Vector(0,0,0))
					+*MatrixGetYVector(cameraViewEntity->dir)*(car->driverPos.y+0.5)
					+*MatrixGetZVector(cameraViewEntity->dir)*car->driverPos.z;
			}
			break;
		case kCameraLeftWheel:
			cameraPos=cameraViewEntity->pos-*MatrixGetXVector(cameraViewEntity->dir)*(gCameraReverse?-1.2:1.2)+*MatrixGetYVector(cameraViewEntity->dir)*0.4+*MatrixGetZVector(cameraViewEntity->dir)*0.5;
			break;		
	}
	

	if(gFrameCount*kFrameTime<1.5&&!gReplay)
	{
		float f=(gFrameCount*kFrameTime-1.5)/3*2*PI;		
		cameraPos=cameraViewEntity->pos-
			*MatrixGetXVector(cameraViewEntity->dir)*sin(f)*15+
			*MatrixGetYVector(cameraViewEntity->dir)*(2.0-20.0*f)-
			*MatrixGetZVector(cameraViewEntity->dir)*cos(f)*15;
	}

	tVector3 cameraViewPos=cameraPos-cameraViewEntity->pos;
	if(sqr(gCameraViewPos-cameraViewPos)<sqr(11)&&gCameraMode!=kCameraTripod&&gCameraMode!=kCameraFree&&cameraViewEntity==cameraViewEntity)
		//camera interpolation
		cameraViewPos=gCameraViewPos+(cameraViewPos-gCameraViewPos)*kFrameTime*10;

	gCameraViewPos=cameraViewPos;
	tVector3 newPos=cameraViewEntity->pos+cameraViewPos;
//	if(sqr(gCameraEntity->pos-newPos)>25)
//		gCameraEntity->lastRoadIndex=0;
	gCameraEntity->pos=newPos;
	cameraViewEntity=cameraViewEntity;

	if(gCameraMode==kCameraCockpit||gCameraMode==kCameraLeftWheel||gCameraMode==kCameraCockpitCarHidden&&!(gFrameCount*kFrameTime<1.5&&!gReplay))
	{
		MemoryMove(gCameraEntity->dir,cameraViewEntity->dir,sizeof(tMatrix4));
		if((gCameraMode==kCameraCockpit||gCameraMode==kCameraCockpitCarHidden)&&gCameraReverse)
		{
			*MatrixGetXVector(gCameraEntity->dir)=-*MatrixGetXVector(gCameraEntity->dir);
			*MatrixGetZVector(gCameraEntity->dir)=-*MatrixGetZVector(gCameraEntity->dir);			
		}
		
	}
	else if(gCameraMode!=kCameraFree)
	{		
		*MatrixGetZVector(gCameraEntity->dir)=!(cameraViewEntity->pos-gCameraEntity->pos+Vector(0,1.2,0));
		*MatrixGetYVector(gCameraEntity->dir)=Vector(0,1,0);
		MatrixReAdjust(gCameraEntity->dir);
	}

	if(gCameraMode!=kCameraFree)
	{
		tVector3 normal;
		float dist=GetGroundOffset(gCameraEntity->pos,&gCameraEntity->lastRoadIndex,&normal,0);
		if(dist<0)
			if(!isinf(dist))
				gCameraEntity->pos=gCameraEntity->pos-normal*dist;
	}
}


void MoveGhostCar()
{
	if(gGameInfo->numLaps!=-1)	
		return;
	tCarPhysics *phys=(tCarPhysics*)gCarEntities[0]->physics;
	if(phys->lapCount<2)
		return;

	LogReplayGhostFrame();	
}

//use this define to disable all game physics.
//useful to test raw graphics performance.
//#define __NOPHYSICS


//Calculate one game physics frame
void GameFrame()
{
/*static int p=false;
if(Button()&&!p){gCarEntities[0]->pos=gCameraEntity->pos;p=true;}else if(!Button())p=false;*/
	if(!gPaused)
	{
	#ifndef __NOPHYSICS
		//Move all entities
		MotionFrame();
		
		//check for collision between entities and ground or each other
		CollisionFrame();
		
		//check if cars have passed any checkpoint or lap start/finish line.
		LapCheck();
		
		//send local physics data to network
		NetworkSendPhysics();
	#endif
	}
	
		//get AI or user input for local cars
		ControlFrame();

	if(!gPaused)
	{
	#ifndef __NOPHYSICS

		//Calculate car sound effects
		SoundFrame();
		
		//calculate all local entities physics
		PhysicsFrame();
		
		//process particles
		ParticlesProcess();
	}	
	//receive physics for remote entities
	NetworkReceivePhysics();
	
	if(!gPaused)
	{			
		MoveGhostCar();	
			
		//create force-feedback effects when the the players car is sliding
		FFBResponse();
	#endif
	}
	
	CalcCamera();

	//check if we have time to draw a graphics frame
	if(CheckFrameTime()||gPaused)
	{
		//draw frame
		RenderFrame(true);
		
		//if we are too fast, waste some time
		CheckTimeSkip();
		
		if(gLightning<gFrameCount-10)
			gLightning=0;
	}
	TextClearBuffer();

	if(!gPaused)
		gFrameCount++;

	if(RandomProb(gEnvironment->flashIntensity*kFrameTime))
		gLightning=gFrameCount;
	
	if(gFrameCount>gNetPauseTime&&gNetPauseTime!=0)
		PauseGame();
		
	if(gFrameCount%30)
		SystemPoll(true);
		
}

int GameReplayFrame()
{
	if(!gPaused||!gBackgroundReplay)
	{
		CollisionFrame();
		LapCheck();
		SoundFrame();
		ParticlesProcess();
		MotionFrame();
		LogReplayFrame();
		if(!(gBackgroundReplay&&gInterfaceType))
			ControlFrame();
		PhysicsFrame();
	}
	CalcCamera();

	if(gGameInfo->numLaps==-1&&!gBackgroundReplay)
		TrackerWaitForLapTimeRegistry();

	int hasRendered=false;
	//check if we have time to draw a graphics frame
	if(CheckFrameTime()||gPaused)
	{
		//draw frame
		RenderFrame(true);

		//if we are too fast, waste some time
		CheckTimeSkip();
		hasRendered=true;
	}
	
	if(gFrameCount%30&&!gBackgroundReplay)
		SystemPoll(true);

	if(!gPaused||!gBackgroundReplay)
		gFrameCount++;
	return hasRendered;
}