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