#define GL_GLEXT_PROTOTYPES

#include <stdio.h>
#include <stdlib.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glext.h>
#include <SDL/SDL.h>
#include <math.h>
#include <string>

using namespace std;

#include "font.h"
#include "utility.h"
#include "textures.h"
#include "backdrop.h"
#include "camera.h"
#include "weather.h"
#include "keyman.h"
#include "mouse.h"
#include "messageq.h"
//#include "terrain.h"
#include "particles.h"
/*#include "mesh.h"			// Header File For The MESH Class
#include "entity.h"			// ...etc
#include "meshlist.h"
#include "entitylist.h"
#include "trees.h"
#include "water.h"
#include "model.h"
#include "gamedb.h"
#include "ships.h"*/
#include "settings.h"
#include "sound.h"
#include "menu.h"
#include "timer.h"
#include "controls.h"
#include "vamosworld.h"
#include "replay.h"
//#include "trees.h"
#include "logo.h"
#include "gamestate.h"
#include "objects.h"
#include "multiplay.h"
#include "net.h"
#include "configfile.h"
#include "bezier.h"
#include "track.h"

#include <vamos/body/Gl_Car.h>
#include <vamos/geometry/Texture_Image.h>
#include <vamos/geometry/Three_Vector.h>
//#include <vamos/track/Strip_Track.h>
#include <vamos/body/Car.h>
#include <vamos/body/Fuel_Tank.h>
#include <vamos/body/Wheel.h>
#include <vamos/geometry/Conversions.h>
//#include <vamos/track/Track.h>
#include <vamos/world/World.h>

#ifdef _WIN32
//#define GL_GLEXT_PROTOTYPES
#include <GL/glext.h>
//#include <GL/wglext.h>
//#include <GL/glprocs.h>
#endif

//#define PERFORMANCE_PROFILE

//fps less than below will not tick the simulation
//#define MIN_FPS 5.0

#ifdef PERFORMANCE_PROFILE
#include <sys/time.h>
suseconds_t GetMicroSeconds()
{
	struct timeval tv;
	struct timezone tz;
		
	tz.tz_minuteswest = 0;
	tz.tz_dsttime = 0;
	
	gettimeofday(&tv, &tz);
	
	return tv.tv_usec;
}
#endif


/* Set up some booleans */
#define TRUE  1
#define FALSE 0

/* screen width, height, and bit depth */
//const int SCREEN_WIDTH = 1024;
//const int SCREEN_HEIGHT = 768;
//const int SCREEN_BPP = 32;

int SCREEN_WIDTH = 1024;
int SCREEN_HEIGHT = 768;
int SCREEN_BPP = 32;

//#define PATCH_DEBUG

bool verbose_output = false;

//bool in_menu = false;
//bool menu_display = false;

/*const int SCREEN_WIDTH = 1280;
const int SCREEN_HEIGHT = 1024;
const int SCREEN_BPP = 32;*/

/* This is our SDL surface */
SDL_Surface *surface;

/* Ambient Light Values ( NEW ) */
//GLfloat LightAmbient[]  = { 1.0f, 1.0f, 1.0f, 1.0f };
GLfloat LightAmbient[]  = { 0.1f, 0.1f, 0.1f, 1.0f };
/* Diffuse Light Values ( NEW ) */
//GLfloat LightDiffuse[]  = { 1.0f, 1.0f, 1.0f, 1.0f };
GLfloat LightDiffuse[]  = { 1.0f, 1.0f, 1.0f, 1.0f };
GLfloat LightSpecular[]  = { 0.0f, 0.0f, 0.0f, 0.0f };
/* Light Position ( NEW ) */
GLfloat LightPosition[4];

/*
ENTITYLIST entities;
MESHLIST meshes;
TERRAIN terrain;
TREES foliage;
WATER water;
GAMEDB gamedb;
SHIPS ships;*/
TEXTURES textures;
KEYMAN keyman;
GAMECONTROLS gamecontrols;
SETTINGS settings;
CAMERA cam;
FONT font;
UTILITY utility;
BACKDROP backdrop;
WEATHER weathersystem;
MOUSE mouse;
MESSAGEQ mq1;
//TERRAIN terrain;
PARTICLE particle;
SOUNDMANAGER sound;
MENU menu;
TIMER timer;
REPLAY replay;
//TREES trees;
LOGO logo;
GAMESTATE state;
OBJECTS objects;
NET net;
MULTIPLAY multiplay;
TRACK track;

VAMOSWORLD world;
//Vamos_Track::Strip_Track *road;

ofstream error_log;

#define CAR_Y_OFFSET 1.0

double day_time;
double abs_time;

//for game timing and FPS stuff
static GLint T0;
static GLint Frames;
static GLfloat fps;

bool showfps = settings.GetFPSEnabled();

static int frameno;

float timefactor = 1.0f;

//#define AVERAGEFRAMES 30
#define AVERAGEFRAMES 5

float pfps;
float lfps[AVERAGEFRAMES];
int lfpspos = 0;
bool lfpsfull = false;

//#define FRAME_TIME 0.002
#define FRAME_TIME 0.004
double execution_time = 0.0;

bool pauserequest = false;
bool unpauserequest = false;

int screenshots = 0;

float FrameTime()
{
	return FRAME_TIME;
}

int Screenshot(char *filename)
{
	SDL_Surface *screen;
	SDL_Surface *temp = NULL;
	unsigned char *pixels;
	int i;
	
	screen = surface;
	
	if (!(screen->flags & SDL_OPENGL))
	{
		SDL_SaveBMP(temp, filename);
		return 0;
	}
	
	temp = SDL_CreateRGBSurface(SDL_SWSURFACE, screen->w, screen->h, 24,
	#if SDL_BYTEORDER == SDL_LIL_ENDIAN
	0x000000FF, 0x0000FF00, 0x00FF0000, 0
	#else
	0x00FF0000, 0x0000FF00, 0x000000FF, 0
	#endif
	);
	if (temp == NULL)
		return -1;
	
	pixels = (unsigned char *) malloc(3 * screen->w * screen->h);
	if (pixels == NULL)
	{
		SDL_FreeSurface(temp);
		return -1;
	}
	
	glReadPixels(0, 0, screen->w, screen->h, GL_RGB, GL_UNSIGNED_BYTE, pixels);
	
	for (i=0; i<screen->h; i++)
		memcpy(((char *) temp->pixels) + temp->pitch * i, pixels + 3*screen->w * (screen->h-i-1), screen->w*3);
	free(pixels);
	
	SDL_SaveBMP(temp, filename);
	SDL_FreeSurface(temp);
	return 0;
}

void ResetWorld(bool fullreset);

/* function to release/destroy our resources and restoring the old desktop */
void Quit( int returnCode )
{
	state.SetGameState(STATE_EXIT);
	
	if (replay.Recording() != -1)
		replay.Stop();
	
	gamecontrols.WriteControlFile();
	
	if (verbose_output)
		cout << "Quit called" << endl;
	
	error_log.close();
	
    /* clean up the window */
    SDL_Quit( );

	if (verbose_output)
		cout << "SDL_Quit finished" << endl;
	
    /* and exit appropriately */
    exit( returnCode );
}

/* function to reset our viewport after a window resize */
int resizeWindow( int width, int height )
{
    /* Height / width ration */
    GLfloat ratio;
 
    /* Protect against a divide by zero */
    if ( height == 0 )
	height = 1;

    ratio = ( GLfloat )width / ( GLfloat )height;

    /* Setup our viewport. */
    glViewport( 0, 0, ( GLint )width, ( GLint )height );

    /* change to the projection matrix and set our viewing volume. */
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity( );

    /* Set our perspective */
    gluPerspective( 45.0f, ratio, 0.1f, 10000.0f );

    /* Make sure we're chaning the model view and not the projection */
    glMatrixMode( GL_MODELVIEW );

    /* Reset The View */
    glLoadIdentity( );

    return( TRUE );
}

void ChangeDisplay(int w, int h, int bpp, bool fullscreen, bool reloadtextures)
{
	SCREEN_WIDTH = w;
	SCREEN_HEIGHT = h;
	SCREEN_BPP = bpp;
	
	/* the flags to pass to SDL_SetVideoMode */
    int videoFlags  = SDL_OPENGL;          /* Enable OpenGL in SDL */
    videoFlags |= SDL_GL_DOUBLEBUFFER; /* Enable double buffering */
    videoFlags |= SDL_HWPALETTE;       /* Store the palette in hardware */
    videoFlags |= SDL_RESIZABLE;       /* Enable window resizing */

    /* This checks to see if surfaces can be stored in memory */
    /*if ( videoInfo->hw_available )
		videoFlags |= SDL_HWSURFACE;
    else
		videoFlags |= SDL_SWSURFACE;

    if ( videoInfo->blit_hw )
	videoFlags |= SDL_HWACCEL;*/

	if (fullscreen)
	{
	  videoFlags |= SDL_HWSURFACE|SDL_ANYFORMAT|SDL_FULLSCREEN;  
	}	
	else
	{
	  videoFlags |= SDL_SWSURFACE|SDL_ANYFORMAT;
	}
	
    /* get a SDL surface */
	if (surface != NULL)
	{
		SDL_FreeSurface(surface);
		surface = NULL;
	}
    surface = SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP,
				videoFlags );
	
	resizeWindow( SCREEN_WIDTH, SCREEN_HEIGHT );
	//if (fullscreen)
	
