//  Gl_Car.cc - a car that handles graphics, sound and input devices.
//
//  Copyright (C) 2001--2004 Sam Varner
//
//  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 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 <vamos/body/Gl_Car.h>
//#include <vamos/geometry/Ac3d.h>
#include <vamos/geometry/Conversions.h>
#include <vamos/body/Fuel_Tank.h>
#include <vamos/body/Gauge.h>
#include <vamos/body/Wheel.h>

//#include <GL/glut.h>

#include <algorithm>
#include <iomanip>
#include <sstream>
#include <string>

using namespace Vamos_Geometry;

//* Struct Rear_View_Mirror

//** Constructor
Vamos_Body::
Rear_View_Mirror::Rear_View_Mirror (const Three_Vector& position,
									double width, double height,
									double direction,
									double field,
									double near_plane, double far_plane,
									std::string mask_file) :
  m_position (position),
  m_width (width),
  m_height (height),
  m_direction (direction),
  m_field (field),
  m_near_plane (near_plane),
  m_far_plane (far_plane),
  mp_mask (new Gl_Texture_Image (mask_file, false, false))
{
}

//** Destructor
Vamos_Body::
Rear_View_Mirror::~Rear_View_Mirror ()
{
  delete mp_mask;
}

void Vamos_Body::
Rear_View_Mirror::activate_viewport ()
{
  glViewport (m_viewport.x, m_viewport.y, m_viewport.width, m_viewport.height);
  glScissor (m_viewport.x, m_viewport.y, m_viewport.width, m_viewport.height);
}

Three_Vector Vamos_Body::
Rear_View_Mirror::get_center () const
{
  return Three_Vector (m_position [0], 
					   m_position [1] - m_width / 2.0,
					   m_position [2] + m_height / 2.0);
}

void Vamos_Body::
Rear_View_Mirror::transform_view () const
{
  glMatrixMode (GL_PROJECTION);
  glLoadIdentity ();
  // Reflect the x-axis.
  glScaled (-1.0, 1.0, 1.0);
  gluPerspective (m_field, m_viewport.aspect (), m_near_plane, m_far_plane);
}

void Vamos_Body::
Rear_View_Mirror::set_view ()
{
  activate_viewport ();
  glClear (GL_DEPTH_BUFFER_BIT);
  transform_view ();
}

//* Class Gl_Car

extern bool verbose_output;

//** Constructor
Vamos_Body::
Gl_Car::Gl_Car (const Three_Vector& pos)
  : Car (pos),
	mp_engine_sample (0),
	mp_dashboard (0),
	cur_paintjob(0),
	num_paintjobs(0)
{
	if (verbose_output)
		std::cout << "gl_car init" << std::endl;
	int i;
	for (i = 0; i < 4; i++)
		tire_source[i] = 0;
}

//** Destructor
Vamos_Body::
Gl_Car::~Gl_Car ()
{
	if (verbose_output)
		std::cout << "gl_car deinit" << std::endl;
	
	//FSOUND_StopSound(real_engine_sample);
	
	//commenting this out stops an error when a spinning car widget changes cars
	/*sound.StopSource(real_engine_sample);
	
	int i;
	for (i = 0; i < 4; i++)
		sound.StopSource(tire_source[i]);*/
	
	/*if (m_body_list_id != 0)
	{
	  glDeleteLists (m_body_list_id, 1);
	}
  if (m_interior_list_id != 0)
	{
	  glDeleteLists (m_interior_list_id, 1);
	}*/
	
  delete mp_engine_sample;

  delete mp_dashboard;

  for (std::vector <Rear_View_Mirror*>::iterator it = m_mirrors.begin ();
	   it != m_mirrors.end ();
	   it++)
	{
	  delete *it;
	}
	
	//textures.Delete(shadowtex);
	//string tex_size = "";
	//settings.Get( "display.texture_size", tex_size );
	//textures.Delete(settings.GetFullDataPath("cars/" + m_car_file + "/textures/" + tex_size  + "/shadow.png"));
	shadowtex.Unload();
	
	//glDeleteTextures(1, &shadowtex);
}

