8bc9a1c909422a2d4a7f84e7febc9bb78cc32675
[xscreensaver] / hacks / glx / glhanoi.c
1 /* -*- Mode: C; tab-width: 4 -*- */
2 /* glhanoi, Copyright (c) 2005 Dave Atkinson <dave.atkinson@uwe.ac.uk>
3  * except noise function code Copyright (c) 2002 Ken Perlin
4  *
5  * Permission to use, copy, modify, distribute, and sell this software and its
6  * documentation for any purpose is hereby granted without fee, provided that
7  * the above copyright notice appear in all copies and that both that
8  * copyright notice and this permission notice appear in supporting
9  * documentation.  No representations are made about the suitability of this
10  * software for any purpose.  It is provided "as is" without express or 
11  * implied warranty.
12  */
13
14 #include <assert.h>
15
16 #include "rotator.h"
17
18 #define DEF_DELAY     "15000"
19 #define DEF_DISKS     "0"         /* < 2 means 3-12 */
20 #define DEF_WIRE      "False"
21 #define DEF_LIGHT     "True"
22 #define DEF_FPS       "False"
23 #define DEF_FOG       "False"
24 #define DEF_TEXTURE   "True"
25
26 #define DEFAULTS "*delay:     " DEF_DELAY     "\n" \
27                                  "*count:     " DEF_DISKS     "\n" \
28                                  "*showFPS:   " DEF_FPS       "\n" \
29                                  "*wireframe: " DEF_WIRE      "\n"
30
31 # define refresh_glhanoi 0
32
33 #define NSLICE 32
34 #define NLOOPS 1
35 #define START_DURATION 1.0
36 #define FINISH_DURATION 1.0
37 #define BASE_LENGTH 30.0
38 #define BOARD_SQUARES 8
39
40 #define MAX_CAMERA_RADIUS 250.0
41 #define MIN_CAMERA_RADIUS 75.0
42
43 #define MARBLE_SCALE 1.01
44
45 #undef BELLRAND
46 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
47
48 enum {
49         MARBLE_TEXURE,
50         N_TEXTURES
51 };
52
53 #define MARBLE_TEXTURE_SIZE 256
54
55 #undef countof
56 #define countof(x) (sizeof((x))/sizeof((*x)))
57
58 #include <math.h>
59 #include "xlockmore.h"
60
61 #ifdef USE_GL                                   /* whole file */
62
63 typedef struct timeval glhtime;
64
65 static double getTime(void)
66 {
67         struct timeval t;
68 #ifdef GETTIMEOFDAY_TWO_ARGS
69         gettimeofday(&t, NULL);
70 #else                                                   /* !GETTIMEOFDAY_TWO_ARGS */
71         gettimeofday(&t);
72 #endif                                                  /* !GETTIMEOFDAY_TWO_ARGS */
73         return t.tv_sec + t.tv_usec / 1000000.0;
74 }
75
76 typedef enum {
77         START,
78         MOVE_DISK,
79         MOVE_FINISHED,
80         FINISHED,
81         MONEY_SHOT,
82         INVALID = -1
83 } State;
84
85 typedef struct {
86         int id;
87         GLuint displayList;
88         GLfloat position[3];
89         GLfloat rotation[3];
90         GLfloat color[4];
91         GLfloat base0;
92         GLfloat base1;
93         GLfloat height;
94         GLfloat xmin, xmax, ymin;
95         GLfloat u1, u2;
96         GLfloat t1, t2;
97         GLfloat ucostheta, usintheta;
98         GLdouble rotAngle;
99 } Disk;
100
101 typedef struct {
102         Disk **data;
103         int count;
104         int size;
105 } Stack;
106
107 typedef struct {
108         GLXContext *glx_context;
109         State state;
110         Bool wire;
111         Bool fog;
112         Bool light;
113         double startTime;
114         double lastTime;
115         double duration;
116         int numberOfDisks;
117         int numberOfMoves;
118         int maxDiskIdx;
119         int magicNumber;
120         Disk *currentDisk;
121         int move;
122         int src;
123         int tmp;
124         int dst;
125         int oldsrc;
126         int oldtmp;
127         int olddst;
128         Stack pole[3];
129         float boardSize;
130         float baseLength;
131         float baseWidth;
132         float baseHeight;
133         float poleRadius;
134         float poleHeight;
135         float poleOffset;
136         float diskHeight;
137         float *diskPos;                         /* pre-computed disk positions on rods */
138         Disk *disk;
139         float speed;
140         GLint floorList;
141         GLint baseList;
142         GLint poleList;
143         GLfloat camera[3];
144         GLfloat centre[3];
145         rotator *the_rotator;
146         Bool button_down_p;
147         Bool texture;
148         GLuint textureNames[N_TEXTURES];
149         int drag_x;
150         int drag_y;
151         int noise_initted;
152
153         int p[512];
154
155 } glhcfg;
156
157 static glhcfg *glhanoi_cfg = NULL;
158 static Bool fog;
159 static Bool light;
160 static Bool texture;
161
162 static XrmOptionDescRec opts[] = {
163         {"-light", ".glhanoi.light", XrmoptionNoArg, "true"},
164         {"+light", ".glhanoi.light", XrmoptionNoArg, "false"},
165         {"-fog", ".glhanoi.fog", XrmoptionNoArg, "true"},
166         {"+fog", ".glhanoi.fog", XrmoptionNoArg, "false"},
167         {"-texture", ".glhanoi.texture", XrmoptionNoArg, "true"},
168         {"+texture", ".glhanoi.texture", XrmoptionNoArg, "false"}
169 };
170
171 static argtype vars[] = {
172         {&light, "light", "Light", DEF_LIGHT, t_Bool},
173         {&fog, "fog", "Fog", DEF_FOG, t_Bool},
174         {&texture, "texture", "Texture", DEF_TEXTURE, t_Bool}
175 };
176
177 static OptionStruct desc[] = {
178         {"+/-light", "whether to light the scene"},
179         {"+/-fog", "whether to apply fog to the scene"},
180         {"+/-texture", "whether to apply texture to the scene"}
181 };
182
183 ENTRYPOINT ModeSpecOpt glhanoi_opts = { countof(opts), opts, countof(vars), vars, desc };
184
185 #ifdef USE_MODULES
186
187 ModStruct glhanoi_description = {
188         "glhanoi", "init_glhanoi", "draw_glhanoi", "release_glhanoi",
189         "draw_glhanoi", "init_glhanoi", NULL, &glhanoi_opts,
190         1000, 1, 2, 1, 4, 1.0, "",
191         "Towers of Hanoi", 0, NULL
192 };
193
194 #endif
195
196 static const GLfloat cBlack[] = { 0.0, 0.0, 0.0, 1.0 };
197 static const GLfloat cWhite[] = { 1.0, 1.0, 1.0, 1.0 };
198 static const GLfloat poleColor[] = { 0.545, 0.137, 0.137 };
199 static const GLfloat baseColor[] = { 0.34, 0.34, 0.48 };
200 static const GLfloat fogcolor[] = { 0.5, 0.5, 0.5 };
201
202 static const float left[] = { 1.0, 0.0, 0.0 };
203 static const float up[] = { 0.0, 1.0, 0.0 };
204 static const float front[] = { 0.0, 0.0, 1.0 };
205 static const float right[] = { -1.0, 0.0, 0.0 };
206 static const float down[] = { 0.0, -1.0, 0.0 };
207 static const float back[] = { 0.0, 0.0, -1.0 };
208
209 static const GLfloat pos0[4] = { 50.0, 50.0, 50.0, 0.0 };
210 static const GLfloat amb0[4] = { 0.0, 0.0, 0.0, 1.0 };
211 static const GLfloat dif0[4] = { 1.0, 1.0, 1.0, 1.0 };
212 static const GLfloat spc0[4] = { 0.0, 1.0, 1.0, 1.0 };
213
214 static const GLfloat pos1[4] = { -50.0, 50.0, -50.0, 0.0 };
215 static const GLfloat amb1[4] = { 0.0, 0.0, 0.0, 1.0 };
216 static const GLfloat dif1[4] = { 1.0, 1.0, 1.0, 1.0 };
217 static const GLfloat spc1[4] = { 1.0, 1.0, 1.0, 1.0 };
218
219 static float g = 3.0 * 9.80665;         /* hmm, looks like we need more gravity, Scotty... */
220
221 #define DOPUSH(X, Y)    (((X)->count) >= ((X)->size)) ? NULL : ((X)->data[(X)->count++] = (Y))
222 #define DOPOP(X)        (X)->count <= 0 ? NULL : ((X)->data[--((X)->count)])
223
224 static Disk *push(glhcfg *glhanoi, int idx, Disk * d)
225 {
226         return DOPUSH(&glhanoi->pole[idx], d);
227 }
228
229 static Disk *pop(glhcfg *glhanoi, int idx)
230 {
231         return DOPOP(&glhanoi->pole[idx]);
232 }
233
234 static inline void swap(int *x, int *y)
235 {
236         *x = *x ^ *y;
237         *y = *x ^ *y;
238         *x = *x ^ *y;
239 }
240
241 /*
242  * magic - it's magic...
243  */
244 static int magic(int i)
245 {
246         int count = 0;
247         if(i <= 1)
248                 return 0;
249         while((i & 0x01) == 0) {
250                 i >>= 1;
251                 count++;
252         }
253         return count % 2 == 0;
254 }
255
256 #if 0
257 static float distance(float *p0, float *p1)
258 {
259         float x, y, z;
260         x = p1[0] - p0[0];
261         y = p1[1] - p0[1];
262         z = p1[2] - p0[2];
263         return (float)sqrt(x * x + y * y + z * z);
264 }
265 #endif
266
267 static GLfloat A(GLfloat a, GLfloat b, GLfloat c)
268 {
269         GLfloat sum = a + b;
270         return c / (a * b - 0.25 * sum * sum);
271 }
272
273 static void moveSetup(glhcfg *glhanoi, Disk * disk)
274 {
275         float h, ymax;
276         float u;
277         int src = glhanoi->src;
278         int dst = glhanoi->dst;
279         GLfloat theta;
280         GLfloat sintheta, costheta;
281
282         if(glhanoi->state != FINISHED && random() % 6 == 0) {
283                 disk->rotAngle =
284                         -180.0 * (2 - 2 * random() % 2) * (random() % 3 + 1);
285         } else {
286                 disk->rotAngle = -180.0;
287         }
288
289         disk->base0 = glhanoi->diskPos[glhanoi->pole[src].count];
290         disk->base1 =
291                 glhanoi->state ==
292                 FINISHED ? disk->base0 : glhanoi->diskPos[glhanoi->pole[dst].
293                                                                                                   count];
294
295         disk->xmin = glhanoi->poleOffset * (src - 1);
296         disk->xmax = glhanoi->poleOffset * (dst - 1);
297         disk->ymin = glhanoi->poleHeight;
298         ymax =
299                 glhanoi->poleHeight + fabs(disk->xmax -
300                                                                    disk->xmin) * (glhanoi->state ==
301                                                                                                   FINISHED ? 1.0 +
302                                                                                                   (double)(glhanoi->
303                                                                                                                    numberOfDisks -
304                                                                                                                    disk->id) /
305                                                                                                   (double)glhanoi->
306                                                                                                   numberOfDisks : 1.0);
307
308         h = ymax - disk->ymin;
309         theta = atan((disk->xmin - disk->xmax) * A(disk->xmin, disk->xmax, h));
310         if(theta < 0.0)
311                 theta += M_PI;
312         costheta = cos(theta);
313         sintheta = sin(theta);
314         u = (float)
315                 sqrt(fabs
316                          (-g /
317                           (2.0 * A(disk->xmin, disk->xmax, h) * costheta * costheta)));
318         disk->usintheta = u * sintheta;
319         disk->ucostheta = u * costheta;
320         disk->t1 =
321                 (-u + sqrt(u * u + 2.0 * g * fabs(disk->ymin - disk->base0))) / g;
322         disk->u1 = u + g * disk->t1;
323         disk->t2 = 2.0 * disk->usintheta / g;
324         disk->u2 = disk->usintheta - g * disk->t2;
325 }
326
327 static void makeMove(glhcfg *glhanoi)
328 {
329         int fudge = glhanoi->move + 2;
330         int magicNumber = magic(fudge);
331
332         glhanoi->currentDisk = pop(glhanoi, glhanoi->src);
333         moveSetup(glhanoi, glhanoi->currentDisk);
334         push(glhanoi, glhanoi->dst, glhanoi->currentDisk);
335         fudge = fudge % 2;
336
337         if(fudge == 1 || magicNumber) {
338                 swap(&glhanoi->src, &glhanoi->tmp);
339         }
340         if(fudge == 0 || glhanoi->magicNumber) {
341                 swap(&glhanoi->dst, &glhanoi->tmp);
342         }
343         glhanoi->magicNumber = magicNumber;
344 }
345
346 static double lerp(double alpha, double start, double end)
347 {
348         return start + alpha * (end - start);
349 }
350
351 static void upfunc(GLdouble t, Disk * d)
352 {
353         d->position[0] = d->xmin;
354         d->position[1] = d->base0 + (d->u1 - 0.5 * g * t) * t;
355
356         d->rotation[1] = 0.0;
357 }
358
359 static void parafunc(GLdouble t, Disk * d)
360 {
361         d->position[0] = d->xmin + d->ucostheta * t;
362         d->position[1] = d->ymin + (d->usintheta - 0.5 * g * t) * t;
363
364         d->rotation[1] =
365                 d->rotAngle * (d->position[0] - d->xmin) / (d->xmax - d->xmin);
366 }
367
368 static void downfunc(GLdouble t, Disk * d)
369 {
370         d->position[0] = d->xmax;
371         d->position[1] = d->ymin + (d->u2 - 0.5 * g * t) * t;
372
373         d->rotation[1] = 0.0;
374 }
375
376 static Bool computePosition(GLfloat t, Disk * d)
377 {
378         Bool finished = False;
379
380         if(t < d->t1) {
381                 upfunc(t, d);
382         } else if(t < d->t1 + d->t2) {
383                 parafunc(t - d->t1, d);
384         } else {
385                 downfunc(t - d->t1 - d->t2, d);
386                 if(d->position[1] <= d->base1) {
387                         d->position[1] = d->base1;
388                         finished = True;
389                 }
390         }
391         return finished;
392 }
393
394 static void updateView(glhcfg *glhanoi)
395 {
396         double longitude, latitude, radius;
397         double a, b, c, A, B;
398
399         get_position(glhanoi->the_rotator, NULL, NULL, &radius,
400                                  !glhanoi->button_down_p);
401         get_rotation(glhanoi->the_rotator, &longitude, &latitude, NULL,
402                                  !glhanoi->button_down_p);
403         longitude += glhanoi->camera[0];
404         latitude += glhanoi->camera[1];
405         radius += glhanoi->camera[2];
406         longitude = longitude - floor(longitude);
407         latitude = latitude - floor(latitude);
408         radius = radius - floor(radius);
409         if(latitude > 0.5) {
410                 latitude = 1.0 - latitude;
411         }
412         if(radius > 0.5) {
413                 radius = 1.0 - radius;
414         }
415
416         b = glhanoi->centre[1];
417         c = (MIN_CAMERA_RADIUS +
418                  radius * (MAX_CAMERA_RADIUS - MIN_CAMERA_RADIUS));
419         A = M_PI / 4.0 * (1.0 - latitude);
420         a = sqrt(b * b + c * c - 2.0 * b * c * cos(A));
421         B = asin(sin(A) * b / a);
422         glRotatef(-B * 180 / M_PI, 1.0, 0.0, 0.0);
423
424         glTranslatef(0.0f, 0.0f,
425                                  -(MIN_CAMERA_RADIUS +
426                                    radius * (MAX_CAMERA_RADIUS - MIN_CAMERA_RADIUS)));
427         glRotatef(longitude * 360.0, 0.0f, 1.0f, 0.0f);
428         glRotatef(latitude * 180.0, cos(longitude * 2.0 * M_PI), 0.0,
429                           sin(longitude * 2.0 * M_PI));
430 }
431
432 static void changeState(glhcfg *glhanoi, State state)
433 {
434         glhanoi->state = state;
435         glhanoi->startTime = getTime();
436 }
437
438 static void update_glhanoi(glhcfg *glhanoi)
439 {
440         double t = getTime() - glhanoi->startTime;
441         int i;
442         Bool done;
443
444         switch (glhanoi->state) {
445         case START:
446                 if(t < glhanoi->duration) {
447                         break;
448                 }
449                 glhanoi->move = 0;
450                 if(glhanoi->numberOfDisks % 2 == 0) {
451                         swap(&glhanoi->tmp, &glhanoi->dst);
452                 }
453                 glhanoi->magicNumber = 1;
454                 makeMove(glhanoi);
455                 changeState(glhanoi, MOVE_DISK);
456                 break;
457
458         case MOVE_DISK:
459                 if(computePosition(t, glhanoi->currentDisk)) {
460                         changeState(glhanoi, MOVE_FINISHED);
461                 }
462                 break;
463
464         case MOVE_FINISHED:
465                 if(++glhanoi->move < glhanoi->numberOfMoves) {
466                         makeMove(glhanoi);
467                         changeState(glhanoi, MOVE_DISK);
468                 } else {
469                         glhanoi->duration = FINISH_DURATION;
470                         changeState(glhanoi, FINISHED);
471                 }
472                 break;
473
474         case FINISHED:
475                 while(t < glhanoi->duration) {
476                         break;
477                 }
478                 glhanoi->src = glhanoi->olddst;
479                 glhanoi->dst = glhanoi->oldsrc;
480                 for(i = 0; i < glhanoi->numberOfDisks; ++i) {
481                         Disk *disk = pop(glhanoi, glhanoi->src);
482                         assert(disk != NULL);
483                         moveSetup(glhanoi, disk);
484                 }
485                 for(i = glhanoi->maxDiskIdx; i >= 0; --i) {
486                         push(glhanoi, glhanoi->dst, &glhanoi->disk[i]);
487                 }
488                 changeState(glhanoi, MONEY_SHOT);
489                 break;
490
491         case MONEY_SHOT:
492                 done = True;
493                 for(i = glhanoi->maxDiskIdx; i >= 0; --i) {
494                         double delay = 0.25 * i;
495                         int finished;
496
497                         if(t - delay < 0) {
498                                 done = False;
499                                 continue;
500                         }
501
502                         finished = computePosition(t - delay, &glhanoi->disk[i]);
503                         glhanoi->disk[i].rotation[1] = 0.0;
504
505                         if(!finished) {
506                                 done = False;
507                         }
508                 }
509                 if(done) {
510                         glhanoi->src = glhanoi->oldsrc;
511                         glhanoi->tmp = glhanoi->oldtmp;
512                         glhanoi->dst = glhanoi->olddst;
513                         changeState(glhanoi, START);
514                 }
515                 break;
516
517         case INVALID:
518         default:
519                 fprintf(stderr, "Invalid state\n");
520                 break;
521         }
522 }
523
524 static void HSVtoRGBf(GLfloat h, GLfloat s, GLfloat v,
525                            GLfloat * r, GLfloat * g, GLfloat * b)
526 {
527         if(s == 0.0) {
528                 *r = v;
529                 *g = v;
530                 *b = v;
531         } else {
532                 GLfloat i, f, p, q, t;
533                 if(h >= 360.0) {
534                         h = 0.0;
535                 }
536                 h /= 60.0;                              /* h now in [0,6). */
537                 i = floor((double)h);   /* i now largest integer <= h */
538                 f = h - i;                              /* f is no fractional part of h */
539                 p = v * (1.0 - s);
540                 q = v * (1.0 - (s * f));
541                 t = v * (1.0 - (s * (1.0 - f)));
542                 switch ((int)i) {
543                 case 0:
544                         *r = v;
545                         *g = t;
546                         *b = p;
547                         break;
548                 case 1:
549                         *r = q;
550                         *g = v;
551                         *b = p;
552                         break;
553                 case 2:
554                         *r = p;
555                         *g = v;
556                         *b = t;
557                         break;
558                 case 3:
559                         *r = p;
560                         *g = q;
561                         *b = v;
562                         break;
563                 case 4:
564                         *r = t;
565                         *g = p;
566                         *b = v;
567                         break;
568                 case 5:
569                         *r = v;
570                         *g = p;
571                         *b = q;
572                         break;
573                 }
574         }
575 }
576
577 static void HSVtoRGBv(GLfloat * hsv, GLfloat * rgb)
578 {
579         HSVtoRGBf(hsv[0], hsv[1], hsv[2], &rgb[0], &rgb[1], &rgb[2]);
580 }
581
582 static void setMaterial(const GLfloat color[3], const GLfloat hlite[3], int shininess)
583 {
584         glColor3fv(color);
585         glMaterialfv(GL_FRONT, GL_SPECULAR, hlite);
586         glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
587         glMateriali(GL_FRONT, GL_SHININESS, shininess); /* [0,128] */
588 }
589
590 /*
591  * drawTube: I know all this stuff is available in gluQuadrics
592  * but I'd originally intended to texture the poles with a 3D wood
593  * texture, but I was having difficulty getting wood... what? Why
594  * are all you Amercians laughing..? Anyway, I don't know if enough
595  * people's hardware supports 3D textures, so I didn't bother (xorg
596  * ATI server doesn't :-( )
597  */
598 static void drawTube(GLdouble bottomRadius, GLdouble topRadius,
599                           GLdouble bottomThickness, GLdouble topThickness,
600                           GLdouble height, GLuint nSlice, GLuint nLoop)
601 {
602         GLfloat y;
603         GLfloat *cosCache = malloc(sizeof(GLfloat) * nSlice);
604         GLfloat *sinCache = malloc(sizeof(GLfloat) * nSlice);
605         GLint slice;
606         GLuint loop;
607         GLint lastSlice = nSlice - 1;
608         GLfloat radius;
609         GLfloat innerRadius;
610         GLfloat maxRadius;
611
612         if(bottomThickness > bottomRadius) {
613                 bottomThickness = bottomRadius;
614         }
615         if(topThickness > topRadius) {
616                 topThickness = topRadius;
617         }
618         if(bottomThickness < 0.0) {
619                 bottomThickness = 0.0;
620         }
621         if(topThickness < 0.0) {
622                 topThickness = 0.0;
623         }
624         if(topRadius >= bottomRadius) {
625                 maxRadius = topRadius;
626         } else {
627                 maxRadius = bottomRadius;
628         }
629
630         /* bottom */
631         y = 0.0;
632         radius = bottomRadius;
633         innerRadius = bottomRadius - bottomThickness;
634         /* innerTexCoordSize = texCoordSize * innerRadius / maxRadius; */
635         /* outerTexCoordSize = texCoordSize * radius / maxRadius; */
636         /* yTexCoord = minTexCoord; */
637
638         glBegin(GL_QUAD_STRIP);
639
640         glNormal3f(0.0, -1.0, 0.0);
641
642         /* glTexCoord3f(midTexCoord, yTexCoord, midTexCoord + innerTexCoordSize); */
643         glVertex3f(0.0, y, innerRadius);
644
645         /* glTexCoord3f(midTexCoord, yTexCoord, midTexCoord + outerTexCoordSize); */
646         glVertex3f(0.0, y, radius);
647
648         for(slice = lastSlice; slice >= 0; --slice) {
649                 GLfloat theta = 2.0 * M_PI * (double)slice / nSlice;
650
651                 cosCache[slice] = cos(theta);
652                 sinCache[slice] = sin(theta);
653
654                 /* glTexCoord3f(midTexCoord + sinCache[slice] * innerTexCoordSize, */
655                 /* yTexCoord, */
656                 /* midTexCoord + cosCache[slice] * innerTexCoordSize); */
657                 glVertex3f(innerRadius * sinCache[slice], y,
658                                    innerRadius * cosCache[slice]);
659                 /* glTexCoord3f(midTexCoord + sinCache[slice] * outerTexCoordSize, */
660                 /* yTexCoord, */
661                 /* midTexCoord + cosCache[slice] * outerTexCoordSize); */
662                 glVertex3f(radius * sinCache[slice], y, radius * cosCache[slice]);
663         }
664         glEnd();
665
666         /* middle */
667         for(loop = 0; loop < nLoop; ++loop) {
668                 GLfloat lowerRadius =
669                         bottomRadius + (topRadius -
670                                                         bottomRadius) * (float)loop / (nLoop);
671                 GLfloat upperRadius =
672                         bottomRadius + (topRadius - bottomRadius) * (float)(loop +
673                                                                                                                                 1) /
674                         (nLoop);
675                 GLfloat lowerY = height * (float)loop / (nLoop);
676                 GLfloat upperY = height * (float)(loop + 1) / (nLoop);
677                 GLfloat factor = (topRadius - topThickness) -
678                         (bottomRadius - bottomThickness);
679
680                 /* outside */
681                 glBegin(GL_QUAD_STRIP);
682                 for(slice = 0; slice < nSlice; ++slice) {
683                         glNormal3f(sinCache[slice], 0.0, cosCache[slice]);
684                         glVertex3f(upperRadius * sinCache[slice], upperY,
685                                            upperRadius * cosCache[slice]);
686                         glVertex3f(lowerRadius * sinCache[slice], lowerY,
687                                            lowerRadius * cosCache[slice]);
688                 }
689                 glNormal3f(0.0, 0.0, 1.0);
690                 glVertex3f(0.0, upperY, upperRadius);
691                 glVertex3f(0.0, lowerY, lowerRadius);
692                 glEnd();
693
694                 /* inside */
695                 lowerRadius = bottomRadius - bottomThickness +
696                         factor * (float)loop / (nLoop);
697                 upperRadius = bottomRadius - bottomThickness +
698                         factor * (float)(loop + 1) / (nLoop);
699
700                 glBegin(GL_QUAD_STRIP);
701                 glNormal3f(0.0, 0.0, -1.0);
702                 glVertex3f(0.0, upperY, upperRadius);
703                 glVertex3f(0.0, lowerY, lowerRadius);
704                 for(slice = lastSlice; slice >= 0; --slice) {
705                         glNormal3f(-sinCache[slice], 0.0, -cosCache[slice]);
706                         glVertex3f(upperRadius * sinCache[slice], upperY,
707                                            upperRadius * cosCache[slice]);
708                         glVertex3f(lowerRadius * sinCache[slice], lowerY,
709                                            lowerRadius * cosCache[slice]);
710                 }
711                 glEnd();
712         }
713
714         /* top */
715         y = height;
716         radius = topRadius;
717         innerRadius = topRadius - topThickness;
718
719         glBegin(GL_QUAD_STRIP);
720         glNormal3f(0.0, 1.0, 0.0);
721         for(slice = 0; slice < nSlice; ++slice) {
722                 glVertex3f(innerRadius * sinCache[slice], y,
723                                    innerRadius * cosCache[slice]);
724
725                 glVertex3f(radius * sinCache[slice], y, radius * cosCache[slice]);
726         }
727         glVertex3f(0.0, y, innerRadius);
728         glVertex3f(0.0, y, radius);
729         glEnd();
730 }
731
732 static void drawPole(GLfloat radius, GLfloat length)
733 {
734         drawTube(radius, radius, radius, radius, length, NSLICE, NLOOPS);
735 }
736
737 static void drawDisk3D(GLdouble inner_radius, GLdouble outer_radius,
738                                 GLdouble height)
739 {
740         drawTube(outer_radius, outer_radius, outer_radius - inner_radius,
741                          outer_radius - inner_radius, height, NSLICE, NLOOPS);
742 }
743
744 static void drawCuboid(GLfloat length, GLfloat width, GLfloat height)
745 {
746         GLfloat xmin = -length / 2.0f;
747         GLfloat xmax = length / 2.0f;
748         GLfloat zmin = -width / 2.0f;
749         GLfloat zmax = width / 2.0f;
750         GLfloat ymin = 0.0f;
751         GLfloat ymax = height;
752
753         glBegin(GL_QUADS);
754         /* front */
755         glNormal3fv(front);
756         glVertex3f(xmin, ymin, zmax);   /* 0 */
757         glVertex3f(xmax, ymin, zmax);   /* 1 */
758         glVertex3f(xmax, ymax, zmax);   /* 2 */
759         glVertex3f(xmin, ymax, zmax);   /* 3 */
760         /* right */
761         glNormal3fv(right);
762         glVertex3f(xmax, ymin, zmax);   /* 1 */
763         glVertex3f(xmax, ymin, zmin);   /* 5 */
764         glVertex3f(xmax, ymax, zmin);   /* 6 */
765         glVertex3f(xmax, ymax, zmax);   /* 2 */
766         /* back */
767         glNormal3fv(back);
768         glVertex3f(xmax, ymin, zmin);   /* 5 */
769         glVertex3f(xmin, ymin, zmin);   /* 4 */
770         glVertex3f(xmin, ymax, zmin);   /* 7 */
771         glVertex3f(xmax, ymax, zmin);   /* 6 */
772         /* left */
773         glNormal3fv(left);
774         glVertex3f(xmin, ymin, zmin);   /* 4 */
775         glVertex3f(xmin, ymin, zmax);   /* 0 */
776         glVertex3f(xmin, ymax, zmax);   /* 3 */
777         glVertex3f(xmin, ymax, zmin);   /* 7 */
778         /* top */
779         glNormal3fv(up);
780         glVertex3f(xmin, ymax, zmax);   /* 3 */
781         glVertex3f(xmax, ymax, zmax);   /* 2 */
782         glVertex3f(xmax, ymax, zmin);   /* 6 */
783         glVertex3f(xmin, ymax, zmin);   /* 7 */
784         /* bottom */
785         glNormal3fv(down);
786         glVertex3f(xmin, ymin, zmin);   /* 4 */
787         glVertex3f(xmax, ymin, zmin);   /* 5 */
788         glVertex3f(xmax, ymin, zmax);   /* 1 */
789         glVertex3f(xmin, ymin, zmax);   /* 0 */
790         glEnd();
791 }
792
793 static void drawDisks(glhcfg *glhanoi)
794 {
795         int i;
796
797         glPushMatrix();
798         glTranslatef(0.0f, glhanoi->baseHeight, 0.0f);
799         for(i = glhanoi->maxDiskIdx; i >= 0; i--) {
800                 Disk *disk = &glhanoi->disk[i];
801                 GLfloat *pos = disk->position;
802                 GLfloat *rot = disk->rotation;
803
804                 glPushMatrix();
805                 glTranslatef(pos[0], pos[1], pos[2]);
806                 if(rot[1] != 0.0) {
807                         glTranslatef(0.0, glhanoi->diskHeight / 2.0, 0.0);
808                         glRotatef(rot[1], 0.0, 0.0, 1.0);
809                         glTranslatef(0.0, -glhanoi->diskHeight / 2.0, 0.0);
810                 }
811                 glCallList(disk->displayList);
812                 glPopMatrix();
813         }
814         glPopMatrix();
815 }
816
817 static GLfloat getDiskRadius(glhcfg *glhanoi, int i)
818 {
819         return ((GLfloat) i + 3.0) * glhanoi->poleRadius;
820 }
821
822 static void initData(glhcfg *glhanoi)
823 {
824         GLfloat maxDiskRadius;
825         int i;
826
827         glhanoi->baseLength = BASE_LENGTH;
828         glhanoi->poleRadius = glhanoi->baseLength /
829                 (2.0 * (3 * glhanoi->numberOfDisks + 7.0));
830         maxDiskRadius = getDiskRadius(glhanoi, glhanoi->numberOfDisks);
831         glhanoi->baseWidth = 2.0 * maxDiskRadius;
832         glhanoi->poleOffset = 2.0 * getDiskRadius(glhanoi, glhanoi->maxDiskIdx);
833         glhanoi->diskHeight = 2.0 * glhanoi->poleRadius;
834         glhanoi->baseHeight = 2.0 * glhanoi->poleRadius;
835         glhanoi->poleHeight = glhanoi->numberOfDisks *
836                 glhanoi->diskHeight + glhanoi->poleRadius;
837         glhanoi->numberOfMoves = (1 << glhanoi->numberOfDisks) - 1;
838         glhanoi->boardSize = glhanoi->baseLength * 0.5 * (1.0 + sqrt(5.0));
839
840         for(i = 0; i < 3; i++) {
841                 if((glhanoi->pole[i].data =
842                         calloc(glhanoi->numberOfDisks, sizeof(Disk *))) == NULL) {
843                         fprintf(stderr, "%s: out of memory creating stack %d\n",
844                                         progname, i);
845                         exit(1);
846                 }
847                 glhanoi->pole[i].size = glhanoi->numberOfDisks;
848         }
849         if((glhanoi->diskPos =
850                 calloc(glhanoi->numberOfDisks, sizeof(double))) == NULL) {
851                 fprintf(stderr, "%s: out of memory creating diskPos\n", progname);
852                 exit(1);
853         }
854
855         glhanoi->the_rotator = make_rotator(0.1, 0.025, 0, 1, 0.005, False);
856         glhanoi->button_down_p = False;
857
858         glhanoi->src = glhanoi->oldsrc = 0;
859         glhanoi->tmp = glhanoi->oldtmp = 1;
860         glhanoi->dst = glhanoi->olddst = 2;
861 }
862
863 static void initView(glhcfg *glhanoi)
864 {
865         glhanoi->camera[0] = 0.0;
866         glhanoi->camera[1] = 0.0;
867         glhanoi->camera[2] = 0.0;
868         glhanoi->centre[0] = 0.0;
869         glhanoi->centre[1] = glhanoi->poleHeight * 3.0;
870         glhanoi->centre[2] = 0.0;
871 }
872
873 /*
874  * noise_improved.c - based on ImprovedNoise.java
875  * JAVA REFERENCE IMPLEMENTATION OF IMPROVED NOISE - COPYRIGHT 2002 KEN PERLIN.
876  */
877 static double fade(double t)
878 {
879         return t * t * t * (t * (t * 6 - 15) + 10);
880 }
881
882 static double grad(int hash, double x, double y, double z)
883 {
884         int h = hash & 15;                      /* CONVERT LO 4 BITS OF HASH CODE */
885         double u = h < 8 ? x : y,       /* INTO 12 GRADIENT DIRECTIONS. */
886                 v = h < 4 ? y : h == 12 || h == 14 ? x : z;
887         return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
888 }
889
890 static const int permutation[] = { 151, 160, 137, 91, 90, 15,
891         131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142,
892         8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26,
893         197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237,
894         149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71,
895         134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60,
896         211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143,
897         54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89,
898         18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198,
899         173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118,
900         126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189,
901         28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70,
902         221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108,
903         110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251,
904         34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145,
905         235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184,
906         84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205,
907         93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61,
908         156, 180
909 };
910
911 static void initNoise(glhcfg *glhanoi)
912 {
913         int i;
914         for(i = 0; i < 256; i++)
915                 glhanoi->p[256 + i] = glhanoi->p[i] = permutation[i];
916 }
917
918 static double improved_noise(glhcfg *glhanoi, double x, double y, double z)
919 {
920         double u, v, w;
921         int A, AA, AB, B, BA, BB;
922         int X = (int)floor(x) & 255,    /* FIND UNIT CUBE THAT */
923                 Y = (int)floor(y) & 255,        /* CONTAINS POINT. */
924                 Z = (int)floor(z) & 255;
925         if(!glhanoi->noise_initted) {
926                 initNoise(glhanoi);
927                 glhanoi->noise_initted = 1;
928         }
929         x -= floor(x);                          /* FIND RELATIVE X,Y,Z */
930         y -= floor(y);                          /* OF POINT IN CUBE. */
931         z -= floor(z);
932         u = fade(x),                            /* COMPUTE FADE CURVES */
933                 v = fade(y),                    /* FOR EACH OF X,Y,Z. */
934                 w = fade(z);
935         A = glhanoi->p[X] + Y, AA = glhanoi->p[A] + Z, AB = glhanoi->p[A + 1] + Z,      /* HASH COORDINATES OF */
936                 B = glhanoi->p[X + 1] + Y, BA = glhanoi->p[B] + Z, BB = glhanoi->p[B + 1] + Z;  /* THE 8 CUBE CORNERS, */
937         return lerp(w, lerp(v, lerp(u, grad(glhanoi->p[AA], x, y, z),   /* AND ADD */
938                                                                 grad(glhanoi->p[BA], x - 1, y, z)),     /* BLENDED */
939                                                 lerp(u, grad(glhanoi->p[AB], x, y - 1, z),      /* RESULTS */
940                                                          grad(glhanoi->p[BB], x - 1, y - 1, z))),       /* FROM 8 CORNERS */
941                                 lerp(v, lerp(u, grad(glhanoi->p[AA + 1], x, y, z - 1), grad(glhanoi->p[BA + 1], x - 1, y, z - 1)),      /* OF CUBE */
942                                          lerp(u, grad(glhanoi->p[AB + 1], x, y - 1, z - 1),
943                                                   grad(glhanoi->p[BB + 1], x - 1, y - 1, z - 1))));
944 }
945
946 /*
947  * end noise_improved.c - based on ImprovedNoise.java
948  */
949
950 struct tex_col_t {
951         GLuint *colours;
952         /* GLfloat *points; */
953         unsigned int ncols;
954 };
955 typedef struct tex_col_t tex_col_t;
956
957 static GLubyte *makeTexture(glhcfg *glhanoi, int x_size, int y_size, int z_size,
958                                          GLuint(*texFunc) (glhcfg *, double, double, double,
959                                                                            tex_col_t *), tex_col_t * colours)
960 {
961         int i, j, k;
962         GLubyte *textureData;
963         GLuint *texturePtr;
964         double x, y, z;
965         double xi, yi, zi;
966
967         if((textureData =
968                 calloc(x_size * y_size * z_size, sizeof(GLuint))) == NULL) {
969                 return NULL;
970         }
971
972         xi = 1.0 / x_size;
973         yi = 1.0 / y_size;
974         zi = 1.0 / z_size;
975
976         z = 0.0;
977         texturePtr = (void *)textureData;
978         for(k = 0; k < z_size; k++, z += zi) {
979                 y = 0.0;
980                 for(j = 0; j < y_size; j++, y += yi) {
981                         x = 0.0;
982                         for(i = 0; i < x_size; i++, x += xi) {
983                                 *texturePtr = texFunc(glhanoi, x, y, z, colours);
984                                 ++texturePtr;
985                         }
986                 }
987         }
988         return textureData;
989 }
990
991 static tex_col_t makeMarbleColours(void)
992 {
993         tex_col_t marbleColours;
994         int ncols = 2;
995
996         marbleColours.colours = calloc(sizeof(GLuint), ncols);
997         marbleColours.ncols = ncols;
998
999         marbleColours.colours[0] = 0x3f3f3f3f;
1000         marbleColours.colours[1] = 0xffffffff;
1001
1002         return marbleColours;
1003 }
1004
1005 static double turb(glhcfg *glhanoi, double x, double y, double z, int octaves)
1006 {
1007         int oct, freq = 1;
1008         double r = 0.0;
1009
1010         for(oct = 0; oct < octaves; ++oct) {
1011                 r += fabs(improved_noise(glhanoi, freq * x, freq * y, freq * z)) / freq;
1012                 freq <<= 1;
1013         }
1014         return r / 2.0;
1015 }
1016
1017 static void perturb(glhcfg *glhanoi, double *x, double *y, double *z, double scale)
1018 {
1019         double t = scale * turb(glhanoi, *x, *y, *z, 4);
1020         *x += t;
1021         *y += t;
1022         *z += t;
1023 }
1024
1025 static double f_m(double x, double y, double z)
1026 {
1027         return sin(3.0 * M_PI * x);
1028 }
1029
1030 static GLuint C_m(double x, const tex_col_t * tex_cols)
1031 {
1032         int r = tex_cols->colours[0] & 0xff;
1033         int g = tex_cols->colours[0] >> 8 & 0xff;
1034         int b = tex_cols->colours[0] >> 16 & 0xff;
1035         double factor;
1036         int r1, g1, b1;
1037         x = x - floor(x);
1038
1039         factor = (1.0 + sin(2.0 * M_PI * x)) / 2.0;
1040
1041         r1 = (tex_cols->colours[1] & 0xff);
1042         g1 = (tex_cols->colours[1] >> 8 & 0xff);
1043         b1 = (tex_cols->colours[1] >> 16 & 0xff);
1044
1045         r += (int)(factor * (r1 - r));
1046         g += (int)(factor * (g1 - g));
1047         b += (int)(factor * (b1 - b));
1048
1049         return 0xff000000 | (b << 16) | (g << 8) | r;
1050 }
1051
1052
1053 static GLuint makeMarbleTexture(glhcfg *glhanoi, double x, double y, double z, tex_col_t * colours)
1054 {
1055         perturb(glhanoi, &x, &y, &z, MARBLE_SCALE);
1056         return C_m(f_m(x, y, z), colours);
1057 }
1058
1059 static void setTexture(glhcfg *glhanoi, int n)
1060 {
1061         glBindTexture(GL_TEXTURE_2D, glhanoi->textureNames[n]);
1062 }
1063
1064 static int makeTextures(glhcfg *glhanoi)
1065 {
1066         GLubyte *marbleTexture;
1067         tex_col_t marbleColours;
1068
1069         glGenTextures(N_TEXTURES, glhanoi->textureNames);
1070
1071         marbleColours = makeMarbleColours();
1072         if((marbleTexture =
1073                 makeTexture(glhanoi, MARBLE_TEXTURE_SIZE, MARBLE_TEXTURE_SIZE, 1,
1074                                         makeMarbleTexture, &marbleColours)) == NULL) {
1075                 return 1;
1076         }
1077
1078         glBindTexture(GL_TEXTURE_2D, glhanoi->textureNames[0]);
1079         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1080         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1081         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
1082         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
1083         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
1084                                  MARBLE_TEXTURE_SIZE, MARBLE_TEXTURE_SIZE, 0,
1085                                  GL_RGBA, GL_UNSIGNED_BYTE, marbleTexture);
1086         free(marbleTexture);
1087
1088         return 0;
1089 }
1090
1091 static void initFloor(glhcfg *glhanoi)
1092 {
1093         int i, j;
1094         float tileSize = glhanoi->boardSize / BOARD_SQUARES;
1095         float x0, x1, z0, z1;
1096         float tx0, tx1, tz0, tz1;
1097         const float *col = cWhite;
1098         float texIncr = 1.0 / BOARD_SQUARES;
1099
1100         if((glhanoi->floorList = glGenLists(1)) == 0) {
1101                 fprintf(stderr, "can't allocate memory for floor display list\n");
1102                 exit(EXIT_FAILURE);
1103         }
1104         glNewList(glhanoi->floorList, GL_COMPILE);
1105         x0 = -glhanoi->boardSize / 2.0;
1106         tx0 = 0.0f;
1107         setMaterial(col, cWhite, 128);
1108         setTexture(glhanoi, 0);
1109         glNormal3fv(up);
1110         for(i = 0; i < BOARD_SQUARES; i++, x0 += tileSize, tx0 += texIncr) {
1111                 x1 = x0 + tileSize;
1112                 tx1 = tx0 + texIncr;
1113                 z0 = -glhanoi->boardSize / 2.0;
1114                 tz0 = 0.0f;
1115                 for(j = 0; j < BOARD_SQUARES; j++, z0 += tileSize, tz0 += texIncr) {
1116                         int colIndex = (i + j) & 0x1;
1117
1118                         z1 = z0 + tileSize;
1119                         tz1 = tz0 + texIncr;
1120
1121                         if(colIndex)
1122                                 col = cWhite;
1123                         else
1124                                 col = cBlack;
1125
1126                         setMaterial(col, cWhite, 100);
1127
1128                         glBegin(GL_QUADS);
1129
1130                         glTexCoord2d(tx0, tz0);
1131                         glVertex3f(x0, 0.0, z0);
1132
1133                         glTexCoord2d(tx0, tz1);
1134                         glVertex3f(x0, 0.0, z1);
1135
1136                         glTexCoord2d(tx1, tz1);
1137                         glVertex3f(x1, 0.0, z1);
1138
1139                         glTexCoord2d(tx1, tz0);
1140                         glVertex3f(x1, 0.0, z0);
1141                         glEnd();
1142                 }
1143         }
1144         glEndList();
1145 }
1146
1147 static void initTowers(glhcfg *glhanoi)
1148 {
1149         if((glhanoi->baseList = glGenLists(1)) == 0) {
1150                 fprintf(stderr, "can't allocate memory for towers display list\n");
1151                 exit(EXIT_FAILURE);
1152         }
1153         glNewList(glhanoi->baseList, GL_COMPILE);
1154         setMaterial(baseColor, cWhite, 50);
1155         drawCuboid(glhanoi->baseLength, glhanoi->baseWidth,
1156                            glhanoi->baseHeight);
1157         glEndList();
1158
1159
1160         if((glhanoi->poleList = glGenLists(1)) == 0) {
1161                 fprintf(stderr, "can't allocate memory for towers display list\n");
1162                 exit(EXIT_FAILURE);
1163         }
1164         glNewList(glhanoi->poleList, GL_COMPILE);
1165         glPushMatrix();
1166         glTranslatef(0.0f, glhanoi->baseHeight, 0.0f);
1167         setMaterial(poleColor, cWhite, 50);
1168         drawPole(glhanoi->poleRadius, glhanoi->poleHeight);
1169         glPushMatrix();
1170         glTranslatef(-glhanoi->poleOffset, 0.0, 0.0);
1171         drawPole(glhanoi->poleRadius, glhanoi->poleHeight);
1172         glPopMatrix();
1173         glTranslatef(glhanoi->poleOffset, 0.0, 0.0);
1174         drawPole(glhanoi->poleRadius, glhanoi->poleHeight);
1175         glPopMatrix();
1176         glEndList();
1177 }
1178
1179 static double cfunc(double x)
1180 {
1181 #define COMP <
1182         if(x < 2.0 / 7.0) {
1183                 return (1.0 / 12.0) / (1.0 / 7.0) * x;
1184         }
1185         if(x < 3.0 / 7.0) {
1186                 return (1.0 + 1.0 / 6.0) * x - 1.0 / 6.0;
1187         }
1188         if(x < 4.0 / 7.0) {
1189                 return (2.0 + 1.0 / 3.0) * x - 2.0 / 3.0;
1190         }
1191         if(x < 5.0 / 7.0) {
1192                 return (1.0 / 12.0) / (1.0 / 7.0) * x + 1.0 / 3.0;
1193         }
1194         return (1.0 / 12.0) / (1.0 / 7.0) * x + 1.0 / 3.0;
1195 }
1196
1197 static void initDisks(glhcfg *glhanoi)
1198 {
1199         int i;
1200         if((glhanoi->disk =
1201                 (Disk *) calloc(glhanoi->numberOfDisks, sizeof(Disk))) == NULL) {
1202                 perror("initDisks");
1203                 exit(EXIT_FAILURE);
1204         }
1205
1206         for(i = glhanoi->maxDiskIdx; i >= 0; i--) {
1207                 GLfloat height = (GLfloat) (glhanoi->maxDiskIdx - i);
1208                 double f = cfunc((GLfloat) i / (GLfloat) glhanoi->numberOfDisks);
1209                 GLfloat diskColor = f * 360.0;
1210                 GLfloat color[3];
1211                 Disk *disk = &glhanoi->disk[i];
1212
1213                 disk->id = i;
1214                 disk->position[0] = -glhanoi->poleOffset;
1215                 disk->position[1] = glhanoi->diskHeight * height;
1216                 disk->position[2] = 0.0;
1217                 disk->rotation[0] = 0.0;
1218                 disk->rotation[1] = 0.0;
1219                 disk->rotation[2] = 0.0;
1220
1221                 color[0] = diskColor;
1222                 color[1] = 1.0f;
1223                 color[2] = 1.0f;
1224                 HSVtoRGBv(color, color);
1225
1226                 if((disk->displayList = glGenLists(1)) == 0) {
1227                         fprintf(stderr,
1228                                         "can't allocate memory for disk %d display list\n", i);
1229                         exit(EXIT_FAILURE);
1230                 }
1231                 glNewList(disk->displayList, GL_COMPILE);
1232                 setMaterial(color, cWhite, 100.0);
1233                 drawDisk3D(glhanoi->poleRadius, 
1234                            getDiskRadius(glhanoi, i),
1235                            glhanoi->diskHeight);
1236                 glEndList();
1237         }
1238         for(i = glhanoi->maxDiskIdx; i >= 0; --i) {
1239                 GLfloat height = (GLfloat) (glhanoi->maxDiskIdx - i);
1240                 int h = glhanoi->maxDiskIdx - i;
1241                 glhanoi->diskPos[h] = glhanoi->diskHeight * height;
1242                 push(glhanoi, glhanoi->src, &glhanoi->disk[i]);
1243         }
1244 }
1245
1246 static void initLights(Bool state)
1247 {
1248         if(state) {
1249                 glLightfv(GL_LIGHT0, GL_POSITION, pos0);
1250                 glLightfv(GL_LIGHT0, GL_AMBIENT, amb0);
1251                 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif0);
1252                 glLightfv(GL_LIGHT0, GL_SPECULAR, spc0);
1253
1254                 glLightfv(GL_LIGHT1, GL_POSITION, pos1);
1255                 glLightfv(GL_LIGHT1, GL_AMBIENT, amb1);
1256                 glLightfv(GL_LIGHT1, GL_DIFFUSE, dif1);
1257                 glLightfv(GL_LIGHT1, GL_SPECULAR, spc1);
1258
1259                 glEnable(GL_LIGHTING);
1260                 glEnable(GL_LIGHT0);
1261                 glEnable(GL_LIGHT1);
1262         } else {
1263                 glDisable(GL_LIGHTING);
1264         }
1265 }
1266
1267 static void drawFloor(glhcfg *glhanoi)
1268 {
1269         glCallList(glhanoi->floorList);
1270 }
1271
1272 static void drawTowers(glhcfg *glhanoi)
1273 {
1274         glCallList(glhanoi->baseList);
1275         glCallList(glhanoi->poleList);
1276 }
1277
1278 /* Window management, etc
1279  */
1280 ENTRYPOINT void reshape_glhanoi(ModeInfo * mi, int width, int height)
1281 {
1282         glViewport(0, 0, (GLint) width, (GLint) height);
1283
1284         glMatrixMode(GL_PROJECTION);
1285         glLoadIdentity();
1286         gluPerspective(30.0, (GLdouble) width / (GLdouble) height, 1.0,
1287                                    2 * MAX_CAMERA_RADIUS);
1288
1289         glMatrixMode(GL_MODELVIEW);
1290         glLoadIdentity();
1291
1292         glClear(GL_COLOR_BUFFER_BIT);
1293 }
1294
1295 ENTRYPOINT void init_glhanoi(ModeInfo * mi)
1296 {
1297         glhcfg *glhanoi;
1298         if(!glhanoi_cfg) {
1299                 glhanoi_cfg =
1300                         (glhcfg *) calloc(MI_NUM_SCREENS(mi), sizeof(glhcfg));
1301                 if(!glhanoi_cfg) {
1302                         fprintf(stderr, "%s: out of memory creating configs\n",
1303                                         progname);
1304                         exit(1);
1305                 }
1306         }
1307
1308         glhanoi = &glhanoi_cfg[MI_SCREEN(mi)];
1309         glhanoi->glx_context = init_GL(mi);
1310         glhanoi->numberOfDisks = MI_BATCHCOUNT(mi);
1311
1312     if (glhanoi->numberOfDisks <= 1)
1313       glhanoi->numberOfDisks = 3 + (int) BELLRAND(9);
1314
1315     /* magicnumber is a bitfield, so we can't have more than 31 discs
1316        on a system with 4-byte ints. */
1317     if (glhanoi->numberOfDisks >= 8 * sizeof(int))
1318       glhanoi->numberOfDisks = (8 * sizeof(int)) - 1;
1319
1320         glhanoi->maxDiskIdx = glhanoi->numberOfDisks - 1;
1321         glhanoi->wire = MI_IS_WIREFRAME(mi);
1322         glhanoi->light = light;
1323         glhanoi->fog = fog;
1324         glhanoi->texture = texture;
1325
1326         reshape_glhanoi(mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1327
1328         if(glhanoi->wire) {
1329                 glhanoi->light = False;
1330                 glhanoi->fog = False;
1331                 glhanoi->texture = False;
1332         }
1333
1334         initLights(!glhanoi->wire && glhanoi->light);
1335         if(makeTextures(glhanoi) != 0) {
1336                 fprintf(stderr, "can't allocate memory for marble texture\n");
1337                 exit(EXIT_FAILURE);
1338         }
1339
1340         initData(glhanoi);
1341         initView(glhanoi);
1342         initFloor(glhanoi);
1343         initTowers(glhanoi);
1344         initDisks(glhanoi);
1345
1346         glEnable(GL_DEPTH_TEST);
1347         glEnable(GL_NORMALIZE);
1348         glEnable(GL_CULL_FACE);
1349         glShadeModel(GL_SMOOTH);
1350         if(glhanoi->fog) {
1351                 glClearColor(fogcolor[0], fogcolor[1], fogcolor[2], 1.0);
1352                 glFogi(GL_FOG_MODE, GL_LINEAR);
1353                 glFogfv(GL_FOG_COLOR, fogcolor);
1354                 glFogf(GL_FOG_DENSITY, 0.35f);
1355                 glHint(GL_FOG_HINT, GL_NICEST);
1356                 glFogf(GL_FOG_START, MIN_CAMERA_RADIUS);
1357                 glFogf(GL_FOG_END, MAX_CAMERA_RADIUS / 1.9);
1358                 glEnable(GL_FOG);
1359         }
1360
1361
1362         glhanoi->duration = START_DURATION;
1363         changeState(glhanoi, START);
1364 }
1365
1366 ENTRYPOINT void draw_glhanoi(ModeInfo * mi)
1367 {
1368         glhcfg *glhanoi = &glhanoi_cfg[MI_SCREEN(mi)];
1369         Display *dpy = MI_DISPLAY(mi);
1370         Window window = MI_WINDOW(mi);
1371
1372         if(!glhanoi->glx_context)
1373                 return;
1374
1375         glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(glhanoi->glx_context));
1376
1377         glPolygonMode(GL_FRONT, glhanoi->wire ? GL_LINE : GL_FILL);
1378
1379         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1380
1381         glLoadIdentity();
1382
1383         update_glhanoi(glhanoi);
1384         updateView(glhanoi);
1385
1386         if(!glhanoi->wire && glhanoi->texture) {
1387                 glEnable(GL_TEXTURE_2D);
1388         }
1389         drawFloor(glhanoi);
1390         glDisable(GL_TEXTURE_2D);
1391
1392         drawTowers(glhanoi);
1393         drawDisks(glhanoi);
1394
1395         if(mi->fps_p) {
1396                 do_fps(mi);
1397         }
1398         glFinish();
1399
1400         glXSwapBuffers(dpy, window);
1401 }
1402
1403 ENTRYPOINT Bool glhanoi_handle_event(ModeInfo * mi, XEvent * event)
1404 {
1405         glhcfg *glhanoi = &glhanoi_cfg[MI_SCREEN(mi)];
1406
1407         if(event->xany.type == ButtonPress && event->xbutton.button == Button1) {
1408                 glhanoi->button_down_p = True;
1409                 glhanoi->drag_x = event->xbutton.x;
1410                 glhanoi->drag_y = event->xbutton.y;
1411                 return True;
1412         } else if(event->xany.type == ButtonRelease
1413                           && event->xbutton.button == Button1) {
1414                 glhanoi->button_down_p = False;
1415                 return True;
1416         } else if(event->xany.type == ButtonPress &&
1417                           (event->xbutton.button == Button4
1418                            || event->xbutton.button == Button5)) {
1419                 switch (event->xbutton.button) {
1420                 case Button4:
1421                         glhanoi->camera[2] += 0.01;
1422                         break;
1423                 case Button5:
1424                         glhanoi->camera[2] -= 0.01;
1425                         break;
1426                 default:
1427                         fprintf(stderr,
1428                                         "glhanoi: unknown button in mousewheel handler\n");
1429                 }
1430                 return True;
1431         } else if(event->xany.type == MotionNotify
1432                           && glhanoi_cfg->button_down_p) {
1433                 int x_diff, y_diff;
1434
1435                 x_diff = event->xbutton.x - glhanoi->drag_x;
1436                 y_diff = event->xbutton.y - glhanoi->drag_y;
1437
1438                 glhanoi->camera[0] = (float)x_diff / (float)MI_WIDTH(mi);
1439                 glhanoi->camera[1] = (float)y_diff / (float)MI_HEIGHT(mi);
1440
1441                 return True;
1442         }
1443         return False;
1444 }
1445
1446 ENTRYPOINT void release_glhanoi(ModeInfo * mi)
1447 {
1448         if(glhanoi_cfg != NULL) {
1449                 int screen;
1450                 for(screen = 0; screen < MI_NUM_SCREENS(mi); screen++) {
1451                         int i;
1452                         int j;
1453                         glhcfg *glh = &glhanoi_cfg[screen];
1454                         glDeleteLists(glh->floorList, 1);
1455                         glDeleteLists(glh->baseList, 1);
1456                         glDeleteLists(glh->poleList, 1);
1457                         glDeleteLists(glh->textureNames[0], 2);
1458                         for(j = 0; j < glh->numberOfDisks; ++j) {
1459                                 glDeleteLists(glh->disk[j].displayList, 1);
1460                         }
1461                         free(glh->disk);
1462                         for(i = 0; i < 3; i++) {
1463                                 if(glh->pole[i].data != NULL) {
1464                                         free(glh->pole[i].data);
1465                                 }
1466                         }
1467                 }
1468         }
1469         free(glhanoi_cfg);
1470         glhanoi_cfg = NULL;
1471 }
1472
1473 XSCREENSAVER_MODULE ("GLHanoi", glhanoi)
1474
1475 #endif                                                  /* USE_GL */