/*
** texobj.c: Draws textured polygons using the texture object extension.
**
** Default operation: Draws three squares using a different texture for
** each square.
**
** Use -h option to display a list of command line options. Use Key h 
** in the created window for key options.
*/

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <math.h>
#include <stdlib.h>
#include <assert.h>
#include <GL/glut.h>
#include "rgb.h"

#if defined(GL_EXT_texture_object)

#define NUM_LIST_TYPES 3
typedef enum {CUBE=0, SQUARE, PUSHPOP} listType;

listType currentList = SQUARE;

GLuint lists[NUM_LIST_TYPES];

GLenum doubleBuffer;

GLboolean multTex = GL_TRUE;
GLboolean immed = GL_FALSE;
GLboolean texObj = GL_TRUE;
GLboolean queryTexobj = GL_TRUE;

#define NUM_TEXTURES 3

GLuint texNames[NUM_TEXTURES];
GLfloat texPriorities[NUM_TEXTURES] = {0.1, 0.2, 0.3};
RGBImageRec *images[NUM_TEXTURES];
char *texFiles[NUM_TEXTURES] = {
	"1.rgb",
	"2.rgb",
	"3.rgb",
};

float *minFilter, *magFilter, *sWrapMode, *tWrapMode;
float decal[] = {GL_DECAL};
float modulate[] = {GL_MODULATE};
float repeat[] = {GL_REPEAT};
float clamp[] = {GL_CLAMP};
float nr[] = {GL_NEAREST};
float ln[] = {GL_LINEAR};
float nr_mipmap_nr[] = {GL_NEAREST_MIPMAP_NEAREST};
float nr_mipmap_ln[] = {GL_NEAREST_MIPMAP_LINEAR};
float ln_mipmap_nr[] = {GL_LINEAR_MIPMAP_NEAREST};
float ln_mipmap_ln[] = {GL_LINEAR_MIPMAP_LINEAR};
GLint sphereMap[] = {GL_SPHERE_MAP};

GLenum doSphere = GL_FALSE;
float xRotation = 0.0, yRotation = 0.0, zTranslate = -3.125;

float c[6][4][3] = {
    {
	{ 1.0, 1.0, -1.0 },
	{ -1.0, 1.0, -1.0 },
	{ -1.0, -1.0, -1.0 },
	{ 1.0, -1.0, -1.0 }
    },
    {
	{ 1.0, 1.0, 1.0 },
	{ 1.0, 1.0, -1.0 },
	{ 1.0, -1.0, -1.0 },
	{ 1.0, -1.0, 1.0 }
    },
    {
	{ -1.0, 1.0, 1.0 },
	{ 1.0, 1.0, 1.0 },
	{ 1.0, -1.0, 1.0 },
	{ -1.0, -1.0, 1.0 }
    },
    {
	{ -1.0, 1.0, -1.0 },
	{ -1.0, 1.0, 1.0 },
	{ -1.0, -1.0, 1.0 },
	{ -1.0, -1.0, -1.0 }
    },
    {
	{ -1.0, 1.0, 1.0 },
	{ -1.0, 1.0, -1.0 },
	{ 1.0, 1.0, -1.0 },
	{ 1.0, 1.0, 1.0 }
    },
    {
	{ -1.0, -1.0, -1.0 },
	{ -1.0, -1.0, 1.0 },
	{ 1.0, -1.0, 1.0 },
	{ 1.0, -1.0, -1.0 }
    }
};
static float n[6][3] = {
    { 0.0, 0.0, -1.0 },
    { 1.0, 0.0, 0.0 },
    { 0.0, 0.0, 1.0 },
    { -1.0, 0.0, 0.0 },
    { 0.0, 1.0, 0.0 },
    { 0.0, -1.0, 0.0 }
};
static float t[6][4][2] = {
    {
	{ 1.1,  1.1 },
	{ -0.1, 1.1 },
	{ -0.1, -0.1 },
	{ 1.1,  -0.1 }
    },
    {
	{ 1.1,  1.1 },
	{ -0.1, 1.1 },
	{ -0.1, -0.1 },
	{ 1.1,  -0.1 }
    },
    {
	{ -0.1,  1.1 },
	{ 1.1, 1.1 },
	{ 1.1, -0.1 },
	{ -0.1,  -0.1 }
    },
    {
	{ 1.1,  1.1 },
	{ -0.1, 1.1 },
	{ -0.1, -0.1 },
	{ 1.1,  -0.1 }
    },
    {
	{ 1.1,  1.1 },
	{ -0.1, 1.1 },
	{ -0.1, -0.1 },
	{ 1.1,  -0.1 }
    },
    {
	{ 1.1,  1.1 },
	{ -0.1, 1.1 },
	{ -0.1, -0.1 },
	{ 1.1,  -0.1 }
    },
};


