/*
 * Demonstrates mixed texgen/non-texgen texture coordinates.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <GL/glew.h>
#include <GL/glut.h>

#undef max
#undef min
#define max( a, b )	((a) >= (b) ? (a) : (b))
#define min( a, b )	((a) <= (b) ? (a) : (b))

GLfloat labelColor0[4] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat labelColor1[4] = { 1.0, 1.0, 0.4, 1.0 };
GLfloat *labelInfoColor = labelColor0;

GLboolean doubleBuffered = GL_TRUE;
GLboolean drawTextured = GL_TRUE;

int textureWidth = 64;
int textureHeight = 64;

int winWidth = 580, winHeight = 720;

const GLfloat texmat_swap_rq[16] = { 1.0, 0.0, 0.0, 0.0,
                                     0.0, 1.0, 0.0, 0.0,
                                     0.0, 0.0, 0.0, 1.0,
                                     0.0, 0.0, 1.0, 0.0};

const GLfloat nullPlane[4] = { 0.0, 0.0, 0.0, 0.0 };
const GLfloat ObjPlaneS1[4] = { 1.0, 0.0, 1.0, 0.0 };
const GLfloat ObjPlaneS2[4] = { 0.5, 0.0, 0.0, 0.0 };
const GLfloat ObjPlaneS3[4] = { 1.0, 0.0, 0.0, 0.0 };
const GLfloat ObjPlaneT[4] = { 0.0, 1.0, 0.0, 0.0 };
const GLfloat ObjPlaneT2[4] = { 0.0, 0.5, 0.0, 0.0 };
const GLfloat ObjPlaneT3[4] = { 0.0, 1.0, 0.0, 0.0 };
const GLfloat ObjPlaneR[4] = { 0.0, 0.0, 1.0, 0.0 };
const GLfloat ObjPlaneQ[4] = { 0.0, 0.0, 0.0, 0.5 };


static void checkErrors( void )
{
   GLenum error;

   while ( (error = glGetError()) != GL_NO_ERROR ) {
      fprintf( stderr, "Error: %s\n", (char *) gluErrorString( error ) );
   }
}

static void drawString( const char *string, GLfloat x, GLfloat y,
                        const GLfloat color[4] )
{
   glColor4fv( color );
   glRasterPos2f( x, y );

   while ( *string ) {
      glutBitmapCharacter( GLUT_BITMAP_TIMES_ROMAN_10, *string );
      string++;
   }
}

static void begin2D( int width, int height )
{
   glMatrixMode( GL_PROJECTION );

   glPushMatrix();
   glLoadIdentity();

   glOrtho( 0, width, 0, height, -1, 1 );
   glMatrixMode( GL_MODELVIEW );

   glPushMatrix();
   glLoadIdentity();
}

static void end2D( void )
{
   glMatrixMode( GL_PROJECTION );
   glPopMatrix();
   glMatrixMode( GL_MODELVIEW );
   glPopMatrix();
}

static void initialize( void )
{
   glMatrixMode( GL_PROJECTION );
   glLoadIdentity();

   glOrtho( -1.5, 1.5, -1.5, 1.5, -1.5, 1.5 );

   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();

   glShadeModel( GL_FLAT );
}

/* ARGSUSED1 */
static void keyboard( unsigned char c, int x, int y )
{
   switch ( c ) {
   case 't':
      drawTextured = !drawTextured;
      break;
   case 27:             /* Escape key should force exit. */
      exit(0);
      break;
   default:
      break;
   }
   glutPostRedisplay();
}

/* ARGSUSED1 */
static void special( int key, int x, int y )
{
   switch ( key ) {
   case GLUT_KEY_DOWN:
      break;
   case GLUT_KEY_UP:
      break;
   case GLUT_KEY_LEFT:
      break;
   case GLUT_KEY_RIGHT:
      break;
   default:
      break;
   }
   glutPostRedisplay();
}

static void
reshape( int w, int h )
{
   winWidth = w;
   winHeight = h;
   /* No need to call glViewPort here since "draw" calls it! */
}

