#include <GL/glut.h>
#include <math.h>
#include <stdio.h>
#include "texture.h"
#include <stdlib.h>

#ifndef __sgi
/* Most math.h's do not define float versions of the math functions. */
#define sqrtf(x) (float)sqrt((x))
#endif

#if !defined(GL_VERSION_1_1) && !defined(GL_VERSION_1_2)
#define glBindTexture glBindTextureEXT
#endif

#define CHECK_ERROR(str)                                           \
{                                                                  \
    GLenum error;                                                  \
    if(error = glGetError())                                       \
       printf("GL Error: %s (%s)\n", gluErrorString(error), str);  \
}

/* display lists */
enum {SPHERE = 1, CONE, LIGHT, DISK, FLOOR, BACK, LEFT, RIGHT, CEIL};

/* texture objects */
enum {DEFAULT, LIGHTMAP, SURFMAP}; 

enum {NONE, LIGHT_XY, LIGHT_Z, LIGHT_INTENS};

#ifndef TRUE
enum {FALSE, TRUE};
#endif
enum {X, Y, Z, W};
enum {R, G, B, A};

GLfloat staticlightpos[] = {50.f, 70.f, -10.f, 1.f};
GLfloat lightpos[] = {50.f, 70.f, -10.f, 1.f};
GLfloat intensity = 1.f;
GLfloat nearScale = .49f, farScale = .0001f;

int dblbuf = TRUE;
int action = NONE;
int winWidth = 512;
int winHeight = 512;
int curtess = 1; /* current tessellation level */
int lasttess = 1; /* last set tessellation level */

void
reshape(int wid, int ht)
{
    winWidth = wid;
    winHeight = ht;
    glViewport(0, 0, wid, ht);
}

void
motion(int x, int y)
{
    switch(action)
    {
    case LIGHT_XY:
	lightpos[X] = (x - winWidth/2) * 200.f/winWidth;
	lightpos[Y] = (winHeight/2 - y) * 200.f/winHeight;
	glutPostRedisplay();
	break;
    case LIGHT_Z:
	lightpos[Z] = (winHeight/2 - y) * 200.f/winWidth;
	glutPostRedisplay();
	break;
    case LIGHT_INTENS:
	intensity = x/(GLfloat)winWidth;
	glutPostRedisplay();
	break;
    }
}

void
mouse(int button, int state, int x, int y)
{
    if(state == GLUT_DOWN)
	switch(button)
	{
	case GLUT_LEFT_BUTTON: /* move the light */
	    action = LIGHT_XY;
	    motion(x, y);
	    break;
	case GLUT_MIDDLE_BUTTON:
	    action = LIGHT_INTENS;
	    motion(x, y);
	    break;
	case GLUT_RIGHT_BUTTON: /* move the polygon */
	    action = LIGHT_Z;
	    motion(x, y);
	    break;
	}
}


/*
** Create a single component texture map
*/
GLfloat *make_texture(int maxs, int maxt)
{
    int s, t;
    GLfloat *texture;

    texture = (GLfloat *)malloc(maxs * maxt * sizeof(GLfloat));
    for(t = 0; t < maxt; t++) {
	for(s = 0; s < maxs; s++) {
	    texture[s + maxs * t] = ((s >> 3) & 0x1) ^ ((t >> 3) & 0x1);
	}
    }
    return texture;
}

