/* 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 "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");
}

void VAMOSWORLD::DeInit()
{
	if (initdone)
	{
		delete world;
		initdone = false;
		track_p->ClearRoads();
		UnloadHUD();
	}
}

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

	lastcamchange = 0.0f;

	world = new Vamos_World::World (track);

	UnloadHUD();
	LoadHUD();

	track_p = 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().ScaleR(-1);
	initcam.y -= 6;
	cam.position = initcam;

	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);

		it->car->SetReflectionTexture(&sphere_reflection);

		utility.SelectTU(0);
			glPushMatrix();

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

		utility.SelectTU(0);
		glPopAttrib();
	}
}

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();
			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::Update(float timefactor, float fps, SDL_Joystick ** js)
{	
	l_timefactor = timefactor;
	l_fps = fps;

	if (l_timefactor <= 0 || fps < 5)
	{
		sound.MuteAll();
		return;
	}

	for (std::vector <Vamos_World::Car_Information>::iterator it = world->m_cars.begin ();
	it != world->m_cars.end ();
	it++)
	{
		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;
				}
			}
		}

		if (advance)
		{
			if (nextsector == 0)
			{
				timer.Lap((it->car->GetSector() >= 0));
			}

			it->car->SetSector(nextsector);
		}
	}

	bool camswitch = false;

	ProcessControls(js, timefactor, fps);

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

	if (GetCar(CONT_PLAYERLOCAL) != 0)
	{

		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]);
		}

		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);			
			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,-7.5);
				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->view_position();
				vp = vp - GetCar(CONT_PLAYERLOCAL)->car->chassis().center_of_mass();

				cam.dir = carorientation;
				cam.MoveRelative(0.0, -vp[2], vp[1]+1.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]);
				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,-7.75);
			}
		}
		else if (cammode == CMOrbit)
		{
			float mindist = 7;
			float maxdist = 20;

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

			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);
		}

	}

	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];
	}

	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
		);

	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
				);
		}
	}

	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);
			}
		}
	}

	world->m_contact_info.clear ();
}

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

double dabs(double val)
{
	if (val < 0)
		return -val;
	else
		return val;
}

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"))
					{
						DoOp(GetCar(CONT_PLAYERREMOTE), curfunc.func_name, curfunc.newval, 0.0, false, timefactor, fps); 
					}
					dofunction = "";
				}
			}
		}
		if (MP_DBGDEEP)
			cout << "multiplay tick done" << endl;

		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())
									dovalue = 1.0;
								else
									dovalue = 0.0;
							}
							else
							{
								dofunction = curctrl.GetName();
								if (curctrl.GetJoyPushDown())
									dovalue = 0.0;
								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
						float max = gamecontrols.GetCalibration(curctrl.GetJoyNum(),curctrl.GetJoyAxis(),true);
						float min = gamecontrols.GetCalibration(curctrl.GetJoyNum(),curctrl.GetJoyAxis(),false);
						float range = max - min;
						float gain = 2.0 / range;
						float offset = 1.0 - max * gain;
						val = val * gain + offset;

						//clip
						if (val < -1)
							val = -1;
						if (val > 1)
							val = 1;

						//apply deadzone
						double deadzone = 0.0;
						if (gamecontrols.GetDeadzone() == "low")
							deadzone = 0.05;
						else if (gamecontrols.GetDeadzone() == "med")
							deadzone = 0.1;
						else if (gamecontrols.GetDeadzone() == "high")
							deadzone = 0.2;
						if (dabs(val) < deadzone)
							val = 0;
						else
						{
							if (val < 0)
								val = (val + deadzone)*(1.0/(1.0-deadzone));
							else
								val = (val - deadzone)*(1.0/(1.0-deadzone));
						}

						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();
							dovalue = 1.0;
						}
						else if (keyman.lastkeys[curctrl.GetKeyCode()])
						{
							dofunction = curctrl.GetName();
							dovalue = 0.0;
						}
					}
					else
					{
						if (!keyman.keys[curctrl.GetKeyCode()])
						{
							dofunction = curctrl.GetName();
							dovalue = 1.0;
						}
						else if (!keyman.lastkeys[curctrl.GetKeyCode()])
						{
							dofunction = curctrl.GetName();
							dovalue = 0.0;
						}
					}
				}
			}
			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 ) )
								dovalue = 1.0;
							else
								dovalue = 0.0;
						}
						else
						{
							if( !mouse.IsPressed( mouse_button ) )
								dovalue = 1.0;
							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 (dofunction == "steer_left")
			{
				steer_set(dovalue, true);
				dofunction = "";
			}
			if (dofunction == "steer_right")
			{
				steer_set(-dovalue, false);
				dofunction = "";
			}

			if (((state.GetGameState() == STATE_PLAYING || dofunction == "gas") && (replay.Playing() == -1 || replay.GhostCar())) || dofunction.find("view_") == 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 (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);
		}
	}
}

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 == "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 == "pan_left")
		{
		}
		else if (dofunction == "pan_right")
		{
		}
		else if (dofunction == "pan_up")
		{
		}
		else if (dofunction == "pan_down")
		{
		}
		else if (dofunction == "view_extfollow") {mq1.AddMessage("Camera set to external follow mode");SetCameraMode(CMExtFollow);}
		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"))
		{
			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"))
		{
			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.25, 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.25, 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.14, tempchar, 0, 9, 1,0,0);

	float pfuel = GetCar(CONT_PLAYERLOCAL)->car->fuel_tank()->fuelpercent();
	float feedback = (GetCar(CONT_PLAYERLOCAL)->car->wheel(0)->GetFeedback()+
		GetCar(CONT_PLAYERLOCAL)->car->wheel(1)->GetFeedback())*0.5;

	feedback = feedback/100.0;
	if (feedback > 0.5)
		feedback = 0.5;
	if (feedback < -0.5)
		feedback = -0.5;
	feedback += 0.5;

	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);
	}

	//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);
	}
}

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")
	{
		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")
	{
		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;

		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 == 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();
}

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->propagate (dt);
			world->interact (it->car, it->segment_index);
		}

		count++;
	}
}


Vamos_World::Car_Information * VAMOSWORLD::GetCar(CONTROLLERTYPE p)
{
	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;
}

void VAMOSWORLD::DrawCars()
{
	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;

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

	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( "display.input_graph", input_graph_enabled );
	settings.Get( "display.car_shadows", car_shadows_enabled );
}

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);

	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();

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

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