/* vim: set noexpandtab shiftwidth=8 cino=:
 ***************************************************************************
 *			vamosworld.cc
 *
 *  Sat Mar 26 14:06:16 2005
 *  Copyright  2005  Joe Venzon
 *  joe@venzon.net
 ****************************************************************************/

/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Library General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */


#include <fstream>
using std::ofstream;
#include "vamosworld.h"

extern void LoadingScreen(string loadtext);

extern void snap_screenshot();

VAMOSWORLD::VAMOSWORLD()
 : world(0), steerpos(0), cammode(CMChaseRigid), joyinfo(false), oldseg(0),initdone(false), shift_time(0)
{
	error_log.open("logs/vamosworld.log");
	lighting_enable=true;
	world=NULL;
}

void VAMOSWORLD::DeInit()
{
	if (initdone)
	{
		delete world;
		dgtl_ctrl_anlg_val.clear();
		initdone = false;
		track_p->ClearRoads();
		UnloadHUD();
		delete ai;
		glDeleteTextures(1, &dynamic_cube_reflection_texture);
		delete trackmap;
	}
}

void VAMOSWORLD::Init (TRACK * track)
{	
	UpdateSettings();

	lastcamchange = 0.0f;
	mousezoom = 0.5;

	world = new Vamos_World::World (track);

	UnloadHUD();
	LoadHUD();

	track_p = track;

	trackmap = new TRACKMAP(track);

	ifstream cf;
	int modenum = 0;
	settings.Get( "game.camera_mode", modenum );
	switch (modenum)
	{
		case 0:
			SetCameraMode(CMChaseRigid);
			break;
		case 1:
			SetCameraMode(CMChase);
			break;
		case 2:
			SetCameraMode(CMOrbit);
			break;
		case 3:
			SetCameraMode(CMHood);
			break;
		case 4:
			SetCameraMode(CMFree);
			break;
		case 5:
			SetCameraMode(CMInCar);
			break;
		case 6:
			SetCameraMode(CMExternal);
			break;
		case 7:
			SetCameraMode(CMExtFollow);
			break;
	}

	VERTEX initcam = track_p->GetStart(0).ScaleR(-1);
	initcam.y -= 6;
	cam.position = initcam;

	auto_shift_time = 0;

	ai = new AI();

	glGenTextures(1, &dynamic_cube_reflection_texture);
	
#ifdef ENABLE_FORCE_FEEDBACK
	force_feedback = new FORCEFEEDBACK( ff_device );
#endif

	initdone = true;
}

extern bool verbose_output;
VAMOSWORLD::~VAMOSWORLD()
{
	if (verbose_output)
		cout << "vamosworld deinit" << endl;

	error_log.close();

	DeInit();
}

extern GLfloat LightPosition[4];

void VAMOSWORLD::DrawShadows()
{
	glPushMatrix();

	if (MP_DBGDEEP)
		cout << "car shadow start" << endl;
	draw_shadows();
	if (MP_DBGDEEP)
		cout << "car shadow done" << endl;

	glPopMatrix();
}

void VAMOSWORLD::Draw()
{
	glPushMatrix();

	QUATERNION goofyfoot;
	goofyfoot.Rotate(-3.141593/2.0, 1,0,0);
	double tempmat[16];
	goofyfoot.GetMat(tempmat);
	glMultMatrixd(tempmat);

	float lp[4];
	lp[0] = LightPosition[0];
	lp[1] = LightPosition[1];
	lp[2] = LightPosition[2];
	lp[3] = 0;
	VERTEX lpv;
	lpv.Set(lp);
	lpv = goofyfoot.ReturnConjugate().RotateVec(lpv);
	lp[0] = lpv.x;
	lp[1] = lpv.y;
	lp[2] = lpv.z;
	glLightfv( GL_LIGHT1, GL_POSITION,  lp);

	glPushMatrix();
	//glEnable(GL_STENCIL_TEST);
	glMatrixMode (GL_MODELVIEW);
	glPopMatrix();

	glPopMatrix();

	lp[0] = LightPosition[0];
	lp[1] = LightPosition[1];
	lp[2] = LightPosition[2];
	lp[3] = 0;
}

void VAMOSWORLD::draw_cars(bool draw_interior, bool draw_focused_car)
{
	glMatrixMode (GL_MODELVIEW);

	for (std::vector <Vamos_World::Car_Information>::iterator it = world->m_cars.begin ();
	   it != world->m_cars.end ();
	   it++)
	{
	  	assert (it->car != 0);

		glPushAttrib(GL_ALL_ATTRIB_BITS);
		glEnable(GL_DEPTH_TEST);
		glDepthMask(1);
		glDisable(GL_CULL_FACE);
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		
		bool cubelighting = StaticCubeLighting() || DynamicCubeLighting();
		if (cubelighting)
		{
			ActivateTextureBasedLighting(false, true);
		}

		utility.SelectTU(0);
		glPushMatrix();

		it->car->draw (true);
		if (draw_interior)
		{
			it->car->draw_interior ();
		}
		glPopMatrix();

		utility.SelectTU(0);
		glPopAttrib();
		
		if (cubelighting)
		{
			DeactivateTextureBasedLighting();
		}
	}
}

void VAMOSWORLD::draw_shadows()
{
	glPushAttrib(GL_ALL_ATTRIB_BITS);
	glMatrixMode (GL_MODELVIEW);

	glDisable(GL_DEPTH_TEST);
	glDepthMask(0);
	glCullFace(GL_FRONT);
	glEnable(GL_CULL_FACE);

	int count = 0;

	std::vector <Vamos_World::Car_Information>::iterator it;
	//draw shadows, make tire sounds, create smoke
	for( it = world->m_cars.begin (); it != world->m_cars.end (); it++ )
	{
		if (MP_DBGDEEP)
			cout << "wheel pos start" << endl;

	  	assert (it->car != 0);

		if (it->car->chassis().IsValid())
		{		
			glPushMatrix();
			VERTEX pos;
			//Vamos_Geometry::Three_Vector cp = it->car->chassis().position() + it->car->chassis().center_of_mass();
			Vamos_Geometry::Three_Vector cp = it->car->chassis().position();
			pos.Set(cp[0], cp[1], cp[2]);

			VERTEX posyup;
			posyup.Set(cp[0], cp[2], -cp[1]);

			float x1, x2, y1, y2;
			x1 = 3;
			x2 = -3;
			y1 = 3;
			y2 = -3;

			VERTEX wheelpos[4];

			int i = 0;

			double slide[4];
			double wheelspeed[4];
			double carspeed = GetCar(CONT_PLAYERLOCAL)->car->chassis().cm_velocity().magnitude();
			for (i = 0; i < 4; i++)
			{
				Vamos_Geometry::Three_Vector tv = it->car->wheel(i)->contact_position();
				wheelpos[i].Set(tv[0],tv[1],tv[2]);
				slide[i] = it->car->wheel(i)->slide();
				wheelspeed[i] = it->car->wheel(i)->rotational_speed();
			}

			VERTEX coord[4];
			coord[0].Set(x1,y1,0);
			coord[1].Set(x1,y2,0);
			coord[2].Set(x2,y2,0);
			coord[3].Set(x2,y1,0);

			VERTEX shadowpos[4];

			for (i = 0; i < 4; i++)
				shadowpos[i] = coord[i];

			if (MP_DBGDEEP)
				cout << "wheel pos done" << endl;

			size_t segidx;
			QUATERNION rot;
			double angle;
			Vamos_Geometry::Three_Vector aa = it->car->chassis().axis_angle(&angle);
			rot.SetAxisAngle(angle*(3.141593/180.0),aa[0],aa[1],aa[2]);

			QUATERNION carorientation;
			carorientation.SetAxisAngle(angle*(3.141593/180), -aa[0], -aa[2], aa[1]);
			carorientation.Rotate(3.141593/2.0,0,1,0);

			for (i = 0; i < 4; i++)
			{
				segidx = 0;
				coord[i] = rot.RotateVec(coord[i]);
			}

			Vamos_Geometry::Three_Vector cm = it->car->chassis().center_of_mass();
			VERTEX cmv;

			if (MP_DBGDEEP)
				cout << "coord adjust start" << endl;

			cmv.Set(cm[0],cm[1],cm[2]);
			for (i = 0; i < 4; i++)
			{
				coord[i].x = coord[i].x - rot.RotateVec(cmv).x;
				coord[i].y = coord[i].y - rot.RotateVec(cmv).y;

				VERTEX origin;
				origin = coord[i]+pos;
				origin.z = -origin.y;
				origin.y = 0;
				coord[i].z = world->p_track->Elevation(origin);
				VERTEX c;
				c.Set(coord[i].x+pos.x, coord[i].y+pos.y, coord[i].z);
			}

			VERTEX tirecoord[4], tc2[4];
			for (i = 0; i < 4; i++)
			{
				tirecoord[i] = rot.RotateVec(wheelpos[i]);
				shadowpos[i] = rot.RotateVec(shadowpos[i]);
				tirecoord[i] = tirecoord[i] - rot.RotateVec(cmv);
				tirecoord[i] = pos + tirecoord[i];
				shadowpos[i] = pos + shadowpos[i];

				float temp = tirecoord[i].z;
				tirecoord[i].z = -tirecoord[i].y;
				tirecoord[i].y = temp;
				tc2[i] = tirecoord[i];

				temp = shadowpos[i].z;
				shadowpos[i].z = -shadowpos[i].y;
				shadowpos[i].y = temp;
			}

			VERTEX tctemp = tc2[2];
			tc2[2] = tc2[3];
			tc2[3] = tctemp;

			if (MP_DBGDEEP)
				cout << "coord adjust stop" << endl;

			if (count == 0 || multiplay.TickCar(count))
			{

				for (i = 0; i < 4; i++)
				{
					float slidemultiple = 1.0; //for venzon tire code

					float speed_offset = -5.0;
					float speed_gain = 0.25;
					float attenuate = (wheelspeed[i] + speed_offset)*speed_gain;
					if (attenuate < 0)
						attenuate = 0;
					if (attenuate > 1)
						attenuate = 1;

					float carfactor = (carspeed-10)*0.25;
					if (carfactor < 0)
						carfactor = 0;
					if (carfactor > 1)
						carfactor = 1;
					carfactor = 1.0 - carfactor;

					slidemultiple *= (attenuate*carfactor+1.0-carfactor);

					float prob = 0;
					float probmultiple = 100;
					float maxprob = 0.1;
					if (slide[i] > 0)
					{
						float squeal = slide[i]*slidemultiple;
						if (squeal < 0)
							squeal = -squeal;
						float offset = 0.2;
						float gain = 1.0;
						squeal -= offset;
						squeal *= gain;
						if (squeal < 0)
							squeal = 0;
						if (squeal > 1)
							squeal = 1;
						prob = (l_timefactor / l_fps) * probmultiple;
						prob = prob * squeal;
						if (prob < 0) //not sure this is necessary
							prob = -prob;
						VERTEX dir;
						dir.y = 1;
						particle.SetParams(1.0*slide[i], 2.0*slide[i], 5.0, 14.0,
							0.3, 1.0, dir, 0.5, 1.0);
						if (prob > maxprob)
							prob = maxprob;
						particle.ProbAddParticle(tirecoord[i], prob);
					}

					float squeal = slide[i]*slidemultiple;
					if (squeal < 0)	//not sure this is necessary
						squeal = -squeal;
					float offset = 0.05;
					float gain = 0.5;
					squeal -= offset;
					squeal *= gain;
					if (squeal < 0)
						squeal = 0;
					if (squeal > 1)
						squeal = 1;

					bool snddebug = false;
					float maxgain = 0.3;
					gain = squeal;
					if (gain > maxgain)
						gain = maxgain;
					if (l_timefactor != 0.0f)
						if (!snddebug) sound.SetGain(it->car->GetTireSoundSource(i), gain);
					float pitch = 1.0-squeal;
					float pitchvariation = 0.4;
					pitch *= pitchvariation;
					pitch = pitch + (1.0-pitchvariation);
					if (!snddebug) sound.SetPitch(it->car->GetTireSoundSource(i), pitch);

					VERTEX tvel;
					if (!tirecoord[i].nan() && !tvel.nan())
						if (!snddebug) sound.SetPosVel(it->car->GetTireSoundSource(i), tirecoord[i], tvel);

					oldtirepos[i] = tirecoord[i];
				}
			}

			if (car_shadows_enabled)
			{
				AABB shadowbox;
				VERTEX minbox, maxbox;
				bool hasmin[3];
				bool hasmax[3];

				VERTEX shadowquad[4];
				for (i = 0; i < 4; i++)
				{
					shadowquad[i] = shadowpos[i];
				}

				for (i = 0; i < 3; i++) hasmin[i] = false;
				for (i = 0; i < 3; i++) hasmax[i] = false;

				for (i = 0; i < 4; i++)
				{
					if (shadowquad[i].x < minbox.x || !hasmin[0])
					{
						minbox.x = shadowquad[i].x;
						hasmin[0] = true;
					}
					if (shadowquad[i].y < minbox.y || !hasmin[1])
					{
						minbox.y = shadowquad[i].y;
						hasmin[1] = true;
					}
					if (-shadowquad[i].z < minbox.z || !hasmin[2])
					{
						minbox.z = -shadowquad[i].z;
						hasmin[2] = true;
					}

					if (shadowquad[i].x > maxbox.x || !hasmax[0])
					{
						maxbox.x = shadowquad[i].x;
						hasmax[0] = true;
					}
					if (shadowquad[i].y > maxbox.y || !hasmax[1])
					{
						maxbox.y = shadowquad[i].y;
						hasmax[1] = true;
					}
					if (-shadowquad[i].z > maxbox.z || !hasmax[2])
					{
						maxbox.z = -shadowquad[i].z;
						hasmax[2] = true;
					}
				}
				maxbox.y += 5;
				minbox.y -= 5;
				shadowbox.SetFromCorners(minbox,maxbox);
				list <OBJECTTRI> trilist;
				objects.GetTrisInBBox(shadowbox, trilist);

				utility.SelectTU(0);
				glEnable(GL_TEXTURE_2D);
				glEnable(GL_BLEND);
				glDisable(GL_LIGHTING);
				glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
				it->car->shadow_texture()->Activate();

				glEnable(GL_DEPTH_TEST);
				glDepthFunc( GL_LEQUAL );
				glColor4f(1,1,1,1);
				glDisable(GL_CULL_FACE);
				glCullFace(GL_FRONT);

				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

				VERTEX shadowdim;
				shadowdim.x = 6;
				shadowdim.z = 6;

				float roadheight = world->p_track->Elevation(posyup);

				glBegin(GL_TRIANGLES);
					for (list <OBJECTTRI>::iterator i = trilist.begin(); i != trilist.end(); i++)
					{
						i->v1.z = -i->v1.z;
						i->v2.z = -i->v2.z;
						i->v3.z = -i->v3.z;

						ShadowTexCoord(posyup, carorientation, shadowdim, i->v1, roadheight);
						glVertex3fv(i->v1.v3());
						ShadowTexCoord(posyup, carorientation, shadowdim, i->v2, roadheight);
						glVertex3fv(i->v2.v3());
						ShadowTexCoord(posyup, carorientation, shadowdim, i->v3, roadheight);
						glVertex3fv(i->v3.v3());
					}
				glEnd();
			}

			glDisable(GL_CULL_FACE);
			glDisable(GL_BLEND);
			glEnable(GL_LIGHTING);
			glEnable(GL_TEXTURE_2D);
			glPopMatrix();
		}
	}

	glEnable(GL_DEPTH_TEST);
	glDepthMask(1);
	glDisable(GL_CULL_FACE);

	utility.SelectTU(0);

	utility.SelectTU(1);
	glDisable(GL_TEXTURE_GEN_S);
	glDisable(GL_TEXTURE_GEN_T);
	glDisable(GL_TEXTURE_2D);
	utility.SelectTU(0);
	glDepthMask(1);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

	glPopAttrib();
}

