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