#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>

#define WIN32_LEAN_AND_MEAN
#define WIN32_EXTRA_LEAN
#include <windows.h>
#include <mmsystem.h>

#include <gl/gl.h>

#define PI_OVER_180    0.01745329F
#define DEG2RAD( a )   ( (a) * PI_OVER_180 )

#define ELEMENTS_PER_VERTEX 3
#define ELEMENTS_PER_NORMAL 3
#define BYTES_PER_CACHELINE 32

/* struct used to manage color ramps */
struct colorIndexState {
    GLfloat amb[3];	/* ambient color / bottom of ramp */
    GLfloat diff[3];	/* diffuse color / middle of ramp */
    GLfloat spec[3];	/* specular color / top of ramp */
    GLfloat ratio;	/* ratio of diffuse to specular in ramp */
    GLint indexes[3];	/* where ramp was placed in palette */
};

#define NUM_COLORS (sizeof(s_colors) / sizeof(s_colors[0]))
struct colorIndexState s_colors[] = {
    {
        { 0.0F, 0.0F, 0.0F },
        { 1.0F, 0.0F, 0.0F },
        { 1.0F, 1.0F, 1.0F },
        0.75F, { 0, 0, 0 },
    },
    {
        { 0.0F, 0.05F, 0.05F },
        { 0.9F, 0.0F, 1.0F },
        { 1.0F, 1.0F, 1.0F },
        1.0F, { 0, 0, 0 },
    },
    {
        { 0.0F, 0.0F, 0.0F },
        { 1.0F, 0.9F, 0.1F },
        { 1.0F, 1.0F, 1.0F },
        0.75F, { 0, 0, 0 },
    },
    {
        { 0.0F, 0.0F, 0.0F },
        { 0.1F, 1.0F, 0.9F },
        { 1.0F, 1.0F, 1.0F },
        0.75F, { 0, 0, 0 },
    },
};

static void (APIENTRY *LockArraysSGI)(GLint first, GLsizei count);
static void (APIENTRY *UnlockArraysSGI)(void);
static void (APIENTRY *CullParameterfvSGI)( GLenum pname, GLfloat *params );

static GLint s_lit_tex_indexes[3];

static int s_num_rows = 16;
static int s_num_cols = 16;

static int s_winwidth  = 320;
static int s_winheight = 240;

#define MS_TO_RENDER       5000

#define DRAW_VERTEX3FV          0
#define DRAW_DRAW_ELEMENTS      1
#define DRAW_DRAW_ARRAYS        2
#define DRAW_ARRAY_ELEMENT      3

static const char *s_class_name  = "GL Sphere";
static const char *s_window_name = "GL Sphere";

static BOOL    s_rgba        = TRUE;
static BOOL    s_lighting    = TRUE;
static BOOL    s_benchmark   = FALSE;
static BOOL    s_remote      = FALSE;
static BOOL    s_lock_arrays = FALSE;
static BOOL    s_vcull       = FALSE;

static HPALETTE s_hPalette = NULL;
static HWND     s_hWnd  = NULL;
static HGLRC    s_hglrc = NULL;
static HDC      s_hDC   = NULL;
static int      s_bpp   = 8;
static int      s_draw_method = DRAW_VERTEX3FV;

static unsigned long s_vertices_processed = 0L;
static unsigned long s_triangles_processed = 0L;
static float         s_elapsed_time;
static unsigned long s_start, s_stop;

/*
** this maintains the data for drawing stuff via tristrips
*/
static float **s_sphere_points;
static float **s_sphere_normals;

/*
** this maintains the data for drawing stuff via vertex arrays and array elements
*/
static float  *s_sphere_point_array;
static float  *s_sphere_normal_array;

/*
** this stores the data for drawing stuff using interleaved arrays with
** draw elements
*/
static float           *s_sphere_interleaved_array;
static unsigned int   **s_sphere_elements;

/*
** this maintains the data for drawing stuff via vertex arrays and drawarrays
*/
static float  **s_sphere_point_draw_array;
static float  **s_sphere_normal_draw_array;

static BOOL    SphereCreateGeometry( int rows, int cols, float scale );
static BOOL    InitSphere( HINSTANCE hInstance, LPSTR lpszCmdLine );
static void    ShutdownSphere( void );
static void    SphereSetDefaults( void );
static void    SphereNormalizeVector( float v[3] );
static void    SphereHelp( void );
static void    SphereSetupPalette( HDC hDC );

static void *AllocAlignedX( int num_bytes, int byte_alignment );

static void SphereRedraw( void );

static BOOL SphereSetupPixelFormat( HDC hDC );

static LRESULT APIENTRY WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);