void VAMOSWORLD::ShadowTexCoord(VERTEX &carpos, QUATERNION &carorientation, VERTEX & shadowdim, VERTEX &vert, float roadheight)
{
	VERTEX vertdiff;
	vertdiff = vert-carpos;
	vertdiff = carorientation.RotateVec(vertdiff);

	float trans = 1.0;
	float transmin = -3;
	float transbotline = -2.0;
	float transtopline = 1.5;
	float transmax = 2;
	float vertheight = vert.y - roadheight;
	if (vertheight < transmin || vertheight > transmax)
		trans = 1.0;
	else if (vertheight > transbotline && vertheight < transtopline)
		trans = 0.0;
	else if (vertheight < transbotline)
		trans = 1.0-((vertheight - transmin)*(1/(transbotline - transmin)));
	else if (vertheight > transtopline)
		trans = 1.0-((transmax - vertheight)*(1/(transmax - transtopline)));

	float opacity = 1.0f-(trans);

	glColor4f(1,1,1,opacity);

	vertdiff.x *= (1.0/shadowdim.x);
	vertdiff.z *= (1.0/shadowdim.z);

	vertdiff.x += 0.5;
	vertdiff.z += 0.5;

	float u = vertdiff.x;
	float v = vertdiff.z;

	glTexCoord2d(u,v);
}

void VAMOSWORLD::DisplayRaceResults()
{
	state.SetGameState ( STATE_ENDRACE );
	state.ClearRaceResult();
	for (unsigned int i=0; i<world->m_cars.size(); i++)
	{
		state.SetRaceResult(i, timer.GetTotalTime(i), timer.GetBestLap(i), timer.GetNumLaps(i), world->m_cars[i].end_race);
	}
	state.SortRaceResult();
}

