From http://www.jwz.org/xscreensaver/xscreensaver-5.36.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  * 10-Nov-14: jwz@jwz.org   Night map. Better stars.
19  * 16-Jan-02: jwz@jwz.org   gdk_pixbuf support.
20  * 21-Mar-01: jwz@jwz.org   Broke sphere routine out into its own file.
21  *
22  * 9-Oct-98:  dek@cgl.ucsf.edu  Added stars.
23  *
24  * 8-Oct-98:  jwz@jwz.org   Made the 512x512x1 xearth image be built in.
25  *                          Made it possible to load XPM or XBM files.
26  *                          Made the planet bounce and roll around.
27  *
28  * 8-Oct-98: Released initial version of "glplanet"
29  * (David Konerding, dek@cgl.ucsf.edu)
30  */
31
32
33 #ifdef STANDALONE
34 #define DEFAULTS        "*delay:                        20000   \n"     \
35                                         "*showFPS:                      False   \n" \
36                                         "*wireframe:            False   \n"     \
37                                         "*imageForeground:      Green   \n" \
38                                         "*imageBackground:      Blue    \n" \
39                                         "*suppressRotationAnimation: True\n" \
40
41 # define refresh_planet 0
42 # include "xlockmore.h"                         /* from the xscreensaver distribution */
43 #else  /* !STANDALONE */
44 # include "xlock.h"                                     /* from the xlockmore distribution */
45 #endif /* !STANDALONE */
46
47 #ifdef USE_GL /* whole file */
48
49 #include "sphere.h"
50
51 #ifdef HAVE_XMU
52 # ifndef VMS
53 #  include <X11/Xmu/Drawing.h>
54 #else  /* VMS */
55 #  include <Xmu/Drawing.h>
56 # endif /* VMS */
57 #endif
58
59 #define DEF_ROTATE  "True"
60 #define DEF_ROLL    "True"
61 #define DEF_WANDER  "True"
62 #define DEF_SPIN    "0.03"
63 #define DEF_TEXTURE "True"
64 #define DEF_STARS   "True"
65 #define DEF_RESOLUTION "128"
66 #define DEF_IMAGE   "BUILTIN"
67 #define DEF_IMAGE2  "BUILTIN"
68
69 #undef countof
70 #define countof(x) (sizeof((x))/sizeof((*x)))
71
72 #undef BELLRAND
73 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
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 char *which_image;
81 static char *which_image2;
82 static int resolution;
83
84 static XrmOptionDescRec opts[] = {
85   {"-rotate",  ".glplanet.rotate",  XrmoptionNoArg, "true" },
86   {"+rotate",  ".glplanet.rotate",  XrmoptionNoArg, "false" },
87   {"-roll",    ".glplanet.roll",    XrmoptionNoArg, "true" },
88   {"+roll",    ".glplanet.roll",    XrmoptionNoArg, "false" },
89   {"-wander",  ".glplanet.wander",  XrmoptionNoArg, "true" },
90   {"+wander",  ".glplanet.wander",  XrmoptionNoArg, "false" },
91   {"-texture", ".glplanet.texture", XrmoptionNoArg, "true" },
92   {"+texture", ".glplanet.texture", XrmoptionNoArg, "false" },
93   {"-stars",   ".glplanet.stars",   XrmoptionNoArg, "true" },
94   {"+stars",   ".glplanet.stars",   XrmoptionNoArg, "false" },
95   {"-spin",    ".glplanet.spin",    XrmoptionSepArg, 0 },
96   {"-image",   ".glplanet.image",   XrmoptionSepArg, 0 },
97   {"-image2",  ".glplanet.image2",  XrmoptionSepArg, 0 },
98   {"-resolution", ".glplanet.resolution", XrmoptionSepArg, 0 },
99 };
100
101 static argtype vars[] = {
102   {&do_rotate,   "rotate",  "Rotate",  DEF_ROTATE,  t_Bool},
103   {&do_roll,     "roll",    "Roll",    DEF_ROLL,    t_Bool},
104   {&do_wander,   "wander",  "Wander",  DEF_WANDER,  t_Bool},
105   {&do_texture,  "texture", "Texture", DEF_TEXTURE, t_Bool},
106   {&do_stars,    "stars",   "Stars",   DEF_STARS,   t_Bool},
107   {&which_image, "image",   "Image",   DEF_IMAGE,   t_String},
108   {&which_image2,"image2",  "Image",   DEF_IMAGE2,  t_String},
109   {&resolution,  "resolution","Resolution", DEF_RESOLUTION, t_Int},
110 };
111
112 ENTRYPOINT ModeSpecOpt planet_opts = {countof(opts), opts, countof(vars), vars, NULL};
113
114 #ifdef USE_MODULES
115 ModStruct   planet_description =
116 {"planet", "init_planet", "draw_planet", "release_planet",
117  "draw_planet", "init_planet", NULL, &planet_opts,
118  1000, 1, 2, 1, 4, 1.0, "",
119  "Animates texture mapped sphere (planet)", 0, NULL};
120 #endif
121
122 # ifdef __GNUC__
123   __extension__  /* don't warn about "string length is greater than the length
124                     ISO C89 compilers are required to support" when including
125                     the following XPM file... */
126 # endif
127 #include "../images/earth.xpm"
128 #include "../images/earth_night.xpm"
129
130 #include "xpm-ximage.h"
131 #include "rotator.h"
132 #include "gltrackball.h"
133
134
135 /*-
136  * slices and stacks are used in the sphere parameterization routine.
137  * more slices and stacks will increase the quality of the sphere,
138  * at the expense of rendering speed
139  */
140
141 /* structure for holding the planet data */
142 typedef struct {
143   GLuint platelist;
144   GLuint shadowlist;
145   GLuint latlonglist;
146   GLuint starlist;
147   int starcount;
148   int screen_width, screen_height;
149   GLXContext *glx_context;
150   Window window;
151   XColor fg, bg;
152   GLfloat z;
153   GLfloat tilt;
154   rotator *rot;
155   trackball_state *trackball;
156   Bool button_down_p;
157   GLuint tex1, tex2;
158   int draw_axis;
159
160 } planetstruct;
161
162
163 static planetstruct *planets = NULL;
164
165
166 /* Set up and enable texturing on our object */
167 static void
168 setup_xpm_texture (ModeInfo *mi, char **xpm_data)
169 {
170   XImage *image = xpm_to_ximage (MI_DISPLAY (mi), MI_VISUAL (mi),
171                                   MI_COLORMAP (mi), xpm_data);
172   char buf[1024];
173   clear_gl_error();
174   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
175   /* iOS invalid enum:
176   glPixelStorei(GL_UNPACK_ROW_LENGTH, image->width);
177   */
178   glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
179                image->width, image->height, 0,
180                GL_RGBA,
181                /* GL_UNSIGNED_BYTE, */
182                GL_UNSIGNED_INT_8_8_8_8_REV,
183                image->data);
184   sprintf (buf, "builtin texture (%dx%d)", image->width, image->height);
185   check_gl_error(buf);
186 }
187
188
189 static Bool
190 setup_file_texture (ModeInfo *mi, char *filename)
191 {
192   Display *dpy = mi->dpy;
193   Visual *visual = mi->xgwa.visual;
194   char buf[1024];
195
196   Colormap cmap = mi->xgwa.colormap;
197   XImage *image = xpm_file_to_ximage (dpy, visual, cmap, filename);
198   if (!image) return False;
199
200   clear_gl_error();
201   glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
202   glPixelStorei(GL_UNPACK_ROW_LENGTH, image->width);
203   glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
204                image->width, image->height, 0,
205                GL_RGBA,
206                /* GL_UNSIGNED_BYTE, */
207                GL_UNSIGNED_INT_8_8_8_8_REV,
208                image->data);
209   sprintf (buf, "texture: %.100s (%dx%d)",
210            filename, image->width, image->height);
211   check_gl_error(buf);
212   return True;
213 }
214
215
216 static void
217 setup_texture(ModeInfo * mi)
218 {
219   planetstruct *gp = &planets[MI_SCREEN(mi)];
220
221   glGenTextures (1, &gp->tex1);
222   glBindTexture (GL_TEXTURE_2D, gp->tex1);
223
224   /* Must be after glBindTexture */
225   glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
226   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
227   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
228   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
229   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
230
231   if (!which_image ||
232           !*which_image ||
233           !strcmp(which_image, "BUILTIN"))
234     {
235     BUILTIN1:
236       setup_xpm_texture (mi, earth_xpm);
237     }
238   else
239     {
240       if (! setup_file_texture (mi, which_image))
241         goto BUILTIN1;
242     }
243
244   check_gl_error("texture 1 initialization");
245
246   glGenTextures (1, &gp->tex2);
247   glBindTexture (GL_TEXTURE_2D, gp->tex2);
248
249   /* Must be after glBindTexture */
250   glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
251   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
252   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
253   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
254   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
255
256   if (!which_image2 ||
257           !*which_image2 ||
258           !strcmp(which_image2, "BUILTIN"))
259     {
260     BUILTIN2:
261       setup_xpm_texture (mi, earth_night_xpm);
262     }
263   else
264     {
265       if (! setup_file_texture (mi, which_image2))
266         goto BUILTIN2;
267     }
268
269   check_gl_error("texture 2 initialization");
270
271   /* Need to flip the texture top for bottom for some reason. */
272   glMatrixMode (GL_TEXTURE);
273   glScalef (1, -1, 1);
274   glMatrixMode (GL_MODELVIEW);
275 }
276
277
278 static void
279 init_stars (ModeInfo *mi)
280 {
281   planetstruct *gp = &planets[MI_SCREEN(mi)];
282   int i, j;
283   int width  = MI_WIDTH(mi);
284   int height = MI_HEIGHT(mi);
285   int size = (width > height ? width : height);
286   int nstars = size * size / 80;
287   int max_size = 3;
288   GLfloat inc = 0.5;
289   int steps = max_size / inc;
290
291   gp->starlist = glGenLists(1);
292   glNewList(gp->starlist, GL_COMPILE);
293   for (j = 1; j <= steps; j++)
294     {
295       glPointSize(inc * j);
296       glBegin (GL_POINTS);
297       for (i = 0; i < nstars / steps; i++)
298         {
299           GLfloat d = 0.1;
300           GLfloat r = 0.15 + frand(0.3);
301           GLfloat g = r + frand(d) - d;
302           GLfloat b = r + frand(d) - d;
303
304           GLfloat x = frand(1)-0.5;
305           GLfloat y = frand(1)-0.5;
306           GLfloat z = ((random() & 1)
307                        ? frand(1)-0.5
308                        : (BELLRAND(1)-0.5)/12);   /* milky way */
309           d = sqrt (x*x + y*y + z*z);
310           x /= d;
311           y /= d;
312           z /= d;
313           glColor3f (r, g, b);
314           glVertex3f (x, y, z);
315           gp->starcount++;
316         }
317       glEnd ();
318     }
319   glEndList ();
320
321   check_gl_error("stars initialization");
322 }
323
324
325 ENTRYPOINT void
326 reshape_planet (ModeInfo *mi, int width, int height)
327 {
328   GLfloat h = (GLfloat) height / (GLfloat) width;
329
330   glViewport(0, 0, (GLint) width, (GLint) height);
331   glMatrixMode(GL_PROJECTION);
332   glLoadIdentity();
333   glFrustum(-1.0, 1.0, -h, h, 5.0, 100.0);
334   glMatrixMode(GL_MODELVIEW);
335   glLoadIdentity();
336   glTranslatef(0.0, 0.0, -40);
337
338 # ifdef HAVE_MOBILE     /* Keep it the same relative size when rotated. */
339   {
340     int o = (int) current_device_rotation();
341     if (o != 0 && o != 180 && o != -180)
342       glScalef (h, h, h);
343   }
344 # endif
345
346   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
347 }
348
349
350 ENTRYPOINT Bool
351 planet_handle_event (ModeInfo *mi, XEvent *event)
352 {
353   planetstruct *gp = &planets[MI_SCREEN(mi)];
354
355   if (gltrackball_event_handler (event, gp->trackball,
356                                  MI_WIDTH (mi), MI_HEIGHT (mi),
357                                  &gp->button_down_p))
358     return True;
359
360   return False;
361 }
362
363
364 ENTRYPOINT void
365 init_planet (ModeInfo * mi)
366 {
367   planetstruct *gp;
368   int screen = MI_SCREEN(mi);
369   Bool wire = MI_IS_WIREFRAME(mi);
370
371   if (planets == NULL) {
372         if ((planets = (planetstruct *) calloc(MI_NUM_SCREENS(mi),
373                                                                                   sizeof (planetstruct))) == NULL)
374           return;
375   }
376   gp = &planets[screen];
377
378   if ((gp->glx_context = init_GL(mi)) != NULL) {
379         reshape_planet(mi, MI_WIDTH(mi), MI_HEIGHT(mi));
380   }
381
382   {
383         char *f = get_string_resource(mi->dpy, "imageForeground", "Foreground");
384         char *b = get_string_resource(mi->dpy, "imageBackground", "Background");
385         char *s;
386         if (!f) f = strdup("white");
387         if (!b) b = strdup("black");
388         
389         for (s = f + strlen(f)-1; s > f; s--)
390           if (*s == ' ' || *s == '\t')
391                 *s = 0;
392         for (s = b + strlen(b)-1; s > b; s--)
393           if (*s == ' ' || *s == '\t')
394                 *s = 0;
395
396     if (!XParseColor(mi->dpy, mi->xgwa.colormap, f, &gp->fg))
397       {
398                 fprintf(stderr, "%s: unparsable color: \"%s\"\n", progname, f);
399                 exit(1);
400       }
401     if (!XParseColor(mi->dpy, mi->xgwa.colormap, b, &gp->bg))
402       {
403                 fprintf(stderr, "%s: unparsable color: \"%s\"\n", progname, f);
404                 exit(1);
405       }
406
407         free (f);
408         free (b);
409   }
410
411   {
412     double spin_speed   = 0.1;
413     double wander_speed = 0.005;
414     gp->rot = make_rotator (do_roll ? spin_speed : 0,
415                             do_roll ? spin_speed : 0,
416                             0, 1,
417                             do_wander ? wander_speed : 0,
418                             True);
419     gp->z = frand (1.0);
420     gp->tilt = frand (23.4);
421     gp->trackball = gltrackball_init (True);
422   }
423
424   if (wire)
425     do_texture = False;
426
427   if (do_texture)
428     setup_texture (mi);
429
430   if (do_stars)
431     init_stars (mi);
432
433   /* construct the polygons of the planet
434    */
435   gp->platelist = glGenLists(1);
436   glNewList (gp->platelist, GL_COMPILE);
437   glFrontFace(GL_CCW);
438   glPushMatrix();
439   glRotatef (90, 1, 0, 0);
440   unit_sphere (resolution, resolution, wire);
441   glPopMatrix();
442   glEndList();
443
444   gp->shadowlist = glGenLists(1);
445   glNewList (gp->shadowlist, GL_COMPILE);
446   glFrontFace(GL_CCW);
447   unit_dome (resolution, resolution, wire);
448   glEndList();
449
450   /* construct the polygons of the latitude/longitude/axis lines.
451    */
452   gp->latlonglist = glGenLists(1);
453   glNewList (gp->latlonglist, GL_COMPILE);
454   glPushMatrix ();
455   glRotatef (90, 1, 0, 0);  /* unit_sphere is off by 90 */
456   glRotatef (8,  0, 1, 0);  /* line up the time zones */
457   unit_sphere (12, 24, 1);
458   unit_sphere (12, 24, 1);
459   glBegin(GL_LINES);
460   glVertex3f(0, -2, 0);
461   glVertex3f(0,  2, 0);
462   glEnd();
463   glPopMatrix ();
464   glEndList();
465 }
466
467
468 ENTRYPOINT void
469 draw_planet (ModeInfo * mi)
470 {
471   planetstruct *gp = &planets[MI_SCREEN(mi)];
472   int wire = MI_IS_WIREFRAME(mi);
473   Display *dpy = MI_DISPLAY(mi);
474   Window window = MI_WINDOW(mi);
475   double x, y, z;
476
477   if (!gp->glx_context)
478         return;
479
480   glDrawBuffer(GL_BACK);
481   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
482
483   glXMakeCurrent (dpy, window, *(gp->glx_context));
484
485   mi->polygon_count = 0;
486
487   if (gp->button_down_p)
488     gp->draw_axis = 60;
489   else if (!gp->draw_axis && !(random() % 1000))
490     gp->draw_axis = 60 + (random() % 90);
491
492   if (do_rotate && !gp->button_down_p)
493     {
494       gp->z -= 0.001;     /* the sun sets in the west */
495       if (gp->z < 0) gp->z += 1;
496     }
497
498   glEnable(GL_LINE_SMOOTH);
499   glEnable(GL_POINT_SMOOTH);
500   glEnable(GL_DEPTH_TEST);
501   glEnable(GL_CULL_FACE);
502   glCullFace(GL_BACK); 
503
504   glPushMatrix();
505
506   get_position (gp->rot, &x, &y, &z, !gp->button_down_p);
507   x = (x - 0.5) * 6;
508   y = (y - 0.5) * 6;
509   z = (z - 0.5) * 3;
510   glTranslatef(x, y, z);
511
512   gltrackball_rotate (gp->trackball);
513
514   if (do_roll)
515     {
516       get_rotation (gp->rot, &x, &y, 0, !gp->button_down_p);
517       glRotatef (x * 360, 1.0, 0.0, 0.0);
518       glRotatef (y * 360, 0.0, 1.0, 0.0);
519     }
520   else
521     glRotatef (current_device_rotation(), 0, 0, 1);
522
523   if (do_stars)
524     {
525       glDisable(GL_TEXTURE_2D);
526       glPushMatrix();
527       glScalef (60, 60, 60);
528       glRotatef (90, 1, 0, 0);
529       glRotatef (35, 1, 0, 0);
530       glCallList (gp->starlist);
531       mi->polygon_count += gp->starcount;
532       glPopMatrix();
533       glClear(GL_DEPTH_BUFFER_BIT);
534     }
535
536   glRotatef (90, 1, 0, 0);
537   glRotatef (35, 1, 0, 0);
538   glRotatef (10, 0, 1, 0);
539   glRotatef (120, 0, 0, 1);
540
541   glScalef (3, 3, 3);
542
543 # ifdef HAVE_MOBILE
544   glScalef (2, 2, 2);
545 # endif
546
547   if (wire)
548     glColor3f (0.5, 0.5, 1);
549   else
550     glColor3f (1, 1, 1);
551
552   if (do_texture)
553     {
554       glEnable(GL_TEXTURE_2D);
555       glBindTexture (GL_TEXTURE_2D, gp->tex1);
556     }
557
558   glPushMatrix();
559   glRotatef (gp->z * 360, 0, 0, 1);
560   glCallList (gp->platelist);
561   mi->polygon_count += resolution*resolution;
562   glPopMatrix();
563
564   /* Originally we just used GL_LIGHT0 to produce the day/night sides of
565      the planet, but that always looked crappy, even with a vast number of
566      polygons, because the day/night terminator didn't exactly line up with
567      the polygon edges.
568
569      So instead, draw the full "day" sphere; clear the depth buffer; draw
570      a rotated/tilted half-sphere into the depth buffer only; then draw
571      the "night" sphere.  That lets us divide the sphere into the two maps,
572      and the dividing line can be at any angle, regardless of polygon layout.
573
574      The half-sphere is scaled slightly larger to avoid polygon fighting,
575      since those triangles won't exactly line up because of the rotation.
576
577      The downside of this is that the day/night terminator is 100% sharp.
578      It would be nice if it was a little blurry.
579    */
580
581   if (wire)
582     {
583       glPushMatrix();
584       glRotatef (gp->tilt, 1, 0, 0);
585       glColor3f(0, 0, 0);
586       glLineWidth(4);
587       glCallList (gp->shadowlist);
588       glLineWidth(1);
589       mi->polygon_count += resolution*(resolution/2);
590       glPopMatrix();
591     }
592   else if (do_texture && gp->tex2)
593     {
594       glClear(GL_DEPTH_BUFFER_BIT);
595       glDisable(GL_TEXTURE_2D);
596       glColorMask (0, 0, 0, 0);
597       glPushMatrix();
598       glRotatef (gp->tilt, 1, 0, 0);
599       glScalef (1.01, 1.01, 1.01);
600       glCallList (gp->shadowlist);
601       mi->polygon_count += resolution*(resolution/2);
602       glPopMatrix();
603       glColorMask (1, 1, 1, 1);
604       glEnable(GL_TEXTURE_2D);
605
606       glBindTexture (GL_TEXTURE_2D, gp->tex2);
607       glPushMatrix();
608       glRotatef (gp->z * 360, 0, 0, 1);
609       glCallList (gp->platelist);
610       mi->polygon_count += resolution*resolution;
611       glPopMatrix();
612     }
613
614   if (gp->draw_axis)
615     {
616       glPushMatrix();
617       glRotatef (gp->z * 360, 0.0, 0.0, 1.0);
618       glScalef (1.02, 1.02, 1.02);
619       glDisable (GL_TEXTURE_2D);
620       glDisable (GL_LIGHTING);
621       glDisable (GL_LINE_SMOOTH);
622       glColor3f (0.1, 0.3, 0.1);
623       glCallList (gp->latlonglist);
624       mi->polygon_count += 24*24;
625       glPopMatrix();
626       if (gp->draw_axis) gp->draw_axis--;
627     }
628   glPopMatrix();
629
630   if (mi->fps_p) do_fps (mi);
631   glFinish();
632   glXSwapBuffers(dpy, window);
633 }
634
635
636 ENTRYPOINT void
637 release_planet (ModeInfo * mi)
638 {
639   if (planets != NULL) {
640         int screen;
641
642         for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++) {
643           planetstruct *gp = &planets[screen];
644
645           if (gp->glx_context) {
646                 /* Display lists MUST be freed while their glXContext is current. */
647         /* but this gets a BadMatch error. -jwz */
648                 /*glXMakeCurrent(MI_DISPLAY(mi), gp->window, *(gp->glx_context));*/
649
650                 if (glIsList(gp->platelist))
651                   glDeleteLists(gp->platelist, 1);
652                 if (glIsList(gp->starlist))
653                   glDeleteLists(gp->starlist, 1);
654           }
655         }
656         (void) free((void *) planets);
657         planets = NULL;
658   }
659   FreeAllGL(mi);
660 }
661
662
663 XSCREENSAVER_MODULE_2 ("GLPlanet", glplanet, planet)
664
665 #endif