static void SelectTexture(GLint i)
{
    RGBImageRec *texImage;

    if (texObj) {
	if (i<NUM_TEXTURES) {
	    glBindTextureEXT(GL_TEXTURE_2D, texNames[i]);
	} else {
	    glBindTextureEXT(GL_TEXTURE_2D, 0);
	}
	return;
    }

    if (i >= NUM_TEXTURES) {
	i = NUM_TEXTURES - 1;
    }

    texImage = images[i];

    gluBuild2DMipmaps(GL_TEXTURE_2D, 3, texImage->sizeX, texImage->sizeY,
		      GL_RGB, GL_UNSIGNED_BYTE, texImage->data);
}

static void DrawCube(void)
{
    GLint i;

    for (i = 0; i < 6; i++) {
	if (multTex) {
	    SelectTexture(i);
	}
	glBegin(GL_POLYGON);
	    glNormal3fv(n[i]); glTexCoord2fv(t[i][0]); glVertex3fv(c[i][0]);
	    glNormal3fv(n[i]); glTexCoord2fv(t[i][1]); glVertex3fv(c[i][1]);
	    glNormal3fv(n[i]); glTexCoord2fv(t[i][2]); glVertex3fv(c[i][2]);
	    glNormal3fv(n[i]); glTexCoord2fv(t[i][3]); glVertex3fv(c[i][3]);
	glEnd();
    }
}

static void BuildCube(void)
{
    GLint i;

    glNewList(lists[CUBE], GL_COMPILE);
    DrawCube();
    glEndList();
}

static void DrawSquare(void)
{

    glBegin(GL_POLYGON);
	glNormal3fv(n[2]); glTexCoord2fv(t[2][0]); glVertex3fv(c[2][0]);
	glNormal3fv(n[2]); glTexCoord2fv(t[2][1]); glVertex3fv(c[2][1]);
	glNormal3fv(n[2]); glTexCoord2fv(t[2][2]); glVertex3fv(c[2][2]);
	glNormal3fv(n[2]); glTexCoord2fv(t[2][3]); glVertex3fv(c[2][3]);
    glEnd();
}

static void DrawSquares(void)
{
    GLint i;
    GLfloat xTranslate = 0.0;

    glTranslatef(-2.0, 0.0, 0.0);
    for (i = 0; i < 3; i++) {
	if (multTex) {
	    SelectTexture(i);
	}
	DrawSquare();
	glTranslatef(2.0, 0.0, 0.0);
    }
}

static void BuildSquare(void)
{
    GLint i;
    GLfloat xTranslate = 0.0;

    glNewList(lists[SQUARE], GL_COMPILE);
    DrawSquares();
    glEndList();
}

static void DoPushPopTest(void)
{
    GLfloat xTranslate = 0.0;

    if (multTex) {
	SelectTexture(1);
    }
    glTranslatef(-4.0, 0.0, 0.0);
    DrawSquare();

    glPushAttrib(GL_TEXTURE_BIT);
    if (multTex) {
	SelectTexture(2);
    }
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, clamp);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, clamp);
    glTranslatef(2.0, 0.0, 0.0);
    DrawSquare();

    glPopAttrib();
    glTranslatef(2.0, 0.0, 0.0);
    DrawSquare();

    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, clamp);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, clamp);
    glTranslatef(2.0, 0.0, 0.0);
    DrawSquare();

}