#define LOADFLOATING(floatmodel, floatfile) {string floatingfile = settings.GetFullDataPath("cars/" + m_car_file + "/" + floatfile + ".joe"); \
	if (utility.FileExists(floatingfile)) \
	{ \
		floatmodel.Load(floatingfile,  false); \
		char texfilename[1024]; \
		sprintf(texfilename, "cars/%s/textures/body%02i.joe",m_car_file.c_str(), cur_paintjob); \
		floatmodel.Texture(settings.GetFullDataPath(texfilename), 0); \
		floatmodel.AdditiveTexture(settings.GetFullDataPath("cars/" + m_car_file + "/textures/brake.joe"), 1); \
	}} \

void Vamos_Body::
Gl_Car::exterior_model (std::string file, double scale, 
						const Three_Vector& translation,
						const Three_Vector& rotation)
{
  /*if (m_body_list_id != 0)
	{
	  glDeleteLists (m_body_list_id, 1);
	}*/

   //Ac3d model (file, scale, translation, rotation);
   //m_body_list_id = model.build ();
	//string tex_size = "";
	//settings.Get( "display.texture_size", tex_size );
	//shadowtex = textures.Load(settings.GetFullDataPath("cars/" + m_car_file + "/textures/" + tex_size  + "/shadow.png"), false);
	shadowtex.Load(settings.GetFullDataPath("cars/" + m_car_file + "/textures/shadow.png"), false);
	joeglass.Load(settings.GetFullDataPath("cars/" + m_car_file + "/glass.joe"));
	joeglass.AdditiveTexture(settings.GetFullDataPath("cars/" + m_car_file + "/textures/brake-glass.joe"), 1);
	//joeglass.Load(settings.GetFullDataPath("cars/" + m_car_file + "/glass.joe"));
	//joeglass.AdditiveTexture(settings.GetFullDataPath("cars/" + m_car_file + "/textures/brake-glass.joe"), 1);
	joeexterior.Load(settings.GetFullDataPath("cars/" + m_car_file + "/body.joe"));
	joeexterior.AdditiveTexture(settings.GetFullDataPath("cars/" + m_car_file + "/textures/brake.joe"), 1);
	
	LOADFLOATING(joefloating_front, "floating_front");
	LOADFLOATING(joefloating_rear, "floating_rear");
	
	joecollision.Load(settings.GetFullDataPath("cars/" + m_car_file + "/collision.joe"));
	
	//check for paint jobs
	char texfile[1024];
	int count = 0;
	sprintf(texfile, "cars/%s/textures/body%02i.png", m_car_file.c_str(), count);
	while (count < 99 && utility.FileExists(settings.GetFullDataPath(texfile)))
	{
		count++;
		sprintf(texfile, "cars/%s/textures/body%02i.png", m_car_file.c_str(), count);
	}
	num_paintjobs = count;
}

void Vamos_Body::
Gl_Car::interior_model (std::string file, double scale, 
						const Three_Vector& translation,
						const Three_Vector& rotation)
{
  /*if (m_interior_list_id != 0)
	{
	  glDeleteLists (m_interior_list_id, 1);
	}

   Ac3d model (file, scale, translation, rotation);
   m_interior_list_id = model.build ();*/
	
	//joeinterior.Load(file);
	joeinterior.Load(settings.GetFullDataPath("cars/" + m_car_file + "/interior.joe"));
}

void Vamos_Body::
Gl_Car::set_perspective (double aspect)
{
  gluPerspective (m_field_of_view, aspect, m_near_plane, m_far_plane);
}

void Vamos_Body::
Gl_Car::set_view (const Vamos_Geometry::Three_Vector& position,
				  double field_of_view,
				  double near_plane, double far_plane,
				  double pan_angle)
{
  m_driver_view = position;
  m_field_of_view = field_of_view;
  m_near_plane = near_plane;
  m_far_plane = far_plane;
  m_pan_angle = pan_angle;
}

void Vamos_Body::
Gl_Car::set_hood_view (const Vamos_Geometry::Three_Vector& position)
{
  m_hood_view = position;
}

