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