#include "GPU_physics.h"
#include "fboSupport.h"
#include "shaderSupport.h"
#include "clock.h"

#define TIMESTEP 0.016f

enum DebugOptions
{
  DRAW_WITHOUT_SHADERS,
  DRAW_WITHOUT_PHYSICS,
  DRAW_ALL
} ;


static float *positionData  = NULL ;
static float *rotationData  = NULL ;
static float *collisionData = NULL ;
static bool   noVertexTextureSupport = false ;
static DebugOptions debugOpt = DRAW_ALL ;

void checkVertexTextureSupport ( bool disableVertexTextureSupport )
{
  GLint nVertTextures ;
  GLint nFragTextures ;
  GLint nCombTextures ;

  glGetIntegerv ( GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, & nVertTextures ) ;
  glGetIntegerv ( GL_MAX_TEXTURE_IMAGE_UNITS, & nFragTextures ) ;
  glGetIntegerv ( GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, & nCombTextures ) ;

  fprintf(stderr, "INFO: This hardware supports at most:\n"
                  "  %2d vert  texture samplers\n"
                  "  %2d frag  texture samplers\n"
                  "  %2d total texture samplers\n",
                  nVertTextures, nFragTextures, nCombTextures ) ;

  noVertexTextureSupport = ( nVertTextures < 2 ) ;

  if ( noVertexTextureSupport && debugOpt != DRAW_WITHOUT_SHADERS )
  {
    fprintf ( stderr, "\n"
                      "********************************************\n"
                      "*                                          *\n"
                      "* WARNING: This graphics card doesn't have *\n"
                      "* vertex shader texture support - a work-  *\n"
                      "* around will be used - but this demo will *\n"
                      "* be much less impressive as a result!     *\n"
                      "*                                          *\n"
                      "********************************************\n\n" ) ;
  }
 
  if ( ! noVertexTextureSupport && disableVertexTextureSupport )
  {
    fprintf ( stderr, "WARNING: Vertex Texture Support has"
                      "been disabled from the command line.\n" ) ;
    noVertexTextureSupport = true ;
  }
}


int irand ( int max )
{
  return rand() % max ;
}


float frand ( float max )
{
  return (float)(rand() % 32767) * max / 32767.0f ;
}


static GLSL_ShaderPair *velocityGenerator      ;
static GLSL_ShaderPair *positionGenerator      ;
static GLSL_ShaderPair *grndCollisionGenerator ;
static GLSL_ShaderPair *collisionGenerator     ;
static GLSL_ShaderPair *forceGenerator         ;
static GLSL_ShaderPair *cubeShader             ;

static FrameBufferObject *position    ;
static FrameBufferObject *rotation    ;
static FrameBufferObject *velocity    ;
static FrameBufferObject *rotvelocity ;
static FrameBufferObject *force       ;
static FrameBufferObject *new_force   ;
static FrameBufferObject *massSizeX   ;
static FrameBufferObject *old         ;
static FrameBufferObject *collisions  ;

#define TEX_SIZE         16

#define NUM_CUBES        ( TEX_SIZE * TEX_SIZE )
#define STRIPS_PER_CUBE  2
#define VERTS_PER_STRIP  8
#define NUM_VERTS        ( NUM_CUBES * STRIPS_PER_CUBE * VERTS_PER_STRIP )

static GLuint vbo_vx = 0 ;
static GLuint vbo_tx = 0 ;
static GLuint vbo_co = 0 ;
static float  vertices  [ NUM_VERTS * 3 ] ;
static float  texcoords [ NUM_VERTS * 2 ] ;
static float  colours   [ NUM_VERTS * 4 ] ;
static int    starts    [ NUM_CUBES * STRIPS_PER_CUBE ] ;
static int    lengths   [ NUM_CUBES * STRIPS_PER_CUBE ] ;

static GLuint vbo_collvx = 0 ;
static GLuint vbo_collt0 = 0 ;
static GLuint vbo_collt1 = 0 ;
static float  collvertices   [ NUM_CUBES * 4 * 3 ] ;
static float  colltexcoords0 [ NUM_CUBES * 4 * 2 ] ;
static float  colltexcoords1 [ NUM_CUBES * 4 * 2 ] ;
static int    collstart  ;
static int    colllength ;

static int win_width  = 640 ;
static int win_height = 480 ;

inline int idToIndex ( int x, int y )
{
  /*
    Convert a coordinate pair within the texture to an integer
    1D array index (eg to index into the data array for that texture)
    by multiplying the Y coordinate by the width of the texture and
    adding the X coordinate.
  */
  return y * TEX_SIZE + x ;
}

inline float idToFloat ( int x, int y )
{
  /*
    Convert a coordinate pair within the texture to a float
    by putting one coordinate into the integer part and the
    other into the fraction so we can retrieve Y using floor()
    and X using fract() to recover them later on inside the shader.
  */
  return ((float) idToIndex ( x, y )) / (float)TEX_SIZE ;
}


void keybd ( unsigned char, int, int )
{
  exit ( 0 ) ;
}