int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
                      LPSTR lpszCmdLine,
                      int nCmdShow )
{
   MSG msg;

   if ( InitSphere( hInstance, lpszCmdLine ) )
   {
      ShowWindow( s_hWnd, nCmdShow );
      UpdateWindow( s_hWnd );

      while ( 1 )
      {
         if ( s_benchmark )
         {
            int i;
            int vertices_to_render = 1000000;
            int frames_to_render = vertices_to_render / ( s_num_rows * ( s_num_cols * 2 + 2 ) );

            if ( frames_to_render < 1 ) frames_to_render = 1;

            s_start = timeGetTime();

            for ( i = 0; i < frames_to_render; i++ )
            {
               SphereRedraw();
            }
            glFinish();
            s_stop = timeGetTime();
            s_elapsed_time = ( s_stop - s_start ) / 1000.0F;
            ShutdownSphere();
            return 0;
         }
         else
         {
            while ( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) == FALSE )
            {
               SphereRedraw();
            }
            if ( GetMessage( &msg, NULL, 0, 0 ) != TRUE )
            {
               ShutdownSphere();
               return msg.wParam;
            }
            TranslateMessage( &msg );
            DispatchMessage( &msg );
         }
      }
   }

   return 0;
}

static LRESULT APIENTRY WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
   switch ( msg )
   {
   case WM_CREATE:
      s_hDC = GetDC( hWnd );

      if ( !SphereSetupPixelFormat( s_hDC ) )
         exit( 1 );
      SphereSetupPalette( s_hDC );
      s_hglrc = wglCreateContext( s_hDC );
      wglMakeCurrent( s_hDC, s_hglrc );
      /*
      ** see what kind of extensions are supported
      */
      if ( strstr( glGetString( GL_EXTENSIONS ), "GL_SGI_compiled_vertex_array " ) != 0 )
      {
          LockArraysSGI   = ( void (APIENTRY *)(GLint, GLint) ) wglGetProcAddress( "glLockArraysSGI" );
          UnlockArraysSGI = ( void (APIENTRY *)( void ) )       wglGetProcAddress( "glUnlockArraysSGI" );
      }
#ifdef GL_SGI_cull_vertex
      if ( s_vcull )
      {
          if ( strstr( glGetString( GL_EXTENSIONS ), "GL_SGI_cull_vertex" ) )
          {
              if ( ( CullParameterfvSGI = ( void (APIENTRY *)(GLenum, GLfloat*) ) wglGetProcAddress( "glCullParameterfvSGI" ) ) == 0 )
              {
                  MessageBox( 0, "-vcull specified but wglGetProcAddress failed", "Sphere Warning", MB_OK | MB_ICONINFORMATION );
                  s_vcull = FALSE;
              }
          }
          else
          {
              s_vcull = FALSE;
              MessageBox( 0, "-vcull specified but no vcull extension exists", "Sphere Warning", MB_OK | MB_ICONINFORMATION );
          }
      }
#endif

      SphereSetDefaults();

      /*
      ** set vertex and normal array pointers
      */
      if ( s_draw_method == DRAW_ARRAY_ELEMENT )
      {
	  glEnableClientState( GL_VERTEX_ARRAY );
	  glEnableClientState( GL_NORMAL_ARRAY );
	  glVertexPointer( ELEMENTS_PER_VERTEX, GL_FLOAT, 0, ( const GLvoid * ) s_sphere_point_array );
	  glNormalPointer( GL_FLOAT, ELEMENTS_PER_NORMAL * sizeof( float ) , ( const GLfloat * ) s_sphere_normal_array );
      }
      else if ( s_draw_method == DRAW_DRAW_ARRAYS )
      {
	  glEnableClientState( GL_VERTEX_ARRAY );
	  glEnableClientState( GL_NORMAL_ARRAY );
      }
      else if ( s_draw_method == DRAW_DRAW_ELEMENTS )
      {
      }

      {
         char  buffer[1000], buf2[1000];
         char *dummy;
         
         SearchPath( NULL, "opengl32.dll", NULL, 1000, buffer, &dummy );
         sprintf( buf2, "GL Sphere (%s)", buffer );
         SetWindowText( hWnd, buf2 );
      }
      return 0;
   case WM_DESTROY:
      PostQuitMessage( 0 );
      return 0;
   case WM_CHAR:	
      switch ( wParam )
      {
      case 'd':
          s_draw_method = DRAW_DRAW_ELEMENTS;
          glDisableClientState( GL_VERTEX_ARRAY );
          glDisableClientState( GL_NORMAL_ARRAY );
          break;
      case 'a':
          s_draw_method = DRAW_DRAW_ARRAYS;
          glEnableClientState( GL_VERTEX_ARRAY );
          glEnableClientState( GL_NORMAL_ARRAY );
          break;
      case 'l':
          s_lock_arrays = !s_lock_arrays;
          break;
      case 27:
         PostQuitMessage( 0 );
         return 0;
      }
      break;
   case WM_SIZE:
       if ( s_hglrc )
       {
           int winWidth  = ( int ) LOWORD( lParam );
           int winHeight = ( int ) HIWORD( lParam );

           glViewport( 0, 0, winWidth, winHeight );
       }
       return 0;
   case WM_PALETTECHANGED:
       if (s_hPalette != NULL && (HWND) wParam != hWnd) 
       {
           UnrealizeObject( s_hPalette );
           SelectPalette( s_hDC, s_hPalette, FALSE );
           RealizePalette( s_hDC );
           return 0;
       }
       break;
   case WM_QUERYNEWPALETTE:
       if ( s_hPalette != NULL ) 
       {
           UnrealizeObject( s_hPalette );
           SelectPalette( s_hDC, s_hPalette, FALSE );
           RealizePalette( s_hDC );
           return TRUE;
       }
       break;
   case WM_COMMAND:
      break;
   }

   return DefWindowProc( hWnd, msg, wParam, lParam);
}