/*
	ofstream vconf;
	vconf.open((settings.GetSettingsDir() + "/videoconfig").c_str());
	vconf << w << " ";
	vconf << h << " ";
	vconf << bpp << " ";
	vconf << fullscreen << " ";
	vconf.close();
*/
	settings.SetDisplayX(w);
	settings.SetDisplayY(h);
	settings.SetDisplayDepth(bpp);
	settings.SetFullscreenEnabled(fullscreen);

	if (reloadtextures)
	{
		textures.ReloadAll();
        font.Load();
        menu.Load();
        menu.MainMenu();
		particle.Load();
		timer.Load();
//		trees.Load();
	}
}

void Update()
{
	//cam.position.DebugPrint();
	
	if (MP_DBGDEEP)
		cout << "main update start" << endl;
	
	bool mainloop = false;
	
	//if unpaused and getting a reasonable framerate
	//if (fps > MIN_FPS && timefactor != 0.0f)
	if (fps > 0.0f && timefactor != 0.0f && state.GetGameState() != STATE_INITIALMENU)
	{
		/*VERTEX campos = cam.position;
		campos.Scale(-1.0);
		cout << campos.x << "," << campos.z << ": " << track.Elevation(campos) << endl;*/
		
		
		execution_time += timefactor / fps;
		int num_updates = (int) (execution_time / FRAME_TIME);
		double this_frame = (double) num_updates * FRAME_TIME;
		
		mouse.Update(cam, SCREEN_WIDTH, SCREEN_HEIGHT, timefactor, fps);
		weathersystem.Update(abs_time);
		
		while (execution_time > FRAME_TIME)
		{
			#ifdef PERFORMANCE_PROFILE
			suseconds_t t1, t2;
			t1 = GetMicroSeconds();
			t1 = GetMicroSeconds();
			#endif
			
			//multiplay.Update(FRAME_TIME);
			
			//weather tick
			
			#ifdef PERFORMANCE_PROFILE
			t2 = GetMicroSeconds();
			cout << "multiplay.Update() ticks: " << t2-t1 << endl;
			t1 = GetMicroSeconds();
			#endif
			
			//timer tick
			//timer.TickTimer(timefactor,fps);
			timer.TickTimer(1.0, 1.0/FRAME_TIME);
			
			cam.Update();
			
			//handle input
			//keyman.DoHeldKeys(timefactor, fps, cam);
			keyman.DoHeldKeys(1.0, 1.0/FRAME_TIME, cam);
			
			#ifdef PERFORMANCE_PROFILE
			t1 = GetMicroSeconds();
			t1 = GetMicroSeconds();
			#endif
			
			//world.Update(timefactor, fps, js);
			world.Update(1.0, 1.0/FRAME_TIME, gamecontrols.Get_js());
			
			#ifdef PERFORMANCE_PROFILE
			t2 = GetMicroSeconds();
			cout << "world update ticks: " << t2-t1 << endl;
			t1 = GetMicroSeconds();
			#endif
			
			keyman.DoOneTimeKeys(cam);
			
			//particle.Update(timefactor, fps);
			//particle.Update(1.0, 1.0/FRAME_TIME);
			
			cam.ExtractFrustum();
			
			LightPosition[0] = cos(backdrop.sunpos_lat);
			LightPosition[1] = sin(backdrop.sunpos_lat);
			if (LightPosition[1] < 0)
				LightPosition[1] *= 10.0f; //to simulate sun disappearing behind horizon
			LightPosition[2] = 0.0f;//tan(backdrop.sunpos_lng);
			
			float dusktime = 0.5;
			float dawntime = 0.0;
			float transtime = 0.05;
			
			float diffuse = 1.0f;
			if (day_time >= dusktime || day_time <= dawntime)
			{
				diffuse = 0.0;
			}
			
			if (day_time > dawntime && day_time < transtime)
			{
				diffuse = day_time / (transtime);
			}
			
			if (day_time > dusktime - transtime && day_time < dusktime)
			{
				diffuse = (day_time - dusktime - transtime) / transtime;
			}
			
			diffuse = diffuse*0.9+0.1;
			
			VERTEX ld;
			ld.Set(LightDiffuse[0], LightDiffuse[1], LightDiffuse[2]);
			ld.Scale(diffuse);
			glLightfv( GL_LIGHT1, GL_DIFFUSE, ld.v3() );
			
			//float timepassed = (timefactor/fps)/86400.0;
			float timepassed = (FRAME_TIME)/86400.0;
			/*if (keyman.keys[keyman.GetKey("AccelTimeVFast")])
				timefactor = 10000.0;
			else if (keyman.keys[keyman.GetKey("AccelTimeFast")])
				timefactor = 1000.0;
			else
				timefactor = 1.0;*/
			//float timepassed = (timefactor/fps)/60.0;
			//float timepassed = (timefactor/fps)/10.0;
			abs_time += timepassed;
			day_time += timepassed;
			if (day_time > 1.0f)
				day_time -= 1.0f;
			
			#ifdef PERFORMANCE_PROFILE
			t2 = GetMicroSeconds();
			cout << "Time increment ticks: " << t2-t1 << endl;
			t1 = GetMicroSeconds();
			#endif
			
			multiplay.Send(FRAME_TIME);
			
			mainloop = true;
			
			//terrain.Update(cam, timefactor, fps, day_time);
			//terrain.Update(cam, 1.0, 1.0/FRAME_TIME, day_time);
			
			execution_time -= FRAME_TIME;
			num_updates++;
			
			//replay.IncrementFrame();
		}
		particle.Update(1.0, 1.0/this_frame);
//		terrain.Update(cam, 1.0, 1.0/this_frame, day_time);
	}
	else
	{
		//print "paused"
		if (state.GetGameState() != STATE_INITIALMENU)
			world.Update(timefactor, fps, gamecontrols.Get_js());
	}
	
	if (pauserequest)
	{
		if (timefactor != 0.0f)
			timefactor = 0.0f;
		pauserequest = false;
	}
	if (unpauserequest)
	{
		if (timefactor == 0.0f)
			timefactor = 1.0f;
		unpauserequest = false;
	}
	
	sound.Update();
	/*if (!mainloop)
		multiplay.Update(0);*/ //don't need this anymore because we're stopping things from being paused in multiplayer mode
	
	if (MP_DBGDEEP)
		cout << "main update done" << endl;
}

void LoadingScreen(string loadtext);

void snap_screenshot()
{
	char scrname[64];
	sprintf(scrname, "%s/screenshots/shot%03d.bmp", (settings.GetSettingsDir()).c_str(), settings.GetNumShots());
	Screenshot(scrname);
	settings.SetNumShots(settings.GetNumShots() + 1);
}

void MainPause()
{
	if (!menu.InMenu() && !multiplay.NumConnected() > 0)
	//if (state.GetGameState() == STATE_PLAYING)
		pauserequest = true;
}

void MainUnpause()
{
	unpauserequest = true;
}

/* function to handle key press events */
void handleKeyPress( SDL_keysym *keysym )
{
	Vamos_Geometry::Three_Vector tvec1(44.464,-126.8737,0);
	VERTEX tvec2;
	QUATERNION trot;
	
	if (menu.InMenu())
	//if (state.GetGameState() == STATE_MENU)
		menu.MenuKey(keysym->sym);
	//else if (timefactor == 0 && !menu.InMenu()) timefactor = 0;
		//		else timefactor = 1.0;
	else
	{
		switch ( keysym->sym )
		{
		case SDLK_ESCAPE:
			/* ESC key was pressed */
			//Quit( 0 );
			menu.MainMenu();
			break;
		/*case SDLK_F7:
			
			tvec1 = tvec1.rotate(0,0,6.21652);
			cout << tvec1[0] << "," << tvec1[1] << endl;
		
			
			tvec2.Set(44.464,-126.8737,0);
			
			trot.Rotate(6.21652, 0, 0, 1);
			tvec2 = trot.RotateVec(tvec2);
			tvec2.DebugPrint();
			break;*/
		/*case SDLK_F7:
			if (timefactor != 100.0f)
				timefactor = 100.0f;
			else
				timefactor = 1.0f;
			break;*/
		/*case SDLK_PAGEUP:
			//tmpvert = cam.GetVelocity();
			cam.MoveRelative(0.0f, 0.0f, -2.0f);
			//cam.Update();
			//cam.LoadVelocityIdentity();
			break;
		case SDLK_PAGEDOWN:
			//tmpvert = cam.GetVelocity();
			cam.MoveRelative(0.0f, 0.0f, 2.0f);
			//cam.Update();
			//cam.LoadVelocityIdentity();
			break;*/
		
		//case SDLK_F11:
			//SDL_WM_ToggleFullScreen( surface );
			//ChangeDisplay(1280,1024,32,true);
			//menu.DisplayMenu();//DisplaySelect();
			//break;
		
		//case SDLK_F12:
			//menu.MainMenu();
			//break;
		
		default:
			if (timefactor == 0.0f)
				MainUnpause();
			else
				keyman.OneTime(keysym->sym);
			break;
		}
	}
	
    return;
}