void reshape ( int wid, int ht )
{
  win_width  = wid ;
  win_height = ht  ;
}


void initGLcontext ( int argc, char **argv,
                     void (*display)(void),
                     bool disableVertexTextureSupport )
{
  glutInit            ( &argc, argv ) ;
  glutInitDisplayMode ( GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE ) ;
  glutInitWindowSize  ( win_width, win_height ) ;
  glutCreateWindow    ( "Shader Math Demo" ) ;
  glutDisplayFunc     ( display  ) ;
  glutKeyboardFunc    ( keybd    ) ;
  glutReshapeFunc     ( reshape  ) ;

#if defined(GPUP_MAC_OSX) && !defined (VMDMESA)
#else
  glewInit () ;
#endif

  checkVertexTextureSupport ( disableVertexTextureSupport ) ;
}


void initMotionTextures ()
{
  if ( debugOpt == DRAW_WITHOUT_SHADERS ) return ;

  position    = new FrameBufferObject ( TEX_SIZE, TEX_SIZE, 3, FBO_FLOAT ) ;
  rotation    = new FrameBufferObject ( TEX_SIZE, TEX_SIZE, 3, FBO_FLOAT ) ;
  old         = new FrameBufferObject ( TEX_SIZE, TEX_SIZE, 3, FBO_FLOAT ) ;

  if ( debugOpt == DRAW_WITHOUT_PHYSICS )
  {
    velocity    = NULL ;
    rotvelocity = NULL ;
    force       = NULL ;
    new_force   = NULL ;
    massSizeX   = NULL ;
    collisions  = NULL ;
  }
  else
  {
    velocity    = new FrameBufferObject ( TEX_SIZE, TEX_SIZE, 3, FBO_FLOAT ) ;
    rotvelocity = new FrameBufferObject ( TEX_SIZE, TEX_SIZE, 3, FBO_FLOAT ) ;
    force       = new FrameBufferObject ( TEX_SIZE, TEX_SIZE, 3, FBO_FLOAT ) ;
    new_force   = new FrameBufferObject ( TEX_SIZE, TEX_SIZE, 3, FBO_FLOAT ) ;
    massSizeX   = new FrameBufferObject ( TEX_SIZE, TEX_SIZE, 3, FBO_FLOAT ) ;
    collisions  = new FrameBufferObject ( TEX_SIZE, TEX_SIZE, 3, FBO_FLOAT ) ;
  }

  positionData    = new float [ TEX_SIZE * TEX_SIZE * 3 ] ;
  rotationData    = new float [ TEX_SIZE * TEX_SIZE * 3 ] ;

  float *velocityData    ;
  float *rotvelocityData ;
  float *forceData       ;
  float *massSizeXData   ;

  if ( debugOpt == DRAW_WITHOUT_PHYSICS )
  {
    velocityData    = NULL ;
    rotvelocityData = NULL ;
    forceData       = NULL ;
    massSizeXData   = NULL ;
    collisionData   = NULL ;
  }
  else
  {
    velocityData    = new float [ TEX_SIZE * TEX_SIZE * 3 ] ;
    rotvelocityData = new float [ TEX_SIZE * TEX_SIZE * 3 ] ;
    forceData       = new float [ TEX_SIZE * TEX_SIZE * 3 ] ;
    massSizeXData   = new float [ TEX_SIZE * TEX_SIZE * 3 ] ;
    collisionData   = new float [ TEX_SIZE * TEX_SIZE * 3 ] ;
  }

  /* Give the objects some initial position, rotation, mass, force, etc */

  for ( int y = 0 ; y < TEX_SIZE ; y++ )
    for ( int x = 0 ; x < TEX_SIZE ; x++ )
    {
      /*
        Start the cubes on a nice, regular 5m grid, 10m above the ground
        centered around the origin
      */

      positionData    [ idToIndex(x,y) * 3 + 0 ] = 5.0f * (float) (x - TEX_SIZE/2) ;
      positionData    [ idToIndex(x,y) * 3 + 1 ] = 0.0f ; // 10.0f ;
      positionData    [ idToIndex(x,y) * 3 + 2 ] = 5.0f * (float) (y - TEX_SIZE/2) ;

      /* Zero their rotations */
      rotationData    [ idToIndex(x,y) * 3 + 0 ] = 0.0f ;
      rotationData    [ idToIndex(x,y) * 3 + 1 ] = 0.0f ;
      rotationData    [ idToIndex(x,y) * 3 + 2 ] = 0.0f ;

      if ( debugOpt != DRAW_WITHOUT_PHYSICS )
      {
        /* Random (but predominantly upwards) velocities. */
if(irand(2)==0)
{
        velocityData    [ idToIndex(x,y) * 3 + 0 ] = frand ( 1.0f ) ;
        velocityData    [ idToIndex(x,y) * 3 + 1 ] = 0.0f ;
        velocityData    [ idToIndex(x,y) * 3 + 2 ] = frand ( 1.0f ) ;
}
else
{
        velocityData    [ idToIndex(x,y) * 3 + 0 ] = 0.0f ; //frand (  10.0f ) - 5.0f;
        velocityData    [ idToIndex(x,y) * 3 + 1 ] = 0.0f ; //frand ( 100.0f ) ;
        velocityData    [ idToIndex(x,y) * 3 + 2 ] = 0.0f ; //frand (  10.0f ) - 5.0f;
}

        /* Random rotational velocities */
        rotvelocityData [ idToIndex(x,y) * 3 + 0 ] = 0.0f ; //frand ( 3.0f ) ;
        rotvelocityData [ idToIndex(x,y) * 3 + 1 ] = 0.0f ; //frand ( 3.0f ) ;
        rotvelocityData [ idToIndex(x,y) * 3 + 2 ] = 0.0f ; //frand ( 3.0f ) ;

        /* Zero forces */
        forceData       [ idToIndex(x,y) * 3 + 0 ] = 0.0f ;
        forceData       [ idToIndex(x,y) * 3 + 1 ] = 0.0f ;
        forceData       [ idToIndex(x,y) * 3 + 2 ] = 0.0f ;

        /* One kg each */
        massSizeXData   [ idToIndex(x,y) * 3 + 0 ] = 0.05f ; /* Mass */
        massSizeXData   [ idToIndex(x,y) * 3 + 1 ] = 1.0f ;  /* Radius */
        massSizeXData   [ idToIndex(x,y) * 3 + 2 ] = 0.0f ;  /* Unused */

        /* Zero out collision data */
        collisionData [ idToIndex(x,y) * 3 + 0 ] = 0.0f ;
        collisionData [ idToIndex(x,y) * 3 + 1 ] = 0.0f ;
        collisionData [ idToIndex(x,y) * 3 + 2 ] = 0.0f ;
      }
    }

  if ( debugOpt != DRAW_WITHOUT_PHYSICS )
  {
    /*
      Object zero is the 'null' object for collision detection
      so put it far away and stop it from moving around.
    */

    positionData  [ 0 ] = 1000000000.0f ;
    positionData  [ 1 ] = 1000000000.0f ;
    positionData  [ 2 ] = 1000000000.0f ;
    velocityData  [ 0 ] = 0.0f ;
    velocityData  [ 1 ] = 0.0f ;
    velocityData  [ 2 ] = 0.0f ;
    massSizeXData [ 0 ] = 10000000.0f ;  /* Mass   */
    massSizeXData [ 1 ] = 0.00000001f ;  /* Radius */
    massSizeXData [ 2 ] = 0.0f ;         /* Unused */
    collisionData [ 0 ] = 0.0f ;
    collisionData [ 1 ] = 0.0f ;
    collisionData [ 2 ] = 0.0f ;
  }

  /* Initialise the textures */

  position    -> fillTexture ( positionData ) ;
  rotation    -> fillTexture ( rotationData ) ;
  old         -> fillTexture ( positionData ) ;  // Doesn't really need it...

  if ( debugOpt != DRAW_WITHOUT_PHYSICS )
  {
    velocity    -> fillTexture ( velocityData ) ;
    rotvelocity -> fillTexture ( rotvelocityData ) ;
    force       -> fillTexture ( forceData ) ;
    new_force   -> fillTexture ( forceData ) ;
    massSizeX   -> fillTexture ( massSizeXData ) ;
    collisions  -> fillTexture ( collisionData ) ;
  }
}