static BOOL InitSphere( HINSTANCE hInstance, LPSTR lpszCmdLine )
{
   WNDCLASS wc;
   
   wc.style       = CS_HREDRAW | CS_VREDRAW;
   wc.lpfnWndProc = WndProc;
   wc.cbClsExtra  = 0;
   wc.cbWndExtra  = 0;
   wc.hInstance   = hInstance;
   wc.hIcon       = LoadIcon(NULL, IDI_APPLICATION);
   wc.hCursor     = LoadCursor(NULL, IDC_ARROW);
   wc.hbrBackground = GetStockObject(WHITE_BRUSH);
   wc.lpszMenuName = NULL;
   wc.lpszClassName = s_class_name;
   
   RegisterClass( &wc );
   
   {
      HDC hdc = GetDC( 0 );
      
      s_bpp = GetDeviceCaps( hdc, BITSPIXEL );

#if NOTYET
      if ( ( s_bpp <= 8 ) && !strstr( lpszCmdLine, "-332" ) )
         s_rgba = 0;
      else 
#endif

      s_rgba = 1;
      
      ReleaseDC( 0, hdc );
   }

   /*
   ** check to see how to draw the sphere
   */
   if ( strstr( lpszCmdLine, "-arrayelement" ) )
      s_draw_method = DRAW_ARRAY_ELEMENT;
   else if ( strstr( lpszCmdLine, "-drawarrays" ) )
      s_draw_method = DRAW_DRAW_ARRAYS;
   else if ( strstr( lpszCmdLine, "-drawelements" ) )
      s_draw_method = DRAW_DRAW_ELEMENTS;
   else
      s_draw_method = DRAW_VERTEX3FV;

   if ( strstr( lpszCmdLine, "-benchmark" ) )
      s_benchmark = TRUE;
   if ( strstr( lpszCmdLine, "-remote" ) )
      s_remote = TRUE;

   if ( strstr( lpszCmdLine, "-rows:" ) )
   {
      s_num_rows = atoi( strchr( strstr( lpszCmdLine, "-rows:" ), ':' ) + 1 );
   }
   if ( strstr( lpszCmdLine, "-cols:" ) )
   {
      s_num_cols = atoi( strchr( strstr( lpszCmdLine, "-cols:" ), ':' ) + 1 );
   }

   if ( strstr( lpszCmdLine, "-lockarrays" ) )
      s_lock_arrays = TRUE;

   if ( strstr( lpszCmdLine, "-vcull" ) )
       s_vcull = TRUE;

   if ( strstr( lpszCmdLine, "-?" ) || strstr( lpszCmdLine, "/?" ) )
   {
      SphereHelp();
      return FALSE;
   }

   if ( strstr( lpszCmdLine, "-width:" ) )
       s_winwidth  = atoi( strchr( strstr( lpszCmdLine, "-width:" ), ':' ) + 1 );
   if ( strstr( lpszCmdLine, "-height:" ) )
       s_winheight = atoi( strchr( strstr( lpszCmdLine, "-height:" ), ':' ) + 1 );

   /*
   ** create geometry
   */
   if ( !SphereCreateGeometry( s_num_rows, s_num_cols, 1.0F ) )
      return FALSE;

   /*
   ** create a window of the previously defined class
   */
   s_hWnd = CreateWindow( s_class_name,
                          s_window_name, 
                          WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
                          10, 10, 
                          s_winwidth, s_winheight,
                          NULL,
                          NULL,
                          hInstance,
                          NULL );

   if ( s_hWnd != NULL )
      return TRUE;
   return FALSE;
}

