From http://www.jwz.org/xscreensaver/xscreensaver-5.37.tar.gz
[xscreensaver] / hacks / glx / raverhoop.c
1 /* raverhoop, 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  * Simulates an LED hula hoop in a dark room. Oontz oontz oontz.
12  */
13
14 #define DEFAULTS        "*delay:        20000       \n" \
15                         "*ncolors:      12          \n" \
16                         "*showFPS:      False       \n" \
17                         "*wireframe:    False       \n" \
18
19 # define refresh_hoop 0
20 # define release_hoop 0
21 #undef countof
22 #define countof(x) (sizeof((x))/sizeof((*x)))
23
24 #include "xlockmore.h"
25 #include "colors.h"
26 #include "rotator.h"
27 #include "gltrackball.h"
28 #include <ctype.h>
29
30 #ifdef USE_GL /* whole file */
31
32
33 #define DEF_SPIN        "False"
34 #define DEF_WANDER      "False"
35 #define DEF_LIGHTS      "200"
36 #define DEF_SPEED       "1.0"
37 #define DEF_LIGHT_SPEED "1.0"
38 #define DEF_SUSTAIN     "1.0"
39
40 typedef struct {
41   GLfloat x,y,z;
42 } XYZ;
43
44 typedef struct afterimage afterimage;
45 struct afterimage {
46   GLfloat color[4];
47   XYZ position;
48   afterimage *next;
49 };
50
51 typedef struct {
52   GLfloat color[4];
53   int duty_cycle[10];   /* off, on, off, on... values add to 100 */
54   GLfloat ratio;
55   Bool on;
56 } light;
57
58
59 typedef struct oscillator oscillator;
60 struct oscillator {
61   GLfloat ratio, from, to, speed, *var;
62   int remaining;
63   oscillator *next;
64 };
65
66
67 typedef struct {
68   GLXContext *glx_context;
69   rotator *rot;
70   trackball_state *trackball;
71   Bool button_down_p;
72
73   int nlights;
74   light *lights;
75   GLfloat radius;
76   GLfloat axial_radius;
77   XYZ midpoint;
78   GLfloat tilt;
79   GLfloat spin;
80   GLfloat th;
81   GLfloat speed;
82   afterimage *trail;
83   oscillator *oscillators;
84
85 } hoop_configuration;
86
87 static hoop_configuration *bps = NULL;
88
89 static Bool do_spin;
90 static Bool do_wander;
91 static int nlights;
92 static GLfloat speed, light_speed, sustain;
93
94 static XrmOptionDescRec opts[] = {
95   { "-spin",   ".spin",   XrmoptionNoArg, "True" },
96   { "+spin",   ".spin",   XrmoptionNoArg, "False" },
97   { "-wander", ".wander", XrmoptionNoArg, "True" },
98   { "+wander", ".wander", XrmoptionNoArg, "False" },
99   { "-lights", ".lights", XrmoptionSepArg, 0 },
100   { "-speed",  ".speed",  XrmoptionSepArg, 0 },
101   { "-light-speed", ".lightSpeed",  XrmoptionSepArg, 0 },
102   { "-sustain", ".sustain", XrmoptionSepArg, 0 },
103 };
104
105 static argtype vars[] = {
106   {&do_spin,   "spin",   "Spin",   DEF_SPIN,   t_Bool},
107   {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
108   {&nlights,   "lights", "Lights", DEF_LIGHTS, t_Int},
109   {&speed,     "speed",  "Speed",  DEF_SPEED,  t_Float},
110   {&light_speed, "lightSpeed", "Speed", DEF_LIGHT_SPEED, t_Float},
111   {&sustain,   "sustain", "Sustain",  DEF_SUSTAIN, t_Float},
112 };
113
114 ENTRYPOINT ModeSpecOpt hoop_opts = {countof(opts), opts, countof(vars), vars, NULL};
115
116
117 static void
118 decay_afterimages (ModeInfo *mi)
119 {
120   hoop_configuration *bp = &bps[MI_SCREEN(mi)];
121   afterimage *prev = 0;
122   afterimage *a = bp->trail;
123   GLfloat tick = 0.05 / sustain;
124
125   while (a)
126     {
127       afterimage *next = a->next;
128       a->color[3] -= tick;
129       if (a->color[3] < 0)
130         {
131           if (prev)
132             prev->next = next;
133           else
134             bp->trail = next;
135           free (a);
136         }
137       else
138         prev = a;
139       a = next;
140     }
141 }
142
143 static void
144 add_afterimage (ModeInfo *mi, GLfloat x, GLfloat y, GLfloat z,
145                 GLfloat color[4])
146 {
147   hoop_configuration *bp = &bps[MI_SCREEN(mi)];
148   afterimage *a = (afterimage *) calloc (1, sizeof (*a));
149   afterimage *b;
150   a->position.x = x;
151   a->position.y = y;
152   a->position.z = z;
153   memcpy (a->color, color, sizeof(a->color));
154
155   /* Put it at the end so that the older, dimmer ones are laid down on
156      the frame buffer before the newer, brighter ones. */
157   if (!bp->trail)
158     bp->trail = a;
159   else
160     {
161       for (b = bp->trail; b->next; b = b->next)
162         ;
163       b->next = a;
164     }
165 }
166
167
168 static void
169 tick_light (light *L)
170 {
171   int i;
172   int n = 0;
173
174   L->ratio += 0.05 * light_speed;
175   while (L->ratio > 1)
176     L->ratio -= 1;
177
178   for (i = 0; i < countof(L->duty_cycle); i++)
179     {
180       n += L->duty_cycle[i];
181       if (n > 100) abort();
182       if (n / 100.0 > L->ratio)
183         {
184           L->on = (i & 1);
185           break;
186         }
187     }
188 }
189
190
191
192 static void
193 tick_hoop (ModeInfo *mi)
194 {
195   hoop_configuration *bp = &bps[MI_SCREEN(mi)];
196   GLfloat m0[16];
197   int i;
198
199   glGetFloatv (GL_MODELVIEW_MATRIX, m0);
200
201   for (i = 0; i < bp->nlights; i++)
202     {
203       light *L = &bp->lights[i];
204       GLfloat m1[16];
205       GLfloat th = M_PI * 2 * i / bp->nlights;
206       GLfloat x = cos (th);
207       GLfloat y = sin (th);
208       GLfloat z;
209       
210       tick_light (L);
211       if (! L->on)
212         continue;
213
214       glPushMatrix();
215
216       glTranslatef (bp->midpoint.x, bp->midpoint.y, bp->midpoint.z);
217       glRotatef (bp->th * 180 / M_PI, 0, 0, 1);
218       glRotatef (bp->tilt, 0, 1, 0);
219       glRotatef (bp->spin, 1, 0, 0);
220       glTranslatef (x * bp->radius, y * bp->radius, 0);
221       glGetFloatv (GL_MODELVIEW_MATRIX, m1);
222       glPopMatrix();
223
224       /* After all of our translations and rotations, figure out where
225          it actually ended up.
226        */
227       x = m1[12] - m0[12];
228       y = m1[13] - m0[13];
229       z = m1[14] - m0[14];
230       add_afterimage (mi, x, y, z, L->color);
231     }
232 }
233
234
235 static void
236 draw_lights (ModeInfo *mi)
237 {
238   hoop_configuration *bp = &bps[MI_SCREEN(mi)];
239   int wire = MI_IS_WIREFRAME(mi);
240   afterimage *a;
241   GLfloat m[4][4];
242
243   if (wire)
244     {
245       int n = 360;
246       int i;
247       glPushMatrix();
248
249       glBegin (GL_LINES);
250       glVertex3f (0, 0, -bp->radius);
251       glVertex3f (0, 0,  bp->radius);
252       glEnd();
253
254       glTranslatef (bp->midpoint.x, bp->midpoint.y, bp->midpoint.z);
255       glRotatef (bp->th * 180 / M_PI, 0, 0, 1);
256       glRotatef (bp->tilt, 0, 1, 0);
257       glRotatef (bp->spin, 1, 0, 0);
258
259       glBegin (GL_LINE_LOOP);
260       glVertex3f (0, 0, 0);
261       for (i = 0; i <= n; i++)
262         {
263           GLfloat th = i * M_PI * 2 / n;
264           glVertex3f (bp->radius * -cos(th),
265                       bp->radius * -sin(th), 0);
266         }
267       for (i = 0; i <= n; i++)
268         {
269           GLfloat th = i * M_PI * 2 / n;
270           glVertex3f (bp->axial_radius * -cos(th),
271                       bp->axial_radius * -sin(th), 0);
272         }
273       glEnd();
274       glPopMatrix();
275     }
276
277   /* Billboard the lights to always face the camera */
278   glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]);
279   m[0][0] = 1; m[1][0] = 0; m[2][0] = 0;
280   m[0][1] = 0; m[1][1] = 1; m[2][1] = 0;
281   m[0][2] = 0; m[1][2] = 0; m[2][2] = 1;
282   glLoadIdentity();
283   glMultMatrixf (&m[0][0]);
284
285   for (a = bp->trail; a; a = a->next)
286     {
287       glPushMatrix();
288
289       glTranslatef (a->position.x, a->position.y, a->position.z);
290
291       if (wire)
292         {
293           GLfloat c[3];
294           c[0] = a->color[0] * a->color[3];
295           c[1] = a->color[1] * a->color[3];
296           c[2] = a->color[2] * a->color[3];
297           glColor3fv (c);
298         }
299       else
300         glColor4fv (a->color);
301
302       glRotatef (45, 0, 0, 1);
303       glScalef (0.15, 0.15, 0.15);
304       glBegin (GL_QUADS);
305       glTexCoord2f (0, 0); glVertex3f (-1, -1, 0);
306       glTexCoord2f (1, 0); glVertex3f ( 1, -1, 0);
307       glTexCoord2f (1, 1); glVertex3f ( 1,  1, 0);
308       glTexCoord2f (0, 1); glVertex3f (-1,  1, 0);
309       glEnd();
310       mi->polygon_count++;
311
312       glPopMatrix();
313     }
314 }
315
316
317 static GLfloat
318 ease_fn (GLfloat r)
319 {
320   return cos ((r/2 + 1) * M_PI) + 1; /* Smooth curve up, end at slope 1. */
321 }
322
323
324 static GLfloat
325 ease_ratio (GLfloat r)
326 {
327   GLfloat ease = 0.35;
328   if      (r <= 0)     return 0;
329   else if (r >= 1)     return 1;
330   else if (r <= ease)  return     ease * ease_fn (r / ease);
331   else if (r > 1-ease) return 1 - ease * ease_fn ((1 - r) / ease);
332   else                 return r;
333 }
334
335
336 static void
337 tick_oscillators (ModeInfo *mi)
338 {
339   hoop_configuration *bp = &bps[MI_SCREEN(mi)];
340   oscillator *prev = 0;
341   oscillator *a = bp->oscillators;
342   GLfloat tick = 0.1 / speed;
343
344   while (a)
345     {
346       oscillator *next = a->next;
347       a->ratio += tick * a->speed;
348       if (a->ratio > 1)
349         a->ratio = 1;
350
351       *a->var = a->from + (a->to - a->from) * ease_ratio (a->ratio);
352
353       if (a->ratio < 1)                 /* mid cycle */
354         prev = a;
355       else if (--a->remaining <= 0)     /* ended, and expired */
356         {
357           if (prev)
358             prev->next = next;
359           else
360             bp->oscillators = next;
361           free (a);
362         }
363       else                              /* keep going the other way */
364         {
365           GLfloat swap = a->from;
366           a->from = a->to;
367           a->to = swap;
368           a->ratio = 0;
369           prev = a;
370         }
371
372       a = next;
373     }
374 }
375
376
377 static void
378 calm_oscillators (ModeInfo *mi)
379 {
380   hoop_configuration *bp = &bps[MI_SCREEN(mi)];
381   oscillator *a;
382   for (a = bp->oscillators; a && a->next; a = a->next)
383     a->remaining = 1;
384 }
385
386
387 static void
388 add_oscillator (ModeInfo *mi, GLfloat *var, GLfloat speed, GLfloat to,
389                 int repeat)
390 {
391   hoop_configuration *bp = &bps[MI_SCREEN(mi)];
392   oscillator *a;
393
394   /* If an oscillator is already running on this variable, don't
395      add another. */
396   for (a = bp->oscillators; a && a->next; a = a->next)
397     if (a->var == var)
398       return;
399
400   a = (oscillator *) calloc (1, sizeof (*a));
401   if (repeat <= 0) abort();
402   a->ratio = 0;
403   a->from = *var;
404   a->to = to;
405   a->speed = speed;
406   a->var = var;
407   a->remaining = repeat;
408   a->next = bp->oscillators;
409   bp->oscillators = a;
410 # if 0
411   fprintf (stderr, "%s: %3d %6.2f -> %6.2f %s\n",
412            progname, repeat, *var, to,
413            (var == &bp->midpoint.z ? "z" :
414             var == &bp->tilt ? "tilt" :
415             var == &bp->axial_radius ? "r" :
416             var == &bp->speed ? "speed" : "?"));
417 # endif
418 }
419
420
421 static void
422 add_random_oscillator (ModeInfo *mi)
423 {
424   hoop_configuration *bp = &bps[MI_SCREEN(mi)];
425   int n = random() % 12;
426   switch (n) {
427   case 0: case 1: case 2:
428     add_oscillator (mi, &bp->midpoint.z, 1,
429                     bp->radius * (0.8 + frand(0.2))
430                     * (random() & 1 ? 1 : -1),
431                     (3 + (random() % 10)));
432     break;
433   case 3: case 4: case 5:
434     add_oscillator (mi, &bp->tilt, 1,
435                     -(GLfloat) (random() % 15),
436                     3 + (random() % 20));
437     break;
438   case 6: case 7: case 8:
439     add_oscillator (mi, &bp->axial_radius, 1,
440                     0.1 + bp->radius * frand(1.4),
441                     1 + (random() % 4));
442     break;
443   case 9: case 10:
444     add_oscillator (mi, &bp->speed, 3,
445                     (0.7 + frand(0.9)) * (random() & 1 ? 1 : -1),
446                     ((random() % 5)
447                      ? 1
448                      : 2 + (random() % 5)));
449     break;
450   case 11: 
451     add_oscillator (mi, &bp->spin, 0.1,
452                     180 * (1 + (random() % 2)),
453                     2 * (1 + (random() % 5)));
454     break;
455   default:
456     abort();
457     break;
458   }
459 }
460
461
462 static void
463 randomize_colors (ModeInfo *mi)
464 {
465   hoop_configuration *bp = &bps[MI_SCREEN(mi)];
466   int ncolors = MI_NCOLORS(mi);
467   GLfloat *colors;
468   int ncycles;
469   int i;
470
471   if (ncolors < 1)
472     ncolors = 1;
473   if (ncolors > bp->nlights)
474     ncolors = bp->nlights;
475
476   if (! (random() % 10))
477     ncolors = 1;
478
479   colors = calloc (ncolors, 4 * sizeof(*colors));
480   for (i = 0; i < ncolors; i++)
481     {
482       GLfloat *c = &colors[i * 4];
483 # define QUANTIZE() (((random() % 16) << 4) | 0xF) / 255.0
484       c[0] = QUANTIZE();
485       c[1] = QUANTIZE();
486       c[2] = QUANTIZE();
487       c[3] = 1;
488     }
489
490   switch (random() % 5) {
491   case 0:  ncycles = 1; break;
492   case 2:  ncycles = ncolors * (1 + (random() % 5)); break;
493   default: ncycles = ncolors; break;
494   }
495
496   for (i = 0; i < bp->nlights; i++)
497     {
498       light *L = &bp->lights[i];
499       int n = i * ncolors / bp->nlights;
500       int m = i * ncycles / bp->nlights;
501       if (n >= ncolors) abort();
502       if (m >= ncycles) abort();
503       memcpy (L->color, &colors[n], sizeof (L->color));
504
505       if (ncycles <= 1)
506         {
507           L->duty_cycle[0] = 0;
508           L->duty_cycle[1] = 100;
509         }
510       else if (m & 1)
511         {
512           L->duty_cycle[0] = 50;
513           L->duty_cycle[1] = 50;
514         }
515       else
516         {
517           L->duty_cycle[0] = 0;
518           L->duty_cycle[1] = 50;
519           L->duty_cycle[2] = 50;
520         }
521     }
522   free (colors);
523 }
524
525
526 static void
527 move_hoop (ModeInfo *mi)
528 {
529   hoop_configuration *bp = &bps[MI_SCREEN(mi)];
530
531   bp->th += 0.2 * speed * bp->speed;
532   while (bp->th > M_PI*2)
533     bp->th -= M_PI*2;
534   while (bp->th < 0)
535     bp->th += M_PI*2;
536
537   bp->midpoint.x = bp->axial_radius * cos (bp->th);
538   bp->midpoint.y = bp->axial_radius * sin (bp->th);
539
540   tick_oscillators (mi);
541
542   if (! (random() % 80))
543     add_random_oscillator (mi);
544
545   if (! (random() % 120))
546     randomize_colors (mi);
547 }
548
549
550 static void
551 build_texture (ModeInfo *mi)
552 {
553   int x, y;
554   int size = 128;
555   int s2 = size / 2;
556   int bpl = size * 2;
557   unsigned char *data = malloc (bpl * size);
558
559   for (y = 0; y < size; y++)
560     {
561       for (x = 0; x < size; x++)
562         {
563           double dist = (sqrt (((s2 - x) * (s2 - x)) +
564                                ((s2 - y) * (s2 - y)))
565                          / s2);
566           unsigned char *c = &data [y * bpl + x * 2];
567           c[0] = 0xFF;
568           c[1] = 0xFF * sin (dist > 1 ? 0 : (1 - dist));
569         }
570     }
571
572   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
573   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
574   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
575   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
576   glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
577   glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
578   check_gl_error ("texture param");
579
580   glTexImage2D (GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, size, size, 0,
581                 GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, data);
582   check_gl_error ("light texture");
583   free (data);
584 }
585
586
587
588 /* Window management, etc
589  */
590 ENTRYPOINT void
591 reshape_hoop (ModeInfo *mi, int width, int height)
592 {
593   GLfloat h = (GLfloat) height / (GLfloat) width;
594
595   glViewport (0, 0, (GLint) width, (GLint) height);
596
597   glMatrixMode(GL_PROJECTION);
598   glLoadIdentity();
599   gluPerspective (30.0, 1/h, 1.0, 100.0);
600
601   glMatrixMode(GL_MODELVIEW);
602   glLoadIdentity();
603   gluLookAt( 0.0, 0.0, 30.0,
604              0.0, 0.0, 0.0,
605              0.0, 1.0, 0.0);
606
607 # ifdef HAVE_MOBILE     /* Keep it the same relative size when rotated. */
608   {
609     int o = (int) current_device_rotation();
610     if (o != 0 && o != 180 && o != -180)
611       glScalef (1/h, 1/h, 1/h);
612   }
613 # endif
614
615   glClear(GL_COLOR_BUFFER_BIT);
616 }
617
618
619 ENTRYPOINT Bool
620 hoop_handle_event (ModeInfo *mi, XEvent *event)
621 {
622   hoop_configuration *bp = &bps[MI_SCREEN(mi)];
623
624   if (gltrackball_event_handler (event, bp->trackball,
625                                  MI_WIDTH (mi), MI_HEIGHT (mi),
626                                  &bp->button_down_p))
627     return True;
628   else if (event->xany.type == KeyPress)
629     {
630       KeySym keysym;
631       char c = 0;
632       XLookupString (&event->xkey, &c, 1, &keysym, 0);
633       if (c == ' ' || c == '\t')
634         {
635           randomize_colors (mi);
636           calm_oscillators (mi);
637           add_random_oscillator (mi);
638           return True;
639         }
640     }
641
642   return False;
643 }
644
645
646 ENTRYPOINT void 
647 init_hoop (ModeInfo *mi)
648 {
649   hoop_configuration *bp;
650   int wire = MI_IS_WIREFRAME(mi);
651
652   MI_INIT (mi, bps, NULL);
653
654   bp = &bps[MI_SCREEN(mi)];
655
656   bp->glx_context = init_GL(mi);
657
658   reshape_hoop (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
659
660   glDisable (GL_LIGHTING);
661   glDisable (GL_DEPTH_TEST);
662   glEnable (GL_CULL_FACE);
663   glEnable (GL_NORMALIZE);
664   glEnable (GL_BLEND);
665   glBlendFunc (GL_SRC_ALPHA, GL_ONE);
666   glPolygonMode (GL_FRONT, GL_FILL);
667   glShadeModel (GL_FLAT);
668
669   if (! wire)
670     {
671       build_texture (mi);
672       glEnable (GL_TEXTURE_2D);
673     }
674
675   {
676     double spin_speed   = 0.3;
677     double wander_speed = 0.005;
678     double spin_accel   = 2.0;
679
680     bp->rot = make_rotator (do_spin ? spin_speed : 0,
681                             do_spin ? spin_speed : 0,
682                             do_spin ? spin_speed : 0,
683                             spin_accel,
684                             do_wander ? wander_speed : 0,
685                             False);
686     bp->trackball = gltrackball_init (True);
687   }
688
689   bp->radius = 30;
690   bp->axial_radius = bp->radius * 0.3;
691   bp->tilt = - (GLfloat) (5 + (random() % 12));
692   bp->speed = (random() % 1 ? 1 : -1);
693   bp->nlights = nlights;
694   bp->lights = (light *) calloc (bp->nlights, sizeof (*bp->lights));
695   randomize_colors (mi);
696   move_hoop (mi);
697   add_random_oscillator (mi);
698 }
699
700
701 ENTRYPOINT void
702 draw_hoop (ModeInfo *mi)
703 {
704   hoop_configuration *bp = &bps[MI_SCREEN(mi)];
705   Display *dpy = MI_DISPLAY(mi);
706   Window window = MI_WINDOW(mi);
707
708   if (!bp->glx_context)
709     return;
710
711   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
712
713   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
714
715   glPushMatrix ();
716
717   {
718     double x, y, z;
719     get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
720     glTranslatef((x - 0.5) * 7,
721                  (y - 0.5) * 0.5,
722                  (z - 0.5) * 15);
723
724     gltrackball_rotate (bp->trackball);
725     glRotatef (current_device_rotation(), 0, 0, 1);
726
727     get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
728     glRotatef (x * 360, 1.0, 0.0, 0.0);
729     glRotatef (y * 360, 0.0, 1.0, 0.0);
730     glRotatef (z * 360, 0.0, 0.0, 1.0);
731   }
732
733   mi->polygon_count = 0;
734
735   glScalef (0.2, 0.2, 0.2);
736
737 # ifdef HAVE_MOBILE
738   glScalef (0.7, 0.7, 0.7);
739 # endif
740
741   glRotatef (70, 1, 0, 0);
742
743   if (! bp->button_down_p)
744     move_hoop (mi);
745
746   decay_afterimages (mi);
747   tick_hoop (mi);
748   draw_lights (mi);
749
750   glPopMatrix ();
751
752   if (mi->fps_p) do_fps (mi);
753   glFinish();
754
755   glXSwapBuffers(dpy, window);
756 }
757
758 XSCREENSAVER_MODULE_2 ("RaverHoop", raverhoop, hoop)
759
760 #endif /* USE_GL */