void VAMOSWORLD::Update(float timefactor, float fps, SDL_Joystick ** js)
{	
	l_timefactor = timefactor;
	l_fps = fps;

	//don't do anything if we're paused
	if (l_timefactor <= 0 || fps < 5)
	{
		sound.MuteAll();
		return;
	}

	//see if a car has entered a new sector
	int carid = 0;
	for (std::vector <Vamos_World::Car_Information>::iterator it = world->m_cars.begin ();
	it != world->m_cars.end ();
	it++,carid++)
	{
		bool advance = false;
		int nextsector = 0;
		if (track_p->NumSectors() > 0)
		{
			nextsector = (it->car->GetSector() + 1) % track_p->NumSectors();
			for (int p = 0; p < 4; p++)
			{
				if (it->car->GetColPatch(p) == track_p->GetLapSequence(nextsector))
				{
					advance = true;
					//cout << "Drove over new sector " << nextsector << " patch " << it->car->GetColPatch(p) << endl;
				}
			}
		}

		if (advance)
		{
			timer.Lap(state.GetTrackName(), state.GetCarName(carid), carid, it->car->GetSector(), nextsector, (it->car->GetSector() >= 0));
			/*if (nextsector == 0)
			{
				timer.Lap((it->car->GetSector() >= 0));
			}*/

			it->car->SetSector(nextsector);
			if (nextsector == 0)
			{
				if ((it->lap_number + 1) > state.GetLaps() &&
				    (state.GetGameMode() == MODE_SINGLERACE))
				{
					it->end_race = true;
				}
				it->lap_number++;
			}
		}
	}

	world->update_race_position();

	bool camswitch = false;

	//get control updates
	ProcessControls(js, timefactor, fps);

	//update ai
	ai->update(timefactor, fps, track_p);

	//stop after the finish line
	if (state.GetGameMode() == MODE_SINGLERACE)
	{
		bool race_ended = true;

		for (std::vector <Vamos_World::Car_Information>::iterator it = world->m_cars.begin ();
		it != world->m_cars.end ();
		it++)
		{
			if (it->end_race)
			{
				DoOp(&(*it), "gas", 0.0, 0.0, true, timefactor, fps);
				DoOp(&(*it), "brake", 1.0, 0.0, true, timefactor, fps);
			}
			else //race not ended yet
			{
				race_ended = false;
			}
		}

		if (race_ended && (state.GetGameState() != STATE_ENDRACE)) //display race results
		{
			DisplayRaceResults();
		}
	}

	//update physics
	const int repeat = 1;
	double dt = (timefactor/fps)/(float)repeat;
	for (int loop = 0; loop < repeat; loop++)
	{
		PhysUpdate(dt);
	}
	dt = (timefactor/fps);

	// check for a speed higher than it should be
	float current_speed_kmph = Vamos_Geometry::m_s_to_km_h (GetCar(CONT_PLAYERLOCAL)->car->chassis().cm_velocity().magnitude());
	/*int high_gear = GetCar(CONT_PLAYERLOCAL)->car->transmission()->forward_gears();
	float high_gear_ratio = GetCar(CONT_PLAYERLOCAL)->car->transmission()->gear_ratio( high_gear );
	float peak_engine_speed = GetCar(CONT_PLAYERLOCAL)->car->engine()->peak_engine_speed();
	float wheel_height = GetCar(CONT_PLAYERLOCAL)->car->wheel(0)->roll_height();
	float wheel_circumference = Vamos_Geometry::pi * wheel_height;
	float car_max_speed = high_gear_ratio * peak_engine_speed * wheel_circumference; */
	//if( ( SDL_GetTicks() % 2000 ) <= 2 ) cout << "high gear: " << high_gear << ", high gear ratio: " << high_gear_ratio << ", wheel circumference: " << wheel_circumference << ", car's max speed: " << car_max_speed << endl;
	float car_max_speed = 600.0;
	if( current_speed_kmph > car_max_speed )
	{
		throw string( "Gentlemen, we have liftoff." );
	}

	if (GetCar(CONT_PLAYERLOCAL) != 0)
	{

		if( GetCar(CONT_PLAYERLOCAL)->car->ABS() != abs )
			GetCar(CONT_PLAYERLOCAL)->car->SetABS( abs );
		if( GetCar(CONT_PLAYERLOCAL)->car->TCS() != tcs )
			GetCar(CONT_PLAYERLOCAL)->car->SetTCS( tcs );

		//update camera
		Vamos_Geometry::Three_Vector cm = GetCar(CONT_PLAYERLOCAL)->car->chassis().position();
		if (cammode != CMFree && cammode != CMChase && cammode != CMExternal && cammode != CMExtFollow)
		{
			cam.position.Set(-cm[0], -cm[2], cm[1]);
			//cam.position.Set(cm[1], -cm[2], cm[0]);
			//cam.position.DebugPrint();
		}

		double angle;
		Vamos_Geometry::Three_Vector axis = GetCar(CONT_PLAYERLOCAL)->car->chassis().axis_angle (&angle);
		Vamos_Geometry::Three_Vector vvel = GetCar(CONT_PLAYERLOCAL)->car->chassis().cm_velocity();
		VERTEX vel;
		vel.Set(vvel[0],vvel[1],vvel[2]);

		if (cammode == CMChase || cammode == CMChaseRigid || cammode == CMHood || cammode == CMInCar)
		{
			QUATERNION carorientation;
			carorientation.SetAxisAngle(angle*(3.141593/180), -axis[0], -axis[2], axis[1]);
			carorientation.Rotate(3.141593/2.0,0,1,0);			
		        float fov;
			settings.Get( "display.FOV", fov );
			float dist_behind = 3.0 * fov / 45.0 - 10.5;
			if (cammode == CMChase)
			{
				VERTEX idealpos;
				idealpos.Set(-cm[0], -cm[2], cm[1]);
				VERTEX temp;
				temp.Set(0,-2,0);
				idealpos = idealpos + carorientation.RelativeMove(temp);
				temp.Set(0,0,dist_behind);
				idealpos = idealpos + carorientation.RelativeMove(temp);

				float dirblend = 1.0;
				float posblend = 10.0*(timefactor/fps);
				if (dirblend < 0)
					dirblend = 0;
				else if (dirblend > 1)
					dirblend = 1;
				if (posblend < 0)
					posblend = 0;
				else if (posblend > 1)
					posblend = 1;
				cam.position = cam.position.interpolatewith(idealpos, posblend);
				cam.dir.LookAt(-cam.position.x, -cam.position.y, -cam.position.z, cm[0], cm[2]+2, -cm[1], 0,1,0);
				cam.dir = cam.dir.ReturnConjugate();

			}
			else if (cammode == CMHood)
			{
				cam.dir = carorientation;

				Vamos_Geometry::Three_Vector vp = GetCar(CONT_PLAYERLOCAL)->car->hood_view_position();
				vp = vp - GetCar(CONT_PLAYERLOCAL)->car->chassis().center_of_mass();

				//VERTEX cmass;
				//cmass.Set(GetCar(CONT_PLAYERLOCAL)->car->chassis().center_of_mass()[0],GetCar(CONT_PLAYERLOCAL)->car->chassis().center_of_mass()[1],GetCar(CONT_PLAYERLOCAL)->car->chassis().center_of_mass()[2]);
				//cmass.DebugPrint();
				
				cam.dir = carorientation;
				
				//cam.MoveRelative(vp[1],-vp[2],vp[0]+1.0);
				//cam.MoveRelative(0.0, -vp[2], vp[1]+1.0);
				for (int i = 0; i < 3; i++)
					vp[i] = -vp[i];
				cam.MoveRelative(-vp[1], vp[2], -vp[0]);
			}
			else if (cammode == CMInCar)
			{
				Vamos_Geometry::Three_Vector vp = GetCar(CONT_PLAYERLOCAL)->car->view_position();
				vp = vp - GetCar(CONT_PLAYERLOCAL)->car->chassis().center_of_mass();
				cam.dir = carorientation;

				VERTEX idealpos, viewpos;
				//viewpos.Set(-vp[0], -vp[2], vp[1]);
				viewpos.Set(vp[1], -vp[2], vp[0]);
				idealpos.Set(-cm[0], -cm[2], cm[1]);
				idealpos = idealpos + carorientation.RelativeMove(viewpos);

				if (camneedupdate)
				{
					cam.position = idealpos;
					cam_lastpos = cam.position;
					cam_lastpos2 = cam.position;
					camneedupdate = false;
					cam_lastvel.zero();
					cam_lastaccel.zero();
					cam_jerk.zero();
				}

				VERTEX accel;
				VERTEX vel;
				vel = cam.position - cam_lastpos2;
				vel.Scale(1.0/dt);
				accel = cam_lastvel - vel;
				cam_lastvel = vel;
				assert (timefactor != 0);
				accel.Scale(1.0/dt);
				accel = cam_lastaccel + (accel-cam_lastaccel).ScaleR(0.002);
				cam_lastpos2 = cam.position;

				VERTEX jerk;
				jerk = accel - cam_lastaccel;
				jerk.Scale(1.0/dt);
				cam_jerk = cam_jerk + (jerk-cam_jerk).ScaleR(0.002);
				cam_lastaccel = accel;

				jerk = accel;
				jerk = cam_jerk;

				float jerkscale = 0.015;
				float jerkmax = 0.1;

				jerk.Scale(jerkscale);
				if (jerk.x >= 0)
				{
					if (jerk.x > jerkmax)
						jerk.x = jerkmax;
				}
				else
				{
					if (jerk.x < -jerkmax)
						jerk.x = -jerkmax;
				}
				if (jerk.y >= 0)
				{
					if (jerk.y > jerkmax)
						jerk.y = jerkmax;
				}
				else
				{
					if (jerk.y < -jerkmax)
						jerk.y = -jerkmax;
				}
				if (jerk.z >= 0)
				{
					if (jerk.z > jerkmax)
						jerk.z = jerkmax;
				}
				else
				{
					if (jerk.z < -jerkmax)
						jerk.z = -jerkmax;
				}

				idealpos = idealpos + jerk;

				cam.position.Set(idealpos.x, idealpos.y, idealpos.z);

			}
			else //CMChaseRigid
			{
				cam.dir = carorientation;
				cam.MoveRelative(0,-2.0,0);
				cam.MoveRelative(0,0,dist_behind-0.25);
			}
		}
		else if (cammode == CMOrbit)
		{
			float mindist = 7;
			float maxdist = 20;

			cam.MoveRelative(0,-1.0,0);
			cam.MoveRelative(0,0,-(mindist*(mousezoom)+maxdist*(1.0f-mousezoom)));

			VERTEX cpos = cam.position.ScaleR(-1.0);
			float elev = track_p->Elevation(cpos)+0.5;
			if (cpos.y < elev)
				cam.position.y = -elev;

			//cam.dir = mouse.GetDir();
		}
		else if (cammode == CMFree)
		{
			//cam.dir = mouse.GetDir();
		}

		if (cammode != CMFree && cammode != CMOrbit)
		{
			//mouse.InitDir(cam.dir);
		}

	}

	//update sound
	int count = 0;
	std::vector <Vamos_World::Car_Information>::iterator it;
	for( it = world->m_cars.begin (); it != world->m_cars.end (); it++ )
	{
		Vamos_Geometry::Three_Vector cm = it->car->chassis().position();

		int sid = it->car->GetSoundSource();
		float rpm = Vamos_Geometry::rad_s_to_rpm (it->car->engine ()->rotational_speed ());
		sound.SetPitch(sid, rpm / 7000.0f);
		count++;
		float egain = it->car->engine()->throttle();
		egain = egain*0.5+0.5;
		if (rpm < 500.0)
		{
			egain *= (rpm / 7000.0f);
		}
		sound.SetGain(sid, egain);
		VERTEX carpos;
		carpos.x = cm[0];
		carpos.y = cm[2];
		carpos.z = -cm[1];
		VERTEX campos = cam.position;
		campos.Scale(-1);
		QUATERNION q = cam.dir;
		VERTEX at, up;
		up.y = 1.0;
		at.z = -1;

		VERTEX camvel = cam.position - cam_lastpos;
		camvel.Scale(1.0/dt);
		if (camswitch)
			camvel.zero();
		sound.SetListener(campos, camvel, q.ReturnConjugate().RotateVec(at), q.ReturnConjugate().RotateVec(up));

		VERTEX carvel;
		carvel.x = carpos.x - it->car->car_lastpos[0];
		carvel.y = carpos.y - it->car->car_lastpos[1];
		carvel.z = carpos.z - it->car->car_lastpos[2];
		carvel.Scale(1.0/dt);
		if (!carpos.nan() && !carvel.nan())
			sound.SetPosVel(sid, carpos, carvel);

		cam_lastpos = cam.position;
		it->car->car_lastpos[0] = cm[0];
		it->car->car_lastpos[1] = cm[2];
		it->car->car_lastpos[2] = -cm[1];
	}

	//get car state
	GetCar(CONT_PLAYERLOCAL)->car->GetState(replay.curstate.chassispos, 
		replay.curstate.chassisorientation,
		replay.curstate.chassisvel,
		replay.curstate.chassisangvel,
		replay.curstate.suspdisp,
		replay.curstate.suspcompvel,
		replay.curstate.whlangvel,
		replay.curstate.gear,
		replay.curstate.enginespeed,
		replay.curstate.clutchspeed,
		replay.curstate.enginedrag,
		replay.curstate.tirespeed
		);

#ifdef ENABLE_FORCE_FEEDBACK
//	static ofstream file("ff_output.txt");
	static double ff_update_time = 0.0;
	ff_update_time += timefactor;
	if( ff_update_time >= 40.0 )
	{
		ff_update_time = 0.0;
		static double feedback;
		feedback = ( GetCar(CONT_PLAYERLOCAL)->car->wheel(0)->GetFeedback() + GetCar(CONT_PLAYERLOCAL)->car->wheel(1)->GetFeedback() ) / 2.0;

		feedback = ff_gain * feedback / 100.0;
		if( ff_invert ) feedback = -feedback;

		if (feedback > 1.0)
			feedback = 1.0;
		if (feedback < -1.0)
			feedback = -1.0;
	//	feedback += 0.5;
/*
		static double motion_frequency = 0.1;
		static double motion_amplitude = 4.0;
		static double spring_strength = 1.0;
*/
		//double center = sin( timefactor * 2 * M_PI * motion_frequency ) * motion_amplitude;
		double force = feedback;

//		file << "ff_update_time: " << ff_update_time << " force: " << force << endl;
		force_feedback->update(force, &feedback);
	}
#endif

	//update replay
	replay.curstate.segment = GetCar(CONT_PLAYERLOCAL)->segment_index;
	replay.curstate.time = replay.GetTime();
	multiplay.GetCurState(0)->CopyFrom(replay.curstate);
	multiplay.GetCurState(0)->time = multiplay.GetTime(0);
	replay.IncrementFrame();
	multiplay.Update(timefactor/fps);
	if (replay.Playing() != -1)
	{
		CARSTATE * cst = replay.LoadState();
		if (cst != NULL)
		{
			Vamos_World::Car_Information * rcar = NULL;
			if (replay.GhostCar())
				rcar = GetCar(CONT_REPLAY);
			else
				rcar = GetCar(CONT_PLAYERLOCAL);
			rcar->segment_index = cst->segment;
			rcar->car->SetState(cst->chassispos, 
				cst->chassisorientation,
				cst->chassisvel,
				cst->chassisangvel,
				cst->suspdisp,
				cst->suspcompvel,
				cst->whlangvel,
				cst->gear,
				cst->enginespeed,
				cst->clutchspeed,
				cst->enginedrag,
				cst->tirespeed
				);
		}
	}

	//update multiplay
	if (multiplay.NumConnected() > 0)
	{
		int p;
		for (p = 0; p < multiplay.NumConnected(); p++)
		{
			if (multiplay.StateToLoad(p+1))
			{
				CARSTATE * cst = multiplay.GetLoadState(p+1);
				if (cst != NULL)
				{
					GetCar(CONT_PLAYERREMOTE)->segment_index = cst->segment;
					GetCar(CONT_PLAYERREMOTE)->car->SetState(cst->chassispos,
					cst->chassisorientation,
					cst->chassisvel,
					cst->chassisangvel,
					cst->suspdisp,
					cst->suspcompvel,
					cst->whlangvel,
					cst->gear,
					cst->enginespeed,
					cst->clutchspeed,
					cst->enginedrag,
					cst->tirespeed
					);
				}

				multiplay.ClearStateToLoad(p+1);
			}
		}
	}

	//clear old contact info
	world->m_contact_info.clear ();
}