void ShutdownSphere( void )
{
   if ( s_benchmark && !s_remote )
   {
      char buffer[1000]="";
      char temp[100];

      sprintf( temp, "Vendor: %s\n\n", glGetString( GL_VENDOR ) );
      strcat( buffer, temp );

      sprintf( temp, "%.2f vertices/second\n", s_vertices_processed / s_elapsed_time );
      strcat( buffer, temp );

      sprintf( temp, "%.2f triangles/second\n", s_triangles_processed / s_elapsed_time );
      strcat( buffer, temp );

      MessageBox( 0, buffer, "Performance", MB_OK | MB_ICONINFORMATION );
   }

   if ( s_hglrc )
   {
      wglMakeCurrent( NULL, NULL );
      wglDeleteContext( s_hglrc );
   }
   ReleaseDC( s_hWnd, s_hDC );

}

static BOOL SphereSetupPixelFormat( HDC hDC )
{
   unsigned char pixelType = s_rgba ? PFD_TYPE_RGBA : PFD_TYPE_COLORINDEX;
   PIXELFORMATDESCRIPTOR pfd = 
   {
      sizeof( PIXELFORMATDESCRIPTOR ),
      1,
      PFD_DRAW_TO_WINDOW |	PFD_SUPPORT_OPENGL |	PFD_DOUBLEBUFFER,
      pixelType,
      s_bpp,
      0, 0, 0, 0, 0, 0,
      0,
      0,
      0,
      0, 0, 0, 0,
      16,                 // 16-bit depth buffer
      0,                  // no stencil buffer
      0,                  // no aux buffers
      PFD_MAIN_PLANE,			/* main layer */
      0,	
      0, 0, 0
   };
   int  selected_pf;
   
   if ( ( selected_pf = ChoosePixelFormat( hDC, &pfd ) ) == 0 )
   {
      MessageBox( 0, "Failed to find acceptable pixel format", "GL Sphere Error", MB_ICONERROR | MB_OK );
      return FALSE;
   }
   
   if ( !SetPixelFormat( hDC, selected_pf, &pfd) )
   {
      MessageBox( 0, "Failed to SetPixelFormat", "GL Sphere Error", MB_ICONERROR | MB_OK );
      return FALSE;
   }
   return TRUE;
}

static void SphereSetDefaults( void )
{
   // setup a default color
   glColor3f( 1.0F, 1.0F, 1.0F );
   glClearColor( 0.0F, 0.0F, 1.0F, 1.0F );

#if 0
   glClearDepth( 1.0F );
   glDepthFunc( GL_LEQUAL );
   glEnable( GL_DEPTH_TEST );
   glDepthMask( GL_TRUE );
#endif

   // setup perspective
   glMatrixMode( GL_PROJECTION );
   glFrustum( -0.5, 0.5, -0.5, 0.5, 1.0, 1000.0 );

   // setup viewer
   glMatrixMode( GL_MODELVIEW );
   glLoadIdentity();
   glTranslatef( 0.0F, 0.0F, -3.0F );

   // setup Z-buffering
   // setup shade model
   glShadeModel( GL_SMOOTH );

   // setup lighting
   if ( !s_lighting )
   {
      glDisable( GL_LIGHTING );
      glDisable( GL_LIGHT0 );
   }
   else
   {
      GLfloat light0Pos[4] = { 0.70F, 0.70F, 1.25F, 0.00F };
      GLfloat matAmb[4] = { 0.01F, 0.01F, 0.01F, 1.00F };
      GLfloat matDiff[4] = { 0.65F, 0.05F, 0.20F, 0.60F };
      GLfloat matSpec[4] = { 0.50F, 0.50F, 0.50F, 1.00F };
      GLfloat matShine = 20.00F;

      glEnable( GL_LIGHTING );
      glEnable( GL_LIGHT0 );
      glLightfv(GL_LIGHT0, GL_POSITION, light0Pos);
      glMaterialfv(GL_FRONT, GL_AMBIENT, matAmb);
      glMaterialfv(GL_FRONT, GL_DIFFUSE, matDiff);
      glMaterialfv(GL_FRONT, GL_SPECULAR, matSpec);
      glMaterialf(GL_FRONT, GL_SHININESS, matShine);
   }

   // setup cull mode
   glEnable( GL_CULL_FACE );

#ifdef GL_SGI_cull_vertex
   if ( s_vcull )
   {
       GLfloat eyeDir[] = { 0.0F, 0.0F, 1.0F, 0.0F };

       glEnable(GL_CULL_VERTEX_SGI);

       if ( CullParameterfvSGI )
           CullParameterfvSGI( GL_CULL_VERTEX_EYE_POSITION_SGI, eyeDir );
   }
   else
   {
       glDisable(GL_CULL_VERTEX_SGI);
   }
#endif

   // setup dither
   glEnable( GL_DITHER );

   glClear( GL_COLOR_BUFFER_BIT );
   SwapBuffers( s_hDC );
}