static void loadTexture( int width, int height )
{
   int		alphaSize = 1;
   int		rgbSize = 3;
   GLubyte	*texImage, *p;
   int		elementsPerGroup, elementSize, groupSize, rowSize;
   int		i, j;


   elementsPerGroup = alphaSize + rgbSize;
   elementSize = sizeof(GLubyte);
   groupSize = elementsPerGroup * elementSize;
   rowSize = width * groupSize;

   if ( (texImage = (GLubyte *) malloc( height * rowSize ) ) == NULL ) {
      fprintf( stderr, "texture malloc failed\n" );
      return;
   }

   for ( i = 0 ; i < height ; i++ )
   {
      p = texImage + i * rowSize;

      for ( j = 0 ; j < width ; j++ )
      {
	 if ( rgbSize > 0 )
	 {
	    /**
	     ** +-----+-----+
	     ** |     |     |
	     ** |  R  |  G  |
	     ** |     |     |
	     ** +-----+-----+
	     ** |     |     |
	     ** |  Y  |  B  |
	     ** |     |     |
	     ** +-----+-----+
	     **/
	    if ( i > height / 2 ) {
	       if ( j < width / 2 ) {
		  p[0] = 0xff;
		  p[1] = 0x00;
		  p[2] = 0x00;
	       } else {
		  p[0] = 0x00;
		  p[1] = 0xff;
		  p[2] = 0x00;
	       }
	    } else {
	       if ( j < width / 2 ) {
		  p[0] = 0xff;
		  p[1] = 0xff;
		  p[2] = 0x00;
	       } else {
		  p[0] = 0x00;
		  p[1] = 0x00;
		  p[2] = 0xff;
	       }
	    }
	    p += 3 * elementSize;
	 }

	 if ( alphaSize > 0 )
	 {
	    /**
	     ** +-----------+
	     ** |     W     |
	     ** |  +-----+  |
	     ** |  |     |  |
	     ** |  |  B  |  |
	     ** |  |     |  |
	     ** |  +-----+  |
	     ** |           |
	     ** +-----------+
	     **/
	    int i2 = i - height / 2;
	    int j2 = j - width / 2;
	    int h8 = height / 8;
	    int w8 = width / 8;
	    if ( -h8 <= i2 && i2 <= h8 && -w8 <= j2 && j2 <= w8 ) {
	       p[0] = 0x00;
	    } else if ( -2 * h8 <= i2 && i2 <= 2 * h8 && -2 * w8 <= j2 && j2 <= 2 * w8 ) {
	       p[0] = 0x55;
	    } else if ( -3 * h8 <= i2 && i2 <= 3 * h8 && -3 * w8 <= j2 && j2 <= 3 * w8 ) {
	       p[0] = 0xaa;
	    } else {
	       p[0] = 0xff;
	    }
	    p += elementSize;
	 }
      }
   }

   glTexImage2D( GL_TEXTURE_2D, 0,
		 GL_RGBA, width, height, 0,
		 GL_RGBA, GL_UNSIGNED_BYTE, texImage );

   free( texImage );
}