void initPhysicsShaders ()
{
  if ( debugOpt == DRAW_WITHOUT_SHADERS ||
       debugOpt == DRAW_WITHOUT_PHYSICS )
    return ;

  /*
    The velocity generator shader calculates:

    velocity = old_velocity + delta_T * ( F / m ) ;
  */

  velocityGenerator = new GLSL_ShaderPair (
    "VelocityGenerator",
    NULL, NULL,
    "uniform vec4      g_dt ;"
    "uniform sampler2D old_velocity ;"
    "uniform sampler2D force ;"
    "uniform sampler2D massSizeX ;"
    "void main() {"
    "   gl_FragColor = vec4 ("
    "                  texture2D ( old_velocity, gl_TexCoord[0].st ).xyz +"
    "                  g_dt.w * ( g_dt.xyz +"
    "                  texture2D ( force       , gl_TexCoord[0].st ).xyz /"
    "                  texture2D ( massSizeX   , gl_TexCoord[0].st ).x),"
    "                  1.0 ) ; }",
    "VelocityGenerator Frag Shader" ) ;
    assert ( velocityGenerator  -> compiledOK () ) ;

  /*
    The position generater shader calculates:

    position = old_position + delta_T * velocity ;

    It's also used to update the rotational velocity.
  */

  positionGenerator = new GLSL_ShaderPair (
    "PositionGenerator",
    NULL, NULL,
    "uniform float delta_T ;"
    "uniform sampler2D old_position ;"
    "uniform sampler2D velocity ;"
    "void main() {"
    "   gl_FragColor = vec4 ("
    "                  texture2D ( old_position, gl_TexCoord[0].st ).xyz +"
    "                  texture2D ( velocity    , gl_TexCoord[0].st ).xyz *"
    "                  delta_T,"
    "                  1.0 ) ; }",
    "PositionGenerator Frag Shader" ) ;
  assert ( positionGenerator  -> compiledOK () ) ;


  collisionGenerator = new GLSL_ShaderPair (
    "CollisionGenerator",
     NULL,
    "collisionShader.frag" ) ;
  assert ( collisionGenerator -> compiledOK () ) ;


  forceGenerator = new GLSL_ShaderPair (
    "ForceGenerator",
    NULL, NULL,
    "uniform sampler2D force      ;"
    "uniform sampler2D position   ;"
    "uniform sampler2D collisions ;"
    "void main() {"
    "  vec3 last_force = texture2D ( force     , gl_TexCoord[0].st ).xyz ;"
    "  vec2         id = texture2D ( collisions, gl_TexCoord[0].st ).xy  ;"
    "  vec3        pos = texture2D ( position  , gl_TexCoord[0].st ).xyz ;"
    "  vec3        rel = pos - texture2D ( position, id ).xyz ;"
    "  float      lrel = max ( length ( rel ), 0.001 ) ;"
    "  gl_FragColor = vec4 ( last_force + (rel / lrel) / lrel, 1.0 ) ;"
    "}",
    "ForceGenerator Frag Shader" ) ;
  assert ( forceGenerator -> compiledOK () ) ;


  grndCollisionGenerator = new GLSL_ShaderPair (
    "GroundCollisionGenerator",
    NULL, NULL,
    "uniform sampler2D position ;"
    "uniform sampler2D old_velocity ;"
    "void main() {"
    "   vec3 pos = texture2D ( position    , gl_TexCoord[0].st ).xyz ;"
    "   vec3 vel = texture2D ( old_velocity, gl_TexCoord[0].st ).xyz ;"
    "   if ( pos [ 1 ] < 0.0 ) vel.y = abs(vel.y) ;"
    "   gl_FragColor = vec4 ( vel, 1.0 ) ; }",
    "GroundCollisionGenerator Frag Shader" ) ;
  assert ( grndCollisionGenerator -> compiledOK () ) ;
}