extern void MainPause();
extern void MainUnpause();

void VAMOSWORLD::ProcessControls(SDL_Joystick ** js, float timefactor, float fps)
{
	string dofunction = "";
	double dovalue = 0.0;
	double dotime = 0.0;
	bool held = false;

	if (GetCar(CONT_PLAYERLOCAL) != 0)
	{
		int i;

		joyinfo_js = js[0];
		joyinfo_jsarray = js;

		if (replay.Playing() != -1)
		{
			for (i = 0; i < replay.GetNumFuncs(); i++)
			{
				FUNCTION_MEMORY curfunc = replay.GetFunc(i);
				if (curfunc.active)
				{
					if (replay.GhostCar())
						DoOp(GetCar(CONT_REPLAY), curfunc.func_name, curfunc.newval, 0.0, false, timefactor, fps); 
					else
						DoOp(GetCar(CONT_PLAYERLOCAL), curfunc.func_name, curfunc.newval, 0.0, false, timefactor, fps); 
				}
			}
		}

		if (MP_DBGDEEP)
			cout << "multiplay tick start" << endl;
		if (multiplay.NumConnected() > 0)
		{
			if (multiplay.TickCar(1) && !multiplay.NOOPTick(1))
			{
				for (i = 0; i < multiplay.NumFuncs(1); i++)
				{
					FUNCTION_MEMORY curfunc = multiplay.GetFuncMem(1)[i];
					dofunction = curfunc.func_name;
					if (curfunc.active && !(dofunction.find("view_") == 0 || dofunction == "screen_shot" || dofunction == "track_shot" || dofunction == "pause" || dofunction == "reset" || dofunction.find("pan_") == 0))
					{
						DoOp(GetCar(CONT_PLAYERREMOTE), curfunc.func_name, curfunc.newval, 0.0, false, timefactor, fps); 
					}
					dofunction = "";
				}
			}
		}
		if (MP_DBGDEEP)
			cout << "multiplay tick done" << endl;

		if (state.GetGameState() != STATE_STAGING)
			DoOp(GetCar(CONT_PLAYERLOCAL), "brake", 0.0, 0.0, true, timefactor, fps);
		
		for (gamecontrols.ControlIteratorReset(); gamecontrols.ControlIteratorGetControl() != NULL; gamecontrols.ControlIteratorIncrement())
		{
			CONTROL & curctrl = *(gamecontrols.ControlIteratorGetControl());
			dofunction = "";
			dovalue = 0.0;
			dotime = 0.0;
			held = false;

			//joystick input
			if (curctrl.GetType() == Joy)
			{
				SDL_Joystick * joytouse;

				int joynum = curctrl.GetJoyNum();

				if (joynum >= 0 && joynum < SDL_NumJoysticks())
				{
					joytouse = js[joynum];

					if (curctrl.GetJoyType() == Button)
					{
						if (curctrl.GetOneTime())
						{
							if (curctrl.GetJoyPushDown())
							{
								if (!curctrl.GetJoyButtonLastState() && SDL_JoystickGetButton(joytouse, curctrl.GetJoyButton()))
								{
									dofunction = curctrl.GetName();
									dovalue = 1.0;
								}
							}
							else if (!curctrl.GetJoyPushDown())
							{
								if (!curctrl.GetJoyButtonLastState() && SDL_JoystickGetButton(joytouse, curctrl.GetJoyButton()))
								{
									dofunction = curctrl.GetName();
									dovalue = 0.0;
								}
							}
						}
						else
						{
							if (SDL_JoystickGetButton(joytouse, curctrl.GetJoyButton()))
							{
								dofunction = curctrl.GetName();
								if (curctrl.GetJoyPushDown())
								{
									if( button_ramp != 0.0 )
									{
										dgtl_ctrl_anlg_val[dofunction] = dovalue = dgtl_ctrl_anlg_val[dofunction] + button_ramp * ( timefactor / fps );
										control_held[dofunction] = true;
									}
									else
										dovalue = 1.0;
								}
								else
								{
									if( button_ramp != 0.0 )
									{
										dovalue = dgtl_ctrl_anlg_val[dofunction];
										control_held[dofunction] = false;
									}
									else
										dovalue = 0.0;
								}
							}
							else
							{
								dofunction = curctrl.GetName();
								if (curctrl.GetJoyPushDown())
								{
									if( button_ramp != 0.0 )
									{
										dovalue = dgtl_ctrl_anlg_val[dofunction];
										control_held[dofunction] = false;
									}
									else
										dovalue = 0.0;
								}
								else
								{
									if( button_ramp != 0.0 )
									{
										dgtl_ctrl_anlg_val[dofunction] = dovalue = dgtl_ctrl_anlg_val[dofunction] + button_ramp * ( timefactor / fps );
										control_held[dofunction] = true;
									}
									else
										dovalue = 1.0;
								}
							}

							held = true;
						}

						curctrl.SetJoyButtonLastState(SDL_JoystickGetButton(joytouse, curctrl.GetJoyButton()));
					}
					else if (curctrl.GetJoyType() == Axis)
					{
						double val = SDL_JoystickGetAxis(joytouse, curctrl.GetJoyAxis());
						val = val / 32768.0;

						//apply calibration
						val = gamecontrols.ApplyCalibration(curctrl.GetJoyNum(),curctrl.GetJoyAxis(),val);

						//apply deadzone
						val = gamecontrols.ApplyDeadzone(curctrl.GetDeadzone(),val);
						
						//apply gain
						val = gamecontrols.ApplyGain(curctrl.GetGain(),val);
												
						//apply exponent
						double absval = val;
						bool neg = false;
						if (val < 0)
						{
							absval = -val;
							neg = true;
						}
						val = gamecontrols.ApplyExponent(curctrl.GetExponent(),absval);
						if (neg)
							val = -val;

						bool crossedover = false;
						switch (curctrl.GetJoyAxisType())
						{
							case Positive:
								if (val < 0)
								{
									crossedover = true;
									val = 0;
								}
								break;
							case Negative:
								if (val > 0)
								{
									val = 0;
									crossedover = true;
								}
								val = -val;
								break;
							case Both:
								val = (val + 1.0)/2.0;
								break;
						}

						dovalue = val;
						held = true;

						dofunction = curctrl.GetName();

					}
				}
				else
				{
					//joystick out of range
				}
			}

			//keyboard input
			else if (curctrl.GetType() == Key)
			{
				//one time
				if (curctrl.GetOneTime())
				{
					if (curctrl.GetKeyPushDown())
					{
						if (keyman.keys[curctrl.GetKeyCode()] && !keyman.lastkeys[curctrl.GetKeyCode()])
						{
							dofunction = curctrl.GetName();
							dovalue = 1.0;
						}
					}
					else
					{
						if (!keyman.keys[curctrl.GetKeyCode()] && keyman.lastkeys[curctrl.GetKeyCode()])
						{
							dofunction = curctrl.GetName();
							dovalue = 1.0;
						}
					}
				}
				else //held
				{
					held = true;

					if (curctrl.GetKeyPushDown())
					{
						if (keyman.keys[curctrl.GetKeyCode()])
						{
							dofunction = curctrl.GetName();
							if( button_ramp != 0.0 )
							{
								dgtl_ctrl_anlg_val[dofunction] = dovalue = dgtl_ctrl_anlg_val[dofunction] + button_ramp * ( timefactor / fps );
								control_held[dofunction] = true;
							}
							else
								dovalue = 1.0;
						}
						else if (keyman.lastkeys[curctrl.GetKeyCode()])
						{
							dofunction = curctrl.GetName();
							if( button_ramp != 0.0 )
							{
								dovalue = dgtl_ctrl_anlg_val[dofunction];
								control_held[dofunction] = false;
							}
							else
								dovalue = 0.0;
						}
					}
					else
					{
						if (!keyman.keys[curctrl.GetKeyCode()])
						{
							dofunction = curctrl.GetName();
							if( button_ramp != 0.0 )
							{
								dgtl_ctrl_anlg_val[dofunction] = dovalue = dgtl_ctrl_anlg_val[dofunction] + button_ramp * ( timefactor / fps );
								control_held[dofunction] = true;
							}
							else
								dovalue = 1.0;
						}
						else if (!keyman.lastkeys[curctrl.GetKeyCode()])
						{
							dofunction = curctrl.GetName();
							if( button_ramp != 0.0 )
							{
								dovalue = dgtl_ctrl_anlg_val[dofunction];
								control_held[dofunction] = false;
							}
							else
								dovalue = 0.0;
						}
					}
				}
			}
			
			//mouse input
			else if( curctrl.GetType() == Mouse )
			{
				if( curctrl.GetMouseType() == MButton )
				{
					int mouse_button = curctrl.GetMouseButton();
					if( curctrl.GetOneTime() )
					{
						if( curctrl.GetMousePushDown() )
						{
							if( mouse.IsPressed( mouse_button ) && !curctrl.GetLastMouseState() )
							{
								dofunction = curctrl.GetName();
								dovalue = 1.0;
							}
						}
						else
						{
							if( !mouse.IsPressed( mouse_button ) && curctrl.GetLastMouseState() )
							{
								dofunction = curctrl.GetName();
								dovalue = 0.0;
							}
						}

						held = false;
					}
					else
					{
						dofunction = curctrl.GetName();
						if( curctrl.GetMousePushDown() )
						{
							if( mouse.IsPressed( mouse_button ) )
							{
								if( button_ramp != 0.0 )
								{
									dgtl_ctrl_anlg_val[dofunction] = dovalue = dgtl_ctrl_anlg_val[dofunction] + button_ramp * ( timefactor / fps );
									control_held[dofunction] = true;
								}
								else
									dovalue = 1.0;
							}
							else
							{
								if( button_ramp != 0.0 )
								{
									dovalue = dgtl_ctrl_anlg_val[dofunction];
									control_held[dofunction] = false;
								}
								else
									dovalue = 0.0;
							}
						}
						else
						{
							if( !mouse.IsPressed( mouse_button ) )
							{
								if( button_ramp != 0.0 )
								{
									dgtl_ctrl_anlg_val[dofunction] = dovalue = dgtl_ctrl_anlg_val[dofunction] + button_ramp * ( timefactor / fps );
									control_held[dofunction] = true;
								}
								else
									dovalue = 1.0;
							}
							else
							{
								if( button_ramp != 0.0 )
								{
									dovalue = dgtl_ctrl_anlg_val[dofunction];
									control_held[dofunction] = false;
								}
								else
									dovalue = 0.0;
							}
						}

						held = true;
					}

					curctrl.SetLastMouseState( mouse.IsPressed( mouse_button ) );
				}
				else if( curctrl.GetMouseType() == Motion )
				{
					dofunction = curctrl.GetName();
					MouseDirEnum mouse_direction = curctrl.GetMouseDirection();
					float x, y;
					bool a, b;
					mouse.GetMouseControls( &x, &y, &a, &b );
					switch( mouse_direction )
					{
					case Up:
						dovalue = y;
						break;
					case Down:
						dovalue = -y;
						break;
					case Left:
						dovalue = x;
						break;
					case Right:
						dovalue = -x;
						break;
					default:
						break;
					}

					held = true;
				}
			}

			if( dovalue > 1.0 ) dovalue = 1.0;
			if( dovalue < 0.0 ) dovalue = 0.0;

			if (dofunction == "steer_left")
			{
				steer_set(dovalue, true);
				dofunction = "";
			}
			if (dofunction == "steer_right")
			{
				steer_set(-dovalue, false);
				dofunction = "";
			}

			if( ( dofunction == "gas" ) && ( auto_shift_time != 0 ) )
			{
				Uint32 ticks_since_shift = SDL_GetTicks() - auto_shift_time;
				double shift_delay = GetCar(CONT_PLAYERLOCAL)->car->shift_delay();
				Uint32 no_gas_duration = 200 + Uint32( shift_delay * 1000 );

				if( ticks_since_shift < no_gas_duration )
				{
					dovalue = 0.0;
					//DoOp(GetCar(CONT_PLAYERLOCAL), "gas", 0.0, 0.0, true, timefactor, fps);
				}
				else if( ( ticks_since_shift >= no_gas_duration ) && ( ticks_since_shift < ( no_gas_duration + 100 ) ) )
				{
					dovalue /= 2.0;
					//DoOp(GetCar(CONT_PLAYERLOCAL), "gas",  / 2.0, 0.0, true, timefactor, fps);
				}
				else
				{
					auto_shift_time = 0;
				}
			}

			if (((state.GetGameState() == STATE_PLAYING || dofunction == "gas") && (replay.Playing() == -1 || replay.GhostCar())) || dofunction.find("view_") == 0 || dofunction.find("pan_") == 0 || dofunction.find("zoom_") == 0 || dofunction.find("replay_") == 0 || dofunction == "screen_shot" || dofunction == "track_shot" || dofunction == "pause")
			{
				if (!auto_clutch || dofunction != "engage")
					DoOp(GetCar(CONT_PLAYERLOCAL), dofunction, dovalue, dotime, held, timefactor, fps);
			}
		}

		if (state.GetGameState() == STATE_STAGING)
			DoOp(GetCar(CONT_PLAYERLOCAL), "brake", 1.0, 0.0, true, timefactor, fps);
	}

	steer_commit();

	if( button_ramp != 0.0 )
	{
		for( map<string, float>::iterator i = dgtl_ctrl_anlg_val.begin(); i != dgtl_ctrl_anlg_val.end(); ++i )
		{
			if( i->second < 0.0 ) i->second = 0.0;
			if( i->second > 1.0 ) i->second = 1.0;
			if( !control_held[i->first] && ( i->second > 0.0 ) )
			{
				i->second -=  2.0 * ( timefactor / fps );
				if( ( i->first != "steer_left" ) && ( i->first != "steer_right" ) )
					DoOp(GetCar(CONT_PLAYERLOCAL), i->first, i->second, dotime, held, timefactor, fps);
			}
		}
	}

	if (auto_clutch)
	{
		float autoclutch = 0.0;

		if (GetCar(CONT_PLAYERLOCAL)->car->engine()->rotational_speed() < AUTO_CLUTCH_THRESH)
		{
			float rotspeed = GetCar(CONT_PLAYERLOCAL)->car->engine()->rotational_speed();
			float stall = GetCar(CONT_PLAYERLOCAL)->car->engine()->stall_speed() + AUTO_CLUTCH_MARGIN;
			float clutch = (rotspeed-stall) / (AUTO_CLUTCH_THRESH-stall);
			if (clutch < 0)
				clutch = 0;
			clutch = 1.0f - clutch;
			if (clutch > autoclutch)
				autoclutch = clutch;
		}

		if (shift_time > 0)
		{
			float clutch = shift_time / AUTO_CLUTCH_ENGAGE_TIME;
			if (clutch > autoclutch)
				autoclutch = clutch;

			shift_time -= timefactor/fps;
		}

		if (!GetCar(CONT_PLAYERLOCAL)->car->ShiftPending())
		{
			DoOp(GetCar(CONT_PLAYERLOCAL), "clutch", autoclutch, 0.0, true, timefactor, fps);
		}
	}

	if( auto_trans )
	{
		int current_gear = GetCar(CONT_PLAYERLOCAL)->car->gear();
		// only autoshift when no shift pending and we are not in neutral or reverse gear
		if( !GetCar(CONT_PLAYERLOCAL)->car->ShiftPending() && ( current_gear > 0 ) )
		{
			double peak_engine_speed = GetCar(CONT_PLAYERLOCAL)->car->engine()->peak_engine_speed();
			// shift up when engine speed exceeds peak speed
			if( ( GetCar(CONT_PLAYERLOCAL)->car->engine()->rotational_speed() > peak_engine_speed ) &&
			//    ( GetCar(CONT_PLAYERLOCAL)->car->engine()->last_rotational_speed() <= peak_engine_speed ) &&
			    ( current_gear < GetCar(CONT_PLAYERLOCAL)->car->transmission()->forward_gears() ) )
			{
				/*if( button_ramp != 0.0 )*/ auto_shift_time = SDL_GetTicks();
				DoOp(GetCar(CONT_PLAYERLOCAL), "disengage_shift_up", 1.0, 0.0, true, timefactor, fps);
			}

			// shift down when engine speed below shift_down_point
			// however, we do not auto shift down from 1st gear to neutral
			if( ( current_gear > 1 ) && shift_time <= 0.0 &&
			    GetCar(CONT_PLAYERLOCAL)->car->engine()->rotational_speed() < 
			    DownshiftPoint( GetCar(CONT_PLAYERLOCAL), current_gear ) )
			{
				/*if( button_ramp != 0.0 )*/ auto_shift_time = SDL_GetTicks();
				DoOp(GetCar(CONT_PLAYERLOCAL), "disengage_shift_down", 1.0, 0.0, true, timefactor, fps);
			}
		}
	}
}