void glSetup()
{


	/* Enable Texture Mapping ( NEW ) */
    glEnable( GL_TEXTURE_2D );
    /* Enable smooth shading */
    glShadeModel( GL_SMOOTH );
    /* Set the background black */
    //glClearColor( 0.53f, 0.74f, 0.91f, 0.0f );
	glClearColor(0,0,0,0);
    /* Depth buffer setup */
    glClearDepth( 1.0f );
    /* Enables Depth Testing */
    glEnable( GL_DEPTH_TEST );
    /* The Type Of Depth Test To Do */
    glDepthFunc( GL_LEQUAL );
    /* Really Nice Perspective Calculations */
    glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
	/* Enable Lighting */
	glEnable( GL_LIGHTING );
	
	//set the clipping planes
	//glFrustum(1000,1000,1000,1000,1,1000);
	
	//utility.Tex2D(3, false);
	//utility.Tex2D(2, false);
	//utility.Tex2D(1, false);
	//utility.Tex2D(0, true);
}

//string otrack_file;
//string car_file;
string data_dir;
//int car_paint;

//int number_of_opponents;
//int focused_car;

void SelectCar(string cfile, bool trim)
{	
	//if (clearold)
		world.clear_cars();
	
	//car_file = cfile;
	state.SetCarName(0, cfile);
	
	Vamos_Body::Gl_Car* car = 0;
	try
	{
	  car = new Vamos_Body::Gl_Car (Vamos_Geometry::Three_Vector (11.0, 0.0, 0.6));
	  car->read (data_dir, cfile);
	  car->SetPaint(state.GetCarPaint(0));
	  car->chassis ().translate (Vamos_Geometry::Three_Vector(track.GetStart().x, -track.GetStart().z, track.GetStart().y));
	  car->chassis ().translate (Vamos_Geometry::Three_Vector (10.0*multiplay.MyNum(), 0.0, 
											   -car->chassis ().lowest_contact_position () + CAR_Y_OFFSET));
	  car->start_engine ();
		car->set_controller(1);
	  world.add_car (car);

	
	  /*for (int i = 0; i < number_of_opponents; i++)
		{
		  car = new Vamos_Body::Gl_Car (Vamos_Geometry::Three_Vector (11.0, 0.0, 0.6));
		  car->read (data_dir, cfile);
		  car->chassis ().translate (Vamos_Geometry::Three_Vector (10.0 * (i + 1), 0.0, 
												   -car->chassis ().lowest_contact_position () + CAR_Y_OFFSET));
		  car->start_engine ();
			car->set_controller(-1);
		  world.add_car (car);
		}*/
		
		int i;
		//if (!multiplay.Server()) //the client re-loads the world, the server doesn't (NOT TRUE ANYMORE)
		{
			for (i = 0; i < multiplay.NumConnected()+1; i++)
			{
				if (i != multiplay.MyNum())
				{
					int idx = i;
					if (idx == 0)
						idx = 1;
					car = new Vamos_Body::Gl_Car (Vamos_Geometry::Three_Vector (11.0, 0.0, 0.6));
					car->read (data_dir, state.GetCarName(idx));
					car->SetPaint(state.GetCarPaint(idx));
					car->chassis ().translate (Vamos_Geometry::Three_Vector(track.GetStart().x, -track.GetStart().z, track.GetStart().y));
					car->chassis ().translate (Vamos_Geometry::Three_Vector (10.0*i, 0.0, 
						-car->chassis ().lowest_contact_position () + CAR_Y_OFFSET));
					car->start_engine ();
					car->set_controller(2);
					world.add_car (car);
				}
			}
		}
		
		if (replay.GhostCar())
		{
			car = new Vamos_Body::Gl_Car (Vamos_Geometry::Three_Vector (11.0, 0.0, 0.6));
			car->read (data_dir, replay.ReplayCar());
			car->SetPaint(replay.ReplayPaint());
			car->chassis ().translate (Vamos_Geometry::Three_Vector (10.0*multiplay.MyNum(), 0.0, 
												   -car->chassis ().lowest_contact_position () + CAR_Y_OFFSET));
			car->start_engine ();
			car->set_controller(3);
			world.add_car (car);
		}
	
		world.set_focused_car (0);
		world.reset();
	}
	catch (Vamos_Geometry::XML_Exception& error)
	{
	  std::cerr << error.message () << std::endl;
	  std::exit (EXIT_FAILURE);
	}
	
	if (trim)
		ResetWorld(false);
}

bool UnloadWorld()
{
	//these should have error catching such that if they're called before
	// init, it's OK

	backdrop.DeInit();
//	terrain.DeInit();
	world.DeInit();
	
	return true;
}

#ifdef PATCH_DEBUG
BEZIER * thepatch;
#endif