void initCollideVBO ()
{
  float *p  = collvertices   ;
  float *t0 = colltexcoords0 ;
  float *t1 = colltexcoords1 ;

  collstart  = 0 ;
  colllength = NUM_CUBES * 4 ;

  for ( int y = 0 ; y < TEX_SIZE ; y++ )
    for ( int x = 0 ; x < TEX_SIZE ; x++ )
    {
      /* Texcoord 0 data sets which corner of the texture this is.  */

      *t0++ = 0.5f /(float)TEX_SIZE ;
      *t0++ = 0.5f /(float)TEX_SIZE ;

      *t0++ = ((float)TEX_SIZE-0.5f)/(float)TEX_SIZE ;
      *t0++ = 0.5f /(float)TEX_SIZE ;

      *t0++ = ((float)TEX_SIZE-0.5f)/(float)TEX_SIZE ;
      *t0++ = ((float)TEX_SIZE-0.5f)/(float)TEX_SIZE ;

      *t0++ = 0.5f /(float)TEX_SIZE ;
      *t0++ =((float)TEX_SIZE-0.5f)/(float)TEX_SIZE ;

      /* Texcoord 1 sets which cube is which. */

      *t1++ = ((float)x+0.5f)/(float)TEX_SIZE ;
      *t1++ = ((float)y+0.5f)/(float)TEX_SIZE ;

      *t1++ = ((float)x+0.5f)/(float)TEX_SIZE ;
      *t1++ = ((float)y+0.5f)/(float)TEX_SIZE ;

      *t1++ = ((float)x+0.5f)/(float)TEX_SIZE ;
      *t1++ = ((float)y+0.5f)/(float)TEX_SIZE ;

      *t1++ = ((float)x+0.5f)/(float)TEX_SIZE ;
      *t1++ = ((float)y+0.5f)/(float)TEX_SIZE ;

      *p++ = -1 ; *p++ = -1 ; *p++ = 0.0f ;
      *p++ = +1 ; *p++ = -1 ; *p++ = 0.0f ;
      *p++ = +1 ; *p++ = +1 ; *p++ = 0.0f ;
      *p++ = -1 ; *p++ = +1 ; *p++ = 0.0f ;
    }

  glGenBuffersARB ( 1, & vbo_collvx ) ;
  glBindBufferARB ( GL_ARRAY_BUFFER_ARB, vbo_collvx ) ;
  glBufferDataARB ( GL_ARRAY_BUFFER_ARB, colllength * 3 * sizeof(float),
                    collvertices, GL_STATIC_DRAW_ARB ) ;

  glGenBuffersARB ( 1, & vbo_collt0 ) ;
  glBindBufferARB ( GL_ARRAY_BUFFER_ARB, vbo_collt0 ) ;
  glBufferDataARB ( GL_ARRAY_BUFFER_ARB, colllength * 2 * sizeof(float),
                    colltexcoords0, GL_STATIC_DRAW_ARB ) ;

  glGenBuffersARB ( 1, & vbo_collt1 ) ;
  glBindBufferARB ( GL_ARRAY_BUFFER_ARB, vbo_collt1 ) ;
  glBufferDataARB ( GL_ARRAY_BUFFER_ARB, colllength * 2 * sizeof(float),
                    colltexcoords1, GL_STATIC_DRAW_ARB ) ;

  glBindBufferARB ( GL_ARRAY_BUFFER_ARB, 0 ) ;
}


