/* topblock, Copyright (c) 2006-2009
 *  rednuht <topblock.xscreensaver@jumpstation.co.uk>
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation.  No representations are made about the suitability of this
 * software for any purpose.  It is provided "as is" without express or 
 * implied warranty.
 *
 * 
 *

topBlock - a simple openGL 3D hack of falling blocks
based on jwz's dangerball hack

The proporations of the blocks and their features is not even close to the commercial building block products offered by a variety companies.

information on this hack might be found at 
http://www.jumpstation.co.uk/xscreensaver/topblock/

History
25/02/2006 v1.0 release
29/04/2006 v1.11 updated to better fit with xscreensaver v5
                 colors defaults to 7 (no black)
19/06/2006 v1.2  fixed dropSpeed = 7 bug, added gltrackball support and some code neatening, thanks to Valdis Kletnieks and JWZ for their input.
*/

#include <math.h>

# define refresh_topBlock 0

#define DEFAULTS	"*delay:	10000       \n" \
			"*count:        30           \n" \
			"*showFPS:      False       \n" \
			"*wireframe:    False       \n" \

#undef countof
#define countof(x) (sizeof((x))/sizeof((*x)))

#include "xlockmore.h"
#include "topblock.h"
#include "sphere.h"
#include "gltrackball.h"
#include <ctype.h>

#ifdef USE_GL /* whole file */

#ifdef HAVE_COCOA
# include <OpenGL/glu.h>
#else
# include <GL/glu.h>
#endif

typedef struct
{
  GLXContext *glx_context;
  trackball_state *trackball;
  Bool button_down_p;
  int numFallingBlocks;
  GLfloat highest,highestFalling;
  GLfloat eyeLine,eyeX,eyeY,eyeZ;
  int carpetWidth, carpetLength;
  int followMode;
  GLfloat followRadius,followAngle;
  int plusheight;
  GLuint	carpet;
  GLuint	block;
  int carpet_polys, block_polys;
  NODE *blockNodeRoot;
  NODE *blockNodeFollow;
  GLfloat rotation;
} topBlockSTATE;

/* parameter vars */
Bool override;
Bool rotate;
Bool follow;
Bool drawCarpet;
Bool drawBlob;
Bool drawNipples;
GLfloat rotateSpeed;
GLfloat camX;
GLfloat camY;
GLfloat camZ;
GLfloat dropSpeed;
int maxFalling;
int maxColors;
int size;
int spawn;
int resolution;

static XrmOptionDescRec opts[] = {
  { "-size",        ".size",        XrmoptionSepArg, 0 },
  { "-spawn",       ".spawn",       XrmoptionSepArg, 0 },
  { "-camX",        ".camX",        XrmoptionSepArg, 0 },
  { "-camY",        ".camY",        XrmoptionSepArg, 0 },
  { "-camZ",        ".camZ",        XrmoptionSepArg, 0 },
  { "+rotate",      ".rotate",      XrmoptionNoArg, "False" },
  { "-rotate",      ".rotate",      XrmoptionNoArg, "True" },
  { "+carpet",      ".carpet",      XrmoptionNoArg, "False" },
  { "+nipples",     ".nipples",     XrmoptionNoArg, "False" },
  { "-blob",        ".blob",        XrmoptionNoArg, "True" },
  { "-rotateSpeed", ".rotateSpeed", XrmoptionSepArg, 0 },
  { "-follow",      ".follow",      XrmoptionNoArg, "True" },
  { "-maxFalling",  ".maxFalling",  XrmoptionSepArg, 0 },
  { "-resolution",  ".resolution",  XrmoptionSepArg, 0 },
  { "-maxColors",   ".maxColors",   XrmoptionSepArg, 0 },
  { "-dropSpeed",   ".dropSpeed",   XrmoptionSepArg, 0 },
  { "-override",    ".override",    XrmoptionNoArg, "True" },
};

#define DEF_OVERRIDE      "False"
#define DEF_ROTATE        "True"
#define DEF_FOLLOW        "False"
#define DEF_CARPET   "True"
#define DEF_BLOB     "False"
#define DEF_NIPPLES  "True"
#define DEF_ROTATE_SPEED  "10"
#define DEF_MAX_FALLING   "500"
#define DEF_MAX_COLORS    "7"
#define DEF_SIZE          "2"
#define DEF_SPAWN         "50"
#define DEF_RESOLUTION    "4"
#define DEF_CAM_X         "1"
#define DEF_CAM_Y         "20"
#define DEF_CAM_Z         "25"
#define DEF_DROP_SPEED    "4"