void Vamos_Body::
Gl_Car::add_rear_view (const Vamos_Geometry::Three_Vector& position,
					   double width, double height,
					   double direction, double field,
					   double near_plane, double far_plane,
					   std::string mask_file)
{
  m_mirrors.push_back (new Rear_View_Mirror (position, width, height,
											 direction, field, 
											 near_plane, far_plane,
											 mask_file));
}


// Fill the stencil buffer for masking the rear-view mirrors.
void Vamos_Body::
Gl_Car::make_rear_view_mask (int window_width, int window_height)
{
  glMatrixMode (GL_PROJECTION);
  glLoadIdentity ();

  glViewport (0, 0, window_width, window_height);
  glScissor (0, 0, window_width, window_height);

  glClearColor (0.0, 0.0, 0.0, 0.0);
  glClearStencil (0);
  glClear (GL_COLOR_BUFFER_BIT 
		   | GL_DEPTH_BUFFER_BIT
		   | GL_STENCIL_BUFFER_BIT);

  const double near_plane = 0.2;
  const double far_plane = 10.0;
  gluPerspective (field_of_view (), double (window_width)/window_height, 
				  near_plane, far_plane);
  view (0.0);
  glMatrixMode (GL_MODELVIEW);
  transform_body ();

  for (std::vector <Rear_View_Mirror*>::iterator it = m_mirrors.begin ();
	   it != m_mirrors.end ();
	   it++)
	{
	  (*it)->make_mask (window_width, window_height,
						m_driver_view, field_of_view ());
	}
}


// Put a mask for one rear-view mirror in the stencil buffer.
void Vamos_Body::
Rear_View_Mirror::make_mask (int window_width, int window_height,
							 const Three_Vector& driver_position, 
							 double driver_field_of_view)
{
  glDisable (GL_LIGHTING);
  set_viewport (window_width, window_height, 
				driver_position, driver_field_of_view);
  draw_mask_shape ();
  set_stencil (window_width, window_height);
  glEnable (GL_LIGHTING);
}


// Find the dimensions for a viewport that's just large enough to hold
// the mirror.
void Vamos_Body::
Rear_View_Mirror::set_viewport (int window_width, int window_height, 
								const Three_Vector& driver_position,
								double driver_field_of_view)
{
  const Three_Vector pos = m_position - driver_position;
  const double y_factor = 
	-1.0 / (pos [0] * tan (0.5 * deg_to_rad (driver_field_of_view)));
  const double aspect = double (window_width) / window_height;
  const double x_factor = -y_factor / aspect;

  const int x0 = to_pixels (window_width, x_factor, pos [1]) - 1;
  m_viewport.x = clip (x0, 0, window_width - 1);
  const int y0 = to_pixels (window_height, y_factor, pos [2]) - 1;
  m_viewport.y = clip (y0, 0, window_height - 1);

  const int x1 = to_pixels (window_width, x_factor, pos [1] - m_width);
  m_viewport.width = clip (x1, 0, window_width - 1) - m_viewport.x;
  const int y1 = to_pixels (window_height, y_factor, pos [2] + m_height);
  m_viewport.height = clip (y1, 0, window_height - 1) - m_viewport.y;
}


// Draw the mask.
void Vamos_Body::
Rear_View_Mirror::draw_mask_shape ()
{
  glStencilFunc (GL_ALWAYS, 1, 1);
  glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);

  mp_mask->activate ();

  glColor3d (1.0, 1.0, 1.0);
  glBegin (GL_QUADS);
  glTexCoord2d (0.0, 1.0);
  glVertex3d (m_position [0], m_position [1], m_position [2]);
  glTexCoord2d (1.0, 1.0);
  glVertex3d (m_position [0], m_position [1] - m_width, m_position [2]);
  glTexCoord2d (1.0, 0.0);
  glVertex3d (m_position [0], m_position [1] - m_width, 
			  m_position [2] + m_height);
  glTexCoord2d (0.0, 0.0);
  glVertex3d (m_position [0], m_position [1], m_position [2] + m_height);
  glEnd ();

  glFlush ();
}