/* make tesselated surface for display list dlist */
/* normal tells you surface position and orientation */
/* count = tesselation count */
void
tess_surface(GLuint dlist, int count)
{
    int i, j;
    GLfloat x0, y0, z0;
    GLfloat over[3], up[3]; /* axes */
    int Sindex, Tindex; /* active texture axes */

    glNewList(dlist, GL_COMPILE);
    glBegin(GL_QUADS);

    switch(dlist)
    {
    case FLOOR:
	glNormal3f( 0.f,  1.f, 0.f);
	x0 = -100.f; y0 = -100.f; z0 = 100.f;
	over[X] = 1.f/count; over[Y] = 0.f; over[Z] = 0.f;
	up[X] = 0.f; up[Y] = 0.f; up[Z] = -1.f/count;
	Sindex = X; Tindex = Z;
	break;
    case CEIL:
	glNormal3f( 0.f, -1.f, 0.f);
	x0 = -100.f; y0 = 100.f; z0 = 100.f;
	over[X] = 0.f; over[Y] = 0.f; over[Z] = -1.f/count;
	up[X] = 1.f/count; up[Y] = 0.f; up[Z] = 0.f;
	Sindex = X; Tindex = Z;
	break;
    case LEFT:
	glNormal3f( 1.f,  0.f, 0.f);
	x0 = -100.f; y0 = -100.f; z0 = 100.f;
	over[X] = 0.f; over[Y] = 0.f; over[Z] = -1.f/count; 
	up[X] = 0.f; up[Y] = 1.f/count; up[Z] = 0.f;
	Sindex = Z; Tindex = Y;
	break;
    case RIGHT:
	glNormal3f(-1.f,  0.f, 0.f);
	x0 =  100.f; y0 = -100.f; z0 = 100.f;
	over[X] = 0.f; over[Y] = 1.f/count; over[Z] = 0.f;
	up[X] = 0.f; up[Y] = 0.f; up[Z] = -1.f/count; 
	Sindex = Z; Tindex = Y;
	break;
    case BACK:
	glNormal3f( 0.f,  0.f, 1.f);
	x0 = -100.f; y0 = -100.f; z0 = -100.f;
	over[X] = 1.f/count; over[Y] = 0.f; over[Z] = 0.f;
	up[X] = 0.f; up[Y] = 1.f/count; up[Z] = 0.f;
	Sindex = X; Tindex = Y;
	break;
    default:
	fprintf(stderr, "tess_surface(): bad display list argument %d\n",
		dlist);
	glEnd();
	glEndList();
	return;
    }

    for(j = 0; j < count; j++)
	for(i = 0; i < count; i++)
	{
	    glTexCoord2f(i * over[Sindex], j * up[Tindex]);
	    glVertex3f(x0 + i * 200 * over[X] + j * 200 * up[X],
		       y0 + i * 200 * over[Y] + j * 200 * up[Y],
		       z0 + i * 200 * over[Z] + j * 200 * up[Z]);
	    glTexCoord2f((i + 1) * over[Sindex], j * up[Tindex]);
	    glVertex3f(x0 + (i + 1) * 200 * over[X] + j * 200 * up[X],
		       y0 + (i + 1) * 200 * over[Y] + j * 200 * up[Y],
		       z0 + (i + 1) * 200 * over[Z] + j * 200 * up[Z]);
	    glTexCoord2f((i + 1) * over[Sindex], (j + 1) * up[Tindex]);
	    glVertex3f(x0 + (i + 1) * 200 * over[X] + (j + 1) * 200 * up[X],
		       y0 + (i + 1) * 200 * over[Y] + (j + 1) * 200 * up[Y],
		       z0 + (i + 1) * 200 * over[Z] + (j + 1) * 200 * up[Z]);
	    glTexCoord2f(i * over[Sindex], (j + 1) * up[Tindex]);
	    glVertex3f(x0 + i * 200 * over[X] + (j + 1) * 200 * up[X],
		       y0 + i * 200 * over[Y] + (j + 1) * 200 * up[Y],
		       z0 + i * 200 * over[Z] + (j + 1) * 200 * up[Z]);
	}
    glEnd();
    glEndList();
}





static GLfloat zero[] = {0.f, 0.f, 0.f, 1.f};
static GLfloat one[] = {1.f, 1.f, 1.f, 1.f};
static GLfloat diff[] = {.25f, .25f, .25f, .25f};