float VAMOSWORLD::DownshiftPoint( Vamos_World::Car_Information * c, int gear )
{
	float shift_down_point = 200.0;
	if( gear > 2 )
	{
		double current_gear_ratio = c->car->transmission()->gear_ratio( gear );
		double lower_gear_ratio = c->car->transmission()->gear_ratio( gear - 1 );
		float peak_engine_speed = c->car->engine()->peak_engine_speed();
		shift_down_point = peak_engine_speed / lower_gear_ratio * current_gear_ratio;
		shift_down_point -= ( 200.0 / gear );
	}
	return shift_down_point;
}

void VAMOSWORLD::DoOp(Vamos_World::Car_Information * c, string dofunction, float dovalue, float dotime, bool held, float timefactor, float fps)
{
	//do the requested operation
	if (dofunction != "")
	{
		if (dofunction == "reset")
			world->reset(c);
		else if (dofunction == "brake")	c->car->brake (dovalue, dotime);
		else if (dofunction == "handbrake") c->car->handbrake (dovalue, dotime);
		else if (dofunction == "gas") c->car->gas (dovalue, dotime);
		else if (dofunction == "clutch") c->car->clutch (1.0f-dovalue, dotime);
		else if (dofunction == "steer_left" || dofunction == "steer_right") c->car->steer (dovalue, dotime);
		else if (dofunction == "disengage_shift_up") 
		{
			c->car->disengage_clutch (0.2);
			c->car->shift_up ();
			if (c == GetCar(CONT_PLAYERLOCAL))
			{
				shift_time = AUTO_CLUTCH_ENGAGE_TIME;
			}
		}
		else if (dofunction == "disengage_shift_down") 
		{
			c->car->disengage_clutch (0.2);
			c->car->shift_down ();
			if (c == GetCar(CONT_PLAYERLOCAL))
			{
				shift_time = AUTO_CLUTCH_ENGAGE_TIME;
			}
		}
		else if (dofunction == "engage")
		{
			if (c->car->last_gear () == 0)
				c->car->engage_clutch (1.0);
			else
				c->car->engage_clutch (0.2);
		}
		else if (dofunction == "neutral")
		{
			c->car->disengage_clutch (0.2);
			c->car->shift(0);
			if (c == GetCar(CONT_PLAYERLOCAL))
			{
				shift_time = AUTO_CLUTCH_ENGAGE_TIME;
			}
		}
		else if (dofunction == "first_gear" && c->car->transmission ()->forward_gears () >= 1)
		{
			c->car->disengage_clutch (0.2);
			c->car->shift(1);
			if (c == GetCar(CONT_PLAYERLOCAL))
			{
				shift_time = AUTO_CLUTCH_ENGAGE_TIME;
			}
		}
		else if (dofunction == "second_gear" && c->car->transmission ()->forward_gears () >= 2)
		{
			c->car->disengage_clutch (0.2);
			c->car->shift(2);
			if (c == GetCar(CONT_PLAYERLOCAL))
			{
				shift_time = AUTO_CLUTCH_ENGAGE_TIME;
			}
		}
		else if (dofunction == "third_gear" && c->car->transmission ()->forward_gears () >= 3)
		{
			c->car->disengage_clutch (0.2);
			c->car->shift(3);
			if (c == GetCar(CONT_PLAYERLOCAL))
			{
				shift_time = AUTO_CLUTCH_ENGAGE_TIME;
			}
		}
		else if (dofunction == "fourth_gear" && c->car->transmission ()->forward_gears () >= 4)
		{
			c->car->disengage_clutch (0.2);
			c->car->shift(4);
			if (c == GetCar(CONT_PLAYERLOCAL))
			{
				shift_time = AUTO_CLUTCH_ENGAGE_TIME;
			}
		}
		else if (dofunction == "fifth_gear" && c->car->transmission ()->forward_gears () >= 5)
		{
			c->car->disengage_clutch (0.2);
			c->car->shift(5);
			if (c == GetCar(CONT_PLAYERLOCAL))
			{
				shift_time = AUTO_CLUTCH_ENGAGE_TIME;
			}
		}
		else if (dofunction == "sixth_gear" && c->car->transmission ()->forward_gears () >= 6)
		{
			c->car->disengage_clutch (0.2);
			c->car->shift(6);
			if (c == GetCar(CONT_PLAYERLOCAL))
			{
				shift_time = AUTO_CLUTCH_ENGAGE_TIME;
			}
		}
		else if (dofunction == "reverse" && c->car->transmission ()->reverse_gears () >= 1)
		{
			c->car->disengage_clutch (0.2);
			c->car->shift(-1);
			if (c == GetCar(CONT_PLAYERLOCAL))
			{
				shift_time = AUTO_CLUTCH_ENGAGE_TIME;
			}
		}
		else if (dofunction == "abs_toggle")
		{
			abs = !abs;
			GetCar(CONT_PLAYERLOCAL)->car->SetABS( abs );
		}
		else if (dofunction == "tcs_toggle")
		{
			tcs = !tcs;
			GetCar(CONT_PLAYERLOCAL)->car->SetTCS( tcs );
		}
		else if (dofunction == "start_engine") c->car->start_engine ();
		else if (dofunction == "view_chaserigid") {mq1.AddMessage("Camera set to rigid chase mode");SetCameraMode(CMChaseRigid);}
		else if (dofunction == "view_chase") {mq1.AddMessage("Camera set to flexible chase mode");SetCameraMode(CMChase);}
		else if (dofunction == "view_orbit") {mq1.AddMessage("Camera set to mouse orbit mode");SetCameraMode(CMOrbit);}
		else if (dofunction == "view_hood") {mq1.AddMessage("Camera set to hood mounted mode");SetCameraMode(CMHood);}
		else if (dofunction == "view_free") {mq1.AddMessage("Camera set to free mode");SetCameraMode(CMFree);}
		else if (dofunction == "view_incar") {mq1.AddMessage("Camera set to in-car mode");SetCameraMode(CMInCar);}
		else if (dofunction == "view_external") {mq1.AddMessage("Camera set to external fixed mode");SetCameraMode(CMExternal);}
		else if (dofunction == "view_extfollow") {mq1.AddMessage("Camera set to external follow mode");SetCameraMode(CMExtFollow);}
		else if (dofunction == "pan_right")
		{
			if( ( cammode == CMFree ) || ( cammode == CMOrbit ) )
				cam.PreRotate(-dovalue/fps, 0,1,0);
		}
		else if (dofunction == "pan_left")
		{
			if( ( cammode == CMFree ) || ( cammode == CMOrbit ) )
				cam.PreRotate(dovalue/fps, 0,1,0);
		}
		else if (dofunction == "pan_up")
		{
			if( ( cammode == CMFree ) || ( cammode == CMOrbit ) )
				cam.Rotate(dovalue/fps, 1,0,0);
		}
		else if (dofunction == "pan_down")
		{
			if( ( cammode == CMFree ) || ( cammode == CMOrbit ) )
				cam.Rotate(-dovalue/fps, 1,0,0);
		}
		else if (dofunction == "zoom_in")
		{
			mousezoom += ZOOMSPEED * timefactor/fps;
			if (mousezoom > 1.0f)
				mousezoom = 1.0f;
		}
		else if (dofunction == "zoom_out")
		{
			mousezoom -= ZOOMSPEED * timefactor/fps;
			if (mousezoom < 0.0f)
				mousezoom = 0.0f;
		}
		else if (dofunction == "view_next_camera")
		{
			if( ( cammode == CMFree ) || ( cammode == CMOrbit ) || ( cammode == CMExternal ) || ( cammode == CMExtFollow ) || ( cammode == CMChase ) )
			{
				SetCameraMode( CMHood );
			}
			else if( cammode == CMHood )
			{
				SetCameraMode( CMInCar );
			}
			else if( cammode == CMInCar )
			{
				SetCameraMode( CMChaseRigid );
			}
			else if( cammode == CMChaseRigid )
			{
				SetCameraMode( CMChase );
			}
		}
		else if (dofunction == "view_prev_camera")
		{
			if( ( cammode == CMFree ) || ( cammode == CMOrbit ) || ( cammode == CMExternal ) || ( cammode == CMExtFollow ) || ( cammode == CMHood ) )
			{
				SetCameraMode( CMChase );
			}
			else if( cammode == CMChase )
			{
				SetCameraMode( CMChaseRigid );
			}
			else if( cammode == CMChaseRigid )
			{
				SetCameraMode( CMInCar );
			}
			else if( cammode == CMInCar )
			{
				SetCameraMode( CMHood );
			}
		}
		else if (dofunction == "joystick_info") joyinfo = !joyinfo;
		else if (dofunction == "proportionalsteer_toggle") propsteer = !propsteer;
		else if (dofunction == "track_shot") build_track_shot();
		else if (dofunction == "screen_shot") snap_screenshot();
		else if (dofunction == "replay_ff") replay.Jump(replay.GetTime()+10);
		else if (dofunction == "replay_rw") {double t = replay.GetTime()-10; if (t < 0) t = 0; replay.Jump(t);}
		else if (dofunction == "pause")
		{
			if (timefactor != 0) 
				MainPause();
		}
		else
		{
			if (verbose_output)
				cout << "No such function: " << dofunction << endl;
		}

		int state = 0;
		if (held)
			state = 1;
		if (replay.Playing() == -1 && !(dofunction.find("view_") == 0 || dofunction == "screen_shot" || dofunction == "track_shot" || dofunction == "pause" || dofunction.find("pan_") == 0))
		{
			if (c->car->get_controller() == CONT_PLAYERLOCAL)
				replay.AddRecord(dofunction, dovalue, state);
		}

		if (MP_DBGDEEP)
			cout << "multiplay add record start" << endl;			
		if (multiplay.NumConnected() > 0 && !(dofunction.find("view_") == 0 || dofunction == "screen_shot" || dofunction == "track_shot" || dofunction == "pause" || dofunction == "reset" || dofunction.find("pan_") == 0))
		{
			if (c->car->get_controller() == CONT_PLAYERLOCAL)
				multiplay.AddRecord(dofunction, dovalue, state);
		}
		if (MP_DBGDEEP)
			cout << "multiplay add record done" << endl;
	}
}