// Use the pixels in the viewport to set the stencil buffer.
void Vamos_Body::
Rear_View_Mirror::set_stencil (int window_width, int window_height)
{
  unsigned char* stencil_buffer = make_stencil_buffer ();

  glMatrixMode (GL_PROJECTION);
  glPushMatrix ();
  glLoadIdentity ();
  gluOrtho2D (0.0, double (window_width), 0.0, double (window_height));
  glMatrixMode (GL_MODELVIEW);
  glPushMatrix ();
  glLoadIdentity ();
  glStencilFunc (GL_EQUAL, 1, 1);
  glStencilOp (GL_KEEP, GL_REPLACE, GL_REPLACE);
  glRasterPos2i (m_viewport.x, m_viewport.y);
  glDrawPixels (m_viewport.width, m_viewport.height, GL_STENCIL_INDEX,
  				GL_UNSIGNED_BYTE, stencil_buffer);
  glPopMatrix ();

  glMatrixMode (GL_PROJECTION);
  glPopMatrix ();

  glFinish ();

  delete stencil_buffer;
}


// Grab the current buffer as an array of pixels.
unsigned char* Vamos_Body::
Rear_View_Mirror::make_stencil_buffer ()
{
  glReadBuffer (GL_BACK);
  const size_t elements = m_viewport.width * m_viewport.height;
  unsigned char* rgba_buffer = new unsigned char [4 * elements];

  glReadPixels (m_viewport.x, m_viewport.y, 
 				m_viewport.width, m_viewport.height, 
 				GL_RGBA, GL_UNSIGNED_BYTE, rgba_buffer);

  unsigned char* buffer = new unsigned char [elements];
  for (size_t i = 0; i < elements; i++)
	{
	  buffer [i] = rgba_buffer [4 * i];
	}
  delete rgba_buffer;
  return buffer;
}


void Vamos_Body::
Gl_Car::draw_rear_view (double aspect, int index)
{
  Rear_View_Mirror* mirror = m_mirrors [index];
  mirror->set_view ();
  view (mirror->get_direction (), mirror->get_center ());
}

// Set the dashboard.
void Vamos_Body::
Gl_Car::dashboard (Dashboard* dash)
{
  delete mp_dashboard;
  mp_dashboard = dash;
}

extern GLfloat LightPosition[4];
#include "quat.h"

void Vamos_Body::
Gl_Car::transform_body ()
{
  // Set the modelview matrix to be the identity matrix
  //glLoadIdentity ();
  
  // Translate and rotate the graphical representation of the object
  Three_Vector cm = m_chassis.position ();
  glTranslatef (cm [0], cm [1], cm [2]);
  
  double angle;
  Three_Vector axis = m_chassis.axis_angle (&angle);
	
	
	
	
	
  glRotatef (angle, axis [0], axis [1], axis [2]);


  
  // The rotation is performed about the origin of the body
  // system.  Since we want the effect to be a rotation about the
  // center of mass, we have to translate the object the distance
  // from the cm to the origin.
  cm = -m_chassis.center_of_mass ();
  glTranslatef (cm [0], cm [1], cm [2]);
  
  	QUATERNION carrot;
	carrot.SetAxisAngle(angle*3.141593/180.0, axis[0], axis[2], axis[1]);
	QUATERNION goofyfoot;
	goofyfoot.Rotate(-3.141593/2.0, 1,0,0);
	double tempmat[16];
	goofyfoot.GetMat(tempmat);
	float lp[4];
	lp[0] = LightPosition[0];
	lp[1] = LightPosition[1];
	lp[2] = LightPosition[2];
	lp[3] = 0;
	VERTEX lpv;
	lpv.Set(lp);
	//goofyfoot.PostMultiply(carrot);
	lpv = goofyfoot.ReturnConjugate().RotateVec(lpv);
	lpv = carrot.ReturnConjugate().RotateVec(lpv);
	lp[0] = lpv.x;
	lp[1] = lpv.z;
	lp[2] = lpv.y;
	//glLightfv( GL_LIGHT1, GL_POSITION,  lp);
}

#define SETFLOATINGILLUM(floatmodel) {if (floatmodel.Loaded()) floatmodel.SetTU(1, illum);}