void initCubeVBO ()
{
  float *p = vertices  ;
  float *t = texcoords ;
  float *c = colours   ;
  int    nverts = 0 ;

  for ( int k = 0 ;
            k < (noVertexTextureSupport ? 1 : NUM_CUBES) * STRIPS_PER_CUBE ; k++ )
  {
    starts  [ k ] = k * VERTS_PER_STRIP ;
    lengths [ k ] =     VERTS_PER_STRIP ;
  }

  for ( int y = 0 ; y < (noVertexTextureSupport ? 1 : TEX_SIZE) ; y++ )
    for ( int x = 0 ; x < (noVertexTextureSupport ? 1 : TEX_SIZE) ; x++ )
    {
      /*
        I use the colour data to set which cube is which in
        the physics textures.
      */

      for ( int k = 0 ; k < STRIPS_PER_CUBE * VERTS_PER_STRIP ; k++ )
      {
        *t++ = ((float)x+0.5f)/(float)TEX_SIZE ;
        *t++ = ((float)y+0.5f)/(float)TEX_SIZE ;

        if ( (x==20||x==100) && (y==20||y==100) )
        {
          *c++ = 1.0f ;
          *c++ = 0.0f ;
          *c++ = 0.0f ;
          *c++ = 1.0f ;
        }
        else
        {
          *c++ = 0.0f ;
          *c++ = frand ( 1.0f ) ;
          *c++ = frand ( 1.0f ) ;
          *c++ = 1.0f ;
        }
      }

      float dx, dy, dz ;

      if ( debugOpt == DRAW_WITHOUT_SHADERS )
      {
        dx = 5.0f * (float) (TEX_SIZE/2 - x) ;
        dy = 10.0f ;
        dz = 5.0f * (float) (TEX_SIZE/2 - y) ;
      }
      else
      {
        dx = 0.0f ;
        dy = 0.0f ;
        dz = 0.0f ;
      }

      *p++ = -1 + dx  ; *p++ = -1 + dy ; *p++ = -1 + dz ;
      *p++ = +1 + dx  ; *p++ = -1 + dy ; *p++ = -1 + dz ;
      *p++ = -1 + dx  ; *p++ = +1 + dy ; *p++ = -1 + dz ;
      *p++ = +1 + dx  ; *p++ = +1 + dy ; *p++ = -1 + dz ;
      *p++ = -1 + dx  ; *p++ = +1 + dy ; *p++ = +1 + dz ;
      *p++ = +1 + dx  ; *p++ = +1 + dy ; *p++ = +1 + dz ;
      *p++ = -1 + dx  ; *p++ = -1 + dy ; *p++ = +1 + dz ;
      *p++ = +1 + dx  ; *p++ = -1 + dy ; *p++ = +1 + dz ;

      *p++ = -1 + dx  ; *p++ = +1 + dy ; *p++ = -1 + dz ;
      *p++ = -1 + dx  ; *p++ = +1 + dy ; *p++ = +1 + dz ;
      *p++ = -1 + dx  ; *p++ = -1 + dy ; *p++ = -1 + dz ;
      *p++ = -1 + dx  ; *p++ = -1 + dy ; *p++ = +1 + dz ;
      *p++ = +1 + dx  ; *p++ = -1 + dy ; *p++ = -1 + dz ;
      *p++ = +1 + dx  ; *p++ = -1 + dy ; *p++ = +1 + dz ;
      *p++ = +1 + dx  ; *p++ = +1 + dy ; *p++ = -1 + dz ;
      *p++ = +1 + dx  ; *p++ = +1 + dy ; *p++ = +1 + dz ;

      nverts += STRIPS_PER_CUBE * VERTS_PER_STRIP ;
    }

  glGenBuffersARB ( 1, & vbo_vx ) ;
  glBindBufferARB ( GL_ARRAY_BUFFER_ARB, vbo_vx ) ;
  glBufferDataARB ( GL_ARRAY_BUFFER_ARB, nverts * 3 * sizeof(float),
                    vertices, GL_STATIC_DRAW_ARB ) ;

  glGenBuffersARB ( 1, & vbo_tx ) ;
  glBindBufferARB ( GL_ARRAY_BUFFER_ARB, vbo_tx ) ;
  glBufferDataARB ( GL_ARRAY_BUFFER_ARB, nverts * 2 * sizeof(float),
                    texcoords, GL_STATIC_DRAW_ARB ) ;

  glGenBuffersARB ( 1, & vbo_co ) ;
  glBindBufferARB ( GL_ARRAY_BUFFER_ARB, vbo_co ) ;
  glBufferDataARB ( GL_ARRAY_BUFFER_ARB, nverts * 4 * sizeof(float),
                    colours, GL_STATIC_DRAW_ARB ) ;

  glBindBufferARB ( GL_ARRAY_BUFFER_ARB, 0 ) ;

  if ( debugOpt == DRAW_WITHOUT_SHADERS )
    cubeShader = NULL ;
  else
  {
    if ( noVertexTextureSupport )
      cubeShader = new GLSL_ShaderPair ( "CubeShader",
                                              "cubeShaderNoTexture.vert",
                                                       "cubeShader.frag" ) ;
    else
      cubeShader = new GLSL_ShaderPair ( "CubeShader", "cubeShader.vert",
                                                       "cubeShader.frag" ) ;
    assert ( cubeShader -> compiledOK () ) ;
  }
}