static void BuildPushPopTest(void)
{
    GLfloat xTranslate = 0.0;

    glNewList(lists[PUSHPOP], GL_COMPILE);
    DoPushPopTest();
    glEndList();
}

static void BuildLists(void)
{

    lists[CUBE] = glGenLists(1);
    BuildCube();
    lists[SQUARE] = glGenLists(1);
    BuildSquare();
    lists[PUSHPOP] = glGenLists(1);
    BuildPushPopTest();
}

static void UnBuildLists(void)
{
    int i;

    for (i = 0; i < NUM_LIST_TYPES; i++) {
	glDeleteLists(lists[i], 1);
    }
}

static void Init(void)
{

    doSphere = GL_FALSE;
    xRotation = 0.0;
    yRotation = 0.0;
    zTranslate = -3.125;

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    if (multTex && texObj) {
	int i;

	glGenTexturesEXT(NUM_TEXTURES, texNames);
	for (i = 1; i < NUM_TEXTURES; i++) {
	    glBindTextureEXT(GL_TEXTURE_2D, texNames[i]);
	    gluBuild2DMipmaps(GL_TEXTURE_2D, 3, images[i]->sizeX,
			      images[i]->sizeY, GL_RGB, GL_UNSIGNED_BYTE,
			      images[i]->data);
	}
	glBindTextureEXT(GL_TEXTURE_2D, texNames[0]);
	glPrioritizeTexturesEXT(NUM_TEXTURES, texNames, texPriorities);
    }
    gluBuild2DMipmaps(GL_TEXTURE_2D, 3, images[0]->sizeX, images[0]->sizeY,
		      GL_RGB, GL_UNSIGNED_BYTE, images[0]->data);

    glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, decal);
    glEnable(GL_TEXTURE_2D);

    glFrontFace(GL_CCW);
    glCullFace(GL_FRONT);
    glEnable(GL_CULL_FACE);

    BuildLists();

    glClearColor(0.0, 0.0, 0.0, 0.0);

    magFilter = nr;
    minFilter = nr;
    sWrapMode = repeat;
    tWrapMode = repeat;
}

static void UnInit(void)
{

    if (multTex && texObj) {
	glDeleteTexturesEXT(NUM_TEXTURES, texNames);
    }
    UnBuildLists();
}