static argtype vars[] = {
  {&override,     "override",     "Override",     DEF_OVERRIDE,     t_Bool},
  {&rotate,       "rotate",       "Rotate",       DEF_ROTATE,       t_Bool},
  {&drawCarpet,   "carpet",       "Carpet",       DEF_CARPET,   t_Bool},
  {&drawNipples,  "nipples",      "Nipples",      DEF_NIPPLES,  t_Bool},
  {&drawBlob,     "blob",         "Blob",         DEF_BLOB,     t_Bool},
  {&rotateSpeed,  "rotateSpeed",  "RotateSpeed",  DEF_ROTATE_SPEED,  t_Float},
  {&follow,       "follow",       "Follow",       DEF_FOLLOW,       t_Bool},
  {&camX,         "camX",         "camX",         DEF_CAM_X,         t_Float},
  {&camY,         "camY",         "camY",         DEF_CAM_Y,         t_Float},
  {&camZ,         "camZ",         "camZ",         DEF_CAM_Z,         t_Float},
  {&size,         "size",         "size",         DEF_SIZE,         t_Int},
  {&spawn,        "spawn",        "spawn",        DEF_SPAWN,        t_Int},
  {&maxFalling,   "maxFalling",   "maxFalling",   DEF_MAX_FALLING,   t_Int},
  {&resolution,   "resolution",   "resolution",   DEF_RESOLUTION,   t_Int},
  {&maxColors,    "maxColors",    "maxColors",    DEF_MAX_COLORS,    t_Int},
  {&dropSpeed,    "dropSpeed",    "DropSpeed",    DEF_DROP_SPEED,    t_Float},
};

static topBlockSTATE *tbs = NULL;

ModeSpecOpt topBlock_opts = {countof(opts), opts, countof(vars), vars, NULL};