void VAMOSWORLD::DrawHUD()
{
	char tempchar[32];

	float w = 0.25;
	float h = 0.333;
	float y = 0.65;
	float x = 0.05;
	utility.Draw2D(x, y, x+w, y+h, &tachbase);

	float maxrpm = Vamos_Geometry::rad_s_to_rpm (GetCar(CONT_PLAYERLOCAL)->car->engine ()->max_rotational_speed ());
	float tachspace = 0.55;
	float curtachrot = -7.0f*tachspace;
	float currpm = 11000;

	//draw RPM band
	while (currpm >= 0)
	{
		if (maxrpm > currpm+500)
			utility.Draw2D(x, y, x+w, y+h, &tachband, curtachrot);
		curtachrot += tachspace;
		currpm -= 1000;
	}

	float nscale = 0.00055;

	//draw red band
	float redline = Vamos_Geometry::rad_s_to_rpm (GetCar(CONT_PLAYERLOCAL)->car->engine ()->peak_engine_speed ());
	redline -= 5000;
	utility.Draw2D(x, y, x+w, y+h, &tachredband, -redline*nscale);

	//draw text
	currpm = 11000;
	curtachrot = -7.0f*tachspace;
	while (currpm >= 0)
	{
		if (maxrpm >= currpm-500)
		{
			float aspect = w / h;
			QUATERNION rot;
			rot.Rotate(curtachrot, 0,0,1);
			VERTEX textoffset;
			textoffset.Set(0,-0.125,0);
			textoffset = rot.RotateVec(textoffset);
			textoffset.x *= -aspect;
			VERTEX tachcenter;
			tachcenter.Set(x+w/2.0,y+h/2.0,0);
			textoffset = textoffset + tachcenter;
			VERTEX offset;
			offset.Set(-0.01*aspect, -0.026, 0);
			textoffset = textoffset + offset;
			char tc[8];
			sprintf(tc,"%i", (int)(currpm / 1000.0));
			font.Print(textoffset.x, textoffset.y, tc, 1, 6, 0,0,0);
		}
		curtachrot += tachspace;
		currpm -= 1000;
	}

	utility.Draw2D(1.0-x-w, y, 1.0-x, y+h, &speedo);

	float rpm = Vamos_Geometry::rad_s_to_rpm (GetCar(CONT_PLAYERLOCAL)->car->engine ()->rotational_speed ());

	rpm *= nscale;
	utility.Draw2D(x, y, x+w, y+h, &needle, -rpm);

	float kph = Vamos_Geometry::m_s_to_km_h (GetCar(CONT_PLAYERLOCAL)->car->chassis().cm_velocity().magnitude());
	if (kph < 0)
		kph = -kph;

	float mph = kph * 0.621371192;
	utility.Draw2D(1.0-x-w, y, 1.0-x, y+h, &needle, -mph*0.034+0.0);

	char tchar[256];

	if( MPH )
	{
		sprintf(tchar, "%03.0f", mph);
		font.Print(1.0-x-w+0.097, y+0.26, tchar, 0, 7, 1,0,0);
		font.Print(1.0-x-w+0.103, y+0.29, "MPH", 0, 6, 0,0,0);
	}
	else
	{
		sprintf(tchar, "%03.0f", kph);
		font.Print(1.0-x-w+0.097, y+0.26, tchar, 0, 7, 1,0,0);
		font.Print(1.0-x-w+0.103, y+0.29, "km/h", 0, 6, 0,0,0);
	}

	int gear = GetCar(CONT_PLAYERLOCAL)->car->transmission ()->gear ();
	if (gear == 0)
		strcpy (tempchar, "N");
	else if (gear < 0)
		strcpy (tempchar, "R");
	else
		sprintf(tempchar, "%i", gear);
	//font.Print(x+w/2.0-0.021,y+h-0.1, tempchar, 0, 9, 1,0,0);
	font.Print(x+w/2.0-0.014,y+h-0.1, tempchar, 0, 9, 1,0,0);

	float pfuel = GetCar(CONT_PLAYERLOCAL)->car->fuel_tank()->fuelpercent();
	pfuel = 0.1*pfuel;
	utility.Draw2D(0,0.87,0.1,1, &fgbox);
	w = .125;
	h = .1725;
	x = -.037;
	y = 0.905;
	utility.Draw2D(x,y,x+w,h+y, &needle, pfuel*20.0+2.42);

	float recspace = replay.Recording();
	if (recspace >= 0)
	{
		sprintf(tempchar, "Recording: %2.0f%% full", (recspace) * 100.0f);
		font.Print(0.4, 0.95, tempchar, 1, 6, 1, 0, 0, 1);
	}
	float playspace = replay.Playing();
	if (playspace >= 0)
	{
		sprintf(tempchar, "Playing: %2.0f%% complete", (playspace) * 100.0f);
		font.Print(0.4, 0.95, tempchar, 1, 6, 1, 0, 0, 1);
	}

	if (multiplay.NumConnected() > 0)
	{
		sprintf(tempchar, "Tx: %2.3fkb/s", multiplay.GetTxRate()/1000.0);
		font.Print(0.4, 0.92, tempchar, 1, 6, 1, 0, 0, 1);

		sprintf(tempchar, "Rx: %2.3fkb/s", multiplay.GetRxRate()/1000.0);
		font.Print(0.4, 0.95, tempchar, 1, 6, 1, 0, 0, 1);
	}
	
	/*if (GetCar(CONT_AI))
	{
	        int gear = GetCar(CONT_AI)->car->transmission ()->gear ();
        	if (gear == 0)
                	strcpy (tempchar, "Gear:N");
	        else if (gear < 0)
        	        strcpy (tempchar, "Gear:R");
	        else
        	        sprintf(tempchar, "Gear:%i", gear);
		font.Print(0.6, 0.89, tempchar, 1, 6, 1, 0, 0, 1);

		float kph = Vamos_Geometry::m_s_to_km_h (GetCar(CONT_AI)->car->chassis().cm_velocity().magnitude());
		if (kph < 0)
			kph = -kph;
       	        sprintf(tempchar, "KM/H:%03.0f", kph);
		font.Print(0.6, 0.92, tempchar, 1, 6, 1, 0, 0, 1);

		float rpm = Vamos_Geometry::rad_s_to_rpm (GetCar(CONT_AI)->car->engine ()->rotational_speed ());
		sprintf(tempchar, "RPM:%5.0f", rpm);
		font.Print(0.6, 0.95, tempchar, 1, 6, 1, 0, 0, 1);

	}*/	

	//draw race position
	switch (state.GetGameMode())
	{
		default:
		case MODE_FREEDRIVE:
			sprintf(tempchar, "LAP: %2d", GetCar(CONT_PLAYERLOCAL)->lap_number);
			font.Print(0.72, 0.04, tempchar, 1, 8, 1);
			break;
		case MODE_SINGLERACE:
			if (GetCar(CONT_PLAYERLOCAL)->end_race)
			{
				if (GetCar(CONT_PLAYERLOCAL)->race_position == 1)
				{
					sprintf(tempchar, "YOU WIN!");
				}
				else
				{
					sprintf(tempchar, "YOU LOSE!");
				}
				int fontsize = 11;
				float sx = (1.0 - font.Width(tempchar, 1, fontsize)) / 2.0;
				float sy = (1.0 - font.Height(tempchar, 1, fontsize)) / 2.0;
				font.Print(sx, sy, tempchar, 1, fontsize, 0,1,0);
			}
			else
			{
				sprintf(tempchar, "LAP: %2d / %2d", GetCar(CONT_PLAYERLOCAL)->lap_number,
					state.GetLaps());
				font.Print(0.72, 0.04, tempchar, 1, 8, 1);
				sprintf(tempchar, "POSITION: %1d / %1d", GetCar(CONT_PLAYERLOCAL)->race_position, 
					world->m_cars.size());
				font.Print(0.72, 0.08, tempchar, 1, 8, 1);
			}
			break;
	}

	//draw ABS indicator
	if (GetPlayerCar()->ABS())
	{
		float op = 0.25;
		if (GetPlayerCar()->ABSActive())
			op = 1.0;

		float yoffset = 0.02;
		//float xoffset = 0.1;
		float xoffset = 0.0;
		utility.Draw2D(0.1+xoffset,0.87+yoffset,0.15+xoffset,.935+yoffset, &abs_indicator, 0, 0, op);
	}
	
	if (GetPlayerCar()->TCS())
	{
		float op = 0.25;
		if (GetPlayerCar()->TCSActive())
			op = 1.0;
		
		float yoffset = 0.02;
		//float xoffset = 0.1;
		float xoffset = 0.0;
		utility.Draw2D(0.1+xoffset,0.87+yoffset,0.15+xoffset,.935+yoffset, &tcs_indicator, 0, 0, op);
	}

	//draw the input graph
	float mx = GetPlayerCar()->steer() / GetPlayerCar()->max_steer_angle();
	float my = GetPlayerCar()->gas() - GetPlayerCar()->brake();
	if( input_graph_enabled && ( replay.Playing() == -1 || replay.GhostCar() ) )
	{
		mx = -mx;
		float dx, dy;
		dx = 0.4;
		dy = 0.8;
		float dw = 0.2;
		float dh = 0.2;
		dh *= 1.33333;
		utility.Draw2D(dx, dy, dx+dw, dy+dh, &m_sliderh);
		float ballscale = 4.0;
		utility.Draw2D((dx+dw/2.0)-dw/(ballscale*2.0)+mx*dw/2.7, (dy+dh/2.0)-dh/(ballscale*2.0), (dx+dw/2.0)-dw/(ballscale*2.0)+mx*dw/2.7+dw/ballscale, (dy+dh/2.0)-dh/(ballscale*2.0)+dh/ballscale, &m_ballh);

		my = -my;
		dx = 0.3;
		dy = 0.87;
		dw = 0.1;
		dh = 0.1;
		dh *= 1.33333;
		ballscale = 1.0;
		utility.Draw2D(dx, dy, dx+dw, dy+dh, &m_sliderv);
		utility.Draw2D((dx+dw/2.0)-dw/(ballscale*2.0), (dy+dh/2.0)-dh/(ballscale*2.0)+my*dh/2.7, (dx+dw/2.0)-dw/(ballscale*2.0)+dw/ballscale, (dy+dh/2.0)-dh/(ballscale*2.0)+my*dh/2.7+dh/ballscale, &m_ballv);
	}

	//draw track map
	if (trackmap_enabled)
	{
		trackmap->DrawMap();

		//draw car dot
		int i=0;
		std::vector <Vamos_World::Car_Information>::iterator it;
		for( it = world->m_cars.begin (); it != world->m_cars.end (); it++ )
		{
			trackmap->DrawCarDot(i, it->car->chassis().position()); 
			i++;
		}
	}
}