/* create a lightmap simulating a local light with attenuation */
void
make_lightmap(GLenum light, int dim)
{
    GLfloat quadratic, linear, constant;
    GLfloat diffuse[4], ambient[4]; /* light color */
    GLfloat *texture;
    GLfloat dist, scale, edge;
    int size;
    int i, j;

    glPushAttrib(GL_TEXTURE_BIT);
    /* get from light to simplify api */
    glGetLightfv(light, GL_QUADRATIC_ATTENUATION, &quadratic);
    glGetLightfv(light, GL_LINEAR_ATTENUATION, &linear);
    glGetLightfv(light, GL_CONSTANT_ATTENUATION, &constant);

    glGetLightfv(light, GL_AMBIENT, ambient);
    glGetLightfv(light, GL_DIFFUSE, diffuse);

    size = dim + 2;
    texture = (GLfloat *)malloc(sizeof(GLfloat) * 3 * size * size);

    dist = dim/2; /* 1 in from border */
    edge = 1.f/(constant + linear * dist + quadratic * dist * dist);

    for(j = 0; j < size; j++)
	for(i = 0; i < size; i++)
	{
	    dist = sqrtf((float)((size/2.f - i) * (size/2.f - i) +
				 (size/2.f - j) * (size/2.f - j)));
	    scale = 1.f/(constant + linear * dist + quadratic * dist * dist);
	    if(dist >= dim/2)
	    {
		texture[3 * (i + size * j) + 0] = 
		    diffuse[R] * edge + ambient[R];
		texture[3 * (i + size * j) + 1] = 
		    diffuse[G] * edge + ambient[G];
		texture[3 * (i + size * j) + 2] = 
		    diffuse[B] * edge + ambient[B];
	    }
	    else
	    {
		texture[3 * (i + size * j) + 0] = 
		    diffuse[R] * scale + ambient[R];
		texture[3 * (i + size * j) + 1] = 
		    diffuse[G] * scale + ambient[G];
		texture[3 * (i + size * j) + 2] = 
		    diffuse[B] * scale + ambient[B];
	    }
	}

    glBindTexture(GL_TEXTURE_2D, LIGHTMAP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

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

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 
		 size, size, 1, GL_RGB, GL_FLOAT, texture);
    free(texture);
    glPopAttrib();
}

/* draw a highly tesselated disk with local light */
void
draw_lightmap(int dim, int dist)
{
    GLUquadricObj *qobj;
    GLfloat *texture;
    GLfloat light[4] = {0.f, 0.f, 1.f, 1.f};

    texture = (GLfloat *)malloc(sizeof(GLfloat) * 3 * dim * dim);

    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    glPushMatrix(); /* starts as modelview */
    glLoadIdentity();
    glTranslatef(0.f, 0.f, -dist);

    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    glOrtho(-dim/2., dim/2., -dim/2., dim/2., 0., (double)dist);

    glPushAttrib(GL_LIGHTING_BIT|GL_VIEWPORT_BIT);
    glViewport(0, 0, dim, dim);
    glEnable(GL_LIGHTING);

    light[Z] = dist;
    glLightfv(GL_LIGHT0, GL_POSITION, light); /* light position */

    /* XXX TODO, range check an report errors */
    glDisable(GL_TEXTURE_2D);

    qobj = gluNewQuadric();
    gluDisk(qobj, 0., dim/2. * sqrt(2.), dim/4, dim/4);
    gluDeleteQuadric(qobj);

    glEnable(GL_TEXTURE_2D);
    glPopAttrib();
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();

    glReadPixels(0, 0, dim, dim, GL_RGB, GL_FLOAT, texture);

    glBindTexture(GL_TEXTURE_2D, LIGHTMAP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, dim, dim, 0, GL_RGB,
		 GL_FLOAT, texture);

    free(texture);
}


int texdim = 256;


/* draw the lightmap texture */
void redraw_lightmap(void)
{
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    glPushAttrib(GL_LIGHTING_BIT|GL_TEXTURE_BIT);

    /* assume GL_MODELVIEW */
    glPushMatrix();
    glLoadIdentity();
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();

    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, LIGHTMAP);
    glDisable(GL_LIGHTING);

    glColor3f(1.f, 1.f, 1.f);
    glBegin(GL_QUADS);
    glTexCoord2i(0, 0);
    glVertex2i(-1, -1);

    glTexCoord2i(1, 0);
    glVertex2i(1, -1);

    glTexCoord2i(1, 1);
    glVertex2i(1,  1);

    glTexCoord2i(0, 1);
    glVertex2i(-1,  1);
    glEnd();

    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();

    glPopAttrib();

    CHECK_ERROR("OpenGL Error in redraw_map()");

    if(dblbuf)
	glutSwapBuffers(); 
    else
	glFlush(); 
}