/* Window management, etc */
ENTRYPOINT void
reshape_topBlock (ModeInfo *mi, int width, int height)
{
  GLfloat h = (GLfloat) height / (GLfloat) width;
  glViewport (0, 0, (GLint) width, (GLint) height);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective (60.0, 1/h, 1.0, 1000.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glClear(GL_COLOR_BUFFER_BIT);
}

/* clean up on exit, not required ... */
ENTRYPOINT void
release_topBlock(ModeInfo *mi)
{
  topBlockSTATE *tb = &tbs[MI_SCREEN(mi)];
	NODE *llCurrent, *llOld;
	llCurrent = tb->blockNodeRoot;
	while (llCurrent != NULL) {
		llOld = llCurrent;
		llCurrent = llCurrent->next;
		free(llOld);
	}
}

/* setup */
ENTRYPOINT void 
init_topBlock (ModeInfo *mi)
{
  topBlockSTATE *tb;
  int wire = MI_IS_WIREFRAME(mi);

  if (!tbs) {
    tbs = (topBlockSTATE *)
      calloc (MI_NUM_SCREENS(mi), sizeof (topBlockSTATE));
    if (!tbs) abort();
  }

  tb = &tbs[MI_SCREEN(mi)];

  tb->glx_context = init_GL(mi);

  reshape_topBlock (mi, MI_WIDTH(mi), MI_HEIGHT(mi));

	if (wire) { drawNipples=False; }
  tb->numFallingBlocks=0;

	if (size>10) { size = 10; }
	if (size<1) { size = 2; }
	tb->carpetWidth = 8 * size;
	tb->carpetLength = tb->carpetWidth;
  
  maxFalling*=size;

	if (spawn<4) { spawn=4; }
	if (spawn>1000) { spawn=1000; }

	if (rotateSpeed<1) {rotateSpeed=1; }
	if (rotateSpeed>1000) {rotateSpeed=1000;}
  rotateSpeed /= 100;

	if (resolution<4) {resolution=4;}
	if (resolution>20) {resolution=20;}
  resolution*=2;

	if (maxColors<1) {maxColors=1;}
	if (maxColors>8) {maxColors=8;}

	if (dropSpeed<1) {dropSpeed=1;}
	if (dropSpeed>9) {dropSpeed=9;} /* 10+ produces blocks that can pass through each other */
  
	dropSpeed = 80/dropSpeed;
	dropSpeed = (blockHeight/dropSpeed); 

  reshape_topBlock (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
  glClearDepth(1.0f);
  if (!wire) {
    GLfloat pos[4] = {10.0, 10.0, 1.0, 0.0};
    GLfloat amb[4] = {0.1, 0.1, 0.1, 1.0};
    GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
    GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glLightfv(GL_LIGHT0, GL_POSITION, pos);
    glLightfv(GL_LIGHT0, GL_AMBIENT,  amb); 
    glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
    glLightfv(GL_LIGHT0, GL_SPECULAR, spc); 
  }
	glDepthFunc(GL_LEQUAL);
  glEnable(GL_DEPTH_TEST);
  glDisable(GL_CULL_FACE); /* all objects exhibit a reverse side */
  glCullFace(GL_BACK); 

	if (drawBlob) {
    buildBlobBlock(mi); 
	} else {
	  buildBlock(mi);		/* build the display list holding the simple block */
	}
  buildCarpet(mi);		/* build the base */
	tb->highest=0;
	tb->highestFalling=0;
	tb->eyeLine=tb->highest;
	tb->eyeX=0;
	tb->eyeY=0;
	tb->eyeZ=0;
	tb->followMode=0;
  if (follow) {
    tb->plusheight=100;
    camZ=camZ-60;
  } else {
    tb->rotation=random() % 360;
    tb->eyeY=10;
    tb->plusheight=30;
  }
	tb->followRadius=0;
  /* override camera settings */
  if (override) {
    tb->plusheight=100;
    drawCarpet=False;
    camX=0;
    camY=1;
    camZ=0;
    tb->eyeX=-1;
    tb->eyeY=20;
    tb->eyeZ=0;
  }
  tb->trackball = gltrackball_init ();
}

/* provides the per frame entertainment */
ENTRYPOINT void
draw_topBlock (ModeInfo *mi)
{
  	Display *dpy = MI_DISPLAY(mi);
  	Window window = MI_WINDOW(mi);
  	NODE *llCurrent;
  	NODE *llNode;
  	topBlockSTATE *tb = &tbs[MI_SCREEN(mi)];
  	GLfloat spcN1x,spcN1y,spcN2x,spcN2y;
  	GLfloat spcC1x,spcC1y,spcC2x,spcC2y;
  	int wire = MI_IS_WIREFRAME(mi);
	  GLfloat color[4];	

  if (!tb->glx_context)
    return;
  glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(tb->glx_context));
  mi->polygon_count = 0;

	generateNewBlock(mi);

	if (rotate && (!tb->button_down_p)) { tb->rotation += rotateSpeed; } 
	if (tb->rotation>=360) { tb->rotation=tb->rotation-360; } 

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);		/* clear current */
	glLoadIdentity();	/* resets directions, do it every time ! */
	if (!follow) {
		if (tb->highest>tb->eyeLine) { tb->eyeLine+=((tb->highest-tb->eyeLine)/100);	} /* creates a smooth camera transition */
		gluLookAt(camX, camY+tb->eyeLine, camZ, tb->eyeX, tb->eyeY+tb->eyeLine, tb->eyeZ, 0.0, 1.0, 0.0);		/* setup viewer, xyz cam, xyz looking at and where is up normaly 0,1,0 */
		glRotatef(90, 1.0, 0.0, 0.0);			/* x axis */
	} else {
		glRotatef(90, 0.0, 0.0, 1.0);     /* z axis */
		followBlock(mi);
	}
	glRotatef(-90, 1.0, 0.0, 0.0);		
  gltrackball_rotate (tb->trackball);
	glRotatef(90, 1.0, 0.0, 0.0);		

	/* rotate the world */
	glRotatef(tb->rotation, 0.0, 0.0, 1.0);		

	llCurrent = tb->blockNodeRoot;
	if (drawCarpet) {
		/* center carpet */
		glTranslatef(0.0-(tb->carpetWidth/2),0.0-(tb->carpetLength/2),0.0);
		glCallList(tb->carpet);
                mi->polygon_count += tb->carpet_polys;
		glTranslatef(0.0+(tb->carpetWidth/2),0.0+(tb->carpetLength/2),0.0);
		glTranslatef(0.0,0.0,-0.55);
	}
	tb->highestFalling=0;
	while (llCurrent != NULL) {	/* for each block */
		glPushMatrix();	/* save state */
		/* set color */
		switch (llCurrent->color) { 
			case 0:
				color[0] = 1.0f;	
				color[1] = 0.0f;	
				color[2] = 0.0f;	
				color[3] = 1.0f;	
				break;
			case 1:
				color[0] = 0.0f;	
				color[1] = 1.0f;	
				color[2] = 0.0f;	
				color[3] = 1.0f;	
				break;
			case 2:
				color[0] = 0.0f;	
				color[1] = 0.0f;	
				color[2] = 1.0f;	
				color[3] = 1.0f;	
				break;
			case 3:
				color[0] = 0.95f;	
				color[1] = 0.95f;	
				color[2] = 0.95f;	
				color[3] = 1.0f;	
				break;
			case 4:
				color[0] = 1.0f;	
				color[1] = 0.5f;	
				color[2] = 0.0f;	
				color[3] = 1.0f;	
				break;
			case 5:
				color[0] = 1.0f;	
				color[1] = 1.0f;	
				color[2] = 0.0f;	
				color[3] = 1.0f;	
				break;
			case 6: 
				color[0] = 0.5f;	
				color[1] = 0.5f;	
				color[2] = 0.5f;	
				color[3] = 1.0f;	
				break;
			case 7:
				color[0] = 0.05f;	
				color[1] = 0.05f;	
				color[2] = 0.05f;	
				color[3] = 1.0f;	
				break;
		} 
		if (wire) { glColor3fv(color); }
		else { glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color); }

		if (llCurrent->falling==1) {
    	spcC2x = 0;
	    spcC2y = 0;
	    spcN2x = 0;
	    spcN2y = 0;
			if (llCurrent->height>tb->highestFalling) {tb->highestFalling=llCurrent->height;}
			/* all blocks fall at the same rate to avoid mid air collisions */
			llCurrent->height-=dropSpeed;
			if (llCurrent->height<=0) {
				llCurrent->falling=0;
				if (tb->highest==0) { 
					tb->highest+=blockHeight; 
				}
			} 
			if ( (llCurrent->height<=tb->highest+1) && (llCurrent->falling==1) ) {
				/* check for collision */
				llNode = tb->blockNodeRoot;
				spcC1x = llCurrent->x;
				spcC1y = llCurrent->y;
				switch(llCurrent->rotation) {
					case getOrientation(0):
						spcC2x = spcC1x;
						spcC2y = spcC1y-2;
						break;
					case getOrientation(1):
						spcC2x = spcC1x+2;
						spcC2y = spcC1y;
						break;
					case getOrientation(2):
						spcC2x = spcC1x;
						spcC2y = spcC1y+2;
						break;
					case getOrientation(3):
						spcC2x = spcC1x-2;
						spcC2y = spcC1y;
						break;
				}
				while (llNode != NULL) {
					if ( (llNode->falling==0) && (llCurrent->falling==1) ) {
						spcN1x = llNode->x;
						spcN1y = llNode->y;
						switch(llNode->rotation) {
							case getOrientation(0):
								spcN2x = spcN1x;
								spcN2y = spcN1y-2;
								break;
							case getOrientation(1):
								spcN2x = spcN1x+2;
								spcN2y = spcN1y;
								break;
							case getOrientation(2):
								spcN2x = spcN1x;
								spcN2y = spcN1y+2;
								break;
							case getOrientation(3):
								spcN2x = spcN1x-2;
								spcN2y = spcN1y;
								break;
						}
						if ( 
							( (spcC1x==spcN1x) && (spcC1y==spcN1y) ) ||
							( (spcC1x==spcN2x) && (spcC1y==spcN2y) ) ||
							( (spcC2x==spcN2x) && (spcC2y==spcN2y) ) ||
							( (spcC2x==spcN1x) && (spcC2y==spcN1y) )
						){
              if ( fabs(llCurrent->height-(llNode->height+blockHeight)) <= TOLERANCE) { 

						    llCurrent->falling=0;
							  llCurrent->height=llNode->height+blockHeight; /* if this is missing then small errors build up until the model fails */
                if ( fabs(llCurrent->height-tb->highest) <= TOLERANCE+blockHeight ) {
                 tb->highest+=blockHeight; 
							  }
						  }
					  }
				  }
				  llNode=llNode->next;
			  }				
		  }
	  } 
  	/* set location in space */
	  glTranslatef(llCurrent->x,llCurrent->y,-llCurrent->height);
	  /* rotate */
  	glRotatef(llCurrent->rotation, 0.0f, 0.0f, 1.0f);
  	if ((tb->followMode==0) && (llCurrent->next==NULL)) {
  		tb->blockNodeFollow = llCurrent;
  		tb->followMode=1;
  	} 
  	llCurrent = llCurrent->next;
  	/* draw   */
  	glCallList(tb->block); 
        mi->polygon_count += tb->block_polys;
  	glPopMatrix();	/* restore state */
  } 
  if (mi->fps_p) do_fps (mi);
  glFinish();

	if (tb->highest>(5*maxFalling)) { drawCarpet=False; }
  glXSwapBuffers(dpy, window);
}