void VAMOSWORLD::reset()
{
	world->reset(true);
}

void VAMOSWORLD::DisplayJoyInfo()
{
	int joynum = 0;

	string joystring = "";
	char tchar[256];

	float xpos = 0.01;

	for (joynum = 0; joynum < SDL_NumJoysticks(); joynum++)
	{	
		if (SDL_NumJoysticks() > joynum)
		{
			int i;
			for (i = 0; i < SDL_JoystickNumAxes(joyinfo_jsarray[joynum]); i++)
			{
					sprintf(tchar, "Axis %i: %f\n", i, (float)SDL_JoystickGetAxis(joyinfo_jsarray[joynum], i)/32768.0f);
					joystring = joystring + tchar;
			}

			for (i = 0; i < SDL_JoystickNumButtons(joyinfo_jsarray[joynum]); i++)
			{
					sprintf(tchar, "Button %i: %d\n", i, SDL_JoystickGetButton(joyinfo_jsarray[joynum], i));
					joystring = joystring + tchar;
			}

			font.Print(xpos,0.11, joystring.c_str(), 1, 5, 1,1,1);
			xpos += 0.2;
			joystring = "";
		}
	}
}

void VAMOSWORLD::build_track_shot()
{
}

void VAMOSWORLD::steer_to(float val, float timefactor, float fps)
{
	bool inhibit = (gamecontrols.GetJoystickType() == "wheel");
/*
	if (gamecontrols.GetCompensation() == "low")
	{
		if (val < 0)
			val = -val*val;
		else
			val = val*val;
	}
	else if (gamecontrols.GetCompensation() == "med")
	{
		val = val*val*val;
	}
	else if (gamecontrols.GetCompensation() == "high")
	{
		if (val < 0)
			val = -val*val*val*val;
		else
			val = val*val*val*val;
	}
//	else if (gamecontrols.GetCompensation() == "900to200") */
	if( joy_200_degree )
	{
		float decimate = 4.5;

		float carmph = Vamos_Geometry::m_s_to_km_h (GetCar(CONT_PLAYERLOCAL)->car->wheel (1)->speed ())*0.621371192;

		float normalat = 30;
		float transat = 15;

		if (carmph < transat)
			decimate = 1.0;
		else if (carmph < normalat)
		{
			float coeff = (carmph - transat)/(normalat - transat);
			decimate = (decimate-1.0f)*coeff + 1.0f;
		}

		val = val/decimate;
	}
//	else if (gamecontrols.GetCompensation() == "speed1" || gamecontrols.GetCompensation() == "speed2")

	if( speed_sens != 0.0 )
	{
		float carmph = Vamos_Geometry::m_s_to_km_h (GetCar(CONT_PLAYERLOCAL)->car->wheel (1)->speed ())*0.621371192;

		if (carmph < 0)
			carmph = -carmph;

		float ratio = 20.0f;
		float coeff = 1.0;
		if (carmph > 1)
			coeff = ratio/(carmph*(0.1+speed_sens*2.0));

		if (coeff > 1)
			coeff = 1.0;
//		if (gamecontrols.GetCompensation() == "speed2")
//			coeff = coeff * coeff;

		val = val*coeff;
	}

	float steerstep = 5.0*timefactor/fps;

	if (val > steerpos)
	{
		if (val - steerpos <= steerstep)
			steerpos = val;
		else
			steerpos += steerstep;
	}
	else
	{
		if (steerpos - val <= steerstep)
			steerpos = val;
		else
			steerpos -= steerstep;
	}

	if (inhibit)
		steerpos = val;

	if (steerpos > 1.0)
		steerpos = 1.0;
	if (steerpos < -1.0)
		steerpos = -1.0;
}

void VAMOSWORLD::steer_set(float val, bool left)
{
	if (left)
		steervals[0] = val;
	else
		steervals[1] = val;
}

void VAMOSWORLD::steer_commit()
{
	if (steervals[0] != 0)
	{
		steer_to(steervals[0], l_timefactor, l_fps);
	}
	else
	{
		steer_to(steervals[1], l_timefactor, l_fps);
	}

	steervals[0] = 0.0;
	steervals[1] = 0.0;

	if (replay.Playing() == -1 || replay.GhostCar())
		DoOp(GetCar(CONT_PLAYERLOCAL), "steer_left", steerpos, 0.0, true, l_timefactor, l_fps);
}

void VAMOSWORLD::SetCameraMode(CameraMode newmode)
{
	cammode = newmode;
	ofstream cf;

	settings.Set( "game.camera_mode", cammode );

	if (newmode == CMFree)
		keyman.freecam = true;
	else
		keyman.freecam = false;
	
	if (newmode == CMOrbit || newmode == CMFree)
	{
		cam.dir.LoadMultIdent();
	}

	if (newmode == CMExternal || newmode == CMExtFollow)
	{
		lastcamchange = 0.0f;
		camneedupdate = true;
	}
	if (newmode == CMInCar)
	{
		cam_lastpos = cam.position;
		cam_lastpos2 = cam.position;
		cam_lastvel.zero();
		cam_lastaccel.zero();
		cam_jerk.zero();
		camneedupdate = true;
	}
}