/* draw the lightmap texture */
void redraw_combomap(void)
{
    GLfloat scale;
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    glPushAttrib(GL_LIGHTING_BIT|GL_TEXTURE_BIT|GL_COLOR_BUFFER_BIT|
		 GL_DEPTH_BUFFER_BIT);

    /* assume GL_MODELVIEW */
    glPushMatrix();
    glLoadIdentity();
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();

    glEnable(GL_TEXTURE_2D);
    glDisable(GL_LIGHTING);


    glBindTexture(GL_TEXTURE_2D, SURFMAP);

    glColor3f(intensity, intensity, intensity);
    glBegin(GL_QUADS);
    glTexCoord2i(0, 0);
    glVertex2i(-1, -1);

    glTexCoord2i(5, 0);
    glVertex2i(1, -1);

    glTexCoord2i(5, 5);
    glVertex2i(1,  1);

    glTexCoord2i(0, 5);
    glVertex2i(-1,  1);
    glEnd();

    glEnable(GL_BLEND);
    glDepthFunc(GL_LEQUAL);
    glBlendFunc(GL_ZERO, GL_SRC_COLOR);
    glBindTexture(GL_TEXTURE_2D, LIGHTMAP);

    glMatrixMode(GL_TEXTURE);
    glPushMatrix();
    glTranslatef(.5f, .5f, 0.f);
    scale = .25f + (lightpos[Z] + 100.f) * 3.75f/200.f;
    glScalef(scale, scale, 0.f);
    glTranslatef(-.5f, -.5f, 0.f);
    glTranslatef(-lightpos[X]/200.f, -lightpos[Y]/200.f, 0.f);

    glColor3f(1.f, 1.f, 1.f);
    glBegin(GL_QUADS);
    glTexCoord2i(0, 0);
    glVertex2i(-1, -1);

    glTexCoord2i(1, 0);
    glVertex2i(1, -1);

    glTexCoord2i(1, 1);
    glVertex2i(1,  1);

    glTexCoord2i(0, 1);
    glVertex2i(-1,  1);
    glEnd();

    /* GL_TEXTURE */
    glPopMatrix();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();

    glPopAttrib();

    CHECK_ERROR("OpenGL Error in redraw_combomap()");

    if(dblbuf)
	glutSwapBuffers(); 
    else
	glFlush(); 
}

/* draw the openGL scene with lighting off */
void render_scene(int tess)
{
    /* material properties for objects in scene */

    /* update tessellation if it changed */
    if(tess != lasttess)
    {
	tess_surface(FLOOR, tess);
	tess_surface(CEIL,  tess);
	tess_surface(LEFT,  tess);
	tess_surface(RIGHT, tess);
	tess_surface(BACK,  tess);
	lasttess = tess;
    }
    glPushAttrib(GL_LIGHTING_BIT|GL_TEXTURE_BIT);

    /* floor */
    glColor3f(.5f, .35f, .35f); /* reddish */
    glEnable(GL_TEXTURE_2D);
    glCallList(FLOOR);
    glDisable(GL_TEXTURE_2D);

    /* ceiling */
    glColor3f(.35f, .5f, .35f); /* greenish */
    glCallList(CEIL);

    /* right wall */
    glColor3f(.35f, .35f, .5f); /* bluish */
    glCallList(RIGHT);

    /* left wall */
    glCallList(LEFT);

    /* back wall */
    glCallList(BACK);

    /* draw the sphere */
    
    glCallList(SPHERE);

    /* draw the cone */
    glCallList(CONE);

    /* draw the light */
    glPushMatrix();
    glTranslatef(lightpos[X], lightpos[Y], lightpos[Z]);
    glCallList(LIGHT);
    glPopMatrix();

    glPopAttrib();
    CHECK_ERROR("OpenGL Error in render_unlit()");
}