/* camera is in follow mode, work out where we should be looking */
static void followBlock(ModeInfo *mi)
{
	GLfloat xLen,yLen,cx,cy,rangle,xTarget,yTarget;
  topBlockSTATE *tb = &tbs[MI_SCREEN(mi)];
  cx=0;cy=0;
	if ((tb->blockNodeFollow!=NULL) && (tb->followMode==1)){

		if (tb->highest>tb->eyeLine) { tb->eyeLine+= ((tb->highest-tb->eyeLine)/100);	} 
		  /*tb->blockNodeFollow->color=1;  only noticable if you set the colors to 1 */
		
			if (tb->blockNodeFollow->height > tb->eyeZ) { tb->eyeZ+= ((tb->blockNodeFollow->height - tb->eyeZ)/100); } 
			if (tb->blockNodeFollow->height < tb->eyeZ) { tb->eyeZ-= ((tb->eyeZ - tb->blockNodeFollow->height)/100); } 
		

		/* when the scene is rotated we need to know where the block is in the 2 dimensional coordinates of the carpet area
		   (see http://www.jumpstation.co.uk/rotation/)
		*/

		if (tb->followRadius==0) {		
			xLen = tb->blockNodeFollow->x-cx;
			yLen = tb->blockNodeFollow->y-cy;
			tb->followRadius=sqrt( (xLen*xLen) + (yLen*yLen) );	
			tb->followAngle = (180/M_PI) * asin(xLen/tb->followRadius); 
			tb->followAngle = quadrantCorrection(tb->followAngle,(int)cx,(int)cy,(int)tb->blockNodeFollow->x,(int)tb->blockNodeFollow->y);
		}
		rangle = (tb->followAngle+tb->rotation) * M_PI /180;
		xTarget = cos(rangle) * tb->followRadius + cx;
		yTarget = sin(rangle) * tb->followRadius + cy;
		if (tb->followAngle>360) { tb->followAngle=tb->followAngle-360; }

		if (xTarget < tb->eyeX) { tb->eyeX-= ((tb->eyeX - xTarget)/100); }
		if (xTarget > tb->eyeX) { tb->eyeX+= ((xTarget - tb->eyeX)/100); }

		if (yTarget < tb->eyeY) { tb->eyeY-= ((tb->eyeY - yTarget)/100); }
		if (yTarget > tb->eyeY) { tb->eyeY+= ((yTarget - tb->eyeY)/100); }
		if (!tb->blockNodeFollow->falling) {  
			tb->followMode=0; 
			/*tb->blockNodeFollow->color=2;  only noticable if you set the colors to 1 */
			tb->followRadius=0;
		} 
	}
	gluLookAt(camX, camY, camZ-tb->eyeLine, tb->eyeX, tb->eyeY, -tb->eyeZ,-1.0,0.0,0.0);
}

