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