void
redraw_opengl(void)
{
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
    glPushAttrib(GL_LIGHTING_BIT);

    glEnable(GL_LIGHTING);
    glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
    glEnable(GL_COLOR_MATERIAL);
    glLightfv(GL_LIGHT0, GL_POSITION, lightpos); /* opengl lit */ 


    render_scene(curtess);

    glPopAttrib();

    if(dblbuf)
	glutSwapBuffers(); 
    else
	glFlush(); 
}


void
redraw_unlit(void)
{
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
    glPushAttrib(GL_LIGHTING_BIT);

    glDisable(GL_LIGHTING);
    render_scene(1);

    glPopAttrib();

    if(dblbuf)
	glutSwapBuffers(); 
    else
	glFlush(); 
}


/* draw the scene white, modulated by lightmap */
/* unlit, uncolored simulated by lighting */
/* texgen overrides texture coords */
void render_white(void)
{
    GLfloat scale;

    /* texgen used to override texure coords */
    static GLfloat XZs[] = {1/200.f, 0.f, 0.f, .5f};
    static GLfloat XZt[] = {0.f, 0.f, 1/200.f, .5f};

    static GLfloat YZs[]  = {0.f, 0.f, 1/200.f, .5f};
    static GLfloat YZt[]  = {0.f, 1/200.f, 0.f, .5f};

    static GLfloat XYs[]  = {1/200.f, 0.f, 0.f, .5f};
    static GLfloat XYt[]  = {0.f, 1/200.f, 0.f, .5f};

    /* material properties for objects in scene */

    glPushAttrib(GL_LIGHTING_BIT|GL_TEXTURE_BIT);

    glTexGenf(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
    glTexGenf(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);

    glEnable(GL_LIGHTING);

    /* simulate unlit, color = white */
    glLightfv(GL_LIGHT0, GL_AMBIENT, diff);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, zero);
    glColorMaterial(GL_FRONT, GL_AMBIENT);
    glEnable(GL_COLOR_MATERIAL);

    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, LIGHTMAP);
    
    glEnable(GL_TEXTURE_GEN_S);
    glEnable(GL_TEXTURE_GEN_T);
    glMatrixMode(GL_TEXTURE);
    glPushMatrix();

    glTexGenfv(GL_S, GL_OBJECT_PLANE, XZs);
    glTexGenfv(GL_T, GL_OBJECT_PLANE, XZt);

    glLoadIdentity();
    glTranslatef(.5f, .5f, 0.f);
    scale = nearScale - (nearScale - farScale)/200.f * (100.f + lightpos[Y]);
    glScalef(scale, scale, 1.f);
    glTranslatef(-.5f, -.5f, 0.f);
    glTranslatef(-lightpos[X]/200.f, -lightpos[Z]/200.f, 0.f);
    /* add intensity (colorchange) */

    scale = lightpos[Y] + 100;
    scale /= 200.f;
    scale *= scale;
    scale = 1.f/(1.f + scale);
    scale = 1.f;
    glColor3f(scale, scale, scale);

    /* floor */
    glCallList(FLOOR);

    glLoadIdentity();
    glTranslatef(.5f, .5f, 0.f);
    scale = nearScale - (nearScale - farScale)/200.f * (100.f - lightpos[Y]);
    glScalef(scale, scale, 1.f);
    glTranslatef(-.5f, -.5f, 0.f);
    glTranslatef(-lightpos[X]/200.f, -lightpos[Z]/200.f, 0.f);

    scale = 100 - lightpos[Y];
    scale /= 100.f;
    scale *= scale;
    scale = 1.f/(1.f + scale);
    scale = 1.f;
    glColor3f(scale, scale, scale);

    /* ceiling */
     glCallList(CEIL);

    glTexGenfv(GL_S, GL_OBJECT_PLANE, YZs);
    glTexGenfv(GL_T, GL_OBJECT_PLANE, YZt);
    glLoadIdentity();
    glTranslatef(.5f, .5f, 0.f);
    scale = nearScale - (nearScale - farScale)/200.f * (100.f - lightpos[X]);
    glScalef(scale, scale, 1.f);
    glTranslatef(-.5f, -.5f, 0.f);
    glTranslatef(-lightpos[Z]/200.f, -lightpos[Y]/200.f, 0.f);

    scale = 100 - lightpos[X];
    scale /= 100.f;
    scale *= scale;
    scale = 1.f/(1.f + scale);
    scale = 1.f;
    glColor3f(scale, scale, scale);

    /* right wall */
    glCallList(RIGHT);

    glLoadIdentity();
    glTranslatef(.5f, .5f, 0.f);
    scale = nearScale - (nearScale - farScale)/200.f * (100.f + lightpos[X]);
    glScalef(scale, scale, 1.f);
    glTranslatef(-.5f, -.5f, 0.f);
    glTranslatef(-lightpos[Z]/200.f, -lightpos[Y]/200.f, 0.f);

    scale = lightpos[X] + 100;
    scale /= 100.f;
    scale *= scale;
    scale = 1.f/(1.f + scale);
    scale = 1.f;
    glColor3f(scale, scale, scale);

    /* left wall */
    glCallList(LEFT);

    glTexGenfv(GL_S, GL_OBJECT_PLANE, XYs);
    glTexGenfv(GL_T, GL_OBJECT_PLANE, XYt);
    glLoadIdentity();
    glTranslatef(.5f, .5f, 0.f);
    scale = nearScale - (nearScale - farScale)/200.f * (100.f + lightpos[Z]);
    glScalef(scale, scale, 1.f);
    glTranslatef(-.5f, -.5f, 0.f);
    glTranslatef(-lightpos[X]/200.f, -lightpos[Y]/200.f, 0.f);

    scale = lightpos[Z] + 100;
    scale /= 100.f;
    scale *= scale;
    scale = 1.f/(1.f + scale);
    scale = 1.f;
    glColor3f(scale, scale, scale);

    /* back wall */
    glCallList(BACK);

    /* done with texture matrix */
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);

    /* no contribution to highly tesselated objects */
    glMaterialfv(GL_FRONT, GL_AMBIENT, zero);
    glLightfv(GL_LIGHT0, GL_AMBIENT, zero);

    /* draw the sphere */
    
    glCallList(SPHERE);

    /* draw the cone */
    glCallList(CONE);

    glPopAttrib();
    CHECK_ERROR("OpenGL Error in render_white()");
}