bool LoadWorld()
{
	UnloadWorld();
	
	//begin loading world
	
	LoadingScreen("Loading...\nLoading backdrop");
	backdrop.Init();
	
	LoadingScreen("Loading...\nConfiguration files");
	
	//car_file = "s2000";
	//ifstream csfile;
	//car_file = "";
	state.SetCarName(0, "");
	//csfile.open((settings.GetSettingsDir() + "/selected_car").c_str());
	//car_paint = 0;
	state.SetCarPaint(0, 0);
/*	if (csfile)
	{
		state.SetCarName(0, utility.sGetLine(csfile));
		state.SetCarPaint(0, utility.iGetParam(csfile));
		csfile.close();
	}
*/
	state.SetCarName(0, settings.GetCarName());
	state.SetCarPaint(0, settings.GetCarPaint());
	if (state.GetCarName(0) == "")
		state.SetCarName(0, "mustang_gt");
	//string otrack_file = "default-track";
	//if (otrack_file == "")
		//otrack_file = "Nurburgring";
	
	//string world_file = "default-world";
	//string controls_file = "default-controls";
	//number_of_opponents = 0;
	//focused_car = 0;
	//string track_file = "tracks/" + state.GetTrackName() + "/" + state.GetTrackName() + ".xml";
	//car_file = "cars/" + car_file + ".xml";
	//world_file = "worlds/" + world_file + ".xml";
	//controls_file = settings.GetSettingsDir() + "/controls/" + controls_file + ".xml";
	data_dir = settings.GetDataDir() + "/";

	/*LoadingScreen("Loading...\nLoading terrain");
	//terrain.Init(2000,200,2000,"outermap3.raw","outermap1-detail.bmp","outermap2-test.png");
	//terrain.Init(2000,200,2000,"shaw4.raw","outermap1-detail.bmp","shawtestmap2.png", REFLECT_RES);
	//terrain.Init(2000,200,2000,"shawlike.raw","detail-lowfreq.bmp","shawlike.png", REFLECT_RES_X, REFLECT_RES_Y);
	//terrain.Init(2000,200,2000,"outermap4.raw","outermap1-detail.bmp","outermap4v1.png");
	string terrainfile;
	string trackpath = settings.GetDataDir() + "/tracks/" + state.GetTrackName() + "/";
	terrainfile = trackpath + "parameters.txt";
	
	ifstream tf(terrainfile.c_str());
	int numparam, i;
	numparam = 7;
	float param[numparam];
	if (tf)
	{
		for (i = 0; i < numparam; i++)
			param[i] = utility.fGetParam(tf);
		terrain.Init((int)param[0],(int)param[1],(int)param[2], trackpath+"terrain.png", trackpath+"mask.png", trackpath+"texture.png", trackpath+"detail.png", trackpath+"detail2.png", trackpath+"parametric.png", param[3], param[4], param[5], param[6]);
	}
	else
	{
		cout << "Couldn't find track terrain parameter file " << terrainfile << endl;
		//terrain.Init(3000,300,3000, otrack_file+".png", otrack_file+"-mask.png", otrack_file+".png", otrack_file+"-detail.png", otrack_file+"-detail2.png", otrack_file+"-parametric.png", -1500, -2000, 150, 100);
		param[0] = 3000; param[1] = 300; param[2] = 3000;
		param[3] = -1500;
		param[4] = -2000;
		param[5] = 150;
		param[6] = 100;
		terrain.Init((int)param[0],(int)param[1],(int)param[2], trackpath+"terrain.png", trackpath+"mask.png", trackpath+"texture.png", trackpath+"detail.png", trackpath+"detail2.png", trackpath+"parametric.png", param[3], param[4], param[5], param[6]);
	}

	LoadingScreen("Loading...\nTrack");
	try
    {
		road = new Vamos_Track::Strip_Track;
      	road->read (data_dir, track_file);
    }
	catch (Vamos_Geometry::XML_Exception& error)
	{
	  std::cerr << error.message () << std::endl;
	  std::exit (EXIT_FAILURE);
	}*/
	
	LoadingScreen("Loading...\nTrack");
	track.Load(state.GetTrackName());
	
	LoadingScreen("Loading...\nLoading scenery objects");
	objects.LoadObjectsFromFolder(settings.GetDataDir() + "/tracks/" + state.GetTrackName() + "/objects");
	
	LoadingScreen("Loading...\nWorld");
	world.Init(&track);
	
	LoadingScreen("Loading...\nCar");
	SelectCar(state.GetCarName(0), true);
	//world.GetPlayerCar()->SetPaint(car_paint); <-done in selectcar instead.
	
	//LoadingScreen("Loading...\nLoading Trees");
	//trees.ReadFromFile(settings.GetFullDataPath("tracks/" + state.GetTrackName() + "/treemap.png"), settings.GetFullDataPath("tracks/" + state.GetTrackName() + "/foliagemap.png"));
	/*trees.DeleteAll();
	int numtrees = 200;
	for (i = 0; i < numtrees/2; i++)
	{
		VERTEX tp;
		tp.x = ((float) rand()/RAND_MAX)*(param[0]/2.0)+param[0]/4.0;
		tp.z = ((float) rand()/RAND_MAX)*(param[2]/2.0)+param[2]/4.0;
		tp.x += param[3];
		tp.z += param[4];
		tp.y = terrain.GetHeight(tp.x, tp.z);
		trees.Add(tp, 40.0, 0, 5);
	}
	
	for (i = 0; i < numtrees/2; i++)
	{
		VERTEX tp;
		tp.x = ((float) rand()/RAND_MAX)*(param[0]/2.0)+param[0]/4.0;
		tp.z = ((float) rand()/RAND_MAX)*(param[2]/2.0)+param[2]/4.0;
		tp.x += param[3];
		tp.z += param[4];
		tp.y = terrain.GetHeight(tp.x, tp.z);
		trees.Add(tp, 60.0, 1, 5);
	}*/
	
	timer.Reset();
	particle.Clear();
	
	LoadingScreen("Loading...\nDone");
	
	mq1.AddMessage("Simulation start");
	
	#ifdef PATCH_DEBUG
	{
		VERTEX v[4];
		/*v[0].Set(-2045.29,604.818,-566.924);
		v[1].Set(-2040.08,605.083,-572.898);
		v[2].Set(-2026.6,602.961,-550.628);
		v[3].Set(-2020.85,602.961,-557.449);*/
		v[0].Set(-2148.81,583.537,-1395.68);
		v[1].Set(-2151.04,583.525,-1396.96);
		v[2].Set(-2145.45,583.378,-1401.53);
		v[3].Set(-2147.67,583.341,-1402.81);

		thepatch = track.GetPatch(v);
		if (thepatch != NULL)
		{
			cout << "Got patch" << endl;
		
			VERTEX p1,p2;
			/*p1.Set(-2032.96,603.927,-562.198);
			p2.Set(-2033,603.931,-562.227);*/
			p1 = (v[0] + v[1]).ScaleR(0.5);
			p2 = (v[2] + v[3]).ScaleR(0.5);
			VERTEX dir;
			dir = p2-p1;
			dir = dir.normalize();
			dir.Scale(7.0);
			p1 = p1 - dir.ScaleR(0.5);
			int steps = 100;
			/*cout << "c(";
			for (int i = 0; i < steps; i++)
			{
				VERTEX p;
				p = p1 + dir.ScaleR((float)i/(float)steps);
				cout << track.Elevation(p) << ",";
				//cout << track.Elevation(p1) << endl;
				//cout << track.Elevation(p2) << endl;
			}
			cout << ")" << endl;*/
			
			cout << "c(";
			for (int i = 0; i < steps; i++)
			{
				VERTEX p;
				p = p1 + dir.ScaleR((float)i/(float)steps);
				VERTEX dir;
				dir.y = -1000;
				p.y += 1000;
				VERTEX tvert, tnorm;
				bool col = thepatch->CollideSubDivQuadSimpleNorm(p, dir, tvert, tnorm);
				if (col)
					cout << tvert.y << ",";
				else
					cout << 600 << ",";
				//cout << track.Elevation(p) << ",";
				//cout << track.Elevation(p1) << endl;
				//cout << track.Elevation(p2) << endl;
			}
			cout << ")" << endl;
		}
		else
			cout << "couldn't find patch" << endl;
	}
	#endif
	
	//triangle-triangle collision testing
	/*if (0)
	{
		VERTEX tri1[3];
		VERTEX tri2[3];
		tri1[0].Set(0,0,0);
		tri1[1].Set(1,0,0);
		tri1[2].Set(1,0,1);
		
		tri2[0].Set(0,1,0);
		tri2[1].Set(1,1,0);
		tri2[2].Set(1,-1,1);
		
		VERTEX colpt, colseg;
		int whichtri;
		
		bool col = utility.BruteForceTriangleIntersectionF(tri1, tri2, colpt, 
				colseg, whichtri);
		if (col)
		{
			colpt.DebugPrint();
			colseg.DebugPrint();
			cout << whichtri << endl;
		}
		else
			cout << "No col" << endl;
	}
	
	if (0)
	{
		VERTEX tri1[3];
		VERTEX tri2[3];
		tri1[0].Set(132.422,-1.18925,1.84825);
		tri1[1].Set(131.82,-0.16846,-0.154412);
		tri1[2].Set(131.803,-0.173352,1.84551);
		
		tri2[0].Set(220.398,220.398,-3.08942e-06);
		tri2[1].Set(-220.398,220.398,1.19984e-05);
		tri2[2].Set(220.398,-220.398,-2.23572e-05);
		
		VERTEX colpt, colseg;
		int whichtri;
		
		bool col = utility.BruteForceTriangleIntersectionF(tri1, tri2, colpt, 
				colseg, whichtri);
		if (col)
		{
			colpt.DebugPrint();
			colseg.DebugPrint();
			cout << whichtri << endl;
		}
		else
			cout << "No col" << endl;
	}*/
	
	return true;
}

bool InitGameData()
{
	//LoadingScreen("Loading...\nInitializing utilities");
	utility.Init();
	
	state.SetGameState(STATE_LOGO);
	logo.run();
	
	//state.SetGameState(STATE_PLAYING);
	state.SetGameState(STATE_INITIALMENU);
	LoadingScreen("Loading...\nSetting time");
	LightPosition[0] = 2.0f;
	LightPosition[1] = 2.0f;
	LightPosition[2] = 2.0f;
	LightPosition[3] = 0.0f;
	
	day_time = abs_time = 0.1;
	//day_time = abs_time = 0.03;
	//day_time = abs_time = 0.48;
	//day_time = abs_time = 0.75;
	
	LoadingScreen("Loading...\nGui files");
	menu.Load();
	
	LoadingScreen("Loading...\nInitializing weather");
	weathersystem.Init();
	weathersystem.Update(abs_time);
	
	LoadingScreen("Loading...\nSetting camera position");
	
	// Set the initial camera position
	//cam.Move(-200,-200,-200);
	//cam.Move(-596.513,-30.9295,-250.623);
	
	cam.Move(0,-5,0);
	cam.Update();
	cam.LoadVelocityIdentity();
	//cam.Rotate(180.0,0,1,0);
	/*
	LoadingScreen("Loading...\nLoading Meshes");
	//load scene objects
	string listval;
	listval = settings.GetSettingsDir() + "/meshlist.txt";
	int ret;
	ret = meshes.LoadList(listval);
	if (!ret)
	{
		error_log << "Fatal error:  could not load mesh list\n";
		return FALSE;
	}
	
	//LoadingScreen("Loading...\nLoading game database");
	//gamedb.LoadAll();
	
	LoadingScreen("Loading...\nLoading new game state");
	*/
	
	LoadingScreen("Loading...\nStarting message queue");
	mq1.SetPersist(5.0f);
	mq1.SetDepth(1);
	mq1.SetPos(0.01,0.09,5,1);
	mq1.SetBuildUp(true);
	//mq1.SetTimePrint(true);
	

	
	/*
	//LoadingScreen("Loading...\nLoading sound");
	//sound.Load(&cam);
	
	//LoadingScreen("Loading...\nLoading HUD");
	//playerhud.Load();
	
	LoadingScreen("Loading...\nLoading water");
	water.initWater(64,64,1,3.0f);
	//water.initWater(32,32,16,100.0f);
	//water.initWater(64,64,8,10.0f);
*/

	LoadingScreen("Loading...\nLoading sound");
	sound.Load();

	LoadingScreen("Loading...\nGame control config");
	keyman.Load();
	gamecontrols.LoadControls();
	
	LoadingScreen("Loading...\nLoading Trees");
//	trees.Load();
	
	LoadingScreen("Loading...\nInitializing particle engine");
	particle.Load();
	VERTEX wind;
	wind.x = 0.3;
	wind.z = 0.1;
	particle.SetWind(wind);
	
	LoadingScreen("Loading...\nLoading Timer");
	timer.Load();
	
	LoadingScreen("Loading...\nInitializing Network");
	net.Init();
	
	state.SetTrackName(settings.GetTrack());
	if( state.GetTrackName() == "" )
		state.SetTrackName("Nurburgring");
	
	ifstream csfile;
	//car_file = "";
	state.SetCarName(0, "");
	//csfile.open("carsettings/selected_car");
	//car_paint = 0;
	state.SetCarPaint(0, 0);
/*	if (csfile)
	{
		state.SetCarName(0, utility.sGetLine(csfile));
		state.SetCarPaint(0, utility.iGetParam(csfile));
		csfile.close();
	}
*/
	state.SetCarName(0, settings.GetCarName());
	state.SetCarPaint(0, settings.GetCarPaint());
	if (state.GetCarName(0) == "")
		state.SetCarName(0, "mustang_gt");
	
	return true;
}

