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