void drawCubesTheHardWay ()
{
  /*
    Without vertex texture support, we have to read the position/rotation
    data back from the hardware every frame and render each cube individually.
  */

  float p0 = positionData [ 0 ] ;
  float p1 = positionData [ 1 ] ;
  float p2 = positionData [ 2 ] ;

  position -> fetchTexture ( positionData ) ;
  rotation -> fetchTexture ( rotationData ) ;

  //if ( positionData [ 0 ] == p0 &&
  //     positionData [ 1 ] == p1 &&
  //     positionData [ 2 ] == p2 )
  //{
  //  fprintf ( stderr, "WARNING: If nothing seems to be working, you may\n"
  //                    "have an old version of the nVidia driver.\n"
  //                    "Version 76.76 is known to be bad.\n" ) ;
  //}

  cubeShader -> use () ;  /* Math = Cube shader */

  glPushClientAttrib   ( GL_CLIENT_VERTEX_ARRAY_BIT ) ;

  glDisableClientState  ( GL_TEXTURE_COORD_ARRAY ) ;

  glEnableClientState  ( GL_COLOR_ARRAY ) ;
  glBindBufferARB      ( GL_ARRAY_BUFFER_ARB, vbo_co ) ;
  glColorPointer       ( 4, GL_FLOAT, 0, vbo_co ? NULL : colours ) ;

  glEnableClientState  ( GL_VERTEX_ARRAY ) ;
  glBindBufferARB      ( GL_ARRAY_BUFFER_ARB, vbo_vx ) ;
  glVertexPointer      ( 3, GL_FLOAT, 0, vbo_vx ? NULL : vertices ) ;

  for ( int y = 0 ; y < TEX_SIZE ; y++ )
    for ( int x = 0 ; x < TEX_SIZE ; x++ )
    {
      float *pos = & positionData [ idToIndex ( x, y ) * 3 ] ;
      float *rot = & rotationData [ idToIndex ( x, y ) * 3 ] ;

      glPushMatrix () ;
      glTranslatef ( pos [ 0 ], pos [ 1 ], pos [ 2 ] ) ;
      glRotatef ( rot [ 0 ] * 180.0f / 3.14159f, 0, 1, 0 ) ;
      glRotatef ( rot [ 1 ] * 180.0f / 3.14159f, 1, 0, 0 ) ;
      glRotatef ( rot [ 2 ] * 180.0f / 3.14159f, 0, 0, 1 ) ;
      glMultiDrawArraysEXT ( GL_TRIANGLE_STRIP, (GLint*)starts,
                                                (GLint*)lengths,
                                                STRIPS_PER_CUBE ) ;
      glPopMatrix () ;
    }

  glBindBufferARB      ( GL_ARRAY_BUFFER_ARB, 0 ) ;
  glPopClientAttrib () ;
}


void drawCubeVBO ()
{
  /*
    With vertex texture support, we can leave the position/rotation
    data on the hardware and render all of the cubes in one big VBO!
  */

  if ( debugOpt != DRAW_WITHOUT_SHADERS )
  {
    cubeShader -> use () ;  /* Math = Cube shader */
    cubeShader -> applyTexture ( "position", position, 0 ) ;
    cubeShader -> applyTexture ( "rotation", rotation, 1 ) ;
  }

  glPushClientAttrib   ( GL_CLIENT_VERTEX_ARRAY_BIT ) ;

  glEnableClientState  ( GL_TEXTURE_COORD_ARRAY ) ;
  glBindBufferARB      ( GL_ARRAY_BUFFER_ARB, vbo_tx ) ;
  glTexCoordPointer    ( 2, GL_FLOAT, 0, vbo_tx ? NULL : texcoords ) ;

  glEnableClientState  ( GL_COLOR_ARRAY ) ;
  glBindBufferARB      ( GL_ARRAY_BUFFER_ARB, vbo_co ) ;
  glColorPointer       ( 4, GL_FLOAT, 0, vbo_co ? NULL : colours ) ;

  glEnableClientState  ( GL_VERTEX_ARRAY ) ;
  glBindBufferARB      ( GL_ARRAY_BUFFER_ARB, vbo_vx ) ;
  glVertexPointer      ( 3, GL_FLOAT, 0, vbo_vx ? NULL : vertices ) ;

  glMultiDrawArraysEXT ( GL_TRIANGLE_STRIP, (GLint*)starts, (GLint*)lengths,
                         NUM_CUBES * STRIPS_PER_CUBE ) ;
  glBindBufferARB      ( GL_ARRAY_BUFFER_ARB, 0 ) ;
  glPopClientAttrib () ;
}