static void drawSample( int x, int y, int w, int h,
                        int texgenenabled, int coordnr )
{
   char buf[255];

   glViewport( x, y, w, h );
   glScissor( x, y, w, h );

   glClearColor( 0.1, 0.1, 0.1, 1.0 );
   glClear( GL_COLOR_BUFFER_BIT );

   begin2D( w, h );
   if (texgenenabled == 2) {
      sprintf( buf, "TexCoord%df", coordnr);
      drawString( buf, 10, h - 15, labelInfoColor );
      sprintf( buf, "texgen enabled for %s coordinate(s)", coordnr == 2 ? "S" : "S/T");
      drawString( buf, 10, 5, labelInfoColor );
   }
   else if (texgenenabled == 0) {
      sprintf( buf, "TexCoord%df", coordnr);
      drawString( buf, 10, h - 15, labelInfoColor );
      drawString( "no texgen", 10, 5, labelInfoColor );
   }
   else if (texgenenabled == 1) {
      drawString( "no TexCoord", 10, h - 15, labelInfoColor );
      sprintf( buf, "texgen enabled for %s coordinate(s)",
         coordnr == 2 ? "S/T" : (coordnr == 3 ? "S/T/R" : "S/T/R/Q"));
      drawString( buf, 10, 5, labelInfoColor );
   }

   end2D();

   glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );

   glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
   glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );

   glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
   glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );

   loadTexture( textureWidth, textureHeight );

   if ( drawTextured ) {
      glEnable( GL_TEXTURE_2D );
   }

   glDisable( GL_TEXTURE_GEN_S );
   glDisable( GL_TEXTURE_GEN_T );
   glDisable( GL_TEXTURE_GEN_R );
   glDisable( GL_TEXTURE_GEN_Q );

   glMatrixMode( GL_TEXTURE );
   glLoadIdentity();
   glMatrixMode( GL_MODELVIEW );
   glPushMatrix();

   switch (coordnr) {
   case 2:
      switch (texgenenabled) {
      case 0:
         glBegin( GL_QUADS );
         glTexCoord2f( 0.0, 0.0 );
         glVertex2f( -0.8, -0.8 );

         glTexCoord2f( 1.0, 0.0 );
         glVertex2f( 0.8, -0.8 );

         glTexCoord2f( 1.0, 1.0 );
         glVertex2f( 0.8, 0.8 );

         glTexCoord2f( 0.0, 1.0 );
         glVertex2f( -0.8, 0.8 );
         glEnd();
         break;
      case 1:
         glTranslatef( -0.8, -0.8, 0.0 );
         glScalef( 1.6, 1.6, 1.0 );
         glTexGeni( GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
         glTexGeni( GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
         glTexGeni( GL_R, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
         glTexGeni( GL_Q, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
         glTexGenfv(GL_S, GL_OBJECT_PLANE, ObjPlaneS3);
         glTexGenfv(GL_T, GL_OBJECT_PLANE, ObjPlaneT3);
         glTexGenfv(GL_R, GL_OBJECT_PLANE, nullPlane);
         glTexGenfv(GL_Q, GL_OBJECT_PLANE, nullPlane);

         glEnable( GL_TEXTURE_GEN_S );
         glEnable( GL_TEXTURE_GEN_T );

         /* Issue a texcoord here to be sure Q isn't left over from a
          * previous sample.
          */
         glTexCoord1f( 0.0 );
         glBegin( GL_QUADS );
         glVertex2f( 0.0, 0.0 );
         glVertex2f( 1.0, 0.0 );
         glVertex2f( 1.0, 1.0 );
         glVertex2f( 0.0, 1.0 );
         glEnd();
         break;
      case 2:
         /* make sure that texgen T and non-texgen S coordinate are wrong */
         glTexGeni( GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
         glTexGeni( GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
         glTexGeni( GL_R, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
         glTexGeni( GL_Q, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
         glTexGenfv(GL_S, GL_OBJECT_PLANE, ObjPlaneS1);
         glTexGenfv(GL_T, GL_OBJECT_PLANE, nullPlane);
         glTexGenfv(GL_R, GL_OBJECT_PLANE, nullPlane);
         glTexGenfv(GL_Q, GL_OBJECT_PLANE, nullPlane);

         glEnable( GL_TEXTURE_GEN_S );

         glBegin( GL_QUADS );
         /* use z coordinate to get correct texgen values... */
         glTexCoord2f( 0.0, 0.0 );
         glVertex3f( -0.8, -0.8, 0.8 );

         glTexCoord2f( 0.0, 0.0 );
         glVertex3f( 0.8, -0.8, 0.2 );

         glTexCoord2f( 0.0, 1.0 );
         glVertex3f( 0.8, 0.8, 0.2 );

         glTexCoord2f( 0.0, 1.0 );
         glVertex3f( -0.8, 0.8, 0.8 );
         glEnd();
         break;
      }
      break;
   case 3:
      glMatrixMode( GL_TEXTURE );
      glLoadMatrixf( texmat_swap_rq );
      glMatrixMode( GL_MODELVIEW );
      glTranslatef( -0.8, -0.8, 0.0 );
      glScalef( 1.6, 1.6, 1.0 );
      switch (texgenenabled) {
      case 0:
         glBegin( GL_QUADS );
         glTexCoord3f( 0.0, 0.0, 0.5 );
         glVertex2f( 0.0, 0.0 );

         glTexCoord3f( 0.5, 0.0, 0.5 );
         glVertex2f( 1.0, 0.0 );

         glTexCoord3f( 0.5, 0.5, 0.5 );
         glVertex2f( 1.0, 1.0 );

         glTexCoord3f( 0.0, 0.5, 0.5 );
         glVertex2f( 0.0, 1.0 );
         glEnd();
         break;
      case 1:
         glTexGeni( GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
         glTexGeni( GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
         glTexGeni( GL_R, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
         glTexGeni( GL_Q, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
         glTexGenfv(GL_S, GL_OBJECT_PLANE, ObjPlaneS2);
         glTexGenfv(GL_T, GL_OBJECT_PLANE, ObjPlaneT2);
         glTexGenfv(GL_R, GL_OBJECT_PLANE, ObjPlaneR);
         glTexGenfv(GL_Q, GL_OBJECT_PLANE, nullPlane);

         glEnable( GL_TEXTURE_GEN_S );
         glEnable( GL_TEXTURE_GEN_T );
         glEnable( GL_TEXTURE_GEN_R );

         glTexCoord1f( 0.0 ); /* to make sure Q is 1.0 */
         glBegin( GL_QUADS );
         glVertex3f( 0.0, 0.0, 0.5 );
         glVertex3f( 1.0, 0.0, 0.5 );
         glVertex3f( 1.0, 1.0, 0.5 );
         glVertex3f( 0.0, 1.0, 0.5 );
         glEnd();
         break;
      case 2:
         /* make sure that texgen R/Q and non-texgen S/T coordinates are wrong */
         glTexGeni( GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
         glTexGeni( GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
         glTexGeni( GL_R, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
         glTexGeni( GL_Q, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
         glTexGenfv(GL_S, GL_OBJECT_PLANE, ObjPlaneS2);
         glTexGenfv(GL_T, GL_OBJECT_PLANE, ObjPlaneT2);
         glTexGenfv(GL_R, GL_OBJECT_PLANE, nullPlane);
         glTexGenfv(GL_Q, GL_OBJECT_PLANE, nullPlane);

         glEnable( GL_TEXTURE_GEN_S );
         glEnable( GL_TEXTURE_GEN_T );

         glBegin( GL_QUADS );
         glTexCoord3f( 0.0, 0.0, 0.5 );
         glVertex2f( 0.0, 0.0);

         glTexCoord3f( 0.0, 0.0, 0.5 );
         glVertex2f( 1.0, 0.0);

         glTexCoord3f( 0.0, 0.0, 0.5 );
         glVertex2f( 1.0, 1.0);

         glTexCoord3f( 0.0, 0.0, 0.5 );
         glVertex2f( 0.0, 1.0);
         glEnd();
         break;
      }
      break;
   case 4:
      switch (texgenenabled) {
      case 0:
         glBegin( GL_QUADS );
         /* don't need r coordinate but still setting it I'm mean */
         glTexCoord4f( 0.0, 0.0, 0.0, 0.5 );
         glVertex2f( -0.8, -0.8 );

         glTexCoord4f( 0.5, 0.0, 0.2, 0.5 );
         glVertex2f( 0.8, -0.8 );

         glTexCoord4f( 0.5, 0.5, 0.5, 0.5 );
         glVertex2f( 0.8, 0.8 );

         glTexCoord4f( 0.0, 0.5, 0.5, 0.5 );
         glVertex2f( -0.8, 0.8 );
         glEnd();
         break;
      case 1:
         glTranslatef( -0.8, -0.8, 0.0 );
         glScalef( 1.6, 1.6, 1.0 );
         /* make sure that texgen R/Q and non-texgen S/T coordinates are wrong */
         glTexGeni( GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
         glTexGeni( GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
         glTexGeni( GL_R, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
         glTexGeni( GL_Q, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
         glTexGenfv(GL_S, GL_OBJECT_PLANE, ObjPlaneS2);
         glTexGenfv(GL_T, GL_OBJECT_PLANE, ObjPlaneT2);
         glTexGenfv(GL_R, GL_OBJECT_PLANE, ObjPlaneR);
         glTexGenfv(GL_Q, GL_OBJECT_PLANE, ObjPlaneQ);

         glEnable( GL_TEXTURE_GEN_S );
         glEnable( GL_TEXTURE_GEN_T );
         glEnable( GL_TEXTURE_GEN_R );
         glEnable( GL_TEXTURE_GEN_Q );

         glBegin( GL_QUADS );
         glVertex2f( 0.0, 0.0 );
         glVertex2f( 1.0, 0.0 );
         glVertex2f( 1.0, 1.0 );
         glVertex2f( 0.0, 1.0 );
         glEnd();
         break;
      case 2:
         glTranslatef( -0.8, -0.8, 0.0 );
         glScalef( 1.6, 1.6, 1.0 );
         /* make sure that texgen R/Q and non-texgen S/T coordinates are wrong */
         glTexGeni( GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
         glTexGeni( GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
         glTexGeni( GL_R, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
         glTexGeni( GL_Q, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
         glTexGenfv(GL_S, GL_OBJECT_PLANE, ObjPlaneS2);
         glTexGenfv(GL_T, GL_OBJECT_PLANE, ObjPlaneT2);
         glTexGenfv(GL_R, GL_OBJECT_PLANE, nullPlane);
         glTexGenfv(GL_Q, GL_OBJECT_PLANE, nullPlane);

         glEnable( GL_TEXTURE_GEN_S );
         glEnable( GL_TEXTURE_GEN_T );

         glBegin( GL_QUADS );
         glTexCoord4f( 0.0, 0.0, 0.0, 0.5 );
         glVertex2f( 0.0, 0.0 );

         glTexCoord4f( 0.0, 0.0, 0.2, 0.5 );
         glVertex2f( 1.0, 0.0 );

         glTexCoord4f( 0.0, 0.0, 0.5, 0.5 );
         glVertex2f( 1.0, 1.0 );

         glTexCoord4f( 0.0, 0.0, 0.75, 0.5 );
         glVertex2f( 0.0, 1.0 );
         glEnd();
         break;
      }
      break;
   }

   glPopMatrix();
   glDisable( GL_TEXTURE_2D );

}

static void display( void )
{
   int		numX = 3, numY = 3;
   float	xBase = (float) winWidth * 0.01;
   float	xOffset = (winWidth - xBase) / numX;
   float	xSize = max( xOffset - xBase, 1 );
   float	yBase = (float) winHeight * 0.01;
   float	yOffset = (winHeight - yBase) / numY;
   float	ySize = max( yOffset - yBase, 1 );
   float	x, y;
   int		i, j;

   glViewport( 0, 0, winWidth, winHeight );
   glDisable( GL_SCISSOR_TEST );
   glClearColor( 0.0, 0.0, 0.0, 0.0 );
   glClear( GL_COLOR_BUFFER_BIT );
   glEnable( GL_SCISSOR_TEST );

   x = xBase;
   y = (winHeight - 1) - yOffset;

   for ( i = 0 ; i < numY ; i++ )
   {

      labelInfoColor = labelColor1;


      for ( j = 0 ; j < numX ; j++ ) {
	 drawSample( x, y, xSize, ySize, i, j+2 );
	 x += xOffset;
      }

      x = xBase;
      y -= yOffset;
   }

   if ( doubleBuffered ) {
      glutSwapBuffers();
   } else {
      glFlush();
   }

   checkErrors();
}

static void usage( char *name )
{
   fprintf( stderr, "usage: %s [ options ]\n", name );
   fprintf( stderr, "\n" );
   fprintf( stderr, "options:\n" );
   fprintf( stderr, "    -sb    single buffered\n" );
   fprintf( stderr, "    -db    double buffered\n" );
   fprintf( stderr, "    -info  print OpenGL driver info\n" );
}

static void instructions( void )
{
   fprintf( stderr, "texgenmix - mixed texgen/non-texgen texture coordinate test\n" );
   fprintf( stderr, "all quads should look the same!\n" );
   fprintf( stderr, "\n" );
   fprintf( stderr, "  [t] - toggle texturing\n" );
}

int main( int argc, char *argv[] )
{
   GLboolean info = GL_FALSE;
   int i;

   glutInit( &argc, argv );

   for ( i = 1 ; i < argc ; i++ ) {
      if ( !strcmp( "-sb", argv[i] ) ) {
	 doubleBuffered = GL_FALSE;
      } else if ( !strcmp( "-db", argv[i] ) ) {
	 doubleBuffered = GL_TRUE;
      } else if ( !strcmp( "-info", argv[i] ) ) {
	 info = GL_TRUE;
      } else {
	 usage( argv[0] );
	 exit( 1 );
      }
   }

   if ( doubleBuffered ) {
      glutInitDisplayMode( GLUT_RGB | GLUT_DOUBLE );
   } else {
      glutInitDisplayMode( GLUT_RGB | GLUT_SINGLE );
   }

   glutInitWindowSize( winWidth, winHeight );
   glutInitWindowPosition( 0, 0 );
   glutCreateWindow( "Mixed texgen/non-texgen texture coordinate test" );
   glewInit();

   initialize();
   instructions();

   if ( info ) {
      printf( "\n" );
      printf( "GL_RENDERER   = %s\n", (char *) glGetString( GL_RENDERER ) );
      printf( "GL_VERSION    = %s\n", (char *) glGetString( GL_VERSION ) );
      printf( "GL_VENDOR     = %s\n", (char *) glGetString( GL_VENDOR ) ) ;
      printf( "GL_EXTENSIONS = %s\n", (char *) glGetString( GL_EXTENSIONS ) );
   }

   glutDisplayFunc( display );
   glutReshapeFunc( reshape );
   glutKeyboardFunc( keyboard );
   glutSpecialFunc( special );
   glutMainLoop();

   return 0;
}