/* general OpenGL initialization function */
int initGL( GLvoid )
{
	//warning:  this must be done BEFORE anything important
	//or else it will override it
	font.Load();
	
    /* Enable Texture Mapping ( NEW ) */
    glEnable( GL_TEXTURE_2D );

    /* Enable smooth shading */
    glShadeModel( GL_SMOOTH );

    /* Set the background black */
    //glClearColor( 0.46f, 0.54f, 0.64f, 0.0f );
	glClearColor(0,0,0,0);

    /* Depth buffer setup */
    glClearDepth( 1.0f );

    /* Enables Depth Testing */
    glEnable( GL_DEPTH_TEST );

    /* The Type Of Depth Test To Do */
    glDepthFunc( GL_LEQUAL );

    /* Really Nice Perspective Calculations */
    glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );

	/* Position The Light */
    glLightfv( GL_LIGHT1, GL_POSITION, LightPosition );
	
    /* Setup The Diffuse Light */
    glLightfv( GL_LIGHT1, GL_DIFFUSE, LightDiffuse );
	glLightfv( GL_LIGHT1, GL_SPECULAR, LightSpecular );
	
	/* Setup The Ambient Light */
    glLightfv( GL_LIGHT1, GL_AMBIENT, LightAmbient );

    /* Enable Light One */
    glEnable( GL_LIGHT1 );
	
	/* Enable Lighting */
	glEnable( GL_LIGHTING );
	
	//our perspective matrix
	glMatrixMode( GL_PROJECTION );
    glLoadIdentity( );
	gluPerspective( 45.0f, (float)SCREEN_WIDTH/SCREEN_HEIGHT, 0.1f, 10000.0f );
	glMatrixMode( GL_MODELVIEW );
	
	
	
	// Enable front face culling, since that's what Quake3 does
    //glCullFace(GL_FRONT);
    //glEnable(GL_CULL_FACE);

	if (!InitGameData())
		return (FALSE);
	
	menu.MainMenu();

    return( TRUE );
}

void LoadingScreen(string loadtext)
{
	if (verbose_output)
    	cout << loadtext << endl;

	glPushAttrib(GL_ALL_ATTRIB_BITS);
	glPushMatrix();
	
	utility.Tex2D(3, false);
	utility.Tex2D(2, false);
	utility.Tex2D(1, false);
	utility.Tex2D(0, true);
	
	glSetup();
	
	glEnable(GL_TEXTURE_2D);
	
	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);		// Clear Screen And Depth Buffer

    Frames++;

	font.Print(0,0,loadtext.c_str(),1,6,1,1,1);
	
	/* Draw it to the screen */
    SDL_GL_SwapBuffers( );

	GLint t = SDL_GetTicks();
	//if (t - T0 >= 50) 
	{
		GLfloat seconds = (t - T0) / 1000.0;
		fps = 1 / seconds;
		//printf("%d frames in %g seconds = %g FPS\n", Frames, seconds, fps);
		T0 = t;
		//Frames = 0;
	}
	
	glPopMatrix();
	glPopAttrib();
}

/*int RenderReflectedScene(bool underwater, float waterheight)
{
	backdrop.SetFogStrength(weathersystem.GetFogDensity());
	backdrop.RefreshFog();
	
	int sizex, sizey;
	sizex = REFLECT_RES_X;
	sizey = REFLECT_RES_Y;
	glViewport(0, 0, sizex, sizey);
	
	glMatrixMode( GL_PROJECTION );
    glLoadIdentity( );
    gluPerspective( 70.0f, (float)SCREEN_WIDTH/SCREEN_HEIGHT, 0.1f, 10000.0f );
	glMatrixMode( GL_MODELVIEW );
	
	
	
	//backdrop.SetFogStrength(0.05f);
	
	backdrop.RefreshFog();
	
	#ifdef _WIN32
    //fix multitexturing state
	pglActiveTexture(GL_TEXTURE0_ARB);
	#else
    //fix multitexturing state
	glActiveTexture(GL_TEXTURE0_ARB);
    #endif
	
	glSetup();
	
	//glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
	float clearcolor[4];
	backdrop.GetHorizonColor(clearcolor);
	clearcolor[3] = 0.0f;
	glClearColor( clearcolor[0], clearcolor[1], clearcolor[2], clearcolor[3] );
	
	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);		// Clear Screen And Depth Buffer
	
	GLdouble temp_matrix[16];
	
	
	
	//QUATERNION oldcamdir = cam.dir;
	//cam.Rotate(3.141593f, 0, 0, 1);
	
	
	
	cam.PutTransformInto(temp_matrix);
	glLoadMatrixd(temp_matrix);
	
	//cam.dir = oldcamdir;
	

	
	
	glPushMatrix();
	//glTranslatef(0, -5, 0);
	glScalef(1.0f,-1.0f,1.0f);
	backdrop.SetCloudTexture(terrain.GetCloudTexture());
	backdrop.DrawSky(true, day_time, 1.0f, underwater);
	glClear (GL_DEPTH_BUFFER_BIT);
	glPopMatrix();
	
	glTranslatef(cam.GetPosition().x,cam.GetPosition().y,cam.GetPosition().z);
	
	glTranslatef(0, waterheight*2, 0);
	
	glScalef(1.0f,-1.0f,1.0f);
	
	
	//reset sun position so it's in the correct frame
	float lp[4];
	lp[0] = LightPosition[0];
	lp[1] = LightPosition[1];
	lp[2] = LightPosition[2];
	lp[3] = 0;
	glLightfv( GL_LIGHT1, GL_POSITION, lp );

	//adjust light color to be dimmer for reflection
	float lc[4];
	float light_atten = 0.0f;
	if (underwater)
		light_atten = 1.0f;
	else
		light_atten = 0.1f;
	lc[0] = LightDiffuse[0]*light_atten;
	lc[1] = LightDiffuse[1]*light_atten;
	lc[2] = LightDiffuse[2]*light_atten;
	lc[3] = LightDiffuse[3]*light_atten;
	glLightfv( GL_LIGHT1, GL_DIFFUSE, lc );
	if (underwater)
		light_atten = 0.0f;
	else
		light_atten = 0.0f;
	lc[0] = LightDiffuse[0]*light_atten;
	lc[1] = LightDiffuse[1]*light_atten;
	lc[2] = LightDiffuse[2]*light_atten;
	lc[3] = LightDiffuse[3]*light_atten;
	glLightfv( GL_LIGHT1, GL_AMBIENT, lc );

	//put a clipping plane at the water level.
	//using a simple plane as below may not be good enough; the world is 
	// curved, so the plane should have the same normal as the spherical normal
	GLdouble equation[4];
	VERTEX temp;
	VERTEX earthnormal = temp - cam.position;
	earthnormal.y += EARTH_RADIUS;
	earthnormal = earthnormal.normalize();
	equation[0] = earthnormal.x;
	equation[1] = earthnormal.y;
	equation[2] = -earthnormal.z;
	equation[3] = -(waterheight);
	glClipPlane(GL_CLIP_PLANE1, equation);
	glEnable(GL_CLIP_PLANE1);
	
	terrain.SetFrustum(cam);
	
	terrain.Draw(cam, 10.0f, true, true, false, true, timefactor, fps, day_time);
	
	//terrain doubling to try to eliminate reflection artifacts...
	// introduces artifacts of its own
	//maybe only do this if underwater?
	glPushMatrix();
	glTranslatef(0, waterheight*2, 0);
	glScalef(1.0f, -1.0f, 1.0f);
	glClipPlane(GL_CLIP_PLANE1, equation);
	glLightfv( GL_LIGHT1, GL_POSITION, lp );
	terrain.Draw(cam, 10.0f, true, true, false, false, timefactor, fps, day_time);
	glPopMatrix();
	
	//restore light color
	glLightfv( GL_LIGHT1, GL_DIFFUSE, LightDiffuse );
	glLightfv( GL_LIGHT1, GL_SPECULAR, LightSpecular );
	glLightfv( GL_LIGHT1, GL_AMBIENT, LightAmbient );

	//draw trees
	foliage.Draw(terrain, cam);
	
	equation[0] = earthnormal.x;
	equation[1] = earthnormal.y;
	equation[2] = -earthnormal.z;
	equation[3] = -(waterheight);
	glClipPlane(GL_CLIP_PLANE1, equation);
	
	
	//glDisable(GL_CLIP_PLANE1);
	//glScalef(1,-1,1);
	
	if (underwater)
	{
		glPushMatrix();
		glTranslatef(0, waterheight*2, 0);
		glScalef(1.0f, -1.0f, 1.0f);
		glClipPlane(GL_CLIP_PLANE1, equation);
		glLightfv( GL_LIGHT1, GL_POSITION, lp );
		ships.Draw(true);
		glPopMatrix();
	}
	else
	{
		glClipPlane(GL_CLIP_PLANE1, equation);
		ships.Draw(true);
	}
	
	
	
	
	
	glDisable(GL_CLIP_PLANE1);
	
	
	//redraw sky so that land reflections appear washed out
	if (!underwater)
	{
		float skytrans = 0.8f;
		cam.PutTransformInto(temp_matrix);
		glLoadMatrixd(temp_matrix);
		glScalef(1.0f,-1.0f,1.0f);
		backdrop.DrawSky(true, day_time, skytrans, underwater);
	}
	
	
	//add a brightness/contrast adjustment
	glClear (GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();
	glPushAttrib(GL_ALL_ATTRIB_BITS);
	glDisable(GL_TEXTURE_2D);
	glEnable(GL_BLEND);
	glDisable(GL_FOG);
	glDisable(GL_LIGHTING);
	glTranslatef(0,0,-40.0f);
	glBlendFunc(GL_ONE, GL_SRC_ALPHA);
	
	float scale = 0.8f;
	float bias = 0.0f;
	
  	glColor4f(bias, bias, bias, scale);
	float x1, y1, x2, y2;
	x1 = -80;
	y1 = -80;
	x2 = 80;
	y2 = 80;
	
	if (scale > 1.0) 
	{
		float remainingScale;
		
		remainingScale = scale;
		glBlendFunc(GL_DST_COLOR, GL_ONE);
		if (remainingScale > 2.0) 
		{
			glColor4f(1, 1, 1, 1);
			while (remainingScale > 2.0) 
			{
				glRectf(x1,y1,x2,y2);
				remainingScale /= 2.0;
			}
		}
		glColor4f(remainingScale - 1,
			remainingScale - 1, remainingScale - 1, 1);
		glRectf(x1,y1,x2,y2);
		glBlendFunc(GL_ONE, GL_ONE);
		if (bias != 0)
		{
			if (bias > 0) 
			{
				glColor4f(bias, bias, bias, 0.0);
			}
			else 
			{
				glColor4f(-bias, -bias, -bias, 0.0);
				//can't do bias < 0
			}
			glRectf(x1,y1,x2,y2);
		}
	}
	else 
	{
		if (bias > 0) 
		{
			glColor4f(bias, bias, bias, scale);
		}
		else 
		{
			glColor4f(-bias, -bias, -bias, scale);
			//can't do bias < 0
		}
		glBlendFunc(GL_ONE, GL_SRC_ALPHA);
		glRectf(x1,y1,x2,y2);
	}
	
	glPopAttrib();

	
	

	// Before we copy the screen to a texture, we need to specify the current
	// texture to draw to by calling glBindTexture() with the appropriate texture 
	glBindTexture(GL_TEXTURE_2D, terrain.reflection_texture);                

	// Now comes the moment we have all been waiting for, we render the screen
	// to the texture.  We pass in the texture type, detail level (0), pixel format,
	// the x and y position to start from, the width and height to grab, and a border.
	// If you only want a part of the screen, this works great for that.
	glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, sizex, sizey, 0);

	// Here we clear the screen and depth bits of the small viewport
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);            

	// Set our viewport back to it's normal size
	glViewport(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);    

	
    
    //glPopMatrix();
	
	return true;
}
*/