// Render the car according to its current position and orientation.
void Vamos_Body::
Gl_Car::draw (bool transform, float opacity)
{
//  if (m_body_list_id != 0)
	{
		if (transform)
	  		transform_body ();

	  // Draw the body.
	  //glCallList (m_body_list_id);
		//joeexterior.Draw(0, 0);
		
		glPushAttrib(GL_ALL_ATTRIB_BITS);
		//glDisable(GL_LIGHTING);
		//glDepthMask(0);
		
		bool illum = false;
		if (brakesetting > 0)
			illum = true;
		
		//utility.SelectTU(1);
		//glEnable(GL_TEXTURE_2D);
		//utility.SelectTU(0);

		//putting tree shadows on the car doesn't work because the car's vertices
		// are in local space, not world space
		/*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);
		}*/
		
		glColor4f(1,1,1,opacity);
		//glColor4f(0.5,0,0,opacity);
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glCullFace(GL_FRONT);
		glEnable(GL_CULL_FACE);
		//joecollision.DrawStatic();
		joeexterior.SetTU(1, illum);
		SETFLOATINGILLUM(joefloating_front);
		SETFLOATINGILLUM(joefloating_rear);
		joeexterior.DrawStatic();
		joeexterior.SetTU(1, false);
		
		glCullFace(GL_BACK);
		glColor4f(0,0,0,opacity);
		joeexterior.DrawStatic();
		
		glColor4f(1,1,1,opacity);
		glDisable(GL_CULL_FACE);
		
		joeinterior.DrawStatic();
		
		// Draw the wheels.
		//  std::for_each (m_wheels.begin (), m_wheels.end (), std::mem_fun (&Wheel::draw));
		VECTORITERATE(Wheel *, m_wheels, i)
		{
			JOEMODEL * floatingmodel = NULL;
			/*if ((*i)->side() == LEFT && (*i)->frontback() == FRONT) floatingmodel = &joefloating_fl;
			if ((*i)->side() == RIGHT && (*i)->frontback() == FRONT) floatingmodel = &joefloating_fr;
			if ((*i)->side() == RIGHT && (*i)->frontback() == BACK) floatingmodel = &joefloating_rr;
			if ((*i)->side() == LEFT && (*i)->frontback() == BACK) floatingmodel = &joefloating_rl;*/
			if ((*i)->frontback() == FRONT) floatingmodel = &joefloating_front;
			if ((*i)->frontback() == BACK) floatingmodel = &joefloating_rear;
			glPushMatrix();
			(*i)->FenderTransform();
			floatingmodel->DrawStatic();
			glPopMatrix();
			(*i)->draw();
		}
		
		//utility.SelectTU(1);
		//glEnable(GL_TEXTURE_2D);
		//utility.SelectTU(0);
		
		glEnable(GL_CULL_FACE);
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glCullFace(GL_FRONT);
		glEnable(GL_CULL_FACE);
		glDepthMask(0);
		glColor4f(1,1,1,opacity);
		joeglass.SetTU(1, illum);
		joeglass.DrawStatic();
		//joeglass.DrawStatic();
		glDepthMask(1);

		glPopAttrib();
		
		/*utility.SelectTU(3);
		glDisable(GL_TEXTURE_2D);
		utility.SelectTU(1);
		glDisable(GL_TEXTURE_2D);
		utility.SelectTU(0);*/
	}
}

void Vamos_Body::
Gl_Car::draw_interior ()
{
  //joeinterior.Draw();
  draw_dashboard ();
}

TEXTURE_HANDLE * Vamos_Body::
Gl_Car::shadow_texture()
{return &shadowtex;}

void Vamos_Body::
Gl_Car::draw_dashboard ()
{
  mp_dashboard->set_tachometer (rad_s_to_rpm (engine ()->rotational_speed ()));
  mp_dashboard->set_speedometer (m_s_to_km_h (wheel (2)->speed ()));
  mp_dashboard->set_fuel_gauge (fuel_tank ()->fuel ());
  mp_dashboard->set_gear_indicator (transmission ()->gear ());
  mp_dashboard->set_steering_wheel (m_steer_key_control.value ());

  mp_dashboard->draw ();
  if (m_show_dashboard_extras)
	{
	  draw_dashboard_extras ();
	}
}