static BOOL SphereCreateGeometry( int rows, int cols, float scale )
{
   float   cap_offset = 10.0F;

   float   x_delta = ( 360.0F - 2.0f * cap_offset ) / cols;
   float   y_delta = ( 180.0F - 2.0f * cap_offset ) / rows;
   int     row, col;
   int     num_vertex_rows = rows + 1;
   int     num_vertex_cols = cols;

   float  *spa, *sna, *sia;

   s_sphere_points  = ( float ** ) AllocAlignedX( sizeof( float ) * num_vertex_rows, BYTES_PER_CACHELINE );
   s_sphere_normals = ( float ** ) AllocAlignedX( sizeof( float ) * num_vertex_rows, BYTES_PER_CACHELINE );

   spa = s_sphere_point_array  = ( float * ) AllocAlignedX( sizeof( float ) * ELEMENTS_PER_VERTEX * num_vertex_rows * num_vertex_cols, BYTES_PER_CACHELINE );
   sna = s_sphere_normal_array = ( float * ) AllocAlignedX( sizeof( float ) * ELEMENTS_PER_NORMAL * num_vertex_rows * num_vertex_cols, BYTES_PER_CACHELINE );

   if ( !s_sphere_points || !s_sphere_normals || !s_sphere_point_array || !s_sphere_normal_array )
      return FALSE;

   for ( row = 0; row < num_vertex_rows; row++ )
   {
      s_sphere_points[row]  = ( float * ) AllocAlignedX( sizeof( float ) * num_vertex_cols * ELEMENTS_PER_VERTEX, BYTES_PER_CACHELINE );
      s_sphere_normals[row] = ( float * ) AllocAlignedX( sizeof( float ) * num_vertex_cols * ELEMENTS_PER_NORMAL, BYTES_PER_CACHELINE );
   }

   for ( row = 0; row < num_vertex_rows; row++ )
   {
      float rowangle;

      rowangle = row*y_delta + cap_offset;

      for ( col = 0; col < num_vertex_cols; col++ )
      {
         float colangle;

         colangle = col*x_delta;

         spa[0] = s_sphere_points[row][col*ELEMENTS_PER_VERTEX+0] = ( float ) ( sin( DEG2RAD( colangle ) ) * sin ( DEG2RAD( rowangle ) ) * scale );
         spa[1] = s_sphere_points[row][col*ELEMENTS_PER_VERTEX+1] = ( float ) ( sin( DEG2RAD( rowangle + 270.0F ) ) * scale );
         spa[2] = s_sphere_points[row][col*ELEMENTS_PER_VERTEX+2] = ( float ) ( sin( DEG2RAD( colangle + 270.0F ) ) * sin ( DEG2RAD( rowangle ) ) * scale );
#if ( ELEMENTS_PER_VERTEX == 4 )
         spa[3] = s_sphere_points[row][col*ELEMENTS_PER_VERTEX+3] = 1.0F;
#endif

         spa += ELEMENTS_PER_VERTEX;

         sna[0] = s_sphere_normals[row][col*ELEMENTS_PER_NORMAL+0] = s_sphere_points[row][col*ELEMENTS_PER_VERTEX+0];
         sna[1] = s_sphere_normals[row][col*ELEMENTS_PER_NORMAL+1] = s_sphere_points[row][col*ELEMENTS_PER_VERTEX+1];
         sna[2] = s_sphere_normals[row][col*ELEMENTS_PER_NORMAL+2] = s_sphere_points[row][col*ELEMENTS_PER_VERTEX+2];
#if ( ELEMENTS_PER_NORMAL == 4 )
         sna[3] = s_sphere_normals[row][col*ELEMENTS_PER_NORMAL+3] = 1.0F;
#endif

         SphereNormalizeVector( &s_sphere_normals[row][col*ELEMENTS_PER_NORMAL] );
         SphereNormalizeVector( sna );

         sna += ELEMENTS_PER_NORMAL;
      }
   }

   /*
   ** create the draw arrays data
   */
   s_sphere_point_draw_array  = ( float ** ) AllocAlignedX( sizeof( float * ) * s_num_rows, BYTES_PER_CACHELINE );
   s_sphere_normal_draw_array = ( float ** ) AllocAlignedX( sizeof( float * ) * s_num_rows, BYTES_PER_CACHELINE );

   for ( row = 0; row < s_num_rows; row++ )
   {
      s_sphere_point_draw_array[row]  = ( float * ) AllocAlignedX( sizeof( float ) * ELEMENTS_PER_VERTEX * ( s_num_cols + 1 ) * 2, BYTES_PER_CACHELINE );
      s_sphere_normal_draw_array[row] = ( float * ) AllocAlignedX( sizeof( float ) * ELEMENTS_PER_NORMAL * ( s_num_cols + 1 ) * 2, BYTES_PER_CACHELINE );
   }
   for ( row = 0; row < s_num_rows; row++ )
   {
      for ( col = 0; col < s_num_cols; col++ )
      {
         float *pdst, *psrc;

         pdst = &s_sphere_point_draw_array[row][col*2*ELEMENTS_PER_VERTEX];
         psrc = &s_sphere_points[row][col*ELEMENTS_PER_VERTEX];
         memcpy( pdst, psrc, sizeof( float ) * ELEMENTS_PER_VERTEX );

         pdst = &s_sphere_point_draw_array[row][(col*2+1)*ELEMENTS_PER_VERTEX];
         psrc = &s_sphere_points[row+1][col*ELEMENTS_PER_VERTEX];
         memcpy( pdst, psrc, sizeof( float ) * ELEMENTS_PER_VERTEX );

         pdst = &s_sphere_normal_draw_array[row][col*2*ELEMENTS_PER_NORMAL];
         psrc = &s_sphere_normals[row][col*ELEMENTS_PER_NORMAL];
         memcpy( pdst, psrc, sizeof( float ) * ELEMENTS_PER_NORMAL );

         pdst = &s_sphere_normal_draw_array[row][(col*2+1)*ELEMENTS_PER_NORMAL];
         psrc = &s_sphere_normals[row+1][col*ELEMENTS_PER_NORMAL];
         memcpy( pdst, psrc, sizeof( float ) * ELEMENTS_PER_NORMAL );
      }
      memcpy( &s_sphere_point_draw_array[row][s_num_cols*2*ELEMENTS_PER_VERTEX],      &s_sphere_points[row][0],    sizeof( float ) * ELEMENTS_PER_VERTEX );
      memcpy( &s_sphere_point_draw_array[row][(s_num_cols*2+1)*ELEMENTS_PER_VERTEX],  &s_sphere_points[row+1][0],  sizeof( float ) * ELEMENTS_PER_VERTEX );
      memcpy( &s_sphere_normal_draw_array[row][s_num_cols*2*ELEMENTS_PER_NORMAL],     &s_sphere_normals[row][0],   sizeof( float ) * ELEMENTS_PER_NORMAL );
      memcpy( &s_sphere_normal_draw_array[row][(s_num_cols*2+1)*ELEMENTS_PER_NORMAL], &s_sphere_normals[row+1][0], sizeof( float ) * ELEMENTS_PER_NORMAL );
   }

   /*
   ** create the draw elements data
   */
   s_sphere_elements = ( unsigned int ** ) malloc( sizeof( unsigned int * ) * s_num_rows );
   for ( row = 0; row < s_num_rows; row++ )
   {
      s_sphere_elements[row] = ( unsigned int * ) malloc( sizeof( unsigned int ) * ( 2 * s_num_cols + 2 ) );
   }
   for ( row = 0; row < s_num_rows; row++ )
   {
      for ( col = 0; col < s_num_cols*2; col += 2 )
      {
         s_sphere_elements[row][col]   = row * s_num_cols + col/2;
         s_sphere_elements[row][col+1] = ( row + 1 ) * s_num_cols + col/2;
      }

      s_sphere_elements[row][s_num_cols*2]   = row * s_num_cols;
      s_sphere_elements[row][s_num_cols*2+1] = ( row + 1 ) * s_num_cols;
   }
   sia = s_sphere_interleaved_array = ( float * ) malloc( sizeof( float ) * ELEMENTS_PER_VERTEX * ELEMENTS_PER_NORMAL * ( s_num_rows + 1 ) * ( s_num_cols + 1 ) );
   memset( s_sphere_interleaved_array, 0, sizeof( float ) * ELEMENTS_PER_VERTEX * ELEMENTS_PER_NORMAL * ( s_num_rows + 1 ) * ( s_num_cols + 1 ) );

   for ( row = 0; row < num_vertex_rows; row++ )
   {
      for ( col = 0; col < s_num_cols; col++ )
      {
         memcpy( sia, &s_sphere_normals[row][col*ELEMENTS_PER_NORMAL], sizeof( float ) * ELEMENTS_PER_NORMAL );

         sia += ELEMENTS_PER_NORMAL;

         memcpy( sia, &s_sphere_points[row][col*ELEMENTS_PER_VERTEX], sizeof( float ) * ELEMENTS_PER_VERTEX );

         sia += ELEMENTS_PER_VERTEX;
      }
   }

   return TRUE;
}

