From http://www.jwz.org/xscreensaver/xscreensaver-5.36.tar.gz
[xscreensaver] / hacks / glx / discoball.c
1 /* discoball, Copyright (c) 2016 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  */
11
12 #define DEFAULTS        "*delay:        30000       \n" \
13                         "*count:        30          \n" \
14                         "*showFPS:      False       \n" \
15                         "*wireframe:    False       \n" \
16
17 # define refresh_ball 0
18 #undef countof
19 #define countof(x) (sizeof((x))/sizeof((*x)))
20
21 #include "xlockmore.h"
22 #include "normals.h"
23 #include "rotator.h"
24 #include "gltrackball.h"
25 #include <ctype.h>
26
27 #ifdef USE_GL /* whole file */
28
29
30 #define DEF_SPIN        "False"
31 #define DEF_WANDER      "True"
32 #define DEF_SPEED       "1.0"
33
34 typedef struct tile tile;
35 struct tile {
36   XYZ position, normal;
37   GLfloat size, tilt;
38   tile *next;
39 };
40
41
42 typedef struct {
43   XYZ normal;
44   GLfloat color[4];
45 } ray;
46
47 typedef struct {
48   GLXContext *glx_context;
49   rotator *rot;
50   GLfloat th;
51   trackball_state *trackball;
52   Bool button_down_p;
53   tile *tiles;
54   int nrays;
55   ray *rays;
56 } ball_configuration;
57
58 static ball_configuration *bps = NULL;
59
60 static Bool do_spin;
61 static GLfloat speed;
62 static Bool do_wander;
63
64 static XrmOptionDescRec opts[] = {
65   { "-spin",   ".spin",   XrmoptionNoArg, "True" },
66   { "+spin",   ".spin",   XrmoptionNoArg, "False" },
67   { "-speed",  ".speed",  XrmoptionSepArg, 0 },
68   { "-wander", ".wander", XrmoptionNoArg, "True" },
69   { "+wander", ".wander", XrmoptionNoArg, "False" }
70 };
71
72 static argtype vars[] = {
73   {&do_spin,   "spin",   "Spin",   DEF_SPIN,   t_Bool},
74   {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
75   {&speed,     "speed",  "Speed",  DEF_SPEED,  t_Float},
76 };
77
78 ENTRYPOINT ModeSpecOpt ball_opts = {countof(opts), opts, countof(vars), vars, NULL};
79
80
81 static XYZ
82 normalize (XYZ p)
83 {
84   GLfloat d = sqrt(p.x*p.x + p.y*p.y * p.z*p.z);
85   if (d < 0.0000001)
86     p.x = p.y = p.z = 0;
87   else
88     {
89       p.x /= d;
90       p.y /= d;
91       p.z /= d;
92     }
93     
94   return p;
95 }
96
97
98 static void
99 build_texture (ModeInfo *mi)
100 {
101   int x, y;
102   int size = 128;
103   int bpl = size * 2;
104   unsigned char *data = malloc (bpl * size);
105
106   for (y = 0; y < size; y++)
107     {
108       for (x = 0; x < size; x++)
109         {
110           unsigned char *c = &data [y * bpl + x * 2];
111           GLfloat X = (x / (GLfloat) (size-1)) - 0.5;
112           GLfloat Y = (y / (GLfloat) (size-1)) - 0.5;
113           X = cos (X * X * 6.2);
114           Y = cos (Y * Y * 6.2);
115           X = X < Y ? X : Y;
116           X *= 0.4;
117           c[0] = 0xFF;
118           c[1] = 0xFF * X;
119         }
120     }
121
122   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
123   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
124   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
125   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
126   glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
127   glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
128   check_gl_error ("texture param");
129
130   glTexImage2D (GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, size, size, 0,
131                 GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, data);
132   check_gl_error ("light texture");
133   free (data);
134 }
135
136
137 static int
138 draw_rays (ModeInfo *mi)
139 {
140   ball_configuration *bp = &bps[MI_SCREEN(mi)];
141   int wire = MI_IS_WIREFRAME(mi);
142   int polys = 0;
143   int i;
144
145   glEnable (GL_TEXTURE_2D);
146   glDisable (GL_LIGHTING);
147   glEnable (GL_BLEND);
148   glDisable (GL_DEPTH_TEST);
149   glBlendFunc (GL_SRC_ALPHA, GL_ONE);
150
151   for (i = 0; i < bp->nrays; i++)
152     {
153       GLfloat x = bp->rays[i].normal.x;
154       GLfloat y = bp->rays[i].normal.y;
155       GLfloat z = bp->rays[i].normal.z;
156       glPushMatrix();
157
158       /* Orient to direction of ray. */
159       glRotatef (-atan2 (x, y)               * (180 / M_PI), 0, 0, 1);
160       glRotatef ( atan2 (z, sqrt(x*x + y*y)) * (180 / M_PI), 1, 0, 0);
161
162       glScalef (5, 5, 10);
163       glTranslatef(0, 0, 1.1);
164       glColor4fv (bp->rays[i].color);
165       glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
166       glTexCoord2f (0, 0); glVertex3f (-0.5, 0, -1);
167       glTexCoord2f (1, 0); glVertex3f ( 0.5, 0, -1);
168       glTexCoord2f (1, 1); glVertex3f ( 0.5, 0,  1);
169       glTexCoord2f (0, 1); glVertex3f (-0.5, 0,  1);
170       glEnd();
171       polys++;
172       glPopMatrix();
173     }
174
175   glDisable (GL_TEXTURE_2D);
176   glEnable (GL_LIGHTING);
177   glDisable (GL_BLEND);
178   glEnable (GL_DEPTH_TEST);
179   glDisable (GL_FOG);
180
181   return polys;
182 }
183
184
185 static int
186 draw_ball_1 (ModeInfo *mi)
187 {
188   ball_configuration *bp = &bps[MI_SCREEN(mi)];
189   int wire = MI_IS_WIREFRAME(mi);
190   int polys = 0;
191   tile *t;
192   GLfloat m[4][4];
193
194   glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]);
195
196   glFrontFace (GL_CW);
197
198 #if 0
199   /* Draw the back rays.
200    */
201   if (! wire)
202     {
203       glPushMatrix();
204       glLoadIdentity();
205       glMultMatrixf (&m[0][0]);
206       glTranslatef(0, 0, -4.1);
207       glRotatef (bp->th, 0, 0, 1);
208       polys += draw_rays (mi);
209       glPopMatrix();
210     }
211   glClear(GL_DEPTH_BUFFER_BIT);
212 #endif
213
214
215   /* Instead of rendering polygons for the foam ball substrate, let's
216      just billboard a quad down the middle to mask out the back-facing
217      tiles. */
218   {
219     glPushMatrix();
220     m[0][0] = 1; m[1][0] = 0; m[2][0] = 0;
221     m[0][1] = 0; m[1][1] = 1; m[2][1] = 0;
222     m[0][2] = 0; m[1][2] = 0; m[2][2] = 1;
223     glLoadIdentity();
224     glMultMatrixf (&m[0][0]);
225     glScalef (40, 40, 40);
226     glTranslatef (-0.5, -0.5, -0.01);
227     if (! wire)
228       glDisable (GL_LIGHTING);
229     /* Draw into the depth buffer but not the frame buffer */
230     glColorMask (GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
231     glColor3f (0, 0, 0);
232     glBegin (GL_QUADS);
233     glVertex3f (0, 0, 0);
234     glVertex3f (0, 1, 0);
235     glVertex3f (1, 1, 0);
236     glVertex3f (1, 0, 0);
237     glEnd();
238     polys++;
239     glColorMask (GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
240     if (! wire)
241       glEnable (GL_LIGHTING);
242     glPopMatrix();
243     glColor3f (1, 1, 1);
244   }
245
246   /* Draw all the tiles.
247    */
248   for (t = bp->tiles; t; t = t->next)
249     {
250       GLfloat x = t->normal.x;
251       GLfloat y = t->normal.y;
252       GLfloat z = t->normal.z;
253       GLfloat s = t->size / 2;
254       glPushMatrix();
255
256       /* Move to location of tile. */
257       glTranslatef (t->position.x, t->position.y, t->position.z);
258
259       /* Orient to direction tile is facing. */
260       glRotatef (-atan2 (x, y)               * (180 / M_PI), 0, 0, 1);
261       glRotatef ( atan2 (z, sqrt(x*x + y*y)) * (180 / M_PI), 1, 0, 0);
262
263       glRotatef (t->tilt, 0, 1, 0);
264
265       glScalef (s, s, s);
266       glNormal3f (0, 1, 0);
267       glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
268       glVertex3f (-1, 0, -1);
269       glVertex3f ( 1, 0, -1);
270       glVertex3f ( 1, 0,  1);
271       glVertex3f (-1, 0,  1);
272       glEnd();
273       polys++;
274
275       if (! wire)
276         {
277           GLfloat d = 0.2;
278           glNormal3f (0, 0, -1);
279           glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
280           glVertex3f (-1,  0, -1);
281           glVertex3f (-1, -d, -1);
282           glVertex3f ( 1, -d, -1);
283           glVertex3f ( 1,  0, -1);
284           glEnd();
285           polys++;
286
287           glNormal3f (0, 0, 1);
288           glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
289           glVertex3f ( 1,  0,  1);
290           glVertex3f ( 1, -d,  1);
291           glVertex3f (-1, -d,  1);
292           glVertex3f (-1,  0,  1);
293           glEnd();
294           polys++;
295
296           glNormal3f (1, 0, 0);
297           glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
298           glVertex3f ( 1,  0, -1);
299           glVertex3f ( 1, -d, -1);
300           glVertex3f ( 1, -d,  1);
301           glVertex3f ( 1,  0,  1);
302           glEnd();
303           polys++;
304
305           glNormal3f (-1, 0, 0);
306           glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
307           glVertex3f (-1,  0,  1);
308           glVertex3f (-1, -d,  1);
309           glVertex3f (-1, -d, -1);
310           glVertex3f (-1,  0, -1);
311           glEnd();
312           polys++;
313         }
314
315       glPopMatrix();
316     }
317
318   /* Draw the front rays.
319    */
320   if (! wire)
321     {
322       glPushMatrix();
323       glLoadIdentity();
324       glMultMatrixf (&m[0][0]);
325       glTranslatef(0, 0, 4.1);
326       glRotatef (-bp->th, 0, 0, 1);
327       polys += draw_rays (mi);
328       glPopMatrix();
329     }
330
331   return polys;
332 }
333
334
335 static GLfloat
336 vector_angle (XYZ a, XYZ b)
337 {
338   double La = sqrt (a.x*a.x + a.y*a.y + a.z*a.z);
339   double Lb = sqrt (b.x*b.x + b.y*b.y + b.z*b.z);
340   double cc, angle;
341
342   if (La == 0 || Lb == 0) return 0;
343   if (a.x == b.x && a.y == b.y && a.z == b.z) return 0;
344
345   /* dot product of two vectors is defined as:
346        La * Lb * cos(angle between vectors)
347      and is also defined as:
348        ax*bx + ay*by + az*bz
349      so:
350       La * Lb * cos(angle) = ax*bx + ay*by + az*bz
351       cos(angle)  = (ax*bx + ay*by + az*bz) / (La * Lb)
352       angle = acos ((ax*bx + ay*by + az*bz) / (La * Lb));
353   */
354   cc = (a.x*b.x + a.y*b.y + a.z*b.z) / (La * Lb);
355   if (cc > 1) cc = 1;  /* avoid fp rounding error (1.000001 => sqrt error) */
356   angle = acos (cc);
357
358   return (angle);
359 }
360
361
362 #undef RANDSIGN
363 #define RANDSIGN() ((random() & 1) ? 1 : -1)
364
365 #undef BELLRAND
366 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
367
368 static void
369 build_ball (ModeInfo *mi)
370 {
371   ball_configuration *bp = &bps[MI_SCREEN(mi)];
372   int rows = MI_COUNT (mi);
373
374   GLfloat tile_size = M_PI / rows;
375   GLfloat th0, th1;
376
377   struct { XYZ position; GLfloat strength; } dents[5];
378   int dent_count = random() % countof(dents);
379   int i;
380   for (i = 0; i < dent_count; i++)
381     {
382       GLfloat dist;
383       dents[i].position.x = RANDSIGN() * (2 - BELLRAND(0.2));
384       dents[i].position.y = RANDSIGN() * (2 - BELLRAND(0.2));
385       dents[i].position.z = RANDSIGN() * (2 - BELLRAND(0.2));
386       dist = sqrt (dents[i].position.x * dents[i].position.x +
387                    dents[i].position.y * dents[i].position.y +
388                    dents[i].position.z * dents[i].position.z);
389       dents[i].strength = dist - (1 - BELLRAND(0.3));
390       dents[i].strength = dist - (1 - BELLRAND(0.3));
391     }
392
393
394   for (th1 = M_PI/2; th1 > -(M_PI/2 + tile_size/2); th1 -= tile_size)
395     {
396       GLfloat x  = cos (th1);
397       GLfloat y  = sin (th1);
398       GLfloat x0 = cos (th1 - tile_size/2);
399       GLfloat x1 = cos (th1 + tile_size/2);
400       GLfloat circ0 = M_PI * x0 * 2;
401       GLfloat circ1 = M_PI * x1 * 2;
402       GLfloat circ  = (circ0 < circ1 ? circ0 : circ1);
403       int row_tiles = floor ((circ < 0 ? 0 : circ) / tile_size);
404       GLfloat spacing;
405       GLfloat dropsy = 0.13 + frand(0.04);
406
407       if (row_tiles <= 0) row_tiles = 1;
408       spacing = M_PI*2 / row_tiles;
409
410       for (th0 = 0; th0 < M_PI*2; th0 += spacing)
411         {
412           tile *t = (tile *) calloc (1, sizeof(*t));
413           t->size = tile_size * 0.85;
414           t->position.x = cos (th0) * x;
415           t->position.y = sin (th0) * x;
416           t->position.z = y;
417
418           t->normal = t->position;
419
420           /* Apply pressure on position from the dents. */
421           for (i = 0; i < dent_count; i++)
422             {
423               GLfloat dist;
424               XYZ direction;
425
426               if (! (random() % 150))   /* Drop tiles randomly */
427                 {
428                   free (t);
429                   goto SKIP;
430                 }
431
432               direction.x = t->position.x - dents[i].position.x;
433               direction.y = t->position.y - dents[i].position.y;
434               direction.z = t->position.z - dents[i].position.z;
435               dist = sqrt (direction.x * direction.x +
436                            direction.y * direction.y +
437                            direction.z * direction.z);
438               if (dist < dents[i].strength)
439                 {
440                   GLfloat s = 1 - (dents[i].strength - dist) * 0.66;
441                   XYZ n2 = t->normal;
442                   GLfloat angle = vector_angle (t->position, dents[i].position);
443
444                   /* Drop out the tiles near the apex of the dent. */
445                   if (angle < dropsy)
446                     {
447                       free (t);
448                       goto SKIP;
449                     }
450
451                   t->position.x *= s;
452                   t->position.y *= s;
453                   t->position.z *= s;
454
455                   direction = normalize (direction);
456                   n2.x -= direction.x;
457                   n2.y -= direction.y;
458                   n2.z -= direction.z;
459
460                   t->normal.x = (t->normal.x + n2.x) / 2;
461                   t->normal.y = (t->normal.y + n2.y) / 2;
462                   t->normal.z = (t->normal.z + n2.z) / 2;
463                 }
464             }
465
466           /* Skew the direction the tile is facing slightly. */
467           t->normal.x += 0.12 - frand(0.06);
468           t->normal.y += 0.12 - frand(0.06);
469           t->normal.z += 0.12 - frand(0.06);
470           t->tilt = 4 - BELLRAND(8);
471
472           t->next = bp->tiles;
473           bp->tiles = t;
474         SKIP: ;
475         }
476     }
477
478   bp->nrays = 5 + BELLRAND(10);
479   bp->rays = (ray *) calloc (bp->nrays, sizeof(*bp->rays));
480   for (i = 0; i < bp->nrays; i++)
481     {
482       GLfloat th = frand(M_PI * 2);
483       bp->rays[i].normal.x = cos (th);
484       bp->rays[i].normal.y = sin (th);
485       bp->rays[i].normal.z = 1;
486       bp->rays[i].normal = normalize (bp->rays[i].normal);
487       bp->rays[i].color[0] = 0.9 + frand(0.1);
488       bp->rays[i].color[1] = 0.6 + frand(0.4);
489       bp->rays[i].color[2] = 0.6 + frand(0.2);
490       bp->rays[i].color[3] = 1;
491     }
492 }
493
494
495 /* Window management, etc
496  */
497 ENTRYPOINT void
498 reshape_ball (ModeInfo *mi, int width, int height)
499 {
500   GLfloat h = (GLfloat) height / (GLfloat) width;
501
502   glViewport (0, 0, (GLint) width, (GLint) height);
503
504   glMatrixMode(GL_PROJECTION);
505   glLoadIdentity();
506   gluPerspective (30.0, 1/h, 1.0, 100.0);
507
508   glMatrixMode(GL_MODELVIEW);
509   glLoadIdentity();
510   gluLookAt( 0.0, 0.0, 30.0,
511              0.0, 0.0, 0.0,
512              0.0, 1.0, 0.0);
513
514 # ifdef HAVE_MOBILE     /* Keep it the same relative size when rotated. */
515   {
516     int o = (int) current_device_rotation();
517     if (o != 0 && o != 180 && o != -180)
518       glScalef (1/h, 1/h, 1/h);
519   }
520 # endif
521
522   glClear(GL_COLOR_BUFFER_BIT);
523 }
524
525
526 ENTRYPOINT Bool
527 ball_handle_event (ModeInfo *mi, XEvent *event)
528 {
529   ball_configuration *bp = &bps[MI_SCREEN(mi)];
530
531   if (gltrackball_event_handler (event, bp->trackball,
532                                  MI_WIDTH (mi), MI_HEIGHT (mi),
533                                  &bp->button_down_p))
534     return True;
535
536   return False;
537 }
538
539
540 ENTRYPOINT void 
541 init_ball (ModeInfo *mi)
542 {
543   ball_configuration *bp;
544   int wire = MI_IS_WIREFRAME(mi);
545
546   if (!bps) {
547     bps = (ball_configuration *)
548       calloc (MI_NUM_SCREENS(mi), sizeof (ball_configuration));
549     if (!bps) {
550       fprintf(stderr, "%s: out of memory\n", progname);
551       exit(1);
552     }
553   }
554
555   bp = &bps[MI_SCREEN(mi)];
556
557   bp->glx_context = init_GL(mi);
558
559   if (! wire)
560     build_texture (mi);
561
562   reshape_ball (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
563
564   bp->th = 180 - frand(360);
565
566   if (MI_COUNT(mi) < 10)
567     MI_COUNT(mi) = 10;
568   if (MI_COUNT(mi) > 200)
569     MI_COUNT(mi) = 200;
570
571   {
572     double spin_speed   = 0.1;
573     double wander_speed = 0.003;
574     double spin_accel   = 1;
575
576     bp->rot = make_rotator (do_spin ? spin_speed : 0,
577                             do_spin ? spin_speed : 0,
578                             do_spin ? spin_speed : 0,
579                             spin_accel,
580                             do_wander ? wander_speed : 0,
581                             False);
582     bp->trackball = gltrackball_init (True);
583   }
584
585   build_ball (mi);
586
587   if (!wire)
588     {
589       GLfloat color[4] = {0.5, 0.5, 0.5, 1};
590       GLfloat cspec[4] = {1, 1, 1, 1};
591       static const GLfloat shiny = 10;
592
593       static GLfloat pos0[4] = { 0.5, -1, -0.5, 0};
594       static GLfloat pos1[4] = {-0.75, -1, 0, 0};
595       static GLfloat amb[4] = {0, 0, 0, 1};
596       static GLfloat dif[4] = {1, 1, 1, 1};
597       static GLfloat spc[4] = {1, 1, 1, 1};
598
599       glEnable(GL_LIGHTING);
600       glEnable(GL_LIGHT0);
601       glEnable(GL_LIGHT1);
602       glEnable(GL_DEPTH_TEST);
603       glEnable(GL_CULL_FACE);
604
605       color[0] += frand(0.2);
606       color[1] += frand(0.2);
607       color[2] += frand(0.2);
608
609       cspec[0] -= frand(0.2);
610       cspec[1] -= frand(0.2);
611       cspec[2] -= frand(0.2);
612
613       glLightfv(GL_LIGHT0, GL_POSITION, pos0);
614       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
615       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
616       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
617
618       glLightfv(GL_LIGHT1, GL_POSITION, pos1);
619       glLightfv(GL_LIGHT1, GL_AMBIENT,  amb);
620       glLightfv(GL_LIGHT1, GL_DIFFUSE,  dif);
621       glLightfv(GL_LIGHT1, GL_SPECULAR, spc);
622
623       glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
624       glMaterialfv (GL_FRONT, GL_SPECULAR,  cspec);
625       glMateriali  (GL_FRONT, GL_SHININESS, shiny);
626     }
627 }
628
629
630 ENTRYPOINT void
631 draw_ball (ModeInfo *mi)
632 {
633   ball_configuration *bp = &bps[MI_SCREEN(mi)];
634   Display *dpy = MI_DISPLAY(mi);
635   Window window = MI_WINDOW(mi);
636
637   if (!bp->glx_context)
638     return;
639
640   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
641
642   glShadeModel(GL_SMOOTH);
643
644   glEnable(GL_DEPTH_TEST);
645   glEnable(GL_NORMALIZE);
646   glEnable(GL_CULL_FACE);
647
648   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
649
650   glPushMatrix ();
651
652   glRotatef(current_device_rotation(), 0, 0, 1);
653
654   {
655     double x, y, z;
656     get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
657     glTranslatef((x - 0.5) * 6,
658                  (y - 0.5) * 6,
659                  (z - 0.5) * 2);
660
661     gltrackball_rotate (bp->trackball);
662
663     get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
664     glRotatef (x * 360, 1.0, 0.0, 0.0);
665     glRotatef (y * 360, 0.0, 1.0, 0.0);
666     glRotatef (z * 360, 0.0, 0.0, 1.0);
667   }
668
669   mi->polygon_count = 0;
670
671   glRotatef (50, 1, 0, 0);
672
673   glScalef (4, 4, 4);
674   glRotatef (bp->th, 0, 0, 1);
675   if (! bp->button_down_p)
676     {
677       bp->th += (bp->th > 0 ? speed : -speed);
678       while (bp->th >  360) bp->th -= 360;
679       while (bp->th < -360) bp->th += 360;
680     }
681
682   mi->polygon_count += draw_ball_1 (mi);
683   glPopMatrix ();
684
685   if (mi->fps_p) do_fps (mi);
686   glFinish();
687
688   glXSwapBuffers(dpy, window);
689 }
690
691
692 ENTRYPOINT void
693 release_ball (ModeInfo *mi)
694 {
695   ball_configuration *bp = &bps[MI_SCREEN(mi)];
696   while (bp->tiles)
697     {
698       tile *t = bp->tiles->next;
699       free (bp->tiles);
700       bp->tiles = t;
701     }
702   free (bp->rays);
703 }
704
705 XSCREENSAVER_MODULE_2 ("Discoball", discoball, ball)
706
707 #endif /* USE_GL */