void Vamos_Body::
Gl_Car::draw_string (const std::string& str, double x, double y)
{
	font.Print(x, y, str.c_str(), 1, 5, 1);
  /*glRasterPos2d (x, y);
  for (std::string::const_iterator it = str.begin (); it != str.end (); it++)
	{
	  glutBitmapCharacter (GLUT_BITMAP_8_BY_13, *it);
	}*/
}

void Vamos_Body::
Gl_Car::draw_dashboard_extras ()
{ 
  glMatrixMode (GL_PROJECTION);
  glPushMatrix ();
  glLoadIdentity ();

  // Set the dimensions of the drawing area.
  gluOrtho2D (0, 10, 0, 10);
  glMatrixMode (GL_MODELVIEW);
  glPushMatrix ();
  glLoadIdentity ();
  
  glDisable (GL_DEPTH_TEST);
  glDisable (GL_LIGHTING);
  glDisable (GL_TEXTURE_2D);
  
  glColor3f (1.0, 1.0, 1.0);

  std::ostringstream b_stream;

  int rpm = 
 	int (Vamos_Geometry::rad_s_to_rpm (engine ()->rotational_speed ()));
  b_stream << "RPM " << rpm;
  draw_string (b_stream.str (), 0.4, 1.8);

  b_stream.str ("");
  b_stream << "Torque " << int (engine ()->drive_torque ()) << " Nm";
  draw_string (b_stream.str (), 0.4, 1.4);

  b_stream.str ("");
  b_stream << "Speed " << int (m_s_to_km_h (wheel (2)->speed ()))
		   << " km/h";
  draw_string (b_stream.str (), 0.4, 1.0);

  b_stream.str ("");
  b_stream << "Mass " << int (m_chassis.mass ()) << " kg";
  draw_string (b_stream.str (), 0.4, 0.6);

  b_stream.str ("");
  char gear = 'N';
  if (transmission ()->gear () == -1)
	{
	  gear = 'R';
	}
  else if (transmission ()->gear () > 0)
	{
	  gear = transmission ()->gear () + '0';
	}
  b_stream << "Gear " << gear;
  draw_string (b_stream.str (), 0.4, 0.2);

  b_stream.str ("");
  b_stream << "Fuel " << std::setprecision (1)
		   << mp_fuel_tank->fuel () << " L";
  draw_string (b_stream.str (), 2.8, 0.2);


  b_stream.str ("");
  b_stream << "Slip Ratios";
  draw_string (b_stream.str (), 2.8, 1.4);

  b_stream.str ("");
  b_stream.setf (std::ios::fixed);
  b_stream << std::setprecision (3) << m_wheels [0]->slip ();
  draw_string (b_stream.str (), 2.8, 1.0);

  b_stream.str ("");
  b_stream << std::setprecision (3) << m_wheels [1]->slip ();
  draw_string (b_stream.str (), 3.6, 1.0);

  b_stream.str ("");
  b_stream << std::setprecision (3) << m_wheels [2]->slip ();
  draw_string (b_stream.str (), 2.8, 0.6);

  b_stream.str ("");
  b_stream << std::setprecision (3) << m_wheels [3]->slip ();
  draw_string (b_stream.str (), 3.6, 0.6);

  glEnable (GL_DEPTH_TEST);
  glEnable (GL_LIGHTING);
  glEnable (GL_TEXTURE_2D);

  glMatrixMode (GL_PROJECTION);
  glPopMatrix ();
  glMatrixMode (GL_MODELVIEW);
  glPopMatrix ();
}

// Perform the transformations for the driver's view.
void Vamos_Body::
Gl_Car::view (double pan, const Three_Vector& view_position)
{
  if (pan == 0.0)
	{
	  pan = m_pan_key_control.value ();
	}

  // Find the angle-axis representation of the car's orientation.
  double angle;
  Three_Vector axis = m_chassis.axis_angle (&angle);

  // Rotate the view.
  glRotated (90, 0.0, 1.0, 0.0);
  glRotated (-90, 1.0, 0.0, 0.0);
  glRotated (-angle, axis [0], axis [1], axis [2]);

  Three_Vector z = m_chassis.rotate_out (Three_Vector (0.0, 0.0, 1.0));
  glRotated (-pan, z [0], z [1], z [2]);

  Three_Vector pos = 
    -m_chassis.transform_out (view_position - m_chassis.center_of_mass ());
  glTranslated (pos [0], pos [1], pos [2]);
}

