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