static void Reshape(int width, int height)
{

    glViewport(0, 0, width, height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(145.0, 1.0, 0.01, 1000);
    glMatrixMode(GL_MODELVIEW);
}

static char *keyList[][2] = {
    {"left arrow", "y rotation - 0.5"},
    {"right arrow", "y rotation + 0.5"},
    {"up arrow", "x rotation - 0.5"},
    {"down arrow", "x rotation + 0.5"},
    {"T", "z translate  + 0.25"},
    {"t", "z translate  - 0.25"},
    {"",""},
    {"s", "toggle texgen sphereMap"},
    {"R", "Reset draw routine"},
    {"C", "Draw Cube"},
    {"S", "Draw Squares"},
    {"P", "Push Pop test"},
    {"G", "Get attrib test"},
    {"",""},
    {"0", "magfilter = nearest"},
    {"1", "magfilter = linear"},
    {"2", "minfilter = nearest"},
    {"3", "minfilter = linear"},
    {"4", "minfilter = nearest mipmap nearest"},
    {"5", "minfilter = nearest mipmap linear"},
    {"6", "minfilter = linear mipmap nearest"},
    {"7", "minfilter = linear mipmap linear"},
    {"",""},
    {"h", "print this list"},
    {"ESC", "exit"},
    {NULL, NULL}
};

static void Key(unsigned char key, int x, int y)
{
    int i;

    switch (key) {
      case 'h':
	for (i = 0; keyList[i][0] != NULL; i++) {
	    printf("%-20s %s\n", keyList[i][0], keyList[i][1]);
	}
	break;

      case 'T':
	zTranslate += 0.25;
	glutPostRedisplay();
	break;
      case 't':
	zTranslate -= 0.25;
	glutPostRedisplay();
	break;

      case 's':
	doSphere = !doSphere;
	if (doSphere) {
	    glTexGeniv(GL_S, GL_TEXTURE_GEN_MODE, sphereMap);
	    glTexGeniv(GL_T, GL_TEXTURE_GEN_MODE, sphereMap);
	    glEnable(GL_TEXTURE_GEN_S);
	    glEnable(GL_TEXTURE_GEN_T);
	} else {
	    glDisable(GL_TEXTURE_GEN_S);
	    glDisable(GL_TEXTURE_GEN_T);
	}
	glutPostRedisplay();
	break;

      case 'r':
	UnInit();
	Init();
	glutPostRedisplay();
	break;

      case 'C':
	currentList = CUBE;
	glutPostRedisplay();
	break;
      case 'S':
	currentList = SQUARE;
	glutPostRedisplay();
	break;
      case 'P':
	currentList = PUSHPOP;
	glutPostRedisplay();
	break;

      case 'G':
	{
	    GLint name;
	    GLfloat priority;
	    GLboolean isTex, resident, residences[NUM_TEXTURES];

	    glGetIntegerv(GL_TEXTURE_2D_BINDING_EXT, &name);
	    glGetTexParameterfv(GL_TEXTURE_2D, 
				GL_TEXTURE_PRIORITY_EXT, &priority);
	    isTex = glIsTextureEXT(name);
	    resident = glAreTexturesResidentEXT(NUM_TEXTURES, 
						texNames, residences);
	    fprintf(stdout, "\tname=%d priority=%f allResident=%d isTex=%d\n",
		    name, priority, resident, isTex);
	}
	glutPostRedisplay();
        break;
	
      case '0':
	magFilter = nr;
	glutPostRedisplay();
	break;
      case '1':
	magFilter = ln;
	glutPostRedisplay();
	break;
      case '2':
	minFilter = nr;
	glutPostRedisplay();
	break;
      case '3':
	minFilter = ln;
	glutPostRedisplay();
	break;
      case '4':
	minFilter = nr_mipmap_nr;
	glutPostRedisplay();
	break;
      case '5':
	minFilter = nr_mipmap_ln;
	glutPostRedisplay();
	break;
      case '6':
	minFilter = ln_mipmap_nr;
	glutPostRedisplay();
	break;
      case '7':
	minFilter = ln_mipmap_ln;
	glutPostRedisplay();
	break;

      case 27:
	exit(0);
    }
}

static void SpecialKey(int key, int x, int y)
{

    switch (key) {
      case GLUT_KEY_LEFT:
	yRotation -= 0.5;
	glutPostRedisplay();
	break;
      case GLUT_KEY_RIGHT:
	yRotation += 0.5;
	glutPostRedisplay();
	break;
      case GLUT_KEY_UP:
	xRotation -= 0.5;
	glutPostRedisplay();
	break;
      case GLUT_KEY_DOWN:
	xRotation += 0.5;
	glutPostRedisplay();
	break;
    }
}

static void Draw(void)
{
    glClear(GL_COLOR_BUFFER_BIT);

    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, sWrapMode);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, tWrapMode);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);

    glPushMatrix();

    glTranslatef(0.0, 0.0, zTranslate);
    glRotatef(xRotation, 1, 0, 0);
    glRotatef(yRotation, 0, 1, 0);

    if (immed) { 		
	switch(currentList) {
	    case SQUARE:
		DrawSquares();
		break;
	    case PUSHPOP:
		DoPushPopTest();
		break;
	    case CUBE:
		DrawCube();
		break;
	}
    } else {
	glCallList(lists[currentList]);
    }

    glPopMatrix();

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

static char *argList[][2] = {
    {"-sb", "single buffered"},
    {"-db", "double buffered"},
    {"-dr", "direct rendering"},
    {"-ir", "indirect rendering"},
    {"-f filename", "filename for texture"},
    {"-texobj", "use texture objects"},
    {"-notexobj", "don't use texture objects"},
    {"-immed", "immediate mode"},
    {"-square", "draw squares"},
    {"-cube", "draw cube"},
    {"-pushpop", "push pop test"},
    {"-nq", "skip query texobj extension"},
    {"-h", "display this list"},
    {"Press H in app window to get key interpretations", ""},
    {NULL, NULL}
};