/* each quater of the circle has to be adjusted for */
static double quadrantCorrection(double angle,int cx,int cy,int x,int y)
{
	if ((x>=cx) && (y>=cy)) {
		angle +=  (90-(angle-90) * 2); 
	} else if ((x>=cx) && (y<=cy)) {
		angle +=  90; 
	} else if ((x<=cx) && (y<=cy)) {
		angle +=  90; 
	} else if ((x<=cx) && (y>=cy)) {
		angle += (90-(angle-90) * 2); 
	}
	return(angle-180);
}

/* if random chance then create a new falling block */
static void generateNewBlock(ModeInfo *mi)
{
	NODE *llCurrent, *llTail;
	GLfloat startOffx, startOffy;
	int endOffx, endOffy;
  topBlockSTATE *tb = &tbs[MI_SCREEN(mi)];
	if ( ((random() % spawn) == 1) && (tb->highestFalling<getHeight((tb->plusheight-blockHeight)+tb->highest)) ) {	
		startOffx=0;
		endOffx=0;
		startOffy=0;
		endOffy=0;
		tb->numFallingBlocks++;
		llTail = tb->blockNodeRoot; 
		if (llTail == NULL) {
                  llCurrent = ((NODE*) malloc(sizeof(NODE)));
                  if (!llCurrent) abort();
                  llTail = llCurrent;
                  tb->blockNodeRoot = llCurrent; 
		} else {
			if (tb->numFallingBlocks>=maxFalling) {
				/* recycle */
				llCurrent=llTail->next;
				tb->blockNodeRoot=llCurrent->next;
			} else {
                          llCurrent = ((NODE*) malloc(sizeof(NODE)));
                          if (!llCurrent) abort();
			}
			while (llTail->next != NULL) { llTail = llTail->next; } /* find last item in list */
		}
		llCurrent->falling=1;
		llCurrent->rotation=getOrientation(random() % 4); 
		if (llCurrent->rotation==getOrientation(0)) {
			startOffx=1.0;
			endOffx=0;
			startOffy=3.0;
			endOffy=-1;
		} else if (llCurrent->rotation==getOrientation(1)) {
			startOffx=1.0;
			endOffx=-1;
			startOffy=1.0;
			endOffy=0;		
		} else if (llCurrent->rotation==getOrientation(2)) {
			startOffx=1.0;
			endOffx=0;
			startOffy=3.0;
			endOffy=-1;		
		} else if (llCurrent->rotation==getOrientation(3)) { 
			startOffx=5.0;
			endOffx=-1;
			startOffy=1.0;
			endOffy=0;		
		}

		llCurrent->x=(startOffx-(tb->carpetLength/2)) + getLocation(random() % ((tb->carpetLength/2)+endOffx) );
		llCurrent->y=(startOffy-(tb->carpetLength/2)) + getLocation(random() % ((tb->carpetLength/2)+endOffy) );
		llCurrent->color=(random() % maxColors);
		llCurrent->height=getHeight(tb->plusheight+tb->highest); 
		if (tb->numFallingBlocks>=maxFalling) {
			tb->numFallingBlocks--;
			tb->numFallingBlocks--;
		} 
		llTail->next = llCurrent;
		llTail = llCurrent;
		llTail->next = NULL;

	}
}

