From http://www.jwz.org/xscreensaver/xscreensaver-5.38.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 release_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", NULL,
117  "draw_planet", "init_planet", "free_planet", &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   planetstruct *gp = &planets[MI_SCREEN(mi)];
329   GLfloat h = (GLfloat) height / (GLfloat) width;
330
331   glXMakeCurrent(MI_DISPLAY(mi), gp->window, *(gp->glx_context));
332
333   glViewport(0, 0, (GLint) width, (GLint) height);
334   glMatrixMode(GL_PROJECTION);
335   glLoadIdentity();
336   glFrustum(-1.0, 1.0, -h, h, 5.0, 100.0);
337   glMatrixMode(GL_MODELVIEW);
338   glLoadIdentity();
339   glTranslatef(0.0, 0.0, -40);
340
341 # ifdef HAVE_MOBILE     /* Keep it the same relative size when rotated. */
342   {
343     int o = (int) current_device_rotation();
344     if (o != 0 && o != 180 && o != -180)
345       glScalef (h, h, h);
346   }
347 # endif
348
349   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
350 }
351
352
353 ENTRYPOINT Bool
354 planet_handle_event (ModeInfo *mi, XEvent *event)
355 {
356   planetstruct *gp = &planets[MI_SCREEN(mi)];
357
358   if (gltrackball_event_handler (event, gp->trackball,
359                                  MI_WIDTH (mi), MI_HEIGHT (mi),
360                                  &gp->button_down_p))
361     return True;
362
363   return False;
364 }
365
366
367 ENTRYPOINT void
368 init_planet (ModeInfo * mi)
369 {
370   planetstruct *gp;
371   int screen = MI_SCREEN(mi);
372   Bool wire = MI_IS_WIREFRAME(mi);
373
374   MI_INIT (mi, planets);
375   gp = &planets[screen];
376
377   gp->window = MI_WINDOW(mi);
378
379   if ((gp->glx_context = init_GL(mi)) != NULL) {
380         reshape_planet(mi, MI_WIDTH(mi), MI_HEIGHT(mi));
381   }
382
383   {
384         char *f = get_string_resource(mi->dpy, "imageForeground", "Foreground");
385         char *b = get_string_resource(mi->dpy, "imageBackground", "Background");
386         char *s;
387         if (!f) f = strdup("white");
388         if (!b) b = strdup("black");
389         
390         for (s = f + strlen(f)-1; s > f; s--)
391           if (*s == ' ' || *s == '\t')
392                 *s = 0;
393         for (s = b + strlen(b)-1; s > b; s--)
394           if (*s == ' ' || *s == '\t')
395                 *s = 0;
396
397     if (!XParseColor(mi->dpy, mi->xgwa.colormap, f, &gp->fg))
398       {
399                 fprintf(stderr, "%s: unparsable color: \"%s\"\n", progname, f);
400                 exit(1);
401       }
402     if (!XParseColor(mi->dpy, mi->xgwa.colormap, b, &gp->bg))
403       {
404                 fprintf(stderr, "%s: unparsable color: \"%s\"\n", progname, f);
405                 exit(1);
406       }
407
408         free (f);
409         free (b);
410   }
411
412   {
413     double spin_speed   = 0.1;
414     double wander_speed = 0.005;
415     gp->rot = make_rotator (do_roll ? spin_speed : 0,
416                             do_roll ? spin_speed : 0,
417                             0, 1,
418                             do_wander ? wander_speed : 0,
419                             True);
420     gp->z = frand (1.0);
421     gp->tilt = frand (23.4);
422     gp->trackball = gltrackball_init (True);
423   }
424
425   if (wire)
426     do_texture = False;
427
428   if (do_texture)
429     setup_texture (mi);
430
431   if (do_stars)
432     init_stars (mi);
433
434   /* construct the polygons of the planet
435    */
436   gp->platelist = glGenLists(1);
437   glNewList (gp->platelist, GL_COMPILE);
438   glFrontFace(GL_CCW);
439   glPushMatrix();
440   glRotatef (90, 1, 0, 0);
441   unit_sphere (resolution, resolution, wire);
442   glPopMatrix();
443   glEndList();
444
445   gp->shadowlist = glGenLists(1);
446   glNewList (gp->shadowlist, GL_COMPILE);
447   glFrontFace(GL_CCW);
448   unit_dome (resolution, resolution, wire);
449   glEndList();
450
451   /* construct the polygons of the latitude/longitude/axis lines.
452    */
453   gp->latlonglist = glGenLists(1);
454   glNewList (gp->latlonglist, GL_COMPILE);
455   glPushMatrix ();
456   glRotatef (90, 1, 0, 0);  /* unit_sphere is off by 90 */
457   glRotatef (8,  0, 1, 0);  /* line up the time zones */
458   unit_sphere (12, 24, 1);
459   unit_sphere (12, 24, 1);
460   glBegin(GL_LINES);
461   glVertex3f(0, -2, 0);
462   glVertex3f(0,  2, 0);
463   glEnd();
464   glPopMatrix ();
465   glEndList();
466 }
467
468
469 ENTRYPOINT void
470 draw_planet (ModeInfo * mi)
471 {
472   planetstruct *gp = &planets[MI_SCREEN(mi)];
473   int wire = MI_IS_WIREFRAME(mi);
474   Display *dpy = MI_DISPLAY(mi);
475   Window window = MI_WINDOW(mi);
476   double x, y, z;
477
478   if (!gp->glx_context)
479         return;
480
481   glDrawBuffer(GL_BACK);
482   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
483
484   glXMakeCurrent (dpy, window, *(gp->glx_context));
485
486   mi->polygon_count = 0;
487
488   if (gp->button_down_p)
489     gp->draw_axis = 60;
490   else if (!gp->draw_axis && !(random() % 1000))
491     gp->draw_axis = 60 + (random() % 90);
492
493   if (do_rotate && !gp->button_down_p)
494     {
495       gp->z -= 0.001;     /* the sun sets in the west */
496       if (gp->z < 0) gp->z += 1;
497     }
498
499   glEnable(GL_LINE_SMOOTH);
500   glEnable(GL_POINT_SMOOTH);
501   glEnable(GL_DEPTH_TEST);
502   glEnable(GL_CULL_FACE);
503   glCullFace(GL_BACK); 
504
505   glPushMatrix();
506
507   get_position (gp->rot, &x, &y, &z, !gp->button_down_p);
508   x = (x - 0.5) * 6;
509   y = (y - 0.5) * 6;
510   z = (z - 0.5) * 3;
511   glTranslatef(x, y, z);
512
513   gltrackball_rotate (gp->trackball);
514
515   if (do_roll)
516     {
517       get_rotation (gp->rot, &x, &y, 0, !gp->button_down_p);
518       glRotatef (x * 360, 1.0, 0.0, 0.0);
519       glRotatef (y * 360, 0.0, 1.0, 0.0);
520     }
521   else
522     glRotatef (current_device_rotation(), 0, 0, 1);
523
524   if (do_stars)
525     {
526       glDisable(GL_TEXTURE_2D);
527       glPushMatrix();
528       glScalef (60, 60, 60);
529       glRotatef (90, 1, 0, 0);
530       glRotatef (35, 1, 0, 0);
531       glCallList (gp->starlist);
532       mi->polygon_count += gp->starcount;
533       glPopMatrix();
534       glClear(GL_DEPTH_BUFFER_BIT);
535     }
536
537   glRotatef (90, 1, 0, 0);
538   glRotatef (35, 1, 0, 0);
539   glRotatef (10, 0, 1, 0);
540   glRotatef (120, 0, 0, 1);
541
542   glScalef (3, 3, 3);
543
544 # ifdef HAVE_MOBILE
545   glScalef (2, 2, 2);
546 # endif
547
548   if (wire)
549     glColor3f (0.5, 0.5, 1);
550   else
551     glColor3f (1, 1, 1);
552
553   if (do_texture)
554     {
555       glEnable(GL_TEXTURE_2D);
556       glBindTexture (GL_TEXTURE_2D, gp->tex1);
557     }
558
559   glPushMatrix();
560   glRotatef (gp->z * 360, 0, 0, 1);
561   glCallList (gp->platelist);
562   mi->polygon_count += resolution*resolution;
563   glPopMatrix();
564
565   /* Originally we just used GL_LIGHT0 to produce the day/night sides of
566      the planet, but that always looked crappy, even with a vast number of
567      polygons, because the day/night terminator didn't exactly line up with
568      the polygon edges.
569
570      So instead, draw the full "day" sphere; clear the depth buffer; draw
571      a rotated/tilted half-sphere into the depth buffer only; then draw
572      the "night" sphere.  That lets us divide the sphere into the two maps,
573      and the dividing line can be at any angle, regardless of polygon layout.
574
575      The half-sphere is scaled slightly larger to avoid polygon fighting,
576      since those triangles won't exactly line up because of the rotation.
577
578      The downside of this is that the day/night terminator is 100% sharp.
579      It would be nice if it was a little blurry.
580    */
581
582   if (wire)
583     {
584       glPushMatrix();
585       glRotatef (gp->tilt, 1, 0, 0);
586       glColor3f(0, 0, 0);
587       glLineWidth(4);
588       glCallList (gp->shadowlist);
589       glLineWidth(1);
590       mi->polygon_count += resolution*(resolution/2);
591       glPopMatrix();
592     }
593   else if (do_texture && gp->tex2)
594     {
595       glClear(GL_DEPTH_BUFFER_BIT);
596       glDisable(GL_TEXTURE_2D);
597       glColorMask (0, 0, 0, 0);
598       glPushMatrix();
599       glRotatef (gp->tilt, 1, 0, 0);
600       glScalef (1.01, 1.01, 1.01);
601       glCallList (gp->shadowlist);
602       mi->polygon_count += resolution*(resolution/2);
603       glPopMatrix();
604       glColorMask (1, 1, 1, 1);
605       glEnable(GL_TEXTURE_2D);
606
607       glBindTexture (GL_TEXTURE_2D, gp->tex2);
608       glPushMatrix();
609       glRotatef (gp->z * 360, 0, 0, 1);
610       glCallList (gp->platelist);
611       mi->polygon_count += resolution*resolution;
612       glPopMatrix();
613     }
614
615   if (gp->draw_axis)
616     {
617       glPushMatrix();
618       glRotatef (gp->z * 360, 0.0, 0.0, 1.0);
619       glScalef (1.02, 1.02, 1.02);
620       glDisable (GL_TEXTURE_2D);
621       glDisable (GL_LIGHTING);
622       glDisable (GL_LINE_SMOOTH);
623       glColor3f (0.1, 0.3, 0.1);
624       glCallList (gp->latlonglist);
625       mi->polygon_count += 24*24;
626       glPopMatrix();
627       if (gp->draw_axis) gp->draw_axis--;
628     }
629   glPopMatrix();
630
631   if (mi->fps_p) do_fps (mi);
632   glFinish();
633   glXSwapBuffers(dpy, window);
634 }
635
636
637 ENTRYPOINT void
638 free_planet (ModeInfo * mi)
639 {
640   planetstruct *gp = &planets[MI_SCREEN(mi)];
641
642   if (gp->glx_context) {
643         glXMakeCurrent(MI_DISPLAY(mi), gp->window, *(gp->glx_context));
644
645         if (glIsList(gp->platelist))
646           glDeleteLists(gp->platelist, 1);
647         if (glIsList(gp->starlist))
648           glDeleteLists(gp->starlist, 1);
649   }
650 }
651
652
653 XSCREENSAVER_MODULE_2 ("GLPlanet", glplanet, planet)
654
655 #endif