From http://www.jwz.org/xscreensaver/xscreensaver-5.30.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 (gltrackball_event_handler (event, gp->trackball,
405                                  MI_WIDTH (mi), MI_HEIGHT (mi),
406                                  &gp->button_down_p))
407     return True;
408
409   return False;
410 }
411
412
413 ENTRYPOINT void
414 init_planet (ModeInfo * mi)
415 {
416   planetstruct *gp;
417   int screen = MI_SCREEN(mi);
418   Bool wire = MI_IS_WIREFRAME(mi);
419
420   if (planets == NULL) {
421         if ((planets = (planetstruct *) calloc(MI_NUM_SCREENS(mi),
422                                                                                   sizeof (planetstruct))) == NULL)
423           return;
424   }
425   gp = &planets[screen];
426
427   if ((gp->glx_context = init_GL(mi)) != NULL) {
428         reshape_planet(mi, MI_WIDTH(mi), MI_HEIGHT(mi));
429   }
430
431   {
432         char *f = get_string_resource(mi->dpy, "imageForeground", "Foreground");
433         char *b = get_string_resource(mi->dpy, "imageBackground", "Background");
434         char *s;
435         if (!f) f = strdup("white");
436         if (!b) b = strdup("black");
437         
438         for (s = f + strlen(f)-1; s > f; s--)
439           if (*s == ' ' || *s == '\t')
440                 *s = 0;
441         for (s = b + strlen(b)-1; s > b; s--)
442           if (*s == ' ' || *s == '\t')
443                 *s = 0;
444
445     if (!XParseColor(mi->dpy, mi->xgwa.colormap, f, &gp->fg))
446       {
447                 fprintf(stderr, "%s: unparsable color: \"%s\"\n", progname, f);
448                 exit(1);
449       }
450     if (!XParseColor(mi->dpy, mi->xgwa.colormap, b, &gp->bg))
451       {
452                 fprintf(stderr, "%s: unparsable color: \"%s\"\n", progname, f);
453                 exit(1);
454       }
455
456         free (f);
457         free (b);
458   }
459
460   {
461     double spin_speed   = 0.5;
462     double wander_speed = 0.02;
463     gp->rot = make_rotator (do_roll ? spin_speed : 0,
464                             do_roll ? spin_speed : 0,
465                             0, 1,
466                             do_wander ? wander_speed : 0,
467                             True);
468     gp->z = frand (1.0);
469     gp->trackball = gltrackball_init (True);
470   }
471
472   if (wire)
473     {
474       do_texture = False;
475       do_light = False;
476     }
477
478   if (do_texture)
479     setup_texture (mi);
480
481   if (do_light)
482         init_sun (mi);
483
484   if (do_stars)
485     init_stars (mi);
486
487   if (random() & 1)
488     star_spin = -star_spin;
489
490   /* construct the polygons of the planet
491    */
492   gp->platelist = glGenLists(1);
493   glNewList (gp->platelist, GL_COMPILE);
494   glColor3f (1,1,1);
495   glPushMatrix ();
496   glScalef (RADIUS, RADIUS, RADIUS);
497   glRotatef (90, 1, 0, 0);
498   glFrontFace(GL_CCW);
499   unit_sphere (resolution, resolution, wire);
500   glPopMatrix ();
501   glEndList();
502
503   /* construct the polygons of the latitude/longitude/axis lines.
504    */
505   gp->latlonglist = glGenLists(1);
506   glNewList (gp->latlonglist, GL_COMPILE);
507   glPushMatrix ();
508   glDisable (GL_TEXTURE_2D);
509   glDisable (GL_LIGHTING);
510   glDisable (GL_LINE_SMOOTH);
511   glColor3f (0.1, 0.3, 0.1);
512   glScalef (RADIUS, RADIUS, RADIUS);
513   glScalef (1.01, 1.01, 1.01);
514   glRotatef (90, 1, 0, 0);
515   unit_sphere (12, 24, 1);
516   glBegin(GL_LINES);
517   glVertex3f(0, -2, 0);
518   glVertex3f(0,  2, 0);
519   glEnd();
520   glPopMatrix ();
521   glEndList();
522 }
523
524 ENTRYPOINT void
525 draw_planet (ModeInfo * mi)
526 {
527   planetstruct *gp = &planets[MI_SCREEN(mi)];
528   Display    *display = MI_DISPLAY(mi);
529   Window      window = MI_WINDOW(mi);
530   double x, y, z;
531
532   if (!gp->glx_context)
533         return;
534
535   glDrawBuffer(GL_BACK);
536   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
537
538   glXMakeCurrent (display, window, *(gp->glx_context));
539
540   mi->polygon_count = 0;
541
542   if (do_stars)
543     {
544       draw_stars (mi);
545       mi->polygon_count += NUM_STARS;
546     }
547
548   if (do_light)   glEnable(GL_LIGHTING);
549   if (do_texture) glEnable(GL_TEXTURE_2D);
550
551   glEnable (GL_LINE_SMOOTH);
552   glEnable(GL_DEPTH_TEST);
553   glEnable(GL_CULL_FACE);
554   glCullFace(GL_BACK); 
555
556   glPushMatrix();
557
558   get_position (gp->rot, &x, &y, &z, !gp->button_down_p);
559   glTranslatef((x - 0.5) * 15,
560                (y - 0.5) * 15,
561                (z - 0.5) * 8);
562
563   gltrackball_rotate (gp->trackball);
564
565   glRotatef (90,1,0,0);
566
567   if (do_roll)
568     {
569       get_rotation (gp->rot, &x, &y, 0, !gp->button_down_p);
570       glRotatef (x * 360, 1.0, 0.0, 0.0);
571       glRotatef (y * 360, 0.0, 1.0, 0.0);
572     }
573
574   glLightfv (GL_LIGHT0, GL_POSITION, gp->sunpos);
575
576   glRotatef (gp->z * 360, 0.0, 0.0, 1.0);
577   if (do_rotate && !gp->button_down_p)
578     {
579       gp->z -= 0.005;     /* the sun sets in the west */
580       if (gp->z < 0) gp->z += 1;
581     }
582
583   glCallList (gp->platelist);
584   mi->polygon_count += resolution*resolution;
585
586   if (gp->button_down_p)
587     {
588       glCallList (gp->latlonglist);
589       mi->polygon_count += 24*24;
590     }
591   glPopMatrix();
592
593   if (mi->fps_p) do_fps (mi);
594   glFinish();
595   glXSwapBuffers(display, window);
596
597   gp->star_theta += star_spin;
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