void drawCubes ()
{
  glMatrixMode      ( GL_PROJECTION ) ;
  glLoadIdentity    () ;
  glFrustum         ( -1.0f, 1.0f,
                      -1.0f / ((float)win_width/(float)win_height),
                       1.0f / ((float)win_width/(float)win_height),
                       1.0f, 1000000.0f) ;

  /* Set up camera position */

  glMatrixMode      ( GL_MODELVIEW ) ;
  glLoadIdentity    () ;
  glTranslatef      ( 10.0f * (float)TEX_SIZE/128.0f,
                    -100.0f * (float)TEX_SIZE/128.0f,
                    -500.0f * (float)TEX_SIZE/128.0f ) ;
  glRotatef         ( 20.0, 1.0, 0.0, 0.0 ) ;

  glEnable          ( GL_DEPTH_TEST ) ;
  glEnable          ( GL_CULL_FACE  ) ;
  glCullFace        ( GL_FRONT ) ;

  glClearColor      ( 0.7f, 0.7f, 0.7f, 1.0f ) ;
  glClear           ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ) ;

  if ( noVertexTextureSupport )
    drawCubesTheHardWay () ;
  else
    drawCubeVBO () ;
}


void runCollisionDetection ()
{
static Clock ck ;
ck.update () ;
double tall=ck.getDeltaTime () ;
  static int firsttime = true ;
  static unsigned int query = 0 ;

  FrameBufferObject *tmp ;
  FrameBufferObject *SCM = old ;
  FrameBufferObject *DCM = collisions ;
  unsigned int numHits ;

  if ( firsttime )
  {
    glGenQueriesARB ( 1, (GLuint*) & query ) ;
    firsttime = false ;
  }

  /* Fill SCM with big numbers */

  glClearColor ( 1.0f, 1.0f, 1.0f, 1.0f ) ;
  SCM -> prepare ( true ) ;

  glClearColor ( 0.0f, 0.0f, 0.0f, 0.0f ) ;
  force -> prepare ( true ) ;  /* Zero out all of the forces. */

  int numPasses = 0 ;

  glPushClientAttrib   ( GL_CLIENT_VERTEX_ARRAY_BIT ) ;

  glClientActiveTexture( GL_TEXTURE1 ) ;
  glEnableClientState  ( GL_TEXTURE_COORD_ARRAY ) ;
  glBindBufferARB      ( GL_ARRAY_BUFFER_ARB, vbo_collt1 ) ;
  glTexCoordPointer    ( 2, GL_FLOAT, 0, vbo_collt1 ? NULL : colltexcoords1 ) ;

  glClientActiveTexture( GL_TEXTURE0 ) ;
  glEnableClientState  ( GL_TEXTURE_COORD_ARRAY ) ;
  glBindBufferARB      ( GL_ARRAY_BUFFER_ARB, vbo_collt0 ) ;
  glTexCoordPointer    ( 2, GL_FLOAT, 0, vbo_collt0 ? NULL : colltexcoords0 ) ;

  glEnableClientState  ( GL_VERTEX_ARRAY ) ;
  glBindBufferARB      ( GL_ARRAY_BUFFER_ARB, vbo_collvx ) ;
  glVertexPointer      ( 3, GL_FLOAT, 0, vbo_collvx ? NULL : collvertices ) ;

  while ( true )
  {
    collisionGenerator -> use () ;
    collisionGenerator -> applyTexture ( "position"  , position, 0 ) ;
    collisionGenerator -> applyTexture ( "old_collisions", SCM , 1 ) ;

    /* Fill DCM with zeroes */
    DCM -> prepare ( true ) ;

    glBeginQueryARB ( GL_SAMPLES_PASSED_ARB, query ) ;

    glMultiDrawArraysEXT ( GL_QUADS, (GLint*)& collstart, (GLint*)& colllength,
                           1 ) ;
numPasses++ ;

    glEndQueryARB   ( GL_SAMPLES_PASSED_ARB ) ;

    forceGenerator -> use () ;
    forceGenerator -> applyTexture ( "position"  , position , 0 ) ;
    forceGenerator -> applyTexture ( "force"     , force    , 1 ) ;
    forceGenerator -> applyTexture ( "collisions", DCM      , 2 ) ;

    GLuint sampleCount ;

    glGetQueryObjectuivARB ( query, GL_QUERY_RESULT_ARB, &sampleCount ) ;

//fprintf ( stderr, "%d ", sampleCount ) ;

    if ( sampleCount == 0 )
      break ;

    new_force -> paint () ;

    tmp = new_force ;
    new_force = force ;
    force = tmp ;

    tmp = DCM ;
    DCM = SCM ;
    SCM = tmp ;
  }

  glBindBufferARB      ( GL_ARRAY_BUFFER_ARB, 0 ) ;
  glPopClientAttrib () ;

ck.update () ;
double tdone=ck.getDeltaTime () ;
static int ii = 0 ;
ii++;
if (ii%100==0)
fprintf ( stderr, "Performance: %d passes %d cubes: other=%fms collisions=%fms\n", numPasses, NUM_CUBES, tall*1000.0, tdone*1000.0 ) ;
}