/* called at init this creates the 'carpet' display list item */
static void buildCarpet(ModeInfo *mi)
{
	int i,c,x,y;
	GLfloat color[4];
  	int wire = MI_IS_WIREFRAME(mi);
  	topBlockSTATE *tb = &tbs[MI_SCREEN(mi)];
  	GLUquadricObj *quadratic;
		color[0] = 0.0f;	
		color[1] = 1.0f;	
		color[2] = 0.0f;	
		color[3] = 1.0f;	
	tb->carpet=glGenLists(1);	/* only one */
	glNewList(tb->carpet,GL_COMPILE);
        tb->carpet_polys=0;
	glPushMatrix();	/* save state */
  	x=tb->carpetWidth;
  	y=tb->carpetLength;
	if (wire) { glColor3fv(color); }
	else { glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color); }
	/* draw carpet plane */
	glBegin( wire ? GL_LINE_LOOP : GL_QUADS );
		/* draw top */
		glNormal3f( 0, 0, -1 );
		glVertex3f(0.0,0.0,0.0);
		glVertex3f(x,0.0,0.0);
		glVertex3f(x,y,0.0);
		glVertex3f(0.0,y,0.0);
                tb->carpet_polys++;
	    if (wire) { glEnd(); } 
		else {
		/* add edge pieces */
		/* side 1 */
		glNormal3f( 0, -1, 0 );
		glVertex3f(0.0,0.0,0.0);
		glVertex3f(x,0.0,0.0);
		glVertex3f(x,0,singleThick);
		glVertex3f(0.0,0,singleThick);
                tb->carpet_polys++;
		/* side 2 */
		glNormal3f( -1, 0, 0 );
		glVertex3f(0.0,0.0,0.0);
		glVertex3f(0,y,0.0);
		glVertex3f(0,y,singleThick);
		glVertex3f(0.0,0,singleThick);
                tb->carpet_polys++;
		/* side 3 */
		glNormal3f( 1, 0, 0 );
		glVertex3f(x,0.0,0.0);
		glVertex3f(x,y,0.0);
		glVertex3f(x,y,singleThick);
		glVertex3f(x,0,singleThick);
                tb->carpet_polys++;
		/* side 4 */
		glNormal3f( 0, 1, 0 );
		glVertex3f(0,y,0.0);
		glVertex3f(x,y,0.0);
		glVertex3f(x,y,singleThick);
		glVertex3f(0,y,singleThick);
                tb->carpet_polys++;
		}
	glEnd();
	/* nipples */
	if (drawNipples) {
		quadratic=gluNewQuadric();			/* Create A Pointer To The Quadric Object */
		gluQuadricNormals(quadratic, GLU_SMOOTH);	/* Create Smooth Normals  */
		gluQuadricTexture(quadratic, GL_TRUE);		/* Create Texture Coords  */
		glTranslatef(0.5f,0.5f,-.25);			/* move to the cylinder center */
		for (c=0;c<x;c++) {
			glPushMatrix();	/* save state */
			for (i=0;i<y;i++) {
				gluCylinder(quadratic, cylSize, cylSize, 0.25f, resolution, resolution);	/* quad, radius(bottom, radius(top), height, subdivisions (around Z), subdevisions (along Z) */
                                tb->carpet_polys += resolution*resolution;
				glRotatef(180, 0.0f, 1.0f, 0.0f); /* they are upside down */
				gluDisk(quadratic, 0.0f, cylSize, resolution, resolution );	 /* inner size (cd hole), outer size (radius), subdivisions radial, subdivisions circular  */
                                tb->carpet_polys += resolution*resolution;
				glRotatef(180, 0.0f, 1.0f, 0.0f); /* recover */
				glTranslatef(0.0f,1.0f,0.0f);			/* move to the next cylinder center (backward) */
			}
			glPopMatrix();	/* save state */
			glTranslatef(1.0f,0.0f,0.0f);			/* reset   */
		}
	}
	glPopMatrix();	/* restore state */
	glEndList();	
}