void
redraw_white(void)
{
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
    render_white();

    if(dblbuf)
	glutSwapBuffers(); 
    else
	glFlush(); 
}


void
redraw_lightmapped(void)
{
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    glPushAttrib(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_LIGHTING_BIT);
    glDisable(GL_LIGHTING);
    render_scene(1);
    glEnable(GL_LIGHTING);
    glEnable(GL_BLEND);
    glBlendFunc(GL_ZERO, GL_SRC_COLOR);
    glDepthFunc(GL_LEQUAL);
    render_white();
    glPopAttrib();

    if(dblbuf)
	glutSwapBuffers(); 
    else
	glFlush(); 
}

void
redraw_maponly(void)
{
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    glPushAttrib(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_LIGHTING_BIT);
    glDisable(GL_LIGHTING);
    render_scene(1);
    glEnable(GL_LIGHTING);
    glEnable(GL_BLEND);
    glBlendFunc(GL_ZERO, GL_SRC_COLOR);
    glDepthFunc(GL_LEQUAL);
    render_white();
    glBlendFunc(GL_ONE, GL_ONE);
    glDisable(GL_LIGHTING); /* add in unlit scene again */
    render_scene(1);
    
    glPopAttrib();

    if(dblbuf)
	glutSwapBuffers(); 
    else
	glFlush(); 
}

void
redraw_complete(void)
{
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    glPushAttrib(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_LIGHTING_BIT);

    glLightfv(GL_LIGHT0, GL_POSITION, lightpos); /* opengl lit */ 
    glDisable(GL_LIGHTING);
    render_scene(1);
    glEnable(GL_LIGHTING);
    glEnable(GL_BLEND);
    glBlendFunc(GL_ZERO, GL_SRC_COLOR);
    glDepthFunc(GL_LEQUAL);
    render_white();
    glBlendFunc(GL_ONE, GL_ONE);
    glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
    glEnable(GL_COLOR_MATERIAL);
    render_scene(1);

    glPopAttrib();

    if(dblbuf)
	glutSwapBuffers(); 
    else
	glFlush(); 
}



