From http://www.jwz.org/xscreensaver/xscreensaver-5.16.tar.gz
[xscreensaver] / hacks / glx / glplanet.c
1 /* -*- Mode: C; tab-width: 4 -*- */
2 /* glplanet --- 3D rotating planet, e.g., Earth.
3  *
4  * Permission to use, copy, modify, and distribute this software and its
5  * documentation for any purpose and without fee is hereby granted,
6  * provided that the above copyright notice appear in all copies and that
7  * both that copyright notice and this permission notice appear in
8  * supporting documentation.
9  *
10  * This file is provided AS IS with no warranties of any kind.  The author
11  * shall have no liability with respect to the infringement of copyrights,
12  * trade secrets or any patents by this file or any part thereof.  In no
13  * event will the author be liable for any lost revenue or profits or
14  * other special, indirect and consequential damages.
15  *
16  * Revision History:
17  *
18  * 16-Jan-02: jwz@jwz.org   gdk_pixbuf support.
19  * 21-Mar-01: jwz@jwz.org   Broke sphere routine out into its own file.
20  *
21  * 9-Oct-98:  dek@cgl.ucsf.edu  Added stars.
22  *
23  * 8-Oct-98:  jwz@jwz.org   Made the 512x512x1 xearth image be built in.
24  *                          Made it possible to load XPM or XBM files.
25  *                          Made the planet bounce and roll around.
26  *
27  * 8-Oct-98: Released initial version of "glplanet"
28  * (David Konerding, dek@cgl.ucsf.edu)
29  *
30  * BUGS:
31  * -bounce is broken
32  * 
33  *   For even more spectacular results, grab the images from the "SSystem"
34  *   package (http://www.msu.edu/user/kamelkev/) and use its JPEGs!
35  */
36
37
38 #ifdef STANDALONE
39 #define DEFAULTS        "*delay:                        20000   \n"     \
40                                         "*showFPS:                      False   \n" \
41                                         "*wireframe:            False   \n"     \
42                                         "*imageForeground:      Green   \n" \
43                                         "*imageBackground:      Blue    \n"
44 # define refresh_planet 0
45 # include "xlockmore.h"                         /* from the xscreensaver distribution */
46 #else  /* !STANDALONE */
47 # include "xlock.h"                                     /* from the xlockmore distribution */
48 #endif /* !STANDALONE */
49
50 #ifdef USE_GL /* whole file */
51
52 #include "sphere.h"
53
54 #ifdef HAVE_XMU
55 # ifndef VMS
56 #  include <X11/Xmu/Drawing.h>
57 #else  /* VMS */
58 #  include <Xmu/Drawing.h>
59 # endif /* VMS */
60 #endif
61
62 #define DEF_ROTATE  "True"
63 #define DEF_ROLL    "True"
64 #define DEF_WANDER  "True"
65 #define DEF_SPIN    "0.03"
66 #define DEF_TEXTURE "True"
67 #define DEF_STARS   "True"
68 #define DEF_LIGHT   "True"
69 #define DEF_RESOLUTION "128"
70 #define DEF_IMAGE   "BUILTIN"
71
72 #undef countof
73 #define countof(x) (sizeof((x))/sizeof((*x)))
74
75 static int do_rotate;
76 static int do_roll;
77 static int do_wander;
78 static int do_texture;
79 static int do_stars;
80 static int do_light;
81 static char *which_image;
82 static int resolution;
83 static float star_spin;
84
85 static XrmOptionDescRec opts[] = {
86   {"-rotate",  ".glplanet.rotate",  XrmoptionNoArg, "true" },
87   {"+rotate",  ".glplanet.rotate",  XrmoptionNoArg, "false" },
88   {"-roll",    ".glplanet.roll",    XrmoptionNoArg, "true" },
89   {"+roll",    ".glplanet.roll",    XrmoptionNoArg, "false" },
90   {"-wander",  ".glplanet.wander",  XrmoptionNoArg, "true" },
91   {"+wander",  ".glplanet.wander",  XrmoptionNoArg, "false" },
92   {"-texture", ".glplanet.texture", XrmoptionNoArg, "true" },
93   {"+texture", ".glplanet.texture", XrmoptionNoArg, "false" },
94   {"-stars",   ".glplanet.stars",   XrmoptionNoArg, "true" },
95   {"+stars",   ".glplanet.stars",   XrmoptionNoArg, "false" },
96   {"-spin",    ".glplanet.spin",    XrmoptionSepArg, 0 },
97   {"-light",   ".glplanet.light",   XrmoptionNoArg, "true" },
98   {"+light",   ".glplanet.light",   XrmoptionNoArg, "false" },
99   {"-image",   ".glplanet.image",  XrmoptionSepArg, 0 },
100   {"-resolution", ".glplanet.resolution", XrmoptionSepArg, 0 },
101 };
102
103 static argtype vars[] = {
104   {&do_rotate,   "rotate",  "Rotate",  DEF_ROTATE,  t_Bool},
105   {&do_roll,     "roll",    "Roll",    DEF_ROLL,    t_Bool},
106   {&do_wander,   "wander",  "Wander",  DEF_WANDER,  t_Bool},
107   {&do_texture,  "texture", "Texture", DEF_TEXTURE, t_Bool},
108   {&do_stars,    "stars",   "Stars",   DEF_STARS,   t_Bool},
109   {&do_light,    "light",   "Light",   DEF_LIGHT,   t_Bool},
110   {&which_image, "image",   "Image",   DEF_IMAGE,   t_String},
111   {&resolution,  "resolution","Resolution", DEF_RESOLUTION, t_Int},
112   {&star_spin,   "spin",    "Float",   DEF_SPIN,    t_Float},
113 };
114
115 ENTRYPOINT ModeSpecOpt planet_opts = {countof(opts), opts, countof(vars), vars, NULL};
116
117 #ifdef USE_MODULES
118 ModStruct   planet_description =
119 {"planet", "init_planet", "draw_planet", "release_planet",
120  "draw_planet", "init_planet", NULL, &planet_opts,
121  1000, 1, 2, 1, 4, 1.0, "",
122  "Animates texture mapped sphere (planet)", 0, NULL};
123 #endif
124
125 # ifdef __GNUC__
126   __extension__  /* don't warn about "string length is greater than the length
127                     ISO C89 compilers are required to support" when including
128                     the following XPM file... */
129 # endif
130 #include "../images/earth.xpm"
131
132 #include "xpm-ximage.h"
133 #include "rotator.h"
134 #include "gltrackball.h"
135
136
137 /*-
138  * slices and stacks are used in the sphere parameterization routine.
139  * more slices and stacks will increase the quality of the sphere,
140  * at the expense of rendering speed
141  */
142
143 #define NUM_STARS 1000
144
145 /* radius of the sphere- fairly arbitrary */
146 #define RADIUS 4
147
148 /* distance away from the sphere model */
149 #define DIST 40
150
151
152
153 /* structure for holding the planet data */
154 typedef struct {
155   GLuint platelist;
156   GLuint latlonglist;
157   GLuint starlist;
158   int screen_width, screen_height;
159   GLXContext *glx_context;
160   Window window;
161   XColor fg, bg;
162   GLfloat sunpos[4];
163   double z;
164   rotator *rot;
165   trackball_state *trackball;
166   double star_theta;
167   Bool button_down_p;
168 } planetstruct;
169
170
171 static planetstruct *planets = NULL;
172
173
174 /* Set up and enable texturing on our object */
175 static void
176 setup_xpm_texture (ModeInfo *mi, char **xpm_data)
177 {
178   XImage *image = xpm_to_ximage (MI_DISPLAY (mi), MI_VISUAL (mi),
179                                   MI_COLORMAP (mi), xpm_data);
180   char buf[1024];
181   clear_gl_error();
182   glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
183                image->width, image->height, 0,
184                GL_RGBA,
185                /* GL_UNSIGNED_BYTE, */
186                GL_UNSIGNED_INT_8_8_8_8_REV,
187                image->data);
188   sprintf (buf, "builtin texture (%dx%d)", image->width, image->height);
189   check_gl_error(buf);
190
191   /* setup parameters for texturing */
192   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
193   glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
194   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
195   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
196   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
197   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
198 }
199
200
201 static void
202 setup_file_texture (ModeInfo *mi, char *filename)
203 {
204   Display *dpy = mi->dpy;
205   Visual *visual = mi->xgwa.visual;
206   char buf[1024];
207
208   Colormap cmap = mi->xgwa.colormap;
209   XImage *image = xpm_file_to_ximage (dpy, visual, cmap, filename);
210
211   clear_gl_error();
212   glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
213                image->width, image->height, 0,
214                GL_RGBA,
215                /* GL_UNSIGNED_BYTE, */
216                GL_UNSIGNED_INT_8_8_8_8_REV,
217                image->data);
218   sprintf (buf, "texture: %.100s (%dx%d)",
219            filename, image->width, image->height);
220   check_gl_error(buf);
221
222   /* setup parameters for texturing */
223   glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
224   glPixelStorei(GL_UNPACK_ROW_LENGTH, image->width);
225
226   glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
227   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
228   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
229   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
230   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
231 }
232
233
234 static void
235 setup_texture(ModeInfo * mi)
236 {
237 /*  planetstruct *gp = &planets[MI_SCREEN(mi)];*/
238
239   glEnable(GL_TEXTURE_2D);
240
241   if (!which_image ||
242           !*which_image ||
243           !strcmp(which_image, "BUILTIN"))
244         setup_xpm_texture (mi, earth_xpm);
245   else
246         setup_file_texture (mi, which_image);
247
248   check_gl_error("texture initialization");
249
250   /* Need to flip the texture top for bottom for some reason. */
251   glMatrixMode (GL_TEXTURE);
252   glScalef (1, -1, 1);
253   glMatrixMode (GL_MODELVIEW);
254 }
255
256
257 static void
258 init_stars (ModeInfo *mi)
259 {
260   planetstruct *gp = &planets[MI_SCREEN(mi)];
261   int i, j;
262   int width  = MI_WIDTH(mi);
263   int height = MI_HEIGHT(mi);
264   int size = (width > height ? width : height);
265   int nstars = size * size / 320;
266   int max_size = 3;
267   GLfloat inc = 0.5;
268   int steps = max_size / inc;
269
270   gp->starlist = glGenLists(1);
271   glNewList(gp->starlist, GL_COMPILE);
272
273   glEnable(GL_POINT_SMOOTH);
274
275   for (j = 1; j <= steps; j++)
276     {
277       glPointSize(inc * j);
278       glBegin (GL_POINTS);
279       for (i = 0; i < nstars / steps; i++)
280         {
281           glColor3f (0.6 + frand(0.3),
282                      0.6 + frand(0.3),
283                      0.6 + frand(0.3));
284           glVertex2f (2 * size * (0.5 - frand(1.0)),
285                       2 * size * (0.5 - frand(1.0)));
286         }
287       glEnd ();
288     }
289   glEndList ();
290
291   check_gl_error("stars initialization");
292 }
293
294
295 static void
296 draw_stars (ModeInfo *mi)
297 {
298   planetstruct *gp = &planets[MI_SCREEN(mi)];
299   
300   glDisable(GL_TEXTURE_2D);
301   glDisable(GL_LIGHTING);
302   glDisable(GL_DEPTH_TEST);
303
304   glMatrixMode (GL_PROJECTION);
305   glPushMatrix ();
306   {
307     glLoadIdentity ();
308
309     glMatrixMode (GL_MODELVIEW);
310     glPushMatrix ();
311     {
312       glLoadIdentity ();
313       glOrtho (-0.5 * MI_WIDTH(mi),  0.5 * MI_WIDTH(mi),
314                -0.5 * MI_HEIGHT(mi), 0.5 * MI_HEIGHT(mi),
315                -100.0, 100.0);
316       glRotatef (gp->star_theta, 0.0, 0.0, 1.0);
317       glCallList (gp->starlist);
318     }
319     glPopMatrix ();
320   }
321   glMatrixMode (GL_PROJECTION);
322   glPopMatrix ();
323
324   glMatrixMode (GL_MODELVIEW);
325 }
326
327
328
329 /* Set up lighting */
330 static void
331 init_sun (ModeInfo * mi)
332 {
333   planetstruct *gp = &planets[MI_SCREEN(mi)];
334
335   GLfloat lamb[4] = { 0.1, 0.1, 0.1, 1.0 };
336   GLfloat ldif[4] = { 1.0, 1.0, 1.0, 1.0 };
337   GLfloat spec[4] = { 1.0, 1.0, 1.0, 1.0 };
338
339   GLfloat mamb[4] = { 0.5, 0.5, 0.5, 1.0 };
340   GLfloat mdif[4] = { 1.0, 1.0, 1.0, 1.0 };
341   GLfloat mpec[4] = { 1.0, 1.0, 1.0, 1.0 };
342   GLfloat shiny = .4;
343
344   {
345     double h =  0.1 + frand(0.8);   /* east-west position - screen-side. */
346     double v = -0.3 + frand(0.6);   /* north-south position */
347
348     if (h > 0.3 && h < 0.8)         /* avoid having the sun at the camera */
349       h += (h > 0.5 ? 0.2 : -0.2);
350
351     gp->sunpos[0] = cos(h * M_PI);
352     gp->sunpos[1] = sin(h * M_PI);
353     gp->sunpos[2] = sin(v * M_PI);
354     gp->sunpos[3] =  0.00;
355   }
356
357   glEnable(GL_LIGHTING);
358   glEnable(GL_LIGHT0);
359
360   glLightfv (GL_LIGHT0, GL_POSITION, gp->sunpos);
361   glLightfv (GL_LIGHT0, GL_AMBIENT,  lamb);
362   glLightfv (GL_LIGHT0, GL_DIFFUSE,  ldif);
363   glLightfv (GL_LIGHT0, GL_SPECULAR, spec);
364
365   check_gl_error("sun");
366   glMaterialfv (GL_FRONT, GL_AMBIENT,  mamb);
367   glMaterialfv (GL_FRONT, GL_DIFFUSE,  mdif);
368   glMaterialfv (GL_FRONT, GL_SPECULAR, mpec);
369   glMaterialf  (GL_FRONT, GL_SHININESS, shiny);
370
371
372 /*  glEnable(GL_BLEND);*/
373 /*  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);*/
374   glShadeModel(GL_SMOOTH);
375
376   check_gl_error("lighting");
377 }
378
379
380 #define RANDSIGN() ((random() & 1) ? 1 : -1)
381
382 ENTRYPOINT void
383 reshape_planet (ModeInfo *mi, int width, int height)
384 {
385   GLfloat h = (GLfloat) height / (GLfloat) width;
386
387   glViewport(0, 0, (GLint) width, (GLint) height);
388   glMatrixMode(GL_PROJECTION);
389   glLoadIdentity();
390   glFrustum(-1.0, 1.0, -h, h, 5.0, 100.0);
391   glMatrixMode(GL_MODELVIEW);
392   glLoadIdentity();
393   glTranslatef(0.0, 0.0, -DIST);
394
395   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
396 }
397
398
399 ENTRYPOINT Bool
400 planet_handle_event (ModeInfo *mi, XEvent *event)
401 {
402   planetstruct *gp = &planets[MI_SCREEN(mi)];
403
404   if (event->xany.type == ButtonPress &&
405       event->xbutton.button == Button1)
406     {
407       gp->button_down_p = True;
408       gltrackball_start (gp->trackball,
409                          event->xbutton.x, event->xbutton.y,
410                          MI_WIDTH (mi), MI_HEIGHT (mi));
411       return True;
412     }
413   else if (event->xany.type == ButtonRelease &&
414            event->xbutton.button == Button1)
415     {
416       gp->button_down_p = False;
417       return True;
418     }
419   else if (event->xany.type == ButtonPress &&
420            (event->xbutton.button == Button4 ||
421             event->xbutton.button == Button5 ||
422             event->xbutton.button == Button6 ||
423             event->xbutton.button == Button7))
424     {
425       gltrackball_mousewheel (gp->trackball, event->xbutton.button, 10,
426                               !!event->xbutton.state);
427       return True;
428     }
429   else if (event->xany.type == MotionNotify &&
430            gp->button_down_p)
431     {
432       gltrackball_track (gp->trackball,
433                          event->xmotion.x, event->xmotion.y,
434                          MI_WIDTH (mi), MI_HEIGHT (mi));
435       return True;
436     }
437
438   return False;
439 }
440
441
442 ENTRYPOINT void
443 init_planet (ModeInfo * mi)
444 {
445   planetstruct *gp;
446   int screen = MI_SCREEN(mi);
447   Bool wire = MI_IS_WIREFRAME(mi);
448
449   if (planets == NULL) {
450         if ((planets = (planetstruct *) calloc(MI_NUM_SCREENS(mi),
451                                                                                   sizeof (planetstruct))) == NULL)
452           return;
453   }
454   gp = &planets[screen];
455
456   if ((gp->glx_context = init_GL(mi)) != NULL) {
457         reshape_planet(mi, MI_WIDTH(mi), MI_HEIGHT(mi));
458   }
459
460   {
461         char *f = get_string_resource(mi->dpy, "imageForeground", "Foreground");
462         char *b = get_string_resource(mi->dpy, "imageBackground", "Background");
463         char *s;
464         if (!f) f = strdup("white");
465         if (!b) b = strdup("black");
466         
467         for (s = f + strlen(f)-1; s > f; s--)
468           if (*s == ' ' || *s == '\t')
469                 *s = 0;
470         for (s = b + strlen(b)-1; s > b; s--)
471           if (*s == ' ' || *s == '\t')
472                 *s = 0;
473
474     if (!XParseColor(mi->dpy, mi->xgwa.colormap, f, &gp->fg))
475       {
476                 fprintf(stderr, "%s: unparsable color: \"%s\"\n", progname, f);
477                 exit(1);
478       }
479     if (!XParseColor(mi->dpy, mi->xgwa.colormap, b, &gp->bg))
480       {
481                 fprintf(stderr, "%s: unparsable color: \"%s\"\n", progname, f);
482                 exit(1);
483       }
484
485         free (f);
486         free (b);
487   }
488
489   {
490     double spin_speed   = 0.5;
491     double wander_speed = 0.02;
492     gp->rot = make_rotator (do_roll ? spin_speed : 0,
493                             do_roll ? spin_speed : 0,
494                             0, 1,
495                             do_wander ? wander_speed : 0,
496                             True);
497     gp->z = frand (1.0);
498     gp->trackball = gltrackball_init ();
499   }
500
501   if (wire)
502     {
503       do_texture = False;
504       do_light = False;
505     }
506
507   if (do_texture)
508     setup_texture (mi);
509
510   if (do_light)
511         init_sun (mi);
512
513   if (do_stars)
514     init_stars (mi);
515
516   if (random() & 1)
517     star_spin = -star_spin;
518
519   /* construct the polygons of the planet
520    */
521   gp->platelist = glGenLists(1);
522   glNewList (gp->platelist, GL_COMPILE);
523   glColor3f (1,1,1);
524   glPushMatrix ();
525   glScalef (RADIUS, RADIUS, RADIUS);
526   glRotatef (90, 1, 0, 0);
527   glFrontFace(GL_CCW);
528   unit_sphere (resolution, resolution, wire);
529   glPopMatrix ();
530   glEndList();
531
532   /* construct the polygons of the latitude/longitude/axis lines.
533    */
534   gp->latlonglist = glGenLists(1);
535   glNewList (gp->latlonglist, GL_COMPILE);
536   glPushMatrix ();
537   glDisable (GL_TEXTURE_2D);
538   glDisable (GL_LIGHTING);
539   glDisable (GL_LINE_SMOOTH);
540   glColor3f (0.1, 0.3, 0.1);
541   glScalef (RADIUS, RADIUS, RADIUS);
542   glScalef (1.01, 1.01, 1.01);
543   glRotatef (90, 1, 0, 0);
544   unit_sphere (12, 24, 1);
545   glBegin(GL_LINES);
546   glVertex3f(0, -2, 0);
547   glVertex3f(0,  2, 0);
548   glEnd();
549   glPopMatrix ();
550   glEndList();
551 }
552
553 ENTRYPOINT void
554 draw_planet (ModeInfo * mi)
555 {
556   planetstruct *gp = &planets[MI_SCREEN(mi)];
557   Display    *display = MI_DISPLAY(mi);
558   Window      window = MI_WINDOW(mi);
559   double x, y, z;
560
561   if (!gp->glx_context)
562         return;
563
564   glDrawBuffer(GL_BACK);
565   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
566
567   glXMakeCurrent (display, window, *(gp->glx_context));
568
569   mi->polygon_count = 0;
570
571   if (do_stars)
572     {
573       draw_stars (mi);
574       mi->polygon_count += NUM_STARS;
575     }
576
577   if (do_light)   glEnable(GL_LIGHTING);
578   if (do_texture) glEnable(GL_TEXTURE_2D);
579
580   glEnable (GL_LINE_SMOOTH);
581   glEnable(GL_DEPTH_TEST);
582   glEnable(GL_CULL_FACE);
583   glCullFace(GL_BACK); 
584
585   glPushMatrix();
586
587   get_position (gp->rot, &x, &y, &z, !gp->button_down_p);
588   glTranslatef((x - 0.5) * 15,
589                (y - 0.5) * 15,
590                (z - 0.5) * 8);
591
592   gltrackball_rotate (gp->trackball);
593
594   glRotatef (90,1,0,0);
595
596   if (do_roll)
597     {
598       get_rotation (gp->rot, &x, &y, 0, !gp->button_down_p);
599       glRotatef (x * 360, 1.0, 0.0, 0.0);
600       glRotatef (y * 360, 0.0, 1.0, 0.0);
601     }
602
603   glLightfv (GL_LIGHT0, GL_POSITION, gp->sunpos);
604
605   glRotatef (gp->z * 360, 0.0, 0.0, 1.0);
606   if (do_rotate && !gp->button_down_p)
607     {
608       gp->z -= 0.005;     /* the sun sets in the west */
609       if (gp->z < 0) gp->z += 1;
610     }
611
612   glCallList (gp->platelist);
613   mi->polygon_count += resolution*resolution;
614
615   if (gp->button_down_p)
616     {
617       glCallList (gp->latlonglist);
618       mi->polygon_count += 24*24;
619     }
620   glPopMatrix();
621
622   if (mi->fps_p) do_fps (mi);
623   glFinish();
624   glXSwapBuffers(display, window);
625
626   gp->star_theta += star_spin;
627 }
628
629
630 ENTRYPOINT void
631 release_planet (ModeInfo * mi)
632 {
633   if (planets != NULL) {
634         int screen;
635
636         for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++) {
637           planetstruct *gp = &planets[screen];
638
639           if (gp->glx_context) {
640                 /* Display lists MUST be freed while their glXContext is current. */
641         /* but this gets a BadMatch error. -jwz */
642                 /*glXMakeCurrent(MI_DISPLAY(mi), gp->window, *(gp->glx_context));*/
643
644                 if (glIsList(gp->platelist))
645                   glDeleteLists(gp->platelist, 1);
646                 if (glIsList(gp->starlist))
647                   glDeleteLists(gp->starlist, 1);
648           }
649         }
650         (void) free((void *) planets);
651         planets = NULL;
652   }
653   FreeAllGL(mi);
654 }
655
656
657 XSCREENSAVER_MODULE_2 ("GLPlanet", glplanet, planet)
658
659 #endif