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