/* using the verticies arrays builds the plane, now with normals */
static void polygonPlane(int wire, int a, int b, int c , int d, int i)
{
	GLfloat topBlockNormals[5][3] = { {0,0,-1},	{0,1,0}, {1,0,0}, {0,0,1}, {0,-1,0} };
	GLfloat topBlockVertices[8][3] = { {-0.49,-2.97,-0.99}, {0.99,-2.97,-0.99}, {0.99,0.99,-0.99}  , {-0.49,0.99,-0.99}, {-0.49,-2.97,0.99} , {0.99,-2.97,0.99}, {0.99,0.99,0.99}   , {-0.49,0.99,0.99} };
	glBegin( wire ? GL_LINE_LOOP : GL_POLYGON);
		glNormal3fv(topBlockNormals[i] );
		glVertex3fv(topBlockVertices[a]);
		glVertex3fv(topBlockVertices[b]);
		glVertex3fv(topBlockVertices[c]);
		glVertex3fv(topBlockVertices[d]);
	glEnd();
}

/* called at init this creates the 'block' display list item */
/* the spheres came about originaly as quick way to test the directional lighting/normals */
static void buildBlock(ModeInfo *mi)
{
	int i,c;
  int wire = MI_IS_WIREFRAME(mi);
  topBlockSTATE *tb = &tbs[MI_SCREEN(mi)];
	GLUquadricObj *quadratic;
	tb->block=glGenLists(1);	/* only one */
	glNewList(tb->block,GL_COMPILE);
        tb->block_polys=0;
	glPushMatrix();	/* save state */
	glRotatef(90, 0.0f, 1.0f, 0.0f);
	/* base */
	polygonPlane(wire, 0,3,2,1,0); tb->block_polys++;
	polygonPlane(wire, 2,3,7,6,1); tb->block_polys++;
	polygonPlane(wire, 1,2,6,5,2); tb->block_polys++;
	polygonPlane(wire, 4,5,6,7,3); tb->block_polys++;
	polygonPlane(wire, 0,1,5,4,4); tb->block_polys++;
	if (drawNipples) {
		/* nipples */
		/* draw 8 cylinders each with a disk cap */
		quadratic=gluNewQuadric();			/* Create A Pointer To The Quadric Object  */
		gluQuadricNormals(quadratic, GLU_SMOOTH);	/* Create Smooth Normals  */
		glRotatef(90, 0.0f, 1.0f, 0.0f);		/* 'aim' the pointer ready for the cylinder */
		glTranslatef(0.5f,0.5f,0.99f);			/* move to the cylinder center */
		for (c=0;c<2;c++) {
			for (i=0;i<4;i++) {
				gluCylinder(quadratic, cylSize, cylSize, 0.25f, resolution, resolution);	/* quad, radius(bottom, radius(top), height, subdivisions (around Z), subdevisions (along Z) */
                                tb->block_polys += resolution*resolution;
				glTranslatef(0.0f,0.0f,0.25f);			/* move to the cylinder cap  */
				gluDisk(quadratic, 0.0f, cylSize, resolution, resolution );	 /* inner size (cd hole), outer size (radius), subdivisions radial, subdivisions circular  */
                                tb->block_polys += resolution*resolution;
				glTranslatef(0.0f,0.0f,-0.25f);			/* move back from the cylinder cap  */
				if (c==0) {	
					glTranslatef(0.0f,-1.0f,0.0f);			/* move to the next cylinder center (forward) */
				} else {
					glTranslatef(0.0f,1.0f,0.0f);			/* move to the next cylinder center (backward) */
				}
			}
			glTranslatef(-1.0f,1.0f,0.0f);			/* move to the cylinder center */
		}
		/* udders */
		/* 3 cylinders on the underside */
		glTranslatef(1.5f,-2.5f,-1.5f);		/* move to the center, under the top of the brick	 */
		for (c=0;c<3;c++) {
			gluCylinder(quadratic, uddSize, uddSize, 1.5f, resolution, resolution);	/* quad, radius(bottom, radius(top), height, subdivisions (around Z), subdevisions (along Z) */
                        tb->block_polys += resolution*resolution;
			glTranslatef(0.0f,-1.0f,0.0f);		/* move to the center */	
		}
	}
	glPopMatrix();	/* restore state */
	glEndList();	
}