void VAMOSWORLD::FuelPlayerCar()
{
	GetCar(CONT_PLAYERLOCAL)->car->fuel_tank()->fill();
}

void VAMOSWORLD::clear_cars()
{
	std::vector<Vamos_World::Car_Information>::iterator it;
	for( it = world->m_cars.begin (); it != world->m_cars.end (); it++ )
	{
		delete it->car;
	}
	world->m_cars.clear();
	ai->clear_cars();
}

void VAMOSWORLD::PhysUpdate(float dt)
{
	int count = 0;
	std::vector<Vamos_World::Car_Information>::iterator it;

	for( it = world->m_cars.begin (); it != world->m_cars.end (); it++ )
	{
		if (count == 0 || multiplay.TickCar(count) || (count == 1 && replay.GhostCar())
		    || it->car->get_controller() == CONT_AI)
		{
			it->car->propagate (dt);
			world->interact (it->car, it->segment_index);
		}
		
		count++;
	}
}


Vamos_World::Car_Information * VAMOSWORLD::GetCar(CONTROLLERTYPE p)
{
	if (world == NULL)
		return NULL;
	
	for (std::vector <Vamos_World::Car_Information>::iterator it = world->m_cars.begin ();
			   it != world->m_cars.end ();
			   it++)
	{
		if (it->car->get_controller() == p)
		{
			return &(*it);
		}
	}

	cout << "Error -- no player car!  Get ready for a crash...." << endl;

	return NULL;
}

extern GLfloat LightAmbient[4];

void VAMOSWORLD::DrawCars()
{
	glPushMatrix();
	
	//GLfloat carLightAmbient[]  = { 0.55f, 0.55f, 0.55f, 1.0f };
	GLfloat carLightAmbient[] = { 2.0f, 2.0f, 2.0f, 1.0f };
	glLightfv( GL_LIGHT1, GL_AMBIENT, carLightAmbient );

	QUATERNION goofyfoot;
	goofyfoot.Rotate(-3.141593/2.0, 1,0,0);
	double tempmat[16];
	goofyfoot.GetMat(tempmat);
	glMultMatrixd(tempmat);

	/*float lp[4];
	lp[0] = LightPosition[0];
	lp[1] = LightPosition[1];
	lp[2] = LightPosition[2];
	lp[3] = 0;
	VERTEX lpv;
	lpv.Set(lp);
	lpv = goofyfoot.ReturnConjugate().RotateVec(lpv);
	lp[0] = lpv.x;
	lp[1] = lpv.y;
	lp[2] = lpv.z;*/

	if (MP_DBGDEEP)
		cout << "draw cars start" << endl;
	draw_cars(false, true);
	if (MP_DBGDEEP)
		cout << "draw cars done" << endl;

	glLightfv( GL_LIGHT1, GL_AMBIENT, LightAmbient );
	
	glPopMatrix();
}

void VAMOSWORLD::DrawTopLevel()
{
	glPushMatrix();

	//glDisable(GL_STENCIL_TEST);
	particle.Draw();

	if (display_hud)
	{
		DrawHUD();
		timer.Draw();
	}

	if (joyinfo)
	 DisplayJoyInfo();

	glPopMatrix();
}

void VAMOSWORLD::UpdateSettings()
{
	settings.Get( "display.show_hud", display_hud );
	settings.Get( "display.mph", MPH );
	settings.Get( "control.autoclutch", auto_clutch );
	settings.Get( "control.autotrans", auto_trans ); 
	if( auto_trans ) auto_clutch = true;
	settings.Get( "display.input_graph", input_graph_enabled );
	settings.Get( "display.trackmap", trackmap_enabled );
	settings.Get( "display.car_shadows", car_shadows_enabled );
	settings.Get( "display.reflections", reflections );
	settings.Get( "display.lighting", lighting );
	settings.Get( "control.button_ramp", button_ramp );
	settings.Get( "game.antilock", abs );
	settings.Get( "game.traction_control", tcs );
	settings.Get( "joystick.two_hundred", joy_200_degree );
	settings.Get( "control.speed_sens_steering", speed_sens );

#ifdef ENABLE_FORCE_FEEDBACK
	if( settings.Get( "joystick.ff_device", ff_device ) )
	{
		cout << "Force feedback device: " << ff_device << endl;
	}
	else
	{
		cout << "Force feedback device name not set in VDrift.config: joystick.ff_device." << endl;
	}

	if( settings.Get( "joystick.ff_gain", ff_gain ) )
	{
		cout << "Force feedback gain: " << ff_gain << endl;
	}
	else
	{
		cout << "Force feedback gain not found in VDrift.config: joystick.ff_gain." << endl;
		ff_gain = 2.0;
		settings.Set( "joystick.ff_gain", ff_gain );
	}

	if( settings.Get( "joystick.ff_invert", ff_invert ) )
	{
		cout << "Force feedback inverted: " << ff_invert << endl;
	}
	else
	{
		cout << "Force feedback invert not found in VDrift.config: joystick.ff_invert." << endl;
		ff_invert = false;
		settings.Set( "joystick.ff_invert", ff_invert );
	}
#endif
}

void VAMOSWORLD::LoadHUD()
{
	tachbase.Load("hud/tachometer.png", false);
	tachredband.Load("hud/redband.png", false);
	tachband.Load("hud/tachband.png", false);
	speedo.Load("hud/speedo.png", false);
	needle.Load("hud/needle.png", false);
	fgbox.Load("hud/fuelgaugebox.png", false);
	sphere_reflection.Load("weather/reflect.png", true);
	
	int w,h;
	cube_reflection.Load("weather/cubereflection.png", false, true, w, h);
	cube_lighting.Load("weather/cubelighting.png", false, true, w, h);
	cube_lighting_car.Load("weather/cubelighting-car.png", false, true, w, h);
	
	abs_indicator.Load("hud/abs.png", false);
	tcs_indicator.Load("hud/tcs.png", false);

	m_ballh.Load("hud/ball2.png", false);
	m_sliderh.Load("hud/slider2.png", false);

	m_ballv.Load("hud/accdec-marker.png", false);
	m_sliderv.Load("hud/accdec-slider.png", false);
}

void VAMOSWORLD::UnloadHUD()
{
	tachbase.Unload();
	tachredband.Unload();
	tachband.Unload();
	speedo.Unload();
	needle.Unload();
	fgbox.Unload();
	sphere_reflection.Unload();
	cube_reflection.Unload();
	cube_lighting.Unload();
	cube_lighting_car.Unload();
	abs_indicator.Unload();
	tcs_indicator.Unload();

	m_ballh.Unload();
	m_sliderh.Unload();

	m_ballv.Unload();
	m_sliderv.Unload();
}

void VAMOSWORLD::add_car(Vamos_Body::Gl_Car* car)
{
	world->add_car(car);
	
	if (DynamicCubeReflectionMapping())
		car->SetDynamicCubeReflectionTexture(dynamic_cube_reflection_texture);
	else if (StaticCubeReflectionMapping())
		car->SetReflectionTexture(&cube_reflection, StaticCubeReflectionMapping());
	else
		car->SetReflectionTexture(&sphere_reflection, StaticCubeReflectionMapping());
	
	if (car->get_controller() == CONT_AI) ai->add_car(car);
}

void VAMOSWORLD::ActivateTextureBasedLighting(bool goofy, bool car)
{
	utility.SelectTU(0);
	
	utility.Tex2D(0, true);
	
	glEnable(GL_TEXTURE_CUBE_MAP_ARB);
	glDisable(GL_LIGHTING);
	
	if (DynamicCubeLighting() && car)
		//glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, dynamic_cube_reflection_texture);
		glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, dynamic_cube_lighting_texture);
	else
	{
		if (car)
			cube_lighting_car.Activate();
		else
			cube_lighting.Activate();		
	}
	
	glMatrixMode(GL_TEXTURE);
	double temp_matrix[16];
	cam.dir.ReturnConjugate().GetMat(temp_matrix);

	if (goofy)
	{
		QUATERNION goofyfoot;
		//goofyfoot.Rotate(-3.141593/2.0, 1,0,0);
		goofyfoot.Rotate(3.141593/2.0, 0,0,1);
		double tempmat[16];
		goofyfoot.GetMat(tempmat);
		glLoadMatrixd(tempmat);
		glMultMatrixd(temp_matrix);
	}
	else
	{
		glLoadMatrixd(temp_matrix);
	}
	
	if (DynamicCubeLighting() && car)
		glLoadIdentity();
	
	glMatrixMode(GL_MODELVIEW);
	
	GLenum genmode = GL_REFLECTION_MAP_ARB;
	genmode = GL_NORMAL_MAP_ARB;
	glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, genmode);
	glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, genmode);
	glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, genmode);
	glEnable(GL_TEXTURE_GEN_S);
	glEnable(GL_TEXTURE_GEN_T);
	glEnable(GL_TEXTURE_GEN_R);
	
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);
	glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE);
	glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE);
	//glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB);
}

void VAMOSWORLD::DeactivateTextureBasedLighting()
{
	utility.SelectTU(0);
	
	glDisable(GL_TEXTURE_GEN_S);
	glDisable(GL_TEXTURE_GEN_T);
	glDisable(GL_TEXTURE_GEN_R);
	
	glDisable(GL_TEXTURE_CUBE_MAP_ARB);
	glEnable(GL_LIGHTING);
	
	glMatrixMode(GL_TEXTURE);
	glLoadIdentity();
	glMatrixMode(GL_MODELVIEW);
	
	utility.Tex2D(0,true);
	
	glEnable(GL_LIGHTING);
	glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);
}

bool VAMOSWORLD::StaticCubeLighting()
{
	bool cubic_lighting_enabled_in_config = false;
	if( lighting == 1 )
	{
		cubic_lighting_enabled_in_config = true;
	}
	return (gfxcard.GetCapability(CARDINFOTYPE::CUBEMAPPING) && cubic_lighting_enabled_in_config && lighting_enable);
}

bool VAMOSWORLD::DynamicCubeLighting()
{
	bool cubic_lighting_enabled_in_config = false;
	if( lighting == 2 )
	{
		cubic_lighting_enabled_in_config = true;
	}
	return (gfxcard.GetCapability(CARDINFOTYPE::CUBEMAPPING) && cubic_lighting_enabled_in_config && lighting_enable);
}

bool VAMOSWORLD::StaticCubeReflectionMapping()
{
	bool cubic_reflections_enabled_in_config = false;
	if( reflections == 1 )
	{
		cubic_reflections_enabled_in_config = true;
	}
	return (gfxcard.GetCapability(CARDINFOTYPE::CUBEMAPPING) && cubic_reflections_enabled_in_config && lighting_enable);
}

bool VAMOSWORLD::DynamicCubeReflectionMapping()
{
	bool cubic_reflections_enabled_in_config = false;
	if( reflections == 2 )
	{
		cubic_reflections_enabled_in_config = true;
	}
	return (gfxcard.GetCapability(CARDINFOTYPE::CUBEMAPPING) && cubic_reflections_enabled_in_config && lighting_enable);
}