/* ARGSUSED1 */
void key(unsigned char key, int x, int y)
{
    switch(key)
    {
    case 'l': /* show lightmap */
	glutDisplayFunc(redraw_lightmap);
	glutPostRedisplay();
	break;
    case 'w': /* show white scene modulated by lightmap */
	glutDisplayFunc(redraw_white);
	glutPostRedisplay();
	break;
    case 'm': /* unlit scene modified by lightmap */
	glutDisplayFunc(redraw_lightmapped);
	glutPostRedisplay();
	break;
    case 'u': /* draw scene without lighting */
	glutDisplayFunc(redraw_unlit);
	glutPostRedisplay();
	break;
    case 'o': /* draw scene lit using OpenGL */
	glutDisplayFunc(redraw_opengl);
	glutPostRedisplay();
	break;
    case 'a': /* show scene lit with OpenGL and lightmaps */
	glutDisplayFunc(redraw_complete);
	glutPostRedisplay();
	break;
    case 'p': /* show scene lit with pure lightmaps */
	glutDisplayFunc(redraw_maponly);
	glutPostRedisplay();
	break;
    case 'x': /* surface texture with lightmap combined */
	glutDisplayFunc(redraw_combomap);
	glutPostRedisplay();
	break;
    case 'T': /* surface texture with lightmap combined */
	curtess++;
	glutPostRedisplay();
	break;
    case 't': /* surface texture with lightmap combined */
	curtess--;
	if(curtess < 1)
	    curtess = 1;
	glutPostRedisplay();
	break;
    case '1': /* surface texture with lightmap combined */
	curtess = 1;
	glutPostRedisplay();
	break;
    case 'y':
	farScale -= .0001f;
	if(farScale < .0001f)
	    farScale = .0001f;
	printf("farScale = %.4f\n", farScale);
	glutPostRedisplay();
	break;
    case 'Y':
	farScale += .0001f;
	printf("farScale = %.4f\n", farScale);
	glutPostRedisplay();
	break;
    case 'z':
	nearScale -= .01f;
	if(nearScale < .01f)
	    nearScale = .01f;
	printf("nearScale = %.2f\n", nearScale);
	glutPostRedisplay();
	break;
    case 'Z':
	nearScale += .01f;
	printf("nearScale = %.2f\n", nearScale);
	glutPostRedisplay();
	break;
    case '\033':
	exit(0);
	break;
    case '?':
    case 'h':
    default:
	fprintf(stderr, 
		"Keyboard Commands\n"
		"l - draw lightmap\n"
		"w - draw white scene modulated by lightmap\n"
		"m - draw unlit scene modified by lightmap\n"
		"u - draw unlit scene\n"
		"o - draw scene lit by OpenGL\n"
		"a - draw scene lit by OpenGL (tess = 1) + lightmaps\n"
		"p - draw scene lit only by lightmaps\n"
		"x - interactive lightmap on brick wall\n"
		"T - increase surface tessellation\n"
		"t - decrease surface tessellation\n"
		"1 - set tessellation to one\n"
		"y - decrease far scale for lightmaps\n"
		"Y - increase far scale for lightmaps\n"
		"z - decrease near scale for lightmaps\n"
		"Z - increase near scale for lightmaps\n");
	break;
    }
    glutPostRedisplay();
}