static void Args(int argc, char **argv)
{
    GLint i;

    doubleBuffer = GL_FALSE;

    for (i = 1; i < argc; i++) {
	if (strcmp(argv[i], "-sb") == 0) {
	    doubleBuffer = GL_FALSE;
	} else if (strcmp(argv[i], "-db") == 0) {
	    doubleBuffer = GL_TRUE;
	} else if (strcmp(argv[i], "-f") == 0) {
	    if (i+1 >= argc || argv[i+1][0] == '-') {
		printf("-f (No file name).\n");
		exit(1);
	    } else {
		texFiles[0] = argv[++i];
		multTex = GL_FALSE;
	    }
	} else if (strcmp(argv[i], "-flat") == 0) {
	    currentList = SQUARE;
	} else if (strcmp(argv[i], "-texobj") == 0) {
	    texObj = GL_TRUE;
	} else if (strcmp(argv[i], "-notexobj") == 0) {
	    texObj = GL_FALSE;
	} else if (strcmp(argv[i], "-immed") == 0) {
	    immed = GL_TRUE;
	} else if (strcmp(argv[i], "-pushpop") == 0) {
	    currentList = PUSHPOP;
	} else if (strcmp(argv[i], "-cube") == 0) {
	    currentList = CUBE;
	} else if (strcmp(argv[i], "-nq") == 0) {
	    queryTexobj = GL_FALSE;
	} else if (strcmp(argv[i], "-h") == 0) {
	    for (i = 0; argList[i][0] != NULL; i++) {
		printf("%-30s %s\n", argList[i][0], argList[i][1]);
	    }
	    exit(0);
	}
    }
}

static GLboolean QueryExtension(char *extName)
{
    /*
    ** Search for extName in the extensions string. Use of strstr()
    ** is not sufficient because extension names can be prefixes of
    ** other extension names. Could use strtok() but the constant
    ** string returned by glGetString can be in read-only memory.
    */
    char *p;
    char *end;
    int extNameLen;

    if (!queryTexobj) {
	return GL_TRUE;
    }

    extNameLen = strlen(extName);

    p = (char *)glGetString(GL_EXTENSIONS);
    if (NULL == p) {
	return GL_FALSE;
    }

    end = p + strlen(p);

    while (p < end) {
	int n = strcspn(p, " ");
	if ((extNameLen == n) && (strncmp(extName, p, n) == 0)) {
	    return GL_TRUE;
	}
	p += (n + 1);
    }
    return GL_FALSE;
}

int main(int argc, char **argv)
{
    GLenum type;

    glutInit(&argc, argv);
    Args(argc, argv);

    type = GLUT_RGB;
    type |= (doubleBuffer) ? GLUT_DOUBLE : GLUT_SINGLE;
    glutInitDisplayMode(type);
    glutInitWindowSize(300, 300);
    glutCreateWindow("Texture Test");

    if (texObj && !QueryExtension("GL_EXT_texture_object")) {
	fprintf(stdout, "Warning: texture object extension not found.\n");
	fprintf(stdout, "         Disabling texture objects.\n");
	texObj = GL_FALSE;
    }

    images[0] = rgbImageLoad(texFiles[0]);
    if (images[0] == NULL) {
	printf("Bad texture file.\n");
	exit(1);
    }
    if (multTex) {
	int i;
	for (i = 1; i < NUM_TEXTURES; i++) {
	    images[i] = rgbImageLoad(texFiles[i]);
	    if (images[i] == NULL) {
		printf("Bad texture file.\n");
		exit(1);
	    }
	}
    }

    Init();

    glutReshapeFunc(Reshape);
    glutKeyboardFunc(Key);
    glutSpecialFunc(SpecialKey);
    glutDisplayFunc(Draw);
    glutMainLoop();
}

#else

int main(int argc, char **argv)
{

    printf("Couldn't find GL_EXT_texture_object extension.\n");
}

#endif