static void SphereRedraw( void )
{
   int row, col;

   if ( !s_benchmark )
      glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

   glRotatef( 1.0f, 0.0F, 1.0F, 0.0F );
   glRotatef( 1.0f, 1.0F, 0.0F, 0.0F );

   if ( s_draw_method == DRAW_VERTEX3FV )
   {
      for ( row = 0; row < s_num_rows; row++ )
      {
         glBegin( GL_TRIANGLE_STRIP );
         for ( col = 0; col < s_num_cols; col++ )
         {
            glNormal3fv( &s_sphere_normals[row][col*ELEMENTS_PER_NORMAL] );
            glVertex3fv( &s_sphere_points[row][col*ELEMENTS_PER_VERTEX] );
            glNormal3fv( &s_sphere_normals[row+1][col*ELEMENTS_PER_NORMAL] );
            glVertex3fv( &s_sphere_points[row+1][col*ELEMENTS_PER_VERTEX] );
         }
         glNormal3fv( &s_sphere_normals[row][0] );
         glVertex3fv( &s_sphere_points[row][0] );
         glNormal3fv( &s_sphere_normals[row+1][0] );
         glVertex3fv( &s_sphere_points[row+1][0] );
         
         glEnd();
      }
   }
   else if ( s_draw_method == DRAW_DRAW_ARRAYS )
   {
      for ( row = 0; row < s_num_rows; row++ )
      {
         glVertexPointer( ELEMENTS_PER_VERTEX, GL_FLOAT, ELEMENTS_PER_VERTEX * sizeof( float ), ( const GLvoid * )  s_sphere_point_draw_array[row] );
         glNormalPointer( GL_FLOAT, ELEMENTS_PER_NORMAL * sizeof( float ), ( const GLfloat * ) s_sphere_normal_draw_array[row] );
         glDrawArrays( GL_TRIANGLE_STRIP, 0, (s_num_cols+1)*2 );
      }
   }
   else if ( s_draw_method == DRAW_ARRAY_ELEMENT )
   {
      for ( row = 0; row < s_num_rows; row++ )
      {
         glBegin( GL_TRIANGLE_STRIP );

         for ( col = 0; col < s_num_cols; col++ )
         {
            glArrayElement( col + row*s_num_cols );
            glArrayElement( col + (row+1)*s_num_cols );
         }

         glArrayElement( row*s_num_cols );
         glArrayElement( (row+1)*s_num_cols );

         glEnd();
      }
   }
   else if ( s_draw_method == DRAW_DRAW_ELEMENTS )
   {
      glInterleavedArrays( GL_N3F_V3F, ( ELEMENTS_PER_VERTEX + ELEMENTS_PER_NORMAL ) * sizeof( float ), s_sphere_interleaved_array );

      if ( LockArraysSGI && s_lock_arrays )
      {
         LockArraysSGI( 0, ( s_num_rows + 1 ) * ( s_num_cols + 1 ) );
      }

      for ( row = 0; row < s_num_rows; row++ )
      {
         glDrawElements( GL_TRIANGLE_STRIP, (s_num_cols+1)*2, GL_UNSIGNED_INT, s_sphere_elements[row] );
      }

      if ( UnlockArraysSGI && s_lock_arrays )
      {
         UnlockArraysSGI();
      }
   }

   s_vertices_processed  += ( ( s_num_rows + 1 ) * ( s_num_cols ) );
   s_triangles_processed += ( s_num_rows * s_num_cols );

   if ( !s_benchmark )
     SwapBuffers( s_hDC );
}