/* Here goes our drawing code */
int drawGLScene( GLvoid )
{
	#ifdef PERFORMANCE_PROFILE
	suseconds_t t1, t2;
	t1 = GetMicroSeconds();
	t1 = GetMicroSeconds();
	#endif
	
	//glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	if (state.GetGameState() != STATE_INITIALMENU)
	{			
		backdrop.SetFogStrength(weathersystem.GetFogDensity());
		backdrop.RefreshFog();
	}
	
	#ifdef PERFORMANCE_PROFILE
	t2 = GetMicroSeconds();
	cout << "RenderReflectedScene() ticks: " << t2-t1 << endl;
	t1 = GetMicroSeconds();
	#endif
	
	glMatrixMode( GL_PROJECTION );
	glLoadIdentity( );
	gluPerspective( 45.0f, (float)SCREEN_WIDTH/SCREEN_HEIGHT, 0.1f, settings.GetViewDistance() );
	glMatrixMode( GL_MODELVIEW );
	
	glSetup();
	
	glClearColor( 0.0f, 0.0f, 0.0f, 0.0f ); 
	//glClearColor( 255.0f, 0.0f, 0.0f, 0.0f ); 
	
	#ifdef PERFORMANCE_PROFILE
	t2 = GetMicroSeconds();
	cout << "glSetup() ticks: " << t2-t1 << endl;
	t1 = GetMicroSeconds();
	#endif
	
	utility.Tex2D(3, false);
	utility.Tex2D(2, false);
	utility.Tex2D(1, false);
	utility.Tex2D(0, true);
	
	glStencilMask(~0);
	glClearStencil(0);
	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);		// Clear Screen And Depth Buffer
	
	glEnable(GL_STENCIL_TEST);
	glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
	glStencilFunc(GL_ALWAYS, 1, ~0);
	glDisable(GL_STENCIL_TEST);

	
	GLdouble temp_matrix[16];
	cam.PutTransformInto(temp_matrix);
	glLoadMatrixd(temp_matrix);
	
	//reset sun position so it's in the correct frame
	float lp[4];
	lp[0] = LightPosition[0];
	lp[1] = LightPosition[1];
	lp[2] = LightPosition[2];
	lp[3] = 0;
	glLightfv( GL_LIGHT1, GL_POSITION, lp );
	
	#ifdef PERFORMANCE_PROFILE
	t2 = GetMicroSeconds();
	cout << "Matrix setup and light setup ticks: " << t2-t1 << endl;
	t1 = GetMicroSeconds();
	#endif
	
	if (state.GetGameState() != STATE_INITIALMENU)
	{
		
		bool normal = true;
		
		if (normal)
			backdrop.DrawSky(day_time, 1.0f);
		glClear (GL_DEPTH_BUFFER_BIT);
		
		#ifdef PERFORMANCE_PROFILE
		t2 = GetMicroSeconds();
		cout << "DrawSky() ticks: " << t2-t1 << endl;
		t1 = GetMicroSeconds();
		#endif
		
		glTranslatef(cam.GetPosition().x,cam.GetPosition().y,cam.GetPosition().z);
	
		/*GLdouble equation[4];
		VERTEX earthnormal = temp - cam.position;
		earthnormal.y += EARTH_RADIUS;
		earthnormal = earthnormal.normalize();
		equation[0] = earthnormal.x;
		equation[1] = -earthnormal.y;
		equation[2] = earthnormal.z;
		equation[3] = (waterheight);
		glClipPlane(GL_CLIP_PLANE1, equation);
		if (underwater)
			glEnable(GL_CLIP_PLANE1);
		else
			glDisable(GL_CLIP_PLANE1);
		*/
		
		if (MP_DBGDEEP)
			cout << "normal draw start" << endl;
		
//		terrain.SetFrustum(cam);
		
		#ifdef PERFORMANCE_PROFILE
		t2 = GetMicroSeconds();
		cout << "terrain.SetFrustum() ticks: " << t2-t1 << endl;
		t1 = GetMicroSeconds();
		#endif
		
		//glPolygonOffset(1.0,1.0);
		glPolygonOffset(0.0,10.0);
		glEnable(GL_POLYGON_OFFSET_FILL);
	
		if (normal)
		{
			//GLfloat LightAmbient2[]  = { 0.3f, 0.3f, 0.3f, 1.0f };
			//glLightfv( GL_LIGHT1, GL_AMBIENT, LightAmbient2 );
			glDisable(GL_STENCIL_TEST);
			
		/*	if (utility.numTUs() > 3 && state.GetTreeDetail() != "Off" && state.GetTreeDetail() != "FoliageOnly")
			{
				utility.SelectTU(3);
				glEnable(GL_TEXTURE_2D);
				glBindTexture(GL_TEXTURE_2D, trees.GetCompositeShadow());
				glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);
				glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB);
				glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE);
				glTexEnvi(GL_TEXTURE_ENV,GL_COMBINE_RGB_ARB,GL_MODULATE);
				glTexEnvi(GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 1);
				utility.SelectTU(0);
			}*/
			
//			terrain.Draw(cam, 1.0f, timefactor, fps, day_time);
			
			#ifdef PERFORMANCE_PROFILE
			t2 = GetMicroSeconds();
			cout << "terrain.Draw() ticks: " << t2-t1 << endl;
			t1 = GetMicroSeconds();
			#endif
			
			utility.SelectTU(3);
			glDisable(GL_TEXTURE_2D);
			utility.SelectTU(0);
			glEnable(GL_TEXTURE_2D);
			
			glEnable(GL_STENCIL_TEST);
//			trees.Draw();
			#ifdef PERFORMANCE_PROFILE
			t2 = GetMicroSeconds();
			cout << "trees.Draw() ticks: " << t2-t1 << endl;
			t1 = GetMicroSeconds();
			#endif
			objects.Draw();
			
			#ifdef PATCH_DEBUG
			track.VisualizeRoads(true, false, NULL);
			#endif
			
			#ifdef PERFORMANCE_PROFILE
			t2 = GetMicroSeconds();
			cout << "objects.Draw() ticks: " << t2-t1 << endl;
			t1 = GetMicroSeconds();
			#endif
			
			//glLightfv( GL_LIGHT1, GL_AMBIENT, LightAmbient );
		}
		
		if (MP_DBGDEEP)
			cout << "normal draw done" << endl;
		
		glDisable(GL_POLYGON_OFFSET_FILL);
		
		//glClear (GL_DEPTH_BUFFER_BIT);
		
		#ifdef PERFORMANCE_PROFILE
		t2 = GetMicroSeconds();
		cout << "normal draw done" << t2-t1 << endl;
		t1 = GetMicroSeconds();
		#endif
	
		/*
		//draw trees
		foliage.Draw(terrain, cam);
		
		#ifdef PERFORMANCE_PROFILE
		t2 = GetMicroSeconds();
		cout << "foliage.Draw() ticks: " << t2-t1 << endl;
		t1 = GetMicroSeconds();
		#endif
	
		//glDisable(GL_CLIP_PLANE1);
		ships.Draw(false);
	
		terrain.Draw(cam, 1.0f, false, false, true, false, timefactor, fps, day_time);
		//terrain.Draw(cam, 1.0f, false, false, true, false, timefactor, fps, day_time);
		
		//rain is drawn over everything else
		if (!underwater)
			backdrop.DrawRain(day_time);
	
		
		#ifdef PERFORMANCE_PROFILE
		t2 = GetMicroSeconds();
		cout << "DrawRain() ticks: " << t2-t1 << endl;
		t1 = GetMicroSeconds();
		#endif
		
		
		glDisable(GL_CLIP_PLANE1);
	*/	
	
		
		//glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		glEnable(GL_STENCIL_TEST);
		if (MP_DBGDEEP)
			cout << "world draw start" << endl;
		world.Draw();
		if (MP_DBGDEEP)
			cout << "world draw done" << endl;
		
		if (MP_DBGDEEP)
			cout << "shadow draw start" << endl;
		
		glEnable(GL_STENCIL_TEST);
		glStencilFunc(GL_EQUAL, 0, ~0);
		glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
		world.DrawShadows();
		glDisable(GL_STENCIL_TEST);
		
		if (MP_DBGDEEP)
			cout << "shadow draw done" << endl;
		
		world.DrawCars();
		
		world.DrawTopLevel();
		
		#ifdef PATCH_DEBUG
		VERTEX c;
		c.Set(0,1,0);
		if (thepatch != NULL)
			thepatch->Visualize(true,true,c);
		#endif
		
		//image in the framebuffer is now complete.
		
		
		/*//add a brightness/contrast adjustment
		glClear (GL_DEPTH_BUFFER_BIT);
		glLoadIdentity();
		glPushAttrib(GL_ALL_ATTRIB_BITS);
		glDisable(GL_TEXTURE_2D);
		glEnable(GL_BLEND);
		glDisable(GL_FOG);
		glDisable(GL_LIGHTING);
		glTranslatef(0,0,-40.0f);
		glBlendFunc(GL_ONE, GL_SRC_ALPHA);
		
		float rd = (float) weathersystem.GetRainDrops();
		
		float rainscale = 0.7f;
		float clearscale = 1.1f;
		float rainbias = 0.035f;
		float clearbias = 0.0f;
		float rainmax = 50.0f;
		
		float rainfactor = (rd/rainmax);
		if (rainfactor > 1.0f)
			rainfactor = 1.0f;
	
		float scale = rainfactor*rainscale+(1.0f-rainfactor)*clearscale;
		float bias = rainfactor*rainbias+(1.0f-rainfactor)*clearbias;
	
	
		glColor4f(bias, bias, bias, scale);
		float x1, y1, x2, y2;
		x1 = -30;
		y1 = -30;
		x2 = 30;
		y2 = 30;
		
		if (scale > 1.0) 
		{
			float remainingScale;
			
			remainingScale = scale;
			glBlendFunc(GL_DST_COLOR, GL_ONE);
			if (remainingScale > 2.0) 
			{
				glColor4f(1, 1, 1, 1);
				while (remainingScale > 2.0) 
				{
					glRectf(x1,y1,x2,y2);
					remainingScale /= 2.0;
				}
			}
			glColor4f(remainingScale - 1,
				remainingScale - 1, remainingScale - 1, 1);
			glRectf(x1,y1,x2,y2);
			glBlendFunc(GL_ONE, GL_ONE);
			if (bias != 0)
			{
				if (bias > 0) 
				{
					glColor4f(bias, bias, bias, 0.0);
				} 
				else 
				{
					glColor4f(-bias, -bias, -bias, 0.0);
					//can't do bias < 0
				}
				glRectf(x1,y1,x2,y2);
			}
		}
		else 
		{
			if (bias > 0) 
			{
				glColor4f(bias, bias, bias, scale);
			}
			else 
			{
				glColor4f(-bias, -bias, -bias, scale);
				//can't do bias < 0
			}
			glBlendFunc(GL_ONE, GL_SRC_ALPHA);
			glRectf(x1,y1,x2,y2);
		}
		
		glPopAttrib();*/
	
		//timer.Draw();
	}

	if (MP_DBGDEEP)
		cout << "menu draw start" << endl;
	
	glDisable(GL_STENCIL_TEST);
	
	if (fps > 0.0f)
		mq1.Draw(timefactor, fps, font);
	
	menu.MenuDraw();
	
	
	#ifdef PERFORMANCE_PROFILE
	t2 = GetMicroSeconds();
	cout << "Brightness/contrast adjustment ticks: " << t2-t1 << endl;
	t1 = GetMicroSeconds();
	#endif

	
	
    Frames++;
	frameno++;
	if (frameno >= 30011)
		frameno -= 30011;
	lfps[lfpspos] = pfps;
	lfpspos++;
	if (lfpspos >= AVERAGEFRAMES)
	{
		lfpspos = lfpspos % AVERAGEFRAMES;
		lfpsfull = true;
	}
	
	int i;
	
	float tfps = 0.0f;
	int tnum = 0;
	for (i = 0; i < AVERAGEFRAMES; i++)
	{
		if (!(!lfpsfull && i >= lfpspos))
		{
			tfps += lfps[i];
			tnum++;
		}
	}
	fps = tfps / (float) tnum;
	
	/*lfps += pfps;
    {
		//const int freq = (int) MIN_FPS;
		const int freq = 60;
		if (Frames >= freq)
		{
			fps = lfps / freq;
			Frames = 0;
			lfps = 0;
		}
	}*/
	
	char tempchar[1024];
	sprintf(tempchar, "Frames per second:  %f\n", 
		fps);
	
	//font.Print(0.5,0,tempchar,0,0,1,1,0);
	if (showfps)
		font.Print( 0.75, 0.0, tempchar, 1, 5, 1.0 );
	

	#ifdef PERFORMANCE_PROFILE
	t2 = GetMicroSeconds();
	cout << "font.Print() ticks: " << t2-t1 << endl;
	t1 = GetMicroSeconds();
	#endif


	/* Draw it to the screen */
    SDL_GL_SwapBuffers( );

    
		GLint t = SDL_GetTicks();
		//if (t - T0 >= 50) 
		{
			GLfloat seconds = (t - T0) / 1000.0;
			pfps = 1 / seconds;
			//printf("%d frames in %g seconds = %g FPS\n", Frames, seconds, fps);
			T0 = t;
			//Frames = 0;
		}
		
	#ifdef PERFORMANCE_PROFILE
	t2 = GetMicroSeconds();
	cout << "SwapBuffers() ticks: " << t2-t1 << endl;
	t1 = GetMicroSeconds();
	cout << endl;
	#endif
	
	if (MP_DBGDEEP)
		cout << "menu draw done" << endl;
		
    return( TRUE );
}