void runPhysics ()
{
  FrameBufferObject *tmp ;

  /* Do some simple physics calculations in four stages */

  /* Copy old velocity into old. */
  tmp = old ;
  old = velocity ;
  velocity = tmp ;

  velocityGenerator -> use () ;
  velocityGenerator -> applyTexture ( "old_velocity", old      , 0 ) ;
  velocityGenerator -> applyTexture ( "force"       , force    , 1 ) ;
  velocityGenerator -> applyTexture ( "massSizeX"   , massSizeX, 2 ) ;
  velocityGenerator -> setUniform4f ( "g_dt", 0.0f, /*-9.8f */ 0.0f, 0.0f, TIMESTEP ) ;
  velocity -> paint ()  ;

  /* Copy old position into old. */
  tmp = old ;
  old = position ;
  position = tmp ;

  positionGenerator -> use () ;
  positionGenerator -> applyTexture ( "old_position", old     , 0 ) ;
  positionGenerator -> applyTexture ( "velocity"    , velocity, 1 ) ;
  positionGenerator -> setUniform1f ( "delta_T", TIMESTEP ) ;
  position -> paint ()  ;

  /* Copy old velocity into old. */

  tmp = old ;
  old = velocity ;
  velocity = tmp ;

  grndCollisionGenerator -> use () ;
  grndCollisionGenerator -> applyTexture ( "position"    , position, 0 ) ;
  grndCollisionGenerator -> applyTexture ( "old_velocity", old     , 1 ) ;
  velocity -> paint ()  ;

  /* Copy old rotation into old. */
  tmp = old ;
  old = rotation ;
  rotation = tmp ;

  positionGenerator -> use () ;
  positionGenerator -> applyTexture ( "old_position", old        , 0 ) ;
  positionGenerator -> applyTexture ( "velocity"    , rotvelocity, 1 ) ;
  positionGenerator -> setUniform1f ( "delta_T", TIMESTEP  ) ;
  rotation -> paint ()  ;

  restoreFrameBuffer () ;
}



void display ( void )
{
  if ( debugOpt != DRAW_WITHOUT_SHADERS &&
       debugOpt != DRAW_WITHOUT_PHYSICS )
  {
    runCollisionDetection () ;
    runPhysics            () ;
  }

  /* Now render the scene using the results */

  glViewport ( 0, 0, win_width, win_height ) ;

  drawCubes () ;

  /* All done! */

  glutSwapBuffers   () ;
  glutPostRedisplay () ;
}


void help ()
{
  fprintf ( stderr, "GPUphysics: Usage -\n\n" ) ;
  fprintf ( stderr, "   GPUphysics_demo  [-c][-p][-v][-a][-v]\n\n" ) ;
  fprintf ( stderr, "Where:\n" ) ;
  fprintf ( stderr, "  -s  -- Draw with shaders at all\n" ) ;
  fprintf ( stderr, "  -p  -- Draw with shaders but no physics\n" ) ;
  fprintf ( stderr, "  -a  -- Draw with all features enabled [default]\n" ) ;
  fprintf ( stderr, "  -v  -- Disable vertex textures even if "
                                 "they are supported in hardware\n" ) ;
  fprintf ( stderr, "\n" ) ;
}


int main ( int argc, char **argv )
{
#ifdef WIN32
	//until there is a first GPU that works under VertexTextureSupport under WIN32, disable it
bool disableVertexTextureSupport = true;
#else
  bool disableVertexTextureSupport = false ;
#endif 
  debugOpt = DRAW_ALL ;

  for ( int i = 1 ; i < argc ; i++ )
  {
    if ( argv [ i ][ 0 ] == '-' || argv [ i ][ 0 ] == '+' )
      for ( int j = 1 ; argv[i][j] != '\0' ; j++ )
        switch ( argv [ i ][ j ] )
        {
          case 's' : debugOpt = DRAW_WITHOUT_SHADERS    ; break ; 
          case 'p' : debugOpt = DRAW_WITHOUT_PHYSICS    ; break ; 
          case 'a' : debugOpt = DRAW_ALL                ; break ;

          case 'v' : disableVertexTextureSupport = true ; break ;
          default  : help () ; exit ( 0 ) ;
        }
    else
    {
      help () ;
      exit ( 0 ) ;
    }
  }

  initGLcontext ( argc, argv, display, disableVertexTextureSupport ) ;
  initMotionTextures () ;
  initPhysicsShaders () ;
  initCubeVBO        () ;
  initCollideVBO     () ;
  glutMainLoop () ;
  return 0 ;
}

