http://www.jwz.org/xscreensaver/xscreensaver-5.13.tar.gz
[xscreensaver] / hacks / glx / boing.c
1 /* boing, Copyright (c) 2005-2008 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  * A clone of the Amiga 1000 "Boing" demo.  This was the first graphics demo
12  * for the Amiga, written by Dale Luck and RJ Mical during a break at the 1984
13  * Consumer Electronics Show (or so the legend goes.)  The boing ball was
14  * briefly the official logo of Amiga Inc., until they were bought by
15  * Commodore later that year.
16  *
17  * With no arguments, this program looks a lot like the original Amiga demo.
18  * With "-smooth -lighting", it looks... less old.
19  *
20  * The amiga version made noise when the ball hit the walls.  This version
21  * does not, obviously.
22  */
23
24 #define DEFAULTS        "*delay:        30000            \n" \
25                         "*showFPS:      False            \n" \
26                         "*wireframe:    False            \n" \
27
28 # define refresh_boing 0
29 # define release_boing 0
30 #undef countof
31 #define countof(x) (sizeof((x))/sizeof((*x)))
32
33 #include "xlockmore.h"
34 #include "gltrackball.h"
35 #include <ctype.h>
36
37 #ifdef USE_GL /* whole file */
38
39
40 #define DEF_SPIN        "True"
41 #define DEF_LIGHTING    "False"
42 #define DEF_SMOOTH      "False"
43 #define DEF_SCANLINES   "True"
44 #define DEF_SPEED       "1.0"
45 #define DEF_BALL_SIZE   "0.5"
46 #define DEF_ANGLE       "15"
47 #define DEF_MERIDIANS   "16"
48 #define DEF_PARALLELS   "8"
49 #define DEF_TILES       "12"
50 #define DEF_THICKNESS   "0.05"
51
52 #define DEF_BALL_COLOR1  "#CC1919"
53 #define DEF_BALL_COLOR2  "#F2F2F2"
54 #define DEF_GRID_COLOR   "#991999"
55 #define DEF_SHADOW_COLOR "#303030"
56 #define DEF_BACKGROUND   "#8C8C8C"
57
58 typedef struct { GLfloat x, y, z; } XYZ;
59
60 typedef struct {
61   GLXContext *glx_context;
62   trackball_state *trackball;
63   Bool button_down_p;
64
65   GLuint ball_list;
66   double ball_x,   ball_y,   ball_z,   ball_th;
67   double ball_dx,  ball_dy,  ball_dz,  ball_dth;
68   double ball_ddx, ball_ddy, ball_ddz;
69
70   GLfloat ball_color1[4], ball_color2[4], grid_color[4];
71   GLfloat bg_color[4], shadow_color[4];
72   GLfloat lightpos[4];
73
74 } boing_configuration;
75
76 static boing_configuration *bps = NULL;
77
78 static Bool spin;
79 static Bool lighting_p;
80 static Bool smooth_p;
81 static Bool scanlines_p;
82 static GLfloat speed;
83 static int angle;
84 static GLfloat ball_size;
85 static unsigned int meridians;
86 static unsigned int parallels;
87 static unsigned int tiles;
88 static GLfloat thickness;
89 static char *ball_color1_str, *ball_color2_str, *grid_color_str,
90   *shadow_str, *bg_str;
91
92 static XrmOptionDescRec opts[] = {
93   { "-spin",       ".spin",      XrmoptionNoArg,  "True"  },
94   { "+spin",       ".spin",      XrmoptionNoArg,  "False" },
95   { "-lighting",   ".lighting",  XrmoptionNoArg,  "True"  },
96   { "+lighting",   ".lighting",  XrmoptionNoArg,  "False" },
97   { "-smooth",     ".smooth",    XrmoptionNoArg,  "True"  },
98   { "+smooth",     ".smooth",    XrmoptionNoArg,  "False" },
99   { "-scanlines",  ".scanlines", XrmoptionNoArg,  "True"  },
100   { "+scanlines",  ".scanlines", XrmoptionNoArg,  "False" },
101   { "-speed",      ".speed",     XrmoptionSepArg, 0 },
102   { "-angle",      ".angle",     XrmoptionSepArg, 0 },
103   { "-size",       ".ballSize",  XrmoptionSepArg, 0 },
104   { "-meridians",  ".meridians", XrmoptionSepArg, 0 },
105   { "-parallels",  ".parallels", XrmoptionSepArg, 0 },
106   { "-tiles",      ".tiles",     XrmoptionSepArg, 0 },
107   { "-thickness",  ".thickness", XrmoptionSepArg, 0 },
108   { "-ball-color1",".ballColor1",XrmoptionSepArg, 0 },
109   { "-ball-color2",".ballColor2",XrmoptionSepArg, 0 },
110   { "-grid-color", ".gridColor", XrmoptionSepArg, 0 },
111   { "-shadow-color",".shadowColor",XrmoptionSepArg, 0 },
112   { "-background",  ".boingBackground",XrmoptionSepArg, 0 },
113   { "-bg",          ".boingBackground",XrmoptionSepArg, 0 },
114 };
115
116 static argtype vars[] = {
117   {&spin,      "spin",      "Spin",       DEF_SPIN,      t_Bool},
118   {&lighting_p,"lighting",  "Lighting",   DEF_LIGHTING,  t_Bool},
119   {&smooth_p,  "smooth",    "Smooth",     DEF_SMOOTH,    t_Bool},
120   {&scanlines_p,"scanlines","Scanlines",  DEF_SCANLINES, t_Bool},
121   {&speed,     "speed",     "Speed",      DEF_SPEED,     t_Float},
122   {&angle,     "angle",     "Angle",      DEF_ANGLE,     t_Int},
123   {&ball_size, "ballSize",  "BallSize",   DEF_BALL_SIZE, t_Float},
124   {&meridians, "meridians", "meridians",  DEF_MERIDIANS, t_Int},
125   {&parallels, "parallels", "parallels",  DEF_PARALLELS, t_Int},
126   {&tiles,     "tiles",     "Tiles",      DEF_TILES,     t_Int},
127   {&thickness, "thickness", "Thickness",  DEF_THICKNESS, t_Float},
128   {&ball_color1_str, "ballColor1", "BallColor1", DEF_BALL_COLOR1, t_String},
129   {&ball_color2_str, "ballColor2", "BallColor2", DEF_BALL_COLOR2, t_String},
130   {&grid_color_str,  "gridColor",  "GridColor",  DEF_GRID_COLOR,  t_String},
131   {&shadow_str,      "shadowColor","ShadowColor",DEF_SHADOW_COLOR,t_String},
132   /* dammit, -background is too magic... */
133   {&bg_str,        "boingBackground", "Background", DEF_BACKGROUND, t_String},
134 };
135
136 ENTRYPOINT ModeSpecOpt boing_opts = {countof(opts), opts, countof(vars), vars, NULL};
137
138 static void
139 parse_color (ModeInfo *mi, const char *name, const char *s, GLfloat *a)
140 {
141   XColor c;
142   a[3] = 1.0;  /* alpha */
143
144   if (! XParseColor (MI_DISPLAY(mi), MI_COLORMAP(mi), s, &c))
145     {
146       fprintf (stderr, "%s: can't parse %s color %s", progname, name, s);
147       exit (1);
148     }
149   a[0] = c.red   / 65536.0;
150   a[1] = c.green / 65536.0;
151   a[2] = c.blue  / 65536.0;
152 }
153
154
155 static void
156 draw_grid (ModeInfo *mi)
157 {
158   boing_configuration *bp = &bps[MI_SCREEN(mi)];
159   int x, y;
160   GLfloat t2  = (GLfloat) tiles / 2;
161   GLfloat s = 1.0 / (tiles + thickness);
162   GLfloat z = 0;
163
164   GLfloat lw = MI_HEIGHT(mi) * 0.06 * thickness;
165
166   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, bp->grid_color);
167   glColor3fv (bp->grid_color);
168
169   glPushMatrix();
170   glScalef(s, s, s);
171   glTranslatef (-t2, -t2, 0);
172
173   glLineWidth (lw);
174   glBegin (GL_LINES);
175   for (y = 0; y <= tiles; y++)
176     {
177       glVertex3f (0,     y, z);
178       glVertex3f (tiles, y, z);
179       /*mi->polygon_count++;*/
180     }
181   for (x = 0; x <= tiles; x++)
182     {
183       glVertex3f (x, tiles, z);
184       glVertex3f (x, 0,     z);
185       /*mi->polygon_count++;*/
186     }
187
188   glEnd();
189   glPopMatrix();
190 }
191
192
193 static void
194 draw_box (ModeInfo *mi)
195 {
196   /* boing_configuration *bp = &bps[MI_SCREEN(mi)]; */
197   glPushMatrix();
198   glTranslatef (0, 0, -0.5);
199 /*  glFrontFace (GL_CCW);*/
200   draw_grid (mi);
201   glPopMatrix();
202
203   glPushMatrix();
204   glRotatef (90, 1, 0, 0);
205   glTranslatef (0, 0, 0.5);
206 /*  glFrontFace (GL_CW);*/
207   draw_grid (mi);
208   glPopMatrix();
209 }
210
211
212 static void
213 draw_ball (ModeInfo *mi)
214 {
215   boing_configuration *bp = &bps[MI_SCREEN(mi)];
216   int wire = MI_IS_WIREFRAME(mi);
217   int x, y;
218   int xx = meridians;
219   int yy = parallels;
220   int scale = (smooth_p ? 5 : 1);
221
222   if (lighting_p && !wire)
223     glEnable (GL_LIGHTING);
224
225   if (parallels < 3)
226     scale *= 2;
227
228   xx *= scale;
229   yy *= scale;
230
231   glFrontFace (GL_CW);
232
233   glPushMatrix();
234   glTranslatef (bp->ball_x, bp->ball_y, bp->ball_z);
235   glScalef (ball_size, ball_size, ball_size);
236   glRotatef (-angle,      0, 0, 1);
237   glRotatef (bp->ball_th, 0, 1, 0);
238
239   for (y = 0; y < yy; y++)
240     {
241       GLfloat thy0 = y     * (M_PI * 2) / (yy * 2) + M_PI_2;
242       GLfloat thy1 = (y+1) * (M_PI * 2) / (yy * 2) + M_PI_2;
243
244       for (x = 0; x < xx; x++)
245         {
246           GLfloat thx0 = x     * (M_PI * 2) / xx;
247           GLfloat thx1 = (x+1) * (M_PI * 2) / xx;
248           XYZ p;
249           Bool bgp = ((x/scale) & 1) ^ ((y/scale) & 1);
250
251           if (wire && bgp) continue;
252
253           glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE,
254                         (bgp ? bp->ball_color2 : bp->ball_color1));
255           glColor3fv (bgp ? bp->ball_color2 : bp->ball_color1);
256
257           glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
258
259           if (!smooth_p)
260             {
261               p.x = cos((thy0+thy1)/2) * cos((thx0+thx1)/2);
262               p.y = sin((thy0+thy1)/2);
263               p.z = cos((thy0+thy1)/2) * sin((thx0+thx1)/2);
264               glNormal3f (-p.x, -p.y, -p.z);
265             }
266
267           p.x = cos(thy0) * cos(thx0) / 2;
268           p.y = sin(thy0)             / 2;
269           p.z = cos(thy0) * sin(thx0) / 2;
270           if (smooth_p)
271             glNormal3f (-p.x, -p.y, -p.z);
272           glVertex3f (p.x, p.y, p.z);
273
274           p.x = cos(thy1) * cos(thx0) / 2;
275           p.y = sin(thy1)             / 2;
276           p.z = cos(thy1) * sin(thx0) / 2;
277           if (smooth_p)
278             glNormal3f (-p.x, -p.y, -p.z);
279           glVertex3f (p.x, p.y, p.z);
280
281           p.x = cos(thy1) * cos(thx1) / 2;
282           p.y = sin(thy1)             / 2;
283           p.z = cos(thy1) * sin(thx1) / 2;
284           if (smooth_p)
285             glNormal3f (-p.x, -p.y, -p.z);
286           glVertex3f (p.x, p.y, p.z);
287
288           p.x = cos(thy0) * cos(thx1) / 2;
289           p.y = sin(thy0)             / 2;
290           p.z = cos(thy0) * sin(thx1) / 2;
291           if (smooth_p)
292             glNormal3f (-p.x, -p.y, -p.z);
293           glVertex3f (p.x, p.y, p.z);
294
295           glEnd ();
296           mi->polygon_count++;
297         }
298     }
299
300   glPopMatrix();
301
302   if (lighting_p && !wire)
303     glDisable(GL_LIGHTING);
304 }
305
306
307 static void
308 draw_shadow (ModeInfo *mi)
309 {
310   boing_configuration *bp = &bps[MI_SCREEN(mi)];
311   int wire = MI_IS_WIREFRAME(mi);
312   GLfloat xoff = 0.14;
313   GLfloat yoff = 0.07;
314   int y;
315   int yy = parallels;
316   int scale = (smooth_p ? 5 : 1);
317
318   if (lighting_p && !wire)
319     glEnable (GL_BLEND);
320
321   if (parallels < 3)
322     scale *= 2;
323
324   yy *= scale;
325
326   glPushMatrix();
327   glTranslatef (bp->ball_x + xoff, bp->ball_y + yoff, -0.49);
328   glScalef (ball_size, ball_size, ball_size);
329   glRotatef (-angle, 0, 0, 1);
330
331   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, bp->shadow_color);
332   glColor4fv (bp->shadow_color);
333
334   glFrontFace (GL_CCW);
335   glNormal3f (0, 0, 1);
336   glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLE_FAN);
337   if (!wire) glVertex3f (0, 0, 0);
338
339   for (y = 0; y < yy*2+1; y++)
340     {
341       GLfloat thy0 = y * (M_PI * 2) / (yy * 2) + M_PI_2;
342       glVertex3f (cos(thy0) / 2, sin(thy0) / 2, 0);
343       mi->polygon_count++;
344     }
345
346   glEnd ();
347
348   glPopMatrix();
349
350   if (lighting_p && !wire)
351     glDisable (GL_BLEND);
352 }
353
354
355 static void
356 draw_scanlines (ModeInfo *mi)
357 {
358   /* boing_configuration *bp = &bps[MI_SCREEN(mi)]; */
359   int wire = MI_IS_WIREFRAME(mi);
360   int w = MI_WIDTH(mi);
361   int h = MI_HEIGHT(mi);
362
363   if (h <= 300) return;
364
365   if (!wire)
366     {
367       glEnable (GL_BLEND);
368       glDisable (GL_DEPTH_TEST);
369     }
370
371   glMatrixMode(GL_PROJECTION);
372   glPushMatrix();
373   {
374     glLoadIdentity();
375     glMatrixMode(GL_MODELVIEW);
376     glPushMatrix();
377     {
378       int lh, ls;
379       int y;
380       glLoadIdentity();
381       gluOrtho2D (0, w, 0, h);
382
383       if      (h > 500) lh = 4, ls = 4;
384       else if (h > 300) lh = 2, ls = 1;
385       else              lh = 1, ls = 1;
386
387       if (lh == 1)
388         glDisable (GL_BLEND);
389
390       glLineWidth (lh);
391       glColor4f (0, 0, 0, 0.3);
392
393       glBegin(GL_LINES);
394       for (y = 0; y < h; y += lh + ls)
395         {
396           glVertex3f (0, y, 0);
397           glVertex3f (w, y, 0);
398         }
399       glEnd();
400     }
401     glPopMatrix();
402   }
403   glMatrixMode(GL_PROJECTION);
404   glPopMatrix();
405   glMatrixMode(GL_MODELVIEW);
406
407   if (!wire)
408     {
409       glDisable (GL_BLEND);
410       glEnable (GL_DEPTH_TEST);
411     }
412 }
413
414
415
416 static void
417 tick_physics (ModeInfo *mi)
418 {
419   boing_configuration *bp = &bps[MI_SCREEN(mi)];
420   GLfloat s2 = ball_size / 2;
421   GLfloat max = 0.5 - s2;
422   GLfloat min = -max;
423
424   bp->ball_th += bp->ball_dth;
425   while (bp->ball_th > 360) bp->ball_th -= 360;
426   while (bp->ball_th < 0)   bp->ball_th += 360;
427
428   bp->ball_dx += bp->ball_ddx;
429   bp->ball_x  += bp->ball_dx;
430   if      (bp->ball_x < min) bp->ball_x = min, bp->ball_dx = -bp->ball_dx,
431     bp->ball_dth = -bp->ball_dth,
432     bp->ball_dx += (frand(speed/2) - speed);
433   else if (bp->ball_x > max) bp->ball_x = max, bp->ball_dx = -bp->ball_dx,
434     bp->ball_dth = -bp->ball_dth,
435     bp->ball_dx += (frand(speed/2) - speed);
436
437   bp->ball_dy += bp->ball_ddy;
438   bp->ball_y  += bp->ball_dy;
439   if      (bp->ball_y < min) bp->ball_y = min, bp->ball_dy = -bp->ball_dy;
440   else if (bp->ball_y > max) bp->ball_y = max, bp->ball_dy = -bp->ball_dy;
441
442   bp->ball_dz += bp->ball_ddz;
443   bp->ball_z  += bp->ball_dz;
444   if      (bp->ball_z < min) bp->ball_z = min, bp->ball_dz = -bp->ball_dz;
445   else if (bp->ball_z > max) bp->ball_z = max, bp->ball_dz = -bp->ball_dz;
446 }
447
448
449
450 /* Window management, etc
451  */
452 ENTRYPOINT void
453 reshape_boing (ModeInfo *mi, int width, int height)
454 {
455   GLfloat h = (GLfloat) height / (GLfloat) width;
456
457   h *= 4.0 / 3.0;   /* Back in the caveman days we couldn't even afford
458                        square pixels! */
459
460   glViewport (0, 0, (GLint) width, (GLint) height);
461
462   glMatrixMode(GL_PROJECTION);
463   glLoadIdentity();
464   gluPerspective (8.0, 1/h, 1.0, 10.0);
465
466   glMatrixMode(GL_MODELVIEW);
467   glLoadIdentity();
468   gluLookAt (0.0, 0.0, 8.0,
469              0.0, 0.0, 0.0,
470              0.0, 1.0, 0.0);
471
472   glClear(GL_COLOR_BUFFER_BIT);
473 }
474
475
476 ENTRYPOINT Bool
477 boing_handle_event (ModeInfo *mi, XEvent *event)
478 {
479   boing_configuration *bp = &bps[MI_SCREEN(mi)];
480
481   if (event->xany.type == ButtonPress &&
482       event->xbutton.button == Button1)
483     {
484       bp->button_down_p = True;
485       gltrackball_start (bp->trackball,
486                          event->xbutton.x, event->xbutton.y,
487                          MI_WIDTH (mi), MI_HEIGHT (mi));
488       return True;
489     }
490   else if (event->xany.type == ButtonRelease &&
491            event->xbutton.button == Button1)
492     {
493       bp->button_down_p = False;
494       return True;
495     }
496   else if (event->xany.type == ButtonPress &&
497            (event->xbutton.button == Button4 ||
498             event->xbutton.button == Button5 ||
499             event->xbutton.button == Button6 ||
500             event->xbutton.button == Button7))
501     {
502       gltrackball_mousewheel (bp->trackball, event->xbutton.button, 10,
503                               !!event->xbutton.state);
504       return True;
505     }
506   else if (event->xany.type == MotionNotify &&
507            bp->button_down_p)
508     {
509       gltrackball_track (bp->trackball,
510                          event->xmotion.x, event->xmotion.y,
511                          MI_WIDTH (mi), MI_HEIGHT (mi));
512       return True;
513     }
514
515   return False;
516 }
517
518
519 ENTRYPOINT void 
520 init_boing (ModeInfo *mi)
521 {
522   boing_configuration *bp;
523   int wire = MI_IS_WIREFRAME(mi);
524
525   if (!bps) {
526     bps = (boing_configuration *)
527       calloc (MI_NUM_SCREENS(mi), sizeof (boing_configuration));
528     if (!bps) {
529       fprintf(stderr, "%s: out of memory\n", progname);
530       exit(1);
531     }
532   }
533
534   bp = &bps[MI_SCREEN(mi)];
535
536   bp->glx_context = init_GL(mi);
537
538   if (tiles < 1) tiles = 1;
539
540   if (smooth_p)
541     {
542       if (meridians < 1) meridians = 1;
543       if (parallels < 1) parallels = 1;
544     }
545   else
546     {
547       if (meridians < 3) meridians = 3;
548       if (parallels < 2) parallels = 2;
549     }
550
551   if (meridians > 1 && meridians & 1) meridians++;  /* odd numbers look bad */
552
553
554   if (thickness <= 0) thickness = 0.001;
555   else if (thickness > 1) thickness = 1;
556
557   reshape_boing (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
558
559   parse_color (mi, "ballColor1",  ball_color1_str,  bp->ball_color1);
560   parse_color (mi, "ballColor2",  ball_color2_str,  bp->ball_color2);
561   parse_color (mi, "gridColor",   grid_color_str,   bp->grid_color);
562   parse_color (mi, "shadowColor", shadow_str,       bp->shadow_color);
563   parse_color (mi, "background",  bg_str,           bp->bg_color);
564
565   bp->shadow_color[3] = 0.9;
566
567   glClearColor (bp->bg_color[0], bp->bg_color[1], bp->bg_color[2], 1);
568
569   if (!wire)
570     {
571       glEnable(GL_DEPTH_TEST);
572       glEnable(GL_CULL_FACE);
573     }
574
575   bp->lightpos[0] = 0.5;
576   bp->lightpos[1] = 0.5;
577   bp->lightpos[2] = -1;
578   bp->lightpos[3] = 0;
579
580   if (lighting_p && !wire)
581     {
582       GLfloat amb[4] = {0, 0, 0, 1};
583       GLfloat dif[4] = {1, 1, 1, 1};
584       GLfloat spc[4] = {1, 1, 1, 1};
585       glEnable(GL_LIGHT0);
586       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
587       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
588       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
589     }
590
591   glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
592
593   speed = speed / 800.0;
594
595   bp->ball_dth = (spin ? -speed * 7 * 360 : 0);
596
597   bp->ball_x   = 0.5 - ((ball_size/2) + frand(1-ball_size));
598   bp->ball_y   = 0.2;
599   bp->ball_dx  = speed * 6 + frand(speed);
600   bp->ball_ddy = -speed;
601
602   bp->ball_dz  = speed * 6 + frand(speed);
603
604   bp->trackball = gltrackball_init ();
605 }
606
607
608 ENTRYPOINT void
609 draw_boing (ModeInfo *mi)
610 {
611   boing_configuration *bp = &bps[MI_SCREEN(mi)];
612   Display *dpy = MI_DISPLAY(mi);
613   Window window = MI_WINDOW(mi);
614
615   if (!bp->glx_context)
616     return;
617
618   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
619
620   mi->polygon_count = 0;
621
622   glShadeModel(GL_SMOOTH);
623
624   glEnable(GL_NORMALIZE);
625
626   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
627
628   if (! bp->button_down_p)
629     tick_physics (mi);
630
631   glPushMatrix ();
632   gltrackball_rotate (bp->trackball);
633
634   glLightfv (GL_LIGHT0, GL_POSITION, bp->lightpos);
635
636   glDisable (GL_CULL_FACE);
637   glDisable (GL_DEPTH_TEST);
638
639   glEnable (GL_LINE_SMOOTH);
640   glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
641   glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
642   glEnable (GL_BLEND);
643
644   draw_box (mi);
645   draw_shadow (mi);
646
647   glEnable (GL_CULL_FACE);
648   glEnable (GL_DEPTH_TEST);
649
650   draw_ball (mi);
651   if (scanlines_p)
652     draw_scanlines (mi);
653
654   glPopMatrix ();
655
656   if (mi->fps_p) do_fps (mi);
657   glFinish();
658
659   glXSwapBuffers(dpy, window);
660 }
661
662 XSCREENSAVER_MODULE ("Boing", boing)
663
664 #endif /* USE_GL */