void ToggleFPSDisplay()
{
	showfps = !showfps;
	settings.SetFPSEnabled(showfps);
}

// This function returns true if the extension is there.
bool isExtensionSupported(string extstring)
{
    char * temp = (char *) glGetString(GL_EXTENSIONS);
	if (temp == 0)
	{
		cout << "Error getting extensions.  Continuing anyway, errors may follow!" << endl;
		return true;
	}
    
	string s = temp;
	string::size_type temppos = s.find(extstring);

	bool hasext = (temppos <= s.length());
	
	if (!hasext)
	{
		cout << "Extension not supported: " << extstring << endl;
	}
	
	//cout << s << endl;
	
    return hasext;
}

void ResetWorld(bool fullreset)
{
	if (fullreset)
	{
		SelectCar(state.GetCarName(0), true);
		
		timer.Reset();
	}
	
	world.reset();
	world.Trim(FRAME_TIME, gamecontrols.Get_js());
}

int main( int argc, char **argv )
{
    /* Flags to pass to SDL_SetVideoMode */
    int videoFlags;
    /* main loop variable */
    int done = FALSE;
    /* used to collect events */
    SDL_Event event;
    /* this holds some info about our display */
    const SDL_VideoInfo *videoInfo;
    /* whether or not the window is active */
    int isActive = TRUE;

	surface = NULL;
	
	//load error log
	error_log.open((settings.GetSettingsDir() + "/logs/main.log").c_str());
	
	//create trig lookups
	//CreateLookupTable();
	
	sound.SetMasterVolume(settings.GetSoundVolume());

	int i;
	for (i = 0; i < argc; i++)
	{
		if (strcmp(argv[i],"-verbose") == 0)
			verbose_output = true;
	}
	
	for (i = 0; i < argc; i++)
	{
		if (strcmp(argv[i],"-nosound") == 0)
		{
			sound.DisableAllSound();
		}
	}
	
	bool setdir = false;
	for (i = 0; i < argc; i++)
	{
		if (strcmp(argv[i],"-datadir") == 0)
			setdir = true;
		else
		{
			if (setdir)
			{
				string new_data_dir = argv[i];
				cout << "Changing data directory to \"" + new_data_dir + "\"..." << endl;
				settings.SetDefaultDataDir(new_data_dir);
				cout << "Done. Now re-run vdrift normally." << endl;
				setdir = false;
				Quit(0);
			}
		}
	}
	if( setdir )
	{
		cout << "You must specify a path with the -datadir and option!" << endl;
		Quit(1);
	}

	for (i = 0; i < argc; i++)
	{
		if (strcmp(argv[i],"-defaultdatadir") == 0)
			setdir = true;
		else
		{
			if (setdir)
			{
				string new_data_dir = argv[i];
				cout << "Changing default data directory to \"" + new_data_dir + "\"..." << endl;
				settings.SetDataDir(new_data_dir);
				cout << "Done. Now delete your \"~/.vdrift/VDrift.config\" and re-run vdrift normally." << endl;
				setdir = false;
				Quit(0);
			}
		}
	}
	if( setdir )
	{
		cout << "You must specify a path with the -defaultdatadir option!" << endl;
		Quit(1);
	}

	//set frame stats to zero
	Frames = 0;
	T0 = 0;
	fps = 0;
	frameno = 0;
	
	
	
    /* initialize SDL */
    if ( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK) < 0 )
	{
	    fprintf( stderr, "SDL initialization failed: %s\n",
		     SDL_GetError( ) );
	    Quit( 1 );
	}
	
	cout << "Run with -verbose for troubleshooting." << endl;
	cout << "Run with -nosound to disable sound." << endl;
	cout << "Run with -datadir /path/to/vdrift/data to change the data directory in your local settings." << endl;
	cout << "Run with -defaultdatadir /path/to/vdrift/data (as root) to change the data directory system-wide." << endl;
	cout << "Note: after running \"vdrift -defaultdatadir ...\" as root, delete your ~/.vdrift/VDrift.config file." << endl;
	
	printf("%i joystick(s) found:\n", SDL_NumJoysticks() );
	//int i;
	for( i=0; i < SDL_NumJoysticks(); i++ ) 
	{
		printf("    %i. %s\n", i, SDL_JoystickName(i));
	}
	/*SDL_Joystick *joystick;
	SDL_JoystickEventState(SDL_ENABLE);
	joystick = SDL_JoystickOpen(0);
	printf("Joystick 0 opened.\n\n");*/
	
	SDL_JoystickEventState(SDL_ENABLE);
	
	gamecontrols.InitJoy();
	
	SDL_WM_SetCaption("VDrift - open source drift racing simulation - powered by Vamos", NULL);
	SDL_WM_SetIcon(IMG_Load(settings.GetFullDataPath("textures/small/icons/vdrift-32x32.png").c_str()), NULL);
	SDL_ShowCursor(SDL_DISABLE);

    /* Fetch the video info */
    videoInfo = SDL_GetVideoInfo( );

    if ( !videoInfo )
	{
	    fprintf( stderr, "Video query failed: %s\n",
		     SDL_GetError( ) );
	    Quit( 1 );
	}

    /* the flags to pass to SDL_SetVideoMode */
    videoFlags  = SDL_OPENGL;          /* Enable OpenGL in SDL */
    videoFlags |= SDL_GL_DOUBLEBUFFER; /* Enable double buffering */
    videoFlags |= SDL_HWPALETTE;       /* Store the palette in hardware */
    videoFlags |= SDL_RESIZABLE;       /* Enable window resizing */

    /* This checks to see if surfaces can be stored in memory */
    if ( videoInfo->hw_available )
	videoFlags |= SDL_HWSURFACE;
    else
	videoFlags |= SDL_SWSURFACE;

    /* This checks if hardware blits can be done */
    if ( videoInfo->blit_hw )
	videoFlags |= SDL_HWACCEL;

    /* Sets up OpenGL double buffering */
    SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
    
    //set up the depth buffer to be 24 bits, 16 bits are NOT enough
    SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 24 );