//extern string car_file;

void Vamos_Body::
Gl_Car::engine_sound (std::string file, 
					  double volume, 
					  double throttle_volume_factor, 
					  double engine_speed_volume_factor,
					  double pitch)
{
  delete mp_engine_sample;

//  if (file != "")
	{
	  m_throttle_volume_factor = throttle_volume_factor;
	  m_engine_speed_volume_factor = engine_speed_volume_factor;
	  mp_engine_sample = new Sample (file, volume, pitch, true, true);
		//real_engine_sample = sound.NewSource(settings.GetDataDir() + "/sounds/engine.wav");
		//cout << "Car " << car_file << endl;
		bool error = false;
		real_engine_sample = sound.NewSource(settings.GetFullDataPath("cars/" + m_car_file + "/engine.wav"), error);
		if (!error)
			sound.SetGain(real_engine_sample, 0);
		//sound.PlaySource(real_engine_sample);
		
		int i;
		for (i = 0; i < 4; i++)
		{
			error = false;
			tire_source[i] = sound.NewSource(settings.GetFullDataPath("sounds/tire_squeal.wav"), error);
			if (!error)
				sound.SetGain(tire_source[i], 0.0);
			//sound.PlaySource(tire_source[i]);
		}
	}
}

double Vamos_Body::
Gl_Car::engine_pitch ()
{
  return engine ()->rotational_speed ();
}

double Vamos_Body::
Gl_Car::engine_volume ()
{
  return 1.0 + m_throttle_volume_factor * engine ()->throttle ()
	+ m_engine_speed_volume_factor * engine ()->rotational_speed ();
}

int Vamos_Body::Gl_Car::GetSoundSource()
{
	return real_engine_sample;
}

int Vamos_Body::Gl_Car::GetTireSoundSource(int i)
{
	return tire_source[i];
}

void Vamos_Body::Gl_Car::SetPaint(int pid)
{
	//joeexterior.SetBaseTexture(pid);
	
	if (num_paintjobs <= 0)
		return;
	
	//cout << pid << ", ";
	
	char texfile[1024];
	if (pid < 0)
		pid = num_paintjobs - ((-pid) % num_paintjobs);
	pid = pid % num_paintjobs;
	sprintf(texfile, "cars/%s/textures/body%02i.png", m_car_file.c_str(), pid);
	joeexterior.Texture(settings.GetFullDataPath(texfile), 0);
	
	cur_paintjob = pid;
	
	//cout << texfile << endl;
	//cout << pid << endl;
}

void Vamos_Body::Gl_Car::SetReflectionTexture(TEXTURE_HANDLE * reftid, bool cubemapping)
{
	int targetid = 2;
	
	if (cubemapping)
	{
		joeexterior.StaticCubeReflectionTextureID(reftid, targetid);
		joeglass.StaticCubeReflectionTextureID(reftid, targetid);
	
		if (joefloating_front.Loaded()) joefloating_front.StaticCubeReflectionTextureID(reftid, targetid);
		if (joefloating_rear.Loaded()) joefloating_rear.StaticCubeReflectionTextureID(reftid, targetid);
	}
	else
	{
		joeexterior.ReflectionTextureID(reftid, targetid);
		joeglass.ReflectionTextureID(reftid, targetid);
	
		if (joefloating_front.Loaded()) joefloating_front.ReflectionTextureID(reftid, targetid);
		if (joefloating_rear.Loaded()) joefloating_rear.ReflectionTextureID(reftid, targetid);
	}
}

void Vamos_Body::Gl_Car::SetDynamicCubeReflectionTexture(GLuint reftid)
{
	int targetid = 2;
	
	joeexterior.DynamicCubeReflectionTextureID(reftid, targetid);
	joeglass.DynamicCubeReflectionTextureID(reftid, targetid);

	if (joefloating_front.Loaded()) joefloating_front.DynamicCubeReflectionTextureID(reftid, targetid);
	if (joefloating_rear.Loaded()) joefloating_rear.DynamicCubeReflectionTextureID(reftid, targetid);
}
