http://ftp.ksu.edu.tw/FTP/FreeBSD/distfiles/xscreensaver-4.23.tar.gz
[xscreensaver] / hacks / glx / glhanoi.c
diff --git a/hacks/glx/glhanoi.c b/hacks/glx/glhanoi.c
new file mode 100644 (file)
index 0000000..b0f91ca
--- /dev/null
@@ -0,0 +1,1484 @@
+/* glhanoi, Copyright (c) 2005 Dave Atkinson <dave.atkinson@uwe.ac.uk>
+ * except noise function code Copyright (c) 2002 Ken Perlin
+ *
+ * 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.
+ */
+
+#include <assert.h>
+#include <X11/Intrinsic.h>
+
+#include <rotator.h>
+
+#define PROGCLASS         "glhanoi"
+#define HACK_INIT         init_glhanoi
+#define HACK_DRAW         draw_glhanoi
+#define HACK_RESHAPE      reshape_glhanoi
+#define HACK_HANDLE_EVENT glhanoi_handle_event
+#define EVENT_MASK        PointerMotionMask
+#define glh_opts          xlockmore_opts
+
+#define DEF_DELAY     "15000"
+#define DEF_DISKS     "7"
+#define DEF_WIRE      "False"
+#define DEF_LIGHT     "True"
+#define DEF_FPS       "False"
+#define DEF_FOG       "False"
+#define DEF_TEXTURE   "True"
+
+#define DEFAULTS "*delay:     " DEF_DELAY     "\n" \
+                                "*count:     " DEF_DISKS     "\n" \
+                                "*showFPS:   " DEF_FPS       "\n" \
+                                "*wireframe: " DEF_WIRE      "\n"
+
+#define NSLICE 32
+#define NLOOPS 1
+#define START_DURATION 1.0
+#define FINISH_DURATION 1.0
+#define BASE_LENGTH 30.0
+#define BOARD_SQUARES 8
+
+#define MAX_CAMERA_RADIUS 250.0
+#define MIN_CAMERA_RADIUS 75.0
+
+#define MARBLE_SCALE 1.01
+
+enum {
+       MARBLE_TEXURE,
+       N_TEXTURES
+};
+
+#define MARBLE_TEXTURE_SIZE 256
+
+#undef countof
+#define countof(x) (sizeof((x))/sizeof((*x)))
+
+#include <math.h>
+#include "xlockmore.h"
+
+#ifdef USE_GL                                  /* whole file */
+
+#include <GL/glu.h>
+
+#ifdef HAVE_GETTIMEOFDAY
+#ifdef GETTIMEOFDAY_TWO_ARGS
+# include <sys/time.h>
+# include <time.h>
+typedef struct timeval glhtime;
+#else                                                  /* GETTIMEOFDAY_TWO_ARGS */
+# include <sys/time.h>
+# include <time.h>
+typedef struct timeval glhtime;
+#endif
+#else                                                  /* HAVE_GETTIMEOFDAY */
+#ifdef HAVE_FTIME
+# include <sys/timeb.h>
+typedef struct timeb glhtime;
+#endif                                                 /* HAVE_FTIME */
+#endif                                                 /* HAVE_GETTIMEOFDAY */
+
+double getTime(void)
+{
+#ifdef HAVE_GETTIMEOFDAY
+       struct timeval t;
+#ifdef GETTIMEOFDAY_TWO_ARGS
+       gettimeofday(&t, NULL);
+#else                                                  /* !GETTIMEOFDAY_TWO_ARGS */
+       gettimeofday(&t);
+#endif                                                 /* !GETTIMEOFDAY_TWO_ARGS */
+#else                                                  /* !HAVE_GETTIMEOFDAY */
+#ifdef HAVE_FTIME
+       ftime(&t);
+#endif                                                 /* HAVE_FTIME */
+#endif                                                 /* !HAVE_GETTIMEOFDAY */
+       return t.tv_sec + t.tv_usec / 1000000.0;
+}
+
+typedef enum {
+       START,
+       MOVE_DISK,
+       MOVE_FINISHED,
+       FINISHED,
+       MONEY_SHOT,
+       INVALID = -1
+} State;
+
+typedef struct {
+       int id;
+       GLuint displayList;
+       GLfloat position[3];
+       GLfloat rotation[3];
+       GLfloat color[4];
+       GLfloat base0;
+       GLfloat base1;
+       GLfloat height;
+       GLfloat xmin, xmax, ymin;
+       GLfloat u1, u2;
+       GLfloat t1, t2;
+       GLfloat ucostheta, usintheta;
+       GLdouble rotAngle;
+} Disk;
+
+typedef struct {
+       Disk **data;
+       int count;
+       int size;
+} Stack;
+
+typedef struct {
+       GLXContext *glx_context;
+       State state;
+       Bool wire;
+       Bool fog;
+       Bool light;
+       double startTime;
+       double lastTime;
+       double duration;
+       int numberOfDisks;
+       int numberOfMoves;
+       int maxDiskIdx;
+       int magicNumber;
+       Disk *currentDisk;
+       int move;
+       int src;
+       int tmp;
+       int dst;
+       int oldsrc;
+       int oldtmp;
+       int olddst;
+       Stack pole[3];
+       float boardSize;
+       float baseLength;
+       float baseWidth;
+       float baseHeight;
+       float poleRadius;
+       float poleHeight;
+       float poleOffset;
+       float diskHeight;
+       float *diskPos;                         /* pre-computed disk positions on rods */
+       Disk *disk;
+       float speed;
+       GLint floorList;
+       GLint baseList;
+       GLint poleList;
+       GLfloat camera[3];
+       GLfloat centre[3];
+       rotator *the_rotator;
+       Bool button_down_p;
+       Bool texture;
+       GLuint textureNames[N_TEXTURES];
+       int drag_x;
+       int drag_y;
+} glhcfg;
+
+static glhcfg *glhanoi_cfg = NULL;
+static glhcfg *glhanoi = NULL;
+static Bool fog;
+static Bool light;
+static Bool texture;
+
+static XrmOptionDescRec opts[] = {
+       {"-light", ".glhanoi.light", XrmoptionNoArg, (caddr_t) "true"},
+       {"+light", ".glhanoi.light", XrmoptionNoArg, (caddr_t) "false"},
+       {"-fog", ".glhanoi.fog", XrmoptionNoArg, (caddr_t) "true"},
+       {"+fog", ".glhanoi.fog", XrmoptionNoArg, (caddr_t) "false"},
+       {"-texture", ".glhanoi.texture", XrmoptionNoArg, (caddr_t) "true"},
+       {"+texture", ".glhanoi.texture", XrmoptionNoArg, (caddr_t) "false"}
+};
+
+static argtype vars[] = {
+       {(caddr_t *) (void *)&light, "light", "Light", DEF_LIGHT, t_Bool},
+       {(caddr_t *) (void *)&fog, "fog", "Fog", DEF_FOG, t_Bool},
+       {(caddr_t *) (void *)&texture, "texture", "Texture", DEF_TEXTURE,
+        t_Bool}
+};
+
+static OptionStruct desc[] = {
+       {"+/-light", "whether to light the scene"},
+       {"+/-fog", "whether to apply fog to the scene"},
+       {"+/-texture", "whether to apply texture to the scene"}
+};
+
+ModeSpecOpt glh_opts = { countof(opts), opts, countof(vars), vars, desc };
+
+#ifdef USE_MODULES
+
+ModStruct glhanoi_description = {
+       "glhanoi", "init_glhanoi", "draw_glhanoi", "release_glhanoi",
+       "draw_glhanoi", "init_glhanoi", NULL, &glhanoi_opts,
+       1000, 1, 2, 1, 4, 1.0, "",
+       "Towers of Hanoi", 0, NULL
+};
+
+#endif
+
+static GLfloat cBlack[] = { 0.0, 0.0, 0.0, 1.0 };
+static GLfloat cWhite[] = { 1.0, 1.0, 1.0, 1.0 };
+static GLfloat poleColor[] = { 0.545, 0.137, 0.137 };
+static GLfloat baseColor[] = { 0.34, 0.34, 0.48 };
+static GLfloat fogcolor[] = { 0.5, 0.5, 0.5 };
+
+static float left[] = { 1.0, 0.0, 0.0 };
+static float up[] = { 0.0, 1.0, 0.0 };
+static float front[] = { 0.0, 0.0, 1.0 };
+static float right[] = { -1.0, 0.0, 0.0 };
+static float down[] = { 0.0, -1.0, 0.0 };
+static float back[] = { 0.0, 0.0, -1.0 };
+
+GLfloat pos0[4] = { 50.0, 50.0, 50.0, 0.0 };
+GLfloat amb0[4] = { 0.0, 0.0, 0.0, 1.0 };
+GLfloat dif0[4] = { 1.0, 1.0, 1.0, 1.0 };
+GLfloat spc0[4] = { 0.0, 1.0, 1.0, 1.0 };
+
+GLfloat pos1[4] = { -50.0, 50.0, -50.0, 0.0 };
+GLfloat amb1[4] = { 0.0, 0.0, 0.0, 1.0 };
+GLfloat dif1[4] = { 1.0, 1.0, 1.0, 1.0 };
+GLfloat spc1[4] = { 1.0, 1.0, 1.0, 1.0 };
+
+float g = 3.0 * 9.80665;               /* hmm, looks like we need more gravity, Scotty... */
+
+#define DOPUSH(X, Y)   (((X)->count) >= ((X)->size)) ? NULL : ((X)->data[(X)->count++] = (Y))
+#define DOPOP(X)       (X)->count <= 0 ? NULL : ((X)->data[--((X)->count)])
+
+Disk *push(int idx, Disk * d)
+{
+       return DOPUSH(&glhanoi->pole[idx], d);
+}
+
+Disk *pop(int idx)
+{
+       return DOPOP(&glhanoi->pole[idx]);
+}
+
+inline void swap(int *x, int *y)
+{
+       *x = *x ^ *y;
+       *y = *x ^ *y;
+       *x = *x ^ *y;
+}
+
+/*
+ * magic - it's magic...
+ */
+int magic(int i)
+{
+       int count = 0;
+       if(i <= 1)
+               return 0;
+       while((i & 0x01) == 0) {
+               i >>= 1;
+               count++;
+       }
+       return count % 2 == 0;
+}
+
+float distance(float *p0, float *p1)
+{
+       float x, y, z;
+       x = p1[0] - p0[0];
+       y = p1[1] - p0[1];
+       z = p1[2] - p0[2];
+       return (float)sqrt(x * x + y * y + z * z);
+}
+
+GLfloat A(GLfloat a, GLfloat b, GLfloat c)
+{
+       GLfloat sum = a + b;
+       return c / (a * b - 0.25 * sum * sum);
+}
+
+void moveSetup(Disk * disk)
+{
+       float h, ymax;
+       float u;
+       int src = glhanoi->src;
+       int dst = glhanoi->dst;
+       GLfloat theta;
+       GLfloat sintheta, costheta;
+
+       if(glhanoi->state != FINISHED && random() % 6 == 0) {
+               disk->rotAngle =
+                       -180.0 * (2 - 2 * random() % 2) * (random() % 3 + 1);
+       } else {
+               disk->rotAngle = -180.0;
+       }
+
+       disk->base0 = glhanoi->diskPos[glhanoi->pole[src].count];
+       disk->base1 =
+               glhanoi->state ==
+               FINISHED ? disk->base0 : glhanoi->diskPos[glhanoi->pole[dst].
+                                                                                                 count];
+
+       disk->xmin = glhanoi->poleOffset * (src - 1);
+       disk->xmax = glhanoi->poleOffset * (dst - 1);
+       disk->ymin = glhanoi->poleHeight;
+       ymax =
+               glhanoi->poleHeight + fabs(disk->xmax -
+                                                                  disk->xmin) * (glhanoi->state ==
+                                                                                                 FINISHED ? 1.0 +
+                                                                                                 (double)(glhanoi->
+                                                                                                                  numberOfDisks -
+                                                                                                                  disk->id) /
+                                                                                                 (double)glhanoi->
+                                                                                                 numberOfDisks : 1.0);
+
+       h = ymax - disk->ymin;
+       theta = atan((disk->xmin - disk->xmax) * A(disk->xmin, disk->xmax, h));
+       if(theta < 0.0)
+               theta += M_PI;
+       costheta = cos(theta);
+       sintheta = sin(theta);
+       u = (float)
+               sqrt(fabs
+                        (-g /
+                         (2.0 * A(disk->xmin, disk->xmax, h) * costheta * costheta)));
+       disk->usintheta = u * sintheta;
+       disk->ucostheta = u * costheta;
+       disk->t1 =
+               (-u + sqrt(u * u + 2.0 * g * fabs(disk->ymin - disk->base0))) / g;
+       disk->u1 = u + g * disk->t1;
+       disk->t2 = 2.0 * disk->usintheta / g;
+       disk->u2 = disk->usintheta - g * disk->t2;
+}
+
+void makeMove(void)
+{
+       int fudge = glhanoi->move + 2;
+       int magicNumber = magic(fudge);
+
+       glhanoi->currentDisk = pop(glhanoi->src);
+       moveSetup(glhanoi->currentDisk);
+       push(glhanoi->dst, glhanoi->currentDisk);
+       fudge = fudge % 2;
+
+       if(fudge == 1 || magicNumber) {
+               swap(&glhanoi->src, &glhanoi->tmp);
+       }
+       if(fudge == 0 || glhanoi->magicNumber) {
+               swap(&glhanoi->dst, &glhanoi->tmp);
+       }
+       glhanoi->magicNumber = magicNumber;
+}
+
+double lerp(double alpha, double start, double end)
+{
+       return start + alpha * (end - start);
+}
+
+void upfunc(GLdouble t, Disk * d)
+{
+       d->position[0] = d->xmin;
+       d->position[1] = d->base0 + (d->u1 - 0.5 * g * t) * t;
+
+       d->rotation[1] = 0.0;
+}
+
+void parafunc(GLdouble t, Disk * d)
+{
+       d->position[0] = d->xmin + d->ucostheta * t;
+       d->position[1] = d->ymin + (d->usintheta - 0.5 * g * t) * t;
+
+       d->rotation[1] =
+               d->rotAngle * (d->position[0] - d->xmin) / (d->xmax - d->xmin);
+}
+
+void downfunc(GLdouble t, Disk * d)
+{
+       d->position[0] = d->xmax;
+       d->position[1] = d->ymin + (d->u2 - 0.5 * g * t) * t;
+
+       d->rotation[1] = 0.0;
+}
+
+Bool computePosition(GLfloat t, Disk * d)
+{
+       Bool finished = FALSE;
+
+       if(t < d->t1) {
+               upfunc(t, d);
+       } else if(t < d->t1 + d->t2) {
+               parafunc(t - d->t1, d);
+       } else {
+               downfunc(t - d->t1 - d->t2, d);
+               if(d->position[1] <= d->base1) {
+                       d->position[1] = d->base1;
+                       finished = TRUE;
+               }
+       }
+       return finished;
+}
+
+void updateView(void)
+{
+       double longitude, latitude, radius;
+       double a, b, c, A, B;
+
+       get_position(glhanoi->the_rotator, NULL, NULL, &radius,
+                                !glhanoi->button_down_p);
+       get_rotation(glhanoi->the_rotator, &longitude, &latitude, NULL,
+                                !glhanoi->button_down_p);
+       longitude += glhanoi->camera[0];
+       latitude += glhanoi->camera[1];
+       radius += glhanoi->camera[2];
+       longitude = longitude - floor(longitude);
+       latitude = latitude - floor(latitude);
+       radius = radius - floor(radius);
+       if(latitude > 0.5) {
+               latitude = 1.0 - latitude;
+       }
+       if(radius > 0.5) {
+               radius = 1.0 - radius;
+       }
+
+       b = glhanoi->centre[1];
+       c = (MIN_CAMERA_RADIUS +
+                radius * (MAX_CAMERA_RADIUS - MIN_CAMERA_RADIUS));
+       A = M_PI / 4.0 * (1.0 - latitude);
+       a = sqrt(b * b + c * c - 2.0 * b * c * cos(A));
+       B = asin(sin(A) * b / a);
+       glRotatef(-B * 180 / M_PI, 1.0, 0.0, 0.0);
+
+       glTranslatef(0.0f, 0.0f,
+                                -(MIN_CAMERA_RADIUS +
+                                  radius * (MAX_CAMERA_RADIUS - MIN_CAMERA_RADIUS)));
+       glRotatef(longitude * 360.0, 0.0f, 1.0f, 0.0f);
+       glRotatef(latitude * 180.0, cos(longitude * 2.0 * M_PI), 0.0,
+                         sin(longitude * 2.0 * M_PI));
+}
+
+void changeState(State state)
+{
+       glhanoi->state = state;
+       glhanoi->startTime = getTime();
+}
+
+void update_glhanoi(void)
+{
+       double t = getTime() - glhanoi->startTime;
+       int i;
+       Bool done;
+
+       switch (glhanoi->state) {
+       case START:
+               if(t < glhanoi->duration) {
+                       break;
+               }
+               glhanoi->move = 0;
+               if(glhanoi->numberOfDisks % 2 == 0) {
+                       swap(&glhanoi->tmp, &glhanoi->dst);
+               }
+               glhanoi->magicNumber = 1;
+               makeMove();
+               changeState(MOVE_DISK);
+               break;
+
+       case MOVE_DISK:
+               if(computePosition(t, glhanoi->currentDisk)) {
+                       changeState(MOVE_FINISHED);
+               }
+               break;
+
+       case MOVE_FINISHED:
+               if(++glhanoi->move < glhanoi->numberOfMoves) {
+                       makeMove();
+                       changeState(MOVE_DISK);
+               } else {
+                       glhanoi->duration = FINISH_DURATION;
+                       changeState(FINISHED);
+               }
+               break;
+
+       case FINISHED:
+               while(t < glhanoi->duration) {
+                       break;
+               }
+               glhanoi->src = glhanoi->olddst;
+               glhanoi->dst = glhanoi->oldsrc;
+               for(i = 0; i < glhanoi->numberOfDisks; ++i) {
+                       Disk *disk = pop(glhanoi->src);
+                       assert(disk != NULL);
+                       moveSetup(disk);
+               }
+               for(i = glhanoi->maxDiskIdx; i >= 0; --i) {
+                       push(glhanoi->dst, &glhanoi->disk[i]);
+               }
+               changeState(MONEY_SHOT);
+               break;
+
+       case MONEY_SHOT:
+               done = True;
+               for(i = glhanoi->maxDiskIdx; i >= 0; --i) {
+                       double delay = 0.25 * i;
+                       int finished;
+
+                       if(t - delay < 0) {
+                               done = False;
+                               continue;
+                       }
+
+                       finished = computePosition(t - delay, &glhanoi->disk[i]);
+                       glhanoi->disk[i].rotation[1] = 0.0;
+
+                       if(!finished) {
+                               done = False;
+                       }
+               }
+               if(done) {
+                       glhanoi->src = glhanoi->oldsrc;
+                       glhanoi->tmp = glhanoi->oldtmp;
+                       glhanoi->dst = glhanoi->olddst;
+                       changeState(START);
+               }
+               break;
+
+       case INVALID:
+       default:
+               fprintf(stderr, "Invalid state\n");
+               break;
+       }
+}
+
+void HSVtoRGBf(GLfloat h, GLfloat s, GLfloat v,
+                          GLfloat * r, GLfloat * g, GLfloat * b)
+{
+       if(s == 0.0) {
+               *r = v;
+               *g = v;
+               *b = v;
+       } else {
+               GLfloat i, f, p, q, t;
+               if(h >= 360.0) {
+                       h = 0.0;
+               }
+               h /= 60.0;                              /* h now in [0,6). */
+               i = floor((double)h);   /* i now largest integer <= h */
+               f = h - i;                              /* f is no fractional part of h */
+               p = v * (1.0 - s);
+               q = v * (1.0 - (s * f));
+               t = v * (1.0 - (s * (1.0 - f)));
+               switch ((int)i) {
+               case 0:
+                       *r = v;
+                       *g = t;
+                       *b = p;
+                       break;
+               case 1:
+                       *r = q;
+                       *g = v;
+                       *b = p;
+                       break;
+               case 2:
+                       *r = p;
+                       *g = v;
+                       *b = t;
+                       break;
+               case 3:
+                       *r = p;
+                       *g = q;
+                       *b = v;
+                       break;
+               case 4:
+                       *r = t;
+                       *g = p;
+                       *b = v;
+                       break;
+               case 5:
+                       *r = v;
+                       *g = p;
+                       *b = q;
+                       break;
+               }
+       }
+}
+
+void HSVtoRGBv(GLfloat * hsv, GLfloat * rgb)
+{
+       HSVtoRGBf(hsv[0], hsv[1], hsv[2], &rgb[0], &rgb[1], &rgb[2]);
+}
+
+void setMaterial(GLfloat color[3], GLfloat hlite[3], int shininess)
+{
+       glColor3fv(color);
+       glMaterialfv(GL_FRONT, GL_SPECULAR, hlite);
+       glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
+       glMateriali(GL_FRONT, GL_SHININESS, shininess); /* [0,128] */
+}
+
+/*
+ * drawTube: I know all this stuff is available in gluQuadrics
+ * but I'd originally intended to texture the poles with a 3D wood
+ * texture, but I was having difficulty getting wood... what? Why
+ * are all you Amercians laughing..? Anyway, I don't know if enough
+ * people's hardware supports 3D textures, so I didn't bother (xorg
+ * ATI server doesn't :-( )
+ */
+void drawTube(GLdouble bottomRadius, GLdouble topRadius,
+                         GLdouble bottomThickness, GLdouble topThickness,
+                         GLdouble height, GLuint nSlice, GLuint nLoop)
+{
+       GLfloat y;
+       GLfloat *cosCache = malloc(sizeof(GLfloat) * nSlice);
+       GLfloat *sinCache = malloc(sizeof(GLfloat) * nSlice);
+       GLint slice;
+       GLuint loop;
+       GLint lastSlice = nSlice - 1;
+       GLfloat radius;
+       GLfloat innerRadius;
+       GLfloat maxRadius;
+
+       if(bottomThickness > bottomRadius) {
+               bottomThickness = bottomRadius;
+       }
+       if(topThickness > topRadius) {
+               topThickness = topRadius;
+       }
+       if(bottomThickness < 0.0) {
+               bottomThickness = 0.0;
+       }
+       if(topThickness < 0.0) {
+               topThickness = 0.0;
+       }
+       if(topRadius >= bottomRadius) {
+               maxRadius = topRadius;
+       } else {
+               maxRadius = bottomRadius;
+       }
+
+       /* bottom */
+       y = 0.0;
+       radius = bottomRadius;
+       innerRadius = bottomRadius - bottomThickness;
+       /* innerTexCoordSize = texCoordSize * innerRadius / maxRadius; */
+       /* outerTexCoordSize = texCoordSize * radius / maxRadius; */
+       /* yTexCoord = minTexCoord; */
+
+       glBegin(GL_QUAD_STRIP);
+
+       glNormal3f(0.0, -1.0, 0.0);
+
+       /* glTexCoord3f(midTexCoord, yTexCoord, midTexCoord + innerTexCoordSize); */
+       glVertex3f(0.0, y, innerRadius);
+
+       /* glTexCoord3f(midTexCoord, yTexCoord, midTexCoord + outerTexCoordSize); */
+       glVertex3f(0.0, y, radius);
+
+       for(slice = lastSlice; slice >= 0; --slice) {
+               GLfloat theta = 2.0 * M_PI * (double)slice / nSlice;
+
+               cosCache[slice] = cos(theta);
+               sinCache[slice] = sin(theta);
+
+               /* glTexCoord3f(midTexCoord + sinCache[slice] * innerTexCoordSize, */
+               /* yTexCoord, */
+               /* midTexCoord + cosCache[slice] * innerTexCoordSize); */
+               glVertex3f(innerRadius * sinCache[slice], y,
+                                  innerRadius * cosCache[slice]);
+               /* glTexCoord3f(midTexCoord + sinCache[slice] * outerTexCoordSize, */
+               /* yTexCoord, */
+               /* midTexCoord + cosCache[slice] * outerTexCoordSize); */
+               glVertex3f(radius * sinCache[slice], y, radius * cosCache[slice]);
+       }
+       glEnd();
+
+       /* middle */
+       for(loop = 0; loop < nLoop; ++loop) {
+               GLfloat lowerRadius =
+                       bottomRadius + (topRadius -
+                                                       bottomRadius) * (float)loop / (nLoop);
+               GLfloat upperRadius =
+                       bottomRadius + (topRadius - bottomRadius) * (float)(loop +
+                                                                                                                               1) /
+                       (nLoop);
+               GLfloat lowerY = height * (float)loop / (nLoop);
+               GLfloat upperY = height * (float)(loop + 1) / (nLoop);
+               GLfloat factor = (topRadius - topThickness) -
+                       (bottomRadius - bottomThickness);
+
+               /* outside */
+               glBegin(GL_QUAD_STRIP);
+               for(slice = 0; slice < nSlice; ++slice) {
+                       glNormal3f(sinCache[slice], 0.0, cosCache[slice]);
+                       glVertex3f(upperRadius * sinCache[slice], upperY,
+                                          upperRadius * cosCache[slice]);
+                       glVertex3f(lowerRadius * sinCache[slice], lowerY,
+                                          lowerRadius * cosCache[slice]);
+               }
+               glNormal3f(0.0, 0.0, 1.0);
+               glVertex3f(0.0, upperY, upperRadius);
+               glVertex3f(0.0, lowerY, lowerRadius);
+               glEnd();
+
+               /* inside */
+               lowerRadius = bottomRadius - bottomThickness +
+                       factor * (float)loop / (nLoop);
+               upperRadius = bottomRadius - bottomThickness +
+                       factor * (float)(loop + 1) / (nLoop);
+
+               glBegin(GL_QUAD_STRIP);
+               glNormal3f(0.0, 0.0, -1.0);
+               glVertex3f(0.0, upperY, upperRadius);
+               glVertex3f(0.0, lowerY, lowerRadius);
+               for(slice = lastSlice; slice >= 0; --slice) {
+                       glNormal3f(-sinCache[slice], 0.0, -cosCache[slice]);
+                       glVertex3f(upperRadius * sinCache[slice], upperY,
+                                          upperRadius * cosCache[slice]);
+                       glVertex3f(lowerRadius * sinCache[slice], lowerY,
+                                          lowerRadius * cosCache[slice]);
+               }
+               glEnd();
+       }
+
+       /* top */
+       y = height;
+       radius = topRadius;
+       innerRadius = topRadius - topThickness;
+
+       glBegin(GL_QUAD_STRIP);
+       glNormal3f(0.0, 1.0, 0.0);
+       for(slice = 0; slice < nSlice; ++slice) {
+               glVertex3f(innerRadius * sinCache[slice], y,
+                                  innerRadius * cosCache[slice]);
+
+               glVertex3f(radius * sinCache[slice], y, radius * cosCache[slice]);
+       }
+       glVertex3f(0.0, y, innerRadius);
+       glVertex3f(0.0, y, radius);
+       glEnd();
+}
+
+void drawPole(GLfloat radius, GLfloat length)
+{
+       drawTube(radius, radius, radius, radius, length, NSLICE, NLOOPS);
+}
+
+void drawDisk3D(GLdouble inner_radius, GLdouble outer_radius,
+                               GLdouble height)
+{
+       drawTube(outer_radius, outer_radius, outer_radius - inner_radius,
+                        outer_radius - inner_radius, height, NSLICE, NLOOPS);
+}
+
+void drawCuboid(GLfloat length, GLfloat width, GLfloat height)
+{
+       GLfloat xmin = -length / 2.0f;
+       GLfloat xmax = length / 2.0f;
+       GLfloat zmin = -width / 2.0f;
+       GLfloat zmax = width / 2.0f;
+       GLfloat ymin = 0.0f;
+       GLfloat ymax = height;
+
+       glBegin(GL_QUADS);
+       /* front */
+       glNormal3fv(front);
+       glVertex3f(xmin, ymin, zmax);   /* 0 */
+       glVertex3f(xmax, ymin, zmax);   /* 1 */
+       glVertex3f(xmax, ymax, zmax);   /* 2 */
+       glVertex3f(xmin, ymax, zmax);   /* 3 */
+       /* right */
+       glNormal3fv(right);
+       glVertex3f(xmax, ymin, zmax);   /* 1 */
+       glVertex3f(xmax, ymin, zmin);   /* 5 */
+       glVertex3f(xmax, ymax, zmin);   /* 6 */
+       glVertex3f(xmax, ymax, zmax);   /* 2 */
+       /* back */
+       glNormal3fv(back);
+       glVertex3f(xmax, ymin, zmin);   /* 5 */
+       glVertex3f(xmin, ymin, zmin);   /* 4 */
+       glVertex3f(xmin, ymax, zmin);   /* 7 */
+       glVertex3f(xmax, ymax, zmin);   /* 6 */
+       /* left */
+       glNormal3fv(left);
+       glVertex3f(xmin, ymin, zmin);   /* 4 */
+       glVertex3f(xmin, ymin, zmax);   /* 0 */
+       glVertex3f(xmin, ymax, zmax);   /* 3 */
+       glVertex3f(xmin, ymax, zmin);   /* 7 */
+       /* top */
+       glNormal3fv(up);
+       glVertex3f(xmin, ymax, zmax);   /* 3 */
+       glVertex3f(xmax, ymax, zmax);   /* 2 */
+       glVertex3f(xmax, ymax, zmin);   /* 6 */
+       glVertex3f(xmin, ymax, zmin);   /* 7 */
+       /* bottom */
+       glNormal3fv(down);
+       glVertex3f(xmin, ymin, zmin);   /* 4 */
+       glVertex3f(xmax, ymin, zmin);   /* 5 */
+       glVertex3f(xmax, ymin, zmax);   /* 1 */
+       glVertex3f(xmin, ymin, zmax);   /* 0 */
+       glEnd();
+}
+
+void drawDisks(void)
+{
+       int i;
+
+       glPushMatrix();
+       glTranslatef(0.0f, glhanoi->baseHeight, 0.0f);
+       for(i = glhanoi->maxDiskIdx; i >= 0; i--) {
+               Disk *disk = &glhanoi->disk[i];
+               GLfloat *pos = disk->position;
+               GLfloat *rot = disk->rotation;
+
+               glPushMatrix();
+               glTranslatef(pos[0], pos[1], pos[2]);
+               if(rot[1] != 0.0) {
+                       glTranslatef(0.0, glhanoi->diskHeight / 2.0, 0.0);
+                       glRotatef(rot[1], 0.0, 0.0, 1.0);
+                       glTranslatef(0.0, -glhanoi->diskHeight / 2.0, 0.0);
+               }
+               glCallList(disk->displayList);
+               glPopMatrix();
+       }
+       glPopMatrix();
+}
+
+GLfloat getDiskRadius(int i)
+{
+       return ((GLfloat) i + 3.0) * glhanoi->poleRadius;
+}
+
+void initData(void)
+{
+       GLfloat maxDiskRadius;
+       int i;
+
+       glhanoi->baseLength = BASE_LENGTH;
+       glhanoi->poleRadius = glhanoi->baseLength /
+               (2.0 * (3 * glhanoi->numberOfDisks + 7.0));
+       maxDiskRadius = getDiskRadius(glhanoi->numberOfDisks);
+       glhanoi->baseWidth = 2.0 * maxDiskRadius;
+       glhanoi->poleOffset = 2.0 * getDiskRadius(glhanoi->maxDiskIdx);
+       glhanoi->diskHeight = 2.0 * glhanoi->poleRadius;
+       glhanoi->baseHeight = 2.0 * glhanoi->poleRadius;
+       glhanoi->poleHeight = glhanoi->numberOfDisks *
+               glhanoi->diskHeight + glhanoi->poleRadius;
+       glhanoi->numberOfMoves = (1 << glhanoi->numberOfDisks) - 1;
+       glhanoi->boardSize = glhanoi->baseLength * 0.5 * (1.0 + sqrt(5.0));
+
+       for(i = 0; i < 3; i++) {
+               if((glhanoi->pole[i].data =
+                       calloc(glhanoi->numberOfDisks, sizeof(Disk *))) == NULL) {
+                       fprintf(stderr, "%s: out of memory creating stack %d\n",
+                                       progname, i);
+                       exit(1);
+               }
+               glhanoi->pole[i].size = glhanoi->numberOfDisks;
+       }
+       if((glhanoi->diskPos =
+               calloc(glhanoi->numberOfDisks, sizeof(double))) == NULL) {
+               fprintf(stderr, "%s: out of memory creating diskPos\n", progname);
+               exit(1);
+       }
+
+       glhanoi->the_rotator = make_rotator(0.1, 0.025, 0, 1, 0.005, False);
+       glhanoi->button_down_p = False;
+
+       glhanoi->src = glhanoi->oldsrc = 0;
+       glhanoi->tmp = glhanoi->oldtmp = 1;
+       glhanoi->dst = glhanoi->olddst = 2;
+}
+
+void initView(void)
+{
+       glhanoi->camera[0] = 0.0;
+       glhanoi->camera[1] = 0.0;
+       glhanoi->camera[2] = 0.0;
+       glhanoi->centre[0] = 0.0;
+       glhanoi->centre[1] = glhanoi->poleHeight * 3.0;
+       glhanoi->centre[2] = 0.0;
+}
+
+/*
+ * noise_improved.c - based on ImprovedNoise.java
+ * JAVA REFERENCE IMPLEMENTATION OF IMPROVED NOISE - COPYRIGHT 2002 KEN PERLIN.
+ */
+static double fade(double t)
+{
+       return t * t * t * (t * (t * 6 - 15) + 10);
+}
+
+static double grad(int hash, double x, double y, double z)
+{
+       int h = hash & 15;                      /* CONVERT LO 4 BITS OF HASH CODE */
+       double u = h < 8 ? x : y,       /* INTO 12 GRADIENT DIRECTIONS. */
+               v = h < 4 ? y : h == 12 || h == 14 ? x : z;
+       return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
+}
+
+static int p[512], permutation[] = { 151, 160, 137, 91, 90, 15,
+       131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142,
+       8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26,
+       197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237,
+       149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71,
+       134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60,
+       211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143,
+       54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89,
+       18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198,
+       173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118,
+       126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189,
+       28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70,
+       221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108,
+       110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251,
+       34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145,
+       235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184,
+       84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205,
+       93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61,
+       156, 180
+};
+
+static void initNoise(void)
+{
+       int i;
+       for(i = 0; i < 256; i++)
+               p[256 + i] = p[i] = permutation[i];
+}
+
+double improved_noise(double x, double y, double z)
+{
+       double u, v, w;
+       int A, AA, AB, B, BA, BB;
+       int X = (int)floor(x) & 255,    /* FIND UNIT CUBE THAT */
+               Y = (int)floor(y) & 255,        /* CONTAINS POINT. */
+               Z = (int)floor(z) & 255;
+       static int start = 0;
+       if(start == 0) {
+               initNoise();
+               start = 1;
+       }
+       x -= floor(x);                          /* FIND RELATIVE X,Y,Z */
+       y -= floor(y);                          /* OF POINT IN CUBE. */
+       z -= floor(z);
+       u = fade(x),                            /* COMPUTE FADE CURVES */
+               v = fade(y),                    /* FOR EACH OF X,Y,Z. */
+               w = fade(z);
+       A = p[X] + Y, AA = p[A] + Z, AB = p[A + 1] + Z, /* HASH COORDINATES OF */
+               B = p[X + 1] + Y, BA = p[B] + Z, BB = p[B + 1] + Z;     /* THE 8 CUBE CORNERS, */
+       return lerp(w, lerp(v, lerp(u, grad(p[AA], x, y, z),    /* AND ADD */
+                                                               grad(p[BA], x - 1, y, z)),      /* BLENDED */
+                                               lerp(u, grad(p[AB], x, y - 1, z),       /* RESULTS */
+                                                        grad(p[BB], x - 1, y - 1, z))),        /* FROM 8 CORNERS */
+                               lerp(v, lerp(u, grad(p[AA + 1], x, y, z - 1), grad(p[BA + 1], x - 1, y, z - 1)),        /* OF CUBE */
+                                        lerp(u, grad(p[AB + 1], x, y - 1, z - 1),
+                                                 grad(p[BB + 1], x - 1, y - 1, z - 1))));
+}
+
+/*
+ * end noise_improved.c - based on ImprovedNoise.java
+ */
+
+struct tex_col_t {
+       GLuint *colours;
+       /* GLfloat *points; */
+       unsigned int ncols;
+};
+typedef struct tex_col_t tex_col_t;
+
+GLubyte *makeTexture(int x_size, int y_size, int z_size,
+                                        GLuint(*texFunc) (double, double, double,
+                                                                          tex_col_t *), tex_col_t * colours)
+{
+       int i, j, k;
+       GLubyte *textureData;
+       GLuint *texturePtr;
+       double x, y, z;
+       double xi, yi, zi;
+
+       if((textureData =
+               calloc(x_size * y_size * z_size, sizeof(GLuint))) == NULL) {
+               return NULL;
+       }
+
+       xi = 1.0 / x_size;
+       yi = 1.0 / y_size;
+       zi = 1.0 / z_size;
+
+       z = 0.0;
+       texturePtr = (void *)textureData;
+       for(k = 0; k < z_size; k++, z += zi) {
+               y = 0.0;
+               for(j = 0; j < y_size; j++, y += yi) {
+                       x = 0.0;
+                       for(i = 0; i < x_size; i++, x += xi) {
+                               *texturePtr = texFunc(x, y, z, colours);
+                               ++texturePtr;
+                       }
+               }
+       }
+       return textureData;
+}
+
+tex_col_t makeMarbleColours(void)
+{
+       tex_col_t marbleColours;
+       int ncols = 2;
+
+       marbleColours.colours = calloc(sizeof(GLuint), ncols);
+       marbleColours.ncols = ncols;
+
+       marbleColours.colours[0] = 0x3f3f3f3f;
+       marbleColours.colours[1] = 0xffffffff;
+
+       return marbleColours;
+}
+
+double turb(double x, double y, double z, int octaves)
+{
+       int oct, freq = 1;
+       double r = 0.0;
+
+       for(oct = 0; oct < octaves; ++oct) {
+               r += fabs(improved_noise(freq * x, freq * y, freq * z)) / freq;
+               freq <<= 1;
+       }
+       return r / 2.0;
+}
+
+void perturb(double *x, double *y, double *z, double scale)
+{
+       double t = scale * turb(*x, *y, *z, 4);
+       *x += t;
+       *y += t;
+       *z += t;
+}
+
+double f_m(double x, double y, double z)
+{
+       return sin(3.0 * M_PI * x);
+}
+
+GLuint C_m(double x, const tex_col_t * tex_cols)
+{
+       int r = tex_cols->colours[0] & 0xff;
+       int g = tex_cols->colours[0] >> 8 & 0xff;
+       int b = tex_cols->colours[0] >> 16 & 0xff;
+       double factor;
+       int r1, g1, b1;
+       x = x - floor(x);
+
+       factor = (1.0 + sin(2.0 * M_PI * x)) / 2.0;
+
+       r1 = (tex_cols->colours[1] & 0xff);
+       g1 = (tex_cols->colours[1] >> 8 & 0xff);
+       b1 = (tex_cols->colours[1] >> 16 & 0xff);
+
+       r += (int)(factor * (r1 - r));
+       g += (int)(factor * (g1 - g));
+       b += (int)(factor * (b1 - b));
+
+       return 0xff000000 | (b << 16) | (g << 8) | r;
+}
+
+
+GLuint makeMarbleTexture(double x, double y, double z, tex_col_t * colours)
+{
+       perturb(&x, &y, &z, MARBLE_SCALE);
+       return C_m(f_m(x, y, z), colours);
+}
+
+void setTexture(int n)
+{
+       glBindTexture(GL_TEXTURE_2D, glhanoi->textureNames[n]);
+}
+
+int makeTextures(void)
+{
+       GLubyte *marbleTexture;
+       tex_col_t marbleColours;
+
+       glGenTextures(N_TEXTURES, glhanoi->textureNames);
+
+       marbleColours = makeMarbleColours();
+       if((marbleTexture =
+               makeTexture(MARBLE_TEXTURE_SIZE, MARBLE_TEXTURE_SIZE, 1,
+                                       makeMarbleTexture, &marbleColours)) == NULL) {
+               return 1;
+       }
+
+       glBindTexture(GL_TEXTURE_2D, glhanoi->textureNames[0]);
+       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+       glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
+                                MARBLE_TEXTURE_SIZE, MARBLE_TEXTURE_SIZE, 0,
+                                GL_RGBA, GL_UNSIGNED_BYTE, marbleTexture);
+       free(marbleTexture);
+
+       return 0;
+}
+
+void initFloor(void)
+{
+       int i, j;
+       float tileSize = glhanoi->boardSize / BOARD_SQUARES;
+       float x0, x1, z0, z1;
+       float tx0, tx1, tz0, tz1;
+       float *col = cWhite;
+       float texIncr = 1.0 / BOARD_SQUARES;
+
+       if((glhanoi->floorList = glGenLists(1)) == 0) {
+               fprintf(stderr, "can't allocate memory for floor display list\n");
+               exit(EXIT_FAILURE);
+       }
+       glNewList(glhanoi->floorList, GL_COMPILE);
+       x0 = -glhanoi->boardSize / 2.0;
+       tx0 = 0.0f;
+       setMaterial(col, cWhite, 128);
+       setTexture(0);
+       glNormal3fv(up);
+       for(i = 0; i < BOARD_SQUARES; i++, x0 += tileSize, tx0 += texIncr) {
+               x1 = x0 + tileSize;
+               tx1 = tx0 + texIncr;
+               z0 = -glhanoi->boardSize / 2.0;
+               tz0 = 0.0f;
+               for(j = 0; j < BOARD_SQUARES; j++, z0 += tileSize, tz0 += texIncr) {
+                       int colIndex = (i + j) & 0x1;
+
+                       z1 = z0 + tileSize;
+                       tz1 = tz0 + texIncr;
+
+                       if(colIndex)
+                               col = cWhite;
+                       else
+                               col = cBlack;
+
+                       setMaterial(col, cWhite, 100);
+
+                       glBegin(GL_QUADS);
+
+                       glTexCoord2d(tx0, tz0);
+                       glVertex3f(x0, 0.0, z0);
+
+                       glTexCoord2d(tx0, tz1);
+                       glVertex3f(x0, 0.0, z1);
+
+                       glTexCoord2d(tx1, tz1);
+                       glVertex3f(x1, 0.0, z1);
+
+                       glTexCoord2d(tx1, tz0);
+                       glVertex3f(x1, 0.0, z0);
+                       glEnd();
+               }
+       }
+       glEndList();
+}
+
+void initTowers(void)
+{
+       if((glhanoi->baseList = glGenLists(1)) == 0) {
+               fprintf(stderr, "can't allocate memory for towers display list\n");
+               exit(EXIT_FAILURE);
+       }
+       glNewList(glhanoi->baseList, GL_COMPILE);
+       setMaterial(baseColor, cWhite, 50);
+       drawCuboid(glhanoi->baseLength, glhanoi->baseWidth,
+                          glhanoi->baseHeight);
+       glEndList();
+
+
+       if((glhanoi->poleList = glGenLists(1)) == 0) {
+               fprintf(stderr, "can't allocate memory for towers display list\n");
+               exit(EXIT_FAILURE);
+       }
+       glNewList(glhanoi->poleList, GL_COMPILE);
+       glPushMatrix();
+       glTranslatef(0.0f, glhanoi->baseHeight, 0.0f);
+       setMaterial(poleColor, cWhite, 50);
+       drawPole(glhanoi->poleRadius, glhanoi->poleHeight);
+       glPushMatrix();
+       glTranslatef(-glhanoi->poleOffset, 0.0, 0.0);
+       drawPole(glhanoi->poleRadius, glhanoi->poleHeight);
+       glPopMatrix();
+       glTranslatef(glhanoi->poleOffset, 0.0, 0.0);
+       drawPole(glhanoi->poleRadius, glhanoi->poleHeight);
+       glPopMatrix();
+       glEndList();
+}
+
+double cfunc(double x)
+{
+#define COMP <
+       if(x < 2.0 / 7.0) {
+               return (1.0 / 12.0) / (1.0 / 7.0) * x;
+       }
+       if(x < 3.0 / 7.0) {
+               return (1.0 + 1.0 / 6.0) * x - 1.0 / 6.0;
+       }
+       if(x < 4.0 / 7.0) {
+               return (2.0 + 1.0 / 3.0) * x - 2.0 / 3.0;
+       }
+       if(x < 5.0 / 7.0) {
+               return (1.0 / 12.0) / (1.0 / 7.0) * x + 1.0 / 3.0;
+       }
+       return (1.0 / 12.0) / (1.0 / 7.0) * x + 1.0 / 3.0;
+}
+
+void initDisks(void)
+{
+       int i;
+       if((glhanoi->disk =
+               (Disk *) calloc(glhanoi->numberOfDisks, sizeof(Disk))) == NULL) {
+               perror("initDisks");
+               exit(EXIT_FAILURE);
+       }
+
+       for(i = glhanoi->maxDiskIdx; i >= 0; i--) {
+               GLfloat height = (GLfloat) (glhanoi->maxDiskIdx - i);
+               double f = cfunc((GLfloat) i / (GLfloat) glhanoi->numberOfDisks);
+               GLfloat diskColor = f * 360.0;
+               GLfloat color[3];
+               Disk *disk = &glhanoi->disk[i];
+
+               disk->id = i;
+               disk->position[0] = -glhanoi->poleOffset;
+               disk->position[1] = glhanoi->diskHeight * height;
+               disk->position[2] = 0.0;
+               disk->rotation[0] = 0.0;
+               disk->rotation[1] = 0.0;
+               disk->rotation[2] = 0.0;
+
+               color[0] = diskColor;
+               color[1] = 1.0f;
+               color[2] = 1.0f;
+               HSVtoRGBv(color, color);
+
+               if((disk->displayList = glGenLists(1)) == 0) {
+                       fprintf(stderr,
+                                       "can't allocate memory for disk %d display list\n", i);
+                       exit(EXIT_FAILURE);
+               }
+               glNewList(disk->displayList, GL_COMPILE);
+               setMaterial(color, cWhite, 100.0);
+               drawDisk3D(glhanoi->poleRadius, getDiskRadius(i),
+                                  glhanoi->diskHeight);
+               glEndList();
+       }
+       for(i = glhanoi->maxDiskIdx; i >= 0; --i) {
+               GLfloat height = (GLfloat) (glhanoi->maxDiskIdx - i);
+               int h = glhanoi->maxDiskIdx - i;
+               glhanoi->diskPos[h] = glhanoi->diskHeight * height;
+               push(glhanoi->src, &glhanoi->disk[i]);
+       }
+}
+
+void initLights(Bool state)
+{
+       if(state) {
+               glLightfv(GL_LIGHT0, GL_POSITION, pos0);
+               glLightfv(GL_LIGHT0, GL_AMBIENT, amb0);
+               glLightfv(GL_LIGHT0, GL_DIFFUSE, dif0);
+               glLightfv(GL_LIGHT0, GL_SPECULAR, spc0);
+
+               glLightfv(GL_LIGHT1, GL_POSITION, pos1);
+               glLightfv(GL_LIGHT1, GL_AMBIENT, amb1);
+               glLightfv(GL_LIGHT1, GL_DIFFUSE, dif1);
+               glLightfv(GL_LIGHT1, GL_SPECULAR, spc1);
+
+               glEnable(GL_LIGHTING);
+               glEnable(GL_LIGHT0);
+               glEnable(GL_LIGHT1);
+       } else {
+               glDisable(GL_LIGHTING);
+       }
+}
+
+void drawFloor(void)
+{
+       glCallList(glhanoi->floorList);
+}
+
+void drawTowers(void)
+{
+       glCallList(glhanoi->baseList);
+       glCallList(glhanoi->poleList);
+}
+
+/* Window management, etc
+ */
+void reshape_glhanoi(ModeInfo * mi, int width, int height)
+{
+       glViewport(0, 0, (GLint) width, (GLint) height);
+
+       glMatrixMode(GL_PROJECTION);
+       glLoadIdentity();
+       gluPerspective(30.0, (GLdouble) width / (GLdouble) height, 1.0,
+                                  2 * MAX_CAMERA_RADIUS);
+
+       glMatrixMode(GL_MODELVIEW);
+       glLoadIdentity();
+
+       glClear(GL_COLOR_BUFFER_BIT);
+}
+
+void init_glhanoi(ModeInfo * mi)
+{
+       if(!glhanoi_cfg) {
+               glhanoi_cfg =
+                       (glhcfg *) calloc(MI_NUM_SCREENS(mi), sizeof(glhcfg));
+               if(!glhanoi_cfg) {
+                       fprintf(stderr, "%s: out of memory creating configs\n",
+                                       progname);
+                       exit(1);
+               }
+               glhanoi = &glhanoi_cfg[MI_SCREEN(mi)];
+       }
+
+       glhanoi = &glhanoi_cfg[MI_SCREEN(mi)];
+       glhanoi->glx_context = init_GL(mi);
+       glhanoi->numberOfDisks = MI_BATCHCOUNT(mi);
+       glhanoi->maxDiskIdx = glhanoi->numberOfDisks - 1;
+       glhanoi->wire = MI_IS_WIREFRAME(mi);
+       glhanoi->light = light;
+       glhanoi->fog = fog;
+       glhanoi->texture = texture;
+
+       reshape_glhanoi(mi, MI_WIDTH(mi), MI_HEIGHT(mi));
+
+       if(glhanoi->wire) {
+               glhanoi->light = FALSE;
+               glhanoi->fog = FALSE;
+               glhanoi->texture = FALSE;
+       }
+
+       initLights(!glhanoi->wire && glhanoi->light);
+       if(makeTextures() != 0) {
+               fprintf(stderr, "can't allocate memory for marble texture\n");
+               exit(EXIT_FAILURE);
+       }
+
+       initData();
+       initView();
+       initFloor();
+       initTowers();
+       initDisks();
+
+       glEnable(GL_DEPTH_TEST);
+       glEnable(GL_NORMALIZE);
+       glEnable(GL_CULL_FACE);
+       glShadeModel(GL_SMOOTH);
+       if(glhanoi->fog) {
+               glClearColor(fogcolor[0], fogcolor[1], fogcolor[2], 1.0);
+               glFogi(GL_FOG_MODE, GL_LINEAR);
+               glFogfv(GL_FOG_COLOR, fogcolor);
+               glFogf(GL_FOG_DENSITY, 0.35f);
+               glHint(GL_FOG_HINT, GL_NICEST);
+               glFogf(GL_FOG_START, MIN_CAMERA_RADIUS);
+               glFogf(GL_FOG_END, MAX_CAMERA_RADIUS / 1.9);
+               glEnable(GL_FOG);
+       }
+
+
+       glhanoi->duration = START_DURATION;
+       changeState(START);
+}
+
+void draw_glhanoi(ModeInfo * mi)
+{
+       Display *dpy = MI_DISPLAY(mi);
+       Window window = MI_WINDOW(mi);
+
+       glhanoi = &glhanoi_cfg[MI_SCREEN(mi)];
+
+       if(!glhanoi->glx_context)
+               return;
+       glPolygonMode(GL_FRONT, glhanoi->wire ? GL_LINE : GL_FILL);
+
+       glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+       glLoadIdentity();
+
+       update_glhanoi();
+       updateView();
+
+       if(!glhanoi->wire && glhanoi->texture) {
+               glEnable(GL_TEXTURE_2D);
+       }
+       drawFloor();
+       glDisable(GL_TEXTURE_2D);
+
+       drawTowers();
+       drawDisks();
+
+       if(mi->fps_p) {
+               do_fps(mi);
+       }
+       glFinish();
+
+       glXSwapBuffers(dpy, window);
+}
+
+Bool glhanoi_handle_event(ModeInfo * mi, XEvent * event)
+{
+       glhcfg *glhanoi = &glhanoi_cfg[MI_SCREEN(mi)];
+
+       if(event->xany.type == ButtonPress && event->xbutton.button == Button1) {
+               glhanoi->button_down_p = True;
+               glhanoi->drag_x = event->xbutton.x;
+               glhanoi->drag_y = event->xbutton.y;
+               return True;
+       } else if(event->xany.type == ButtonRelease
+                         && event->xbutton.button == Button1) {
+               glhanoi->button_down_p = False;
+               return True;
+       } else if(event->xany.type == ButtonPress &&
+                         (event->xbutton.button == Button4
+                          || event->xbutton.button == Button5)) {
+               switch (event->xbutton.button) {
+               case Button4:
+                       glhanoi->camera[2] += 0.01;
+                       break;
+               case Button5:
+                       glhanoi->camera[2] -= 0.01;
+                       break;
+               default:
+                       fprintf(stderr,
+                                       "glhanoi: unknown button in mousewheel handler\n");
+               }
+               return True;
+       } else if(event->xany.type == MotionNotify
+                         && glhanoi_cfg->button_down_p) {
+               int x_diff, y_diff;
+
+               x_diff = event->xbutton.x - glhanoi->drag_x;
+               y_diff = event->xbutton.y - glhanoi->drag_y;
+
+               glhanoi->camera[0] = (float)x_diff / (float)MI_WIDTH(mi);
+               glhanoi->camera[1] = (float)y_diff / (float)MI_HEIGHT(mi);
+
+               return True;
+       }
+       return False;
+}
+
+void release_glhanoi(ModeInfo * mi)
+{
+       if(glhanoi_cfg != NULL) {
+               int screen;
+               for(screen = 0; screen < MI_NUM_SCREENS(mi); screen++) {
+                       int i;
+                       int j;
+                       glhcfg *glh = &glhanoi_cfg[screen];
+                       glDeleteLists(glh->floorList, 1);
+                       glDeleteLists(glh->baseList, 1);
+                       glDeleteLists(glh->poleList, 1);
+                       for(j = 0; j < glh->numberOfDisks; ++j) {
+                               glDeleteLists(glh->disk[j].displayList, 1);
+                       }
+                       free(glh->disk);
+                       for(i = 0; i < 3; i++) {
+                               if(glh->pole[i].data != NULL) {
+                                       free(glh->pole[i].data);
+                               }
+                       }
+               }
+       }
+       free(glhanoi_cfg);
+       glhanoi_cfg = NULL;
+       glDeleteLists(glhanoi->textureNames[0], 2);
+}
+
+#endif                                                 /* USE_GL */