/*	ifstream vconf;
	vconf.open((settings.GetSettingsDir() + "/videoconfig").c_str());
	if (vconf)
	{
		vconf >> w;
		vconf >> h;
		vconf >> bpp;
		vconf >> fs;
		vconf.close();
	}
*/
	int w, h, bpp, fs;
	w = settings.GetDisplayX();
	h = settings.GetDisplayY();
	bpp = settings.GetDisplayDepth();
	fs = settings.GetFullscreenEnabled();
	
	if (SDL_VideoModeOK(w, h, bpp, videoFlags) != 0)
	{
		ChangeDisplay(w, h, bpp, fs, true);
	}
	else
	{
		surface = SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP,
					videoFlags );
	}
	
    /* Verify there is a surface */
    if ( !surface )
	{
	    fprintf( stderr,  "Video mode set failed: %s\n", SDL_GetError( ) );
	    Quit( 1 );
	}

    /* initialize OpenGL */
    initGL( );

    /* resize the initial window */
    resizeWindow( SCREEN_WIDTH, SCREEN_HEIGHT );

	if (!(isExtensionSupported("GL_ARB_multitexture")))
	{
		cout << "OpenGL extensions not present." << endl;
		Quit(0);
		return(0);
	}
	
	if (verbose_output)
	{
		#ifdef HAVE_OPENAL
		cout << "Using OpenAL for sound" << endl;
		#else
		cout << "Using FMOD for sound" << endl;
		#endif
	}

	// grab & hide mouse
    //SDL_WM_GrabInput(SDL_GRAB_ON);
    //if ( SDL_ShowCursor(SDL_DISABLE) != SDL_DISABLE )
      //cerr << SDL_GetError() << endl;

	timefactor = 1.0f;
	
    /* wait for events */
    while ( !done )
	{
	    /* handle the events in the queue */

	    while ( SDL_PollEvent( &event ) )
		{
			float v;
			
		    switch( event.type )
			{
			case SDL_ACTIVEEVENT:
			    /* Something's happend with our focus
			     * If we lost focus or we are iconified, we
			     * shouldn't draw the screen
			     */
			    if ( event.active.gain == 0 )
				isActive = FALSE;
			    else
				isActive = TRUE;
			    break;			    
			case SDL_VIDEORESIZE:
			    /* handle resize event */
			    surface = SDL_SetVideoMode( event.resize.w,
							event.resize.h,
							16, videoFlags );
			    if ( !surface )
				{
				    fprintf( stderr, "Could not get a surface after resize: %s\n", SDL_GetError( ) );
				    Quit( 1 );
				}
			    resizeWindow( event.resize.w, event.resize.h );
			    break;
			case SDL_KEYDOWN:
			    /* handle key presses */
			    handleKeyPress( &event.key.keysym );
				keyman.KeyDown(event.key.keysym.sym);
			    break;
			case SDL_KEYUP:
				keyman.KeyUp(event.key.keysym.sym);
				break;
			case SDL_QUIT:
			    /* handle quit requests */
			    done = 1;
			    break;
			case SDL_JOYBUTTONDOWN:
				if (timefactor == 0.0f && !menu.InMenu())
				//if (timefactor == 0.0f && state.GetGameState() != STATE_MENU)
					MainUnpause();
				menu.AssignJoyButton(event.jbutton.which, event.jbutton.button);
				break;
			case SDL_JOYAXISMOTION:
				v = event.jaxis.value / 32768.0f;
				if (v > 0.1 || v < -0.1)
					menu.AssignJoyAxis(event.jaxis.which, event.jaxis.axis, v);
				break;
			default:
			    break;
			}
		}

	    /* do the game logic & draw the screen*/
	    //if ( isActive )
		{
			Update();
			if (MP_DBGDEEP)
				cout << "main draw start" << endl;
			drawGLScene( );
			if (MP_DBGDEEP)
				cout << "main draw done" << endl;
		}
	}
	
	gamecontrols.DeinitJoy();
	
    /* clean ourselves up and exit */
    Quit( 0 );

    /* Should never get here */
    return( 0 );
}