main(int argc, char *argv[])
{
    GLfloat *tex;

    GLUquadricObj *qobj;

    glutInit(&argc, argv);
    glutInitWindowSize(winWidth, winHeight);
    if(argc > 1)
    {
	char *args = argv[1];
	int done = FALSE;
	while(!done)
	{
	    switch(*args)
	    {
	    case 's': /* single buffer */
		printf("Single Buffered\n");
		dblbuf = FALSE;
		break;
	    case '-': /* do nothing */
		break;
	    case 0:
		done = TRUE;
		break;
	    }
	    args++;
	}
    }

    if(dblbuf)
	glutInitDisplayMode(GLUT_RGBA|GLUT_DEPTH|GLUT_DOUBLE);
    else
	glutInitDisplayMode(GLUT_RGBA|GLUT_DEPTH);


    (void)glutCreateWindow("example program");
    glutDisplayFunc(redraw_opengl);
    glutKeyboardFunc(key);
    glutMouseFunc(mouse);
    glutMotionFunc(motion);
    glutReshapeFunc(reshape);

    /* draw a perspective scene */
    glMatrixMode(GL_PROJECTION);
    glFrustum(-100., 100., -100., 100., 300., 501.); 
    glMatrixMode(GL_MODELVIEW);
    /* look at scene from (0, 0, 400) */
    gluLookAt(0., 0., 400., 0., 0., 0., 0., 1., 0.);

    /* turn on features */
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_LIGHT0);
    glLightfv(GL_LIGHT0, GL_POSITION, staticlightpos);

    /* remove back faces to speed things up */
    glCullFace(GL_BACK);


    /* make a display list containing a sphere */
    glNewList(SPHERE, GL_COMPILE);
    {
	glPushMatrix();
	glTranslatef(0.f, -80.f, -80.f);
	qobj = gluNewQuadric();
	gluQuadricTexture(qobj, GL_TRUE);
	glColor3f(.5f, .25f, 0.f);
	gluSphere(qobj, 20.f, 20, 20);
	gluDeleteQuadric(qobj);
	glPopMatrix();
    }
    glEndList();

    /* make a display list containing a sphere */
    glNewList(LIGHT, GL_COMPILE);
    {
	qobj = gluNewQuadric();
	glColor3f(1.f, 1.f, 1.f);
	glPushAttrib(GL_LIGHTING_BIT);
	glDisable(GL_LIGHTING);
	gluSphere(qobj, 3.f, 20, 20);
	glPopAttrib();
	gluDeleteQuadric(qobj);
    }
    glEndList();

    /* create a display list containing a cone */
    glNewList(CONE, GL_COMPILE);
    {
	glPushMatrix();
	glTranslatef(-60.f, -100.f, -5.f);

	qobj = gluNewQuadric();
	gluQuadricTexture(qobj, GL_TRUE);
	glRotatef(-90.f, 1.f, 0.f, 0.f);
	glColor3f(0.f, .25f, .5f);

	gluCylinder(qobj, 20., 0., 60., 20, 20);
	gluDeleteQuadric(qobj);

	qobj = gluNewQuadric();
	gluQuadricOrientation(qobj, GLU_INSIDE);
	gluDisk(qobj, 0., 20., 20, 1);
	gluDeleteQuadric(qobj);
	glPopMatrix();
    }
    glEndList();

    /* load pattern for current 2d texture */
    tex = make_texture(128, 128);

    /* makes texturing faster, and looks better than GL_LINEAR */
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexImage2D(GL_TEXTURE_2D, 0, 1, 128, 128, 0, GL_RED, GL_FLOAT, tex);
    free(tex);

    {
	GLuint *surftex;
	int texwid, texht, texcomps;
	surftex = read_texture("data/brick.rgb", &texwid, &texht, &texcomps);

	glBindTexture(GL_TEXTURE_2D, SURFMAP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texwid, texht, 0, GL_RGBA,
		     GL_UNSIGNED_BYTE, surftex);

    }

    glBindTexture(GL_TEXTURE_2D, DEFAULT);

    tess_surface(FLOOR, 1);
    tess_surface(CEIL, 1);
    tess_surface(LEFT, 1);
    tess_surface(RIGHT, 1);
    tess_surface(BACK, 1);

    /* 1/80 intensity at edges */
    glLightf(GL_LIGHT1, GL_QUADRATIC_ATTENUATION, 100.f/(texdim * texdim/4));
    /* 1/2 intensity at edges */
    glLightf(GL_LIGHT1, GL_LINEAR_ATTENUATION, 1.f/texdim/2);
    glLightfv(GL_LIGHT1, GL_DIFFUSE, one);

    make_lightmap(GL_LIGHT1, 128);

    key('?', 0, 0);

    CHECK_ERROR("end of main");

    glutMainLoop();

    return 0;
}