static void SphereNormalizeVector( float v[3] )
{
   float m = 1.0F / ( float ) ( sqrt( v[0] * v[0] + v[1] * v[1] + v[2] * v[2] ) );

   v[0] *= m;
   v[1] *= m;
   v[2] *= m;
}

static void *AllocAlignedX( int num_bytes, int byte_alignment )
{
   void *buf = malloc( num_bytes + byte_alignment );
   unsigned long q = ( unsigned long ) buf;

   q += byte_alignment;

   q &= ~( byte_alignment - 1 );

   return ( void * ) q;
}

static void SphereHelp( void )
{
   char superbuffer[1000]=
      "-?\t\tthis help screen\n"
      "-arrayelement\tuse glArrayElement\n"
      "-drawelements\tuse glDrawElements\n"
      "-drawarrays\tuse glDrawArrays\n"
      "-tristrip\t\tuse triangle strips w/ glVertex3fv\n"
      "-vcull\t\tuse SGI's vertex culling extension\n"
      "-lockarrays\tuse SGI's lock arrays extension when doing drawelements\n\n"
      "-cols:val\t\tspecify number of vertical (longitudinal) bands in sphere\n"
      "-rows:val\t\tspecify number of horizontal (latitudinal) bands in sphere\n\n"
      "-width:val\t\twidth of window, in pixels (default is 320)\n"
      "-height:val\t\theight of window, in pixels (default is 240)\n\n"
      "-benchmark\trun as benchmark and immediately quit when done\n"
      "-remote\t\tdon't print performance data\n";

   MessageBox( 0, superbuffer, *  ========================================================================
*
*    The contents of this file are subject to the SciTech MGL Public
*    License Version 1.0 (the "License"); you may not use this file
*    except in compliance with the License. You may obtain a copy of
*    the License at http://www.scitechsoft.com/mgl-license.txt
*
*    Software distributed under the License is distributed on an
*    "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
*    implied. See the License for the specific language governing
*    rights and limitations under the License.
*
*    The Original Code is Copyright (C) 1991-1998 SciTech Software, Inc.
*
*    The Initial Developer of the Original Code is SciTech Software, Inc.
*    All Rights Reserved.
*
*  ========================================================================
static void SphereSetupPalette( HDC hDC )
{
    PIXELFORMATDESCRIPTOR  pfd;
    LOGPALETTE            *pPal;
    int                    paletteSize;
    int                    pixelFormat = GetPixelFormat(hDC);
    
    DescribePixelFormat( hDC, pixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &pfd);

    if (!(pfd.dwFlags & PFD_NEED_PALETTE || pfd.iPixelType == PFD_TYPE_COLORINDEX))
        return;
    
    paletteSize = 1 << pfd.cColorBits;

    pPal                = (LOGPALETTE * ) malloc( sizeof(LOGPALETTE) + paletteSize * sizeof(PALETTEENTRY) );
    pPal->palVersion    = 0x300;
    pPal->palNumEntries = paletteSize;
    
    /* start with a copy of the current system palette */
    GetSystemPaletteEntries( hDC, 0, paletteSize, &pPal->palPalEntry[0] );
    
    if ( pfd.iPixelType == PFD_TYPE_RGBA )
    {
        /* fill in an RGBA color palette */
        int redMask = (1 << pfd.cRedBits) - 1;
        int greenMask = (1 << pfd.cGreenBits) - 1;
        int blueMask = (1 << pfd.cBlueBits) - 1;
        int i;
        
        for (i=0; i<paletteSize; ++i) 
        {
            pPal->palPalEntry[i].peRed =
                (((i >> pfd.cRedShift) & redMask) * 255) / redMask;
            pPal->palPalEntry[i].peGreen =
                (((i >> pfd.cGreenShift) & greenMask) * 255) / greenMask;
            pPal->palPalEntry[i].peBlue =
                (((i >> pfd.cBlueShift) & blueMask) * 255) / blueMask;
            pPal->palPalEntry[i].peFlags = 0;
        }
    } 
    else 
    {
        /* fill in a Color Index ramp color palette */
        int numRamps = NUM_COLORS;
        int rampSize = (paletteSize - 20) / numRamps;
        int extra = (paletteSize - 20) - (numRamps * rampSize);
        int i, r;
        
        for (r=0; r<numRamps; ++r) 
        {
            int rampBase = r * rampSize + 10;
            PALETTEENTRY *pe = &pPal->palPalEntry[rampBase];
            int diffSize = (int) (rampSize * s_colors[r].ratio);
            int specSize = rampSize - diffSize;
            
            for (i=0; i<rampSize; ++i) 
            {
                GLfloat *c0, *c1;
                GLint a;
                
                if (i < diffSize) 
                {
                    c0 = s_colors[r].amb;
                    c1 = s_colors[r].diff;
                    a = (i * 255) / (diffSize - 1);
                } 
                else 
                {
                    c0 = s_colors[r].diff;
                    c1 = s_colors[r].spec;
                    a = ((i - diffSize) * 255) / (specSize - 1);
                }
                
                pe[i].peRed = (BYTE) (a * (c1[0] - c0[0]) + 255 * c0[0]);
                pe[i].peGreen = (BYTE) (a * (c1[1] - c0[1]) + 255 * c0[1]);
                pe[i].peBlue = (BYTE) (a * (c1[2] - c0[2]) + 255 * c0[2]);
                pe[i].peFlags = PC_NOCOLLAPSE;
            }
            
            s_colors[r].indexes[0] = rampBase;
            s_colors[r].indexes[1] = rampBase + (diffSize-1);
            s_colors[r].indexes[2] = rampBase + (rampSize-1);
        }
        s_lit_tex_indexes[0] = 0;
        s_lit_tex_indexes[1] = (GLint)(rampSize*s_colors[0].ratio)-1;
        s_lit_tex_indexes[2] = rampSize-1;
        
        for (i=0; i<extra; ++i) 
        {
            int index = numRamps*rampSize+10+i;
            PALETTEENTRY *pe = &pPal->palPalEntry[index];
            
            pe->peRed = (BYTE) 0;
            pe->peGreen = (BYTE) 0;
            pe->peBlue = (BYTE) 0;
            pe->peFlags = PC_NOCOLLAPSE;
        }
    }
    
    s_hPalette = CreatePalette( pPal );
    free( pPal );
    
    if ( s_hPalette )
    {
        SelectPalette( hDC, s_hPalette, FALSE );
        RealizePalette( hDC );
    }
}