/* 
	rip off of the builBlock() function creating the GL compilied pointer "block" but only creates two spheres.
	spheres are created with unit_sphere from spheres.h to allow wire frame 
*/
static void buildBlobBlock(ModeInfo *mi)
{
  int wire = MI_IS_WIREFRAME(mi);
  topBlockSTATE *tb = &tbs[MI_SCREEN(mi)];
	tb->block=glGenLists(1);	/* only one */
	glNewList(tb->block,GL_COMPILE);
	glPushMatrix();
  glScalef(1.4,1.4,1.4);
  unit_sphere (resolution/2,resolution, wire);
  glPopMatrix();
  glTranslatef(0.0f,-2.0f,0.0f);
  glScalef(1.4,1.4,1.4);
  unit_sphere (resolution/2,resolution, wire);
	glEndList();	
}


/* handle input events or not if daemon running the show */
ENTRYPOINT Bool 
topBlock_handle_event (ModeInfo *mi, XEvent *event)
{
  topBlockSTATE *tb = &tbs[MI_SCREEN(mi)];
	if (event->xany.type == KeyPress)    {
    KeySym keysym;
    char c = 0;
    XLookupString (&event->xkey, &c, 1, &keysym, 0);
    if (c == 'a') {
			tb->eyeX++;
			return True;
		} else if (c == 'z') {
			tb->eyeX--;
			return True;
		} else if (c == 's') {
			tb->eyeY--;
			return True;
		} else if (c == 'x') {
			tb->eyeY++;
			return True;
		} else if (c == 'd') {
			tb->eyeZ++;
			return True;
		} else if (c == 'c') {
			tb->eyeZ--;
			return True;
		} else if (c == 'f') {
			camX++;
			return True;
		} else if (c == 'v') {
			camX--;
			return True;
		} else if (c == 'g') {
			camY++;
			return True;
		} else if (c == 'b') {
			camY--;
			return True;
		} else if (c == 'h') {
			camZ++;
			return True;
		} else if (c == 'n') {
			camZ--;
			return True;
		} else if (c == 'r') {
			tb->rotation++;
			return True;
		}
	}
  if (event->xany.type == ButtonPress &&
      event->xbutton.button == Button1)
    {
      tb->button_down_p = True;
      gltrackball_start (tb->trackball,
                         event->xbutton.x, event->xbutton.y,
                         MI_WIDTH (mi), MI_HEIGHT (mi));
      return True;
    }
  else if (event->xany.type == ButtonRelease &&
           event->xbutton.button == Button1)
    {
      tb->button_down_p = False;
      return True;
    }
  else if (event->xany.type == ButtonPress &&
           (event->xbutton.button == Button4 ||
            event->xbutton.button == Button5 ||
            event->xbutton.button == Button6 ||
            event->xbutton.button == Button7))
    {
      gltrackball_mousewheel (tb->trackball, event->xbutton.button, 10,
                              !!event->xbutton.state);
      return True;
    }
  else if (event->xany.type == MotionNotify &&
           tb->button_down_p)
    {
      gltrackball_track (tb->trackball,
                         event->xmotion.x, event->xmotion.y,
                         MI_WIDTH (mi), MI_HEIGHT (mi));
      return True;
    }
	return False;
}

/* this is tha main change for v5 compatability and acompanying ENTRYPOINTS */
XSCREENSAVER_MODULE_2 ("TopBlock", topblock, topBlock)

#endif /* USE_GL */
