From http://www.jwz.org/xscreensaver/xscreensaver-5.40.tar.gz
[xscreensaver] / hacks / glx / dymaxionmap.c
1 /* dymaxionmap --- Buckminster Fuller's unwrapped icosahedral globe.
2  * Copyright (c) 2016-2018 Jamie Zawinski.
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
17 #define LABEL_FONT "-*-helvetica-bold-r-normal-*-*-240-*-*-*-*-*-*"
18
19 #ifdef STANDALONE
20 #define DEFAULTS    "*delay:            20000   \n" \
21                     "*showFPS:          False   \n" \
22                     "*wireframe:        False   \n" \
23                     "*labelFont:  " LABEL_FONT "\n"
24 # define release_planet 0
25 # include "xlockmore.h"             /* from the xscreensaver distribution */
26 #else  /* !STANDALONE */
27 # include "xlock.h"                 /* from the xlockmore distribution */
28 #endif /* !STANDALONE */
29
30 #ifdef USE_GL /* whole file */
31
32 #include "sphere.h"
33 #include "normals.h"
34 #include "texfont.h"
35 #include "dymaxionmap-coords.h"
36
37 #ifdef HAVE_XMU
38 # ifndef VMS
39 #  include <X11/Xmu/Drawing.h>
40 #else  /* VMS */
41 #  include <Xmu/Drawing.h>
42 # endif /* VMS */
43 #endif
44
45 #define DEF_ROTATE  "True"
46 #define DEF_ROLL    "True"
47 #define DEF_WANDER  "True"
48 #define DEF_TEXTURE "True"
49 #define DEF_STARS   "True"
50 #define DEF_GRID    "True"
51 #define DEF_SPEED   "1.0"
52 #define DEF_IMAGE   "BUILTIN_FLAT"
53 #define DEF_IMAGE2  "NONE"
54 #define DEF_FRAMES  "720"
55
56 #undef countof
57 #define countof(x) (sizeof((x))/sizeof((*x)))
58
59 #undef BELLRAND
60 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
61 #undef RANDSIGN
62 #define RANDSIGN() ((random() & 1) ? 1 : -1)
63
64 static int do_roll;
65 static int do_wander;
66 static int do_texture;
67 static int do_stars;
68 static int do_grid;
69 static int frames;
70 static GLfloat speed;
71 static char *which_image;
72 static char *which_image2;
73
74 static XrmOptionDescRec opts[] = {
75   {"-speed",    ".speed",   XrmoptionSepArg, 0 },
76   {"-roll",     ".roll",    XrmoptionNoArg, "true" },
77   {"+roll",     ".roll",    XrmoptionNoArg, "false" },
78   {"-wander",   ".wander",  XrmoptionNoArg, "true" },
79   {"+wander",   ".wander",  XrmoptionNoArg, "false" },
80   {"-texture",  ".texture", XrmoptionNoArg, "true" },
81   {"+texture",  ".texture", XrmoptionNoArg, "false" },
82   {"-stars",    ".stars",   XrmoptionNoArg, "true" },
83   {"+stars",    ".stars",   XrmoptionNoArg, "false" },
84   {"-grid",     ".grid",    XrmoptionNoArg, "true" },
85   {"+grid",     ".grid",    XrmoptionNoArg, "false" },
86   {"-flat",     ".image",   XrmoptionNoArg, "BUILTIN_FLAT" },
87   {"-satellite",".image",   XrmoptionNoArg, "BUILTIN_SAT"  },
88   {"-image",    ".image",   XrmoptionSepArg, 0 },
89   {"-image2",   ".image2",  XrmoptionSepArg, 0 },
90   {"-frames",   ".frames",  XrmoptionSepArg, 0 },
91 };
92
93 static argtype vars[] = {
94   {&speed,       "speed",   "Speed",   DEF_SPEED,   t_Float},
95   {&do_roll,     "roll",    "Roll",    DEF_ROLL,    t_Bool},
96   {&do_wander,   "wander",  "Wander",  DEF_WANDER,  t_Bool},
97   {&do_texture,  "texture", "Texture", DEF_TEXTURE, t_Bool},
98   {&do_stars,    "stars",   "Stars",   DEF_STARS,   t_Bool},
99   {&do_grid,     "grid",    "Grid",    DEF_GRID,    t_Bool},
100   {&which_image, "image",   "Image",   DEF_IMAGE,   t_String},
101   {&which_image2,"image2",  "Image2",  DEF_IMAGE2,  t_String},
102   {&frames,      "frames",  "Frames",  DEF_FRAMES,  t_Int},
103 };
104
105 ENTRYPOINT ModeSpecOpt planet_opts = {countof(opts), opts, countof(vars), vars, NULL};
106
107 #ifdef USE_MODULES
108 ModStruct   planet_description =
109 {"planet", "init_planet", "draw_planet", NULL,
110  "draw_planet", "init_planet", "free_planet", &planet_opts,
111  1000, 1, 2, 1, 4, 1.0, "",
112  "Buckminster Fuller's unwrapped icosahedral globe", 0, NULL};
113 #endif
114
115 # ifdef __GNUC__
116   __extension__  /* don't warn about "string length is greater than the length
117                     ISO C89 compilers are required to support" when including
118                     the following XPM file... */
119 # endif
120
121 #include "images/gen/earth_flat_png.h"
122 #include "images/gen/earth_png.h"
123 #include "images/gen/earth_night_png.h"
124 #include "images/gen/ground_png.h"
125
126 #include "ximage-loader.h"
127 #include "rotator.h"
128 #include "gltrackball.h"
129
130
131 typedef struct {
132   GLXContext *glx_context;
133   GLuint starlist;
134   int starcount;
135   rotator *rot, *rot2;
136   trackball_state *trackball;
137   Bool button_down_p;
138   enum { STARTUP, FLAT, FOLD, 
139          ICO, STEL_IN, AXIS, SPIN, STEL, STEL_OUT,
140          ICO2, UNFOLD } state;
141   GLfloat ratio;
142   GLuint tex1, tex2;
143   texture_font_data *font_data;
144   int loading_sw, loading_sh;
145
146   XImage *day, *night, *dusk, *cvt;
147   XImage **images;  /* One image for each frame of time-of-day. */
148   int nimages;
149
150   double current_frame;
151   Bool cache_p;
152   long delay;
153
154 } planetstruct;
155
156
157 static planetstruct *planets = NULL;
158
159
160 static double
161 double_time (void)
162 {
163   struct timeval now;
164 # ifdef GETTIMEOFDAY_TWO_ARGS
165   struct timezone tzp;
166   gettimeofday(&now, &tzp);
167 # else
168   gettimeofday(&now);
169 # endif
170
171   return (now.tv_sec + ((double) now.tv_usec * 0.000001));
172 }
173
174
175 /* Draw faint latitude and longitude lines into the RGBA XImage.
176  */
177 static void
178 add_grid_lines (XImage *image)
179 {
180   int i;
181   int off = 24;
182   for (i = 0; i < 24; i++)
183     {
184       int x = (i + 0.5) * image->width / (double) 24;
185       int y;
186       for (y = 0; y < image->height; y++)
187         {
188           unsigned long rgba = XGetPixel (image, x, y);
189           int r = (rgba >> 24) & 0xFF;
190           int g = (rgba >> 16) & 0xFF;
191           int b = (rgba >>  8) & 0xFF;
192           int a = (rgba >>  0) & 0xFF;
193           int off2 = (((r + g + b) / 3) < 0x7F ? off : -off);
194           r = MAX (0, MIN (0xFF, r + off2));
195           g = MAX (0, MIN (0xFF, g + off2));
196           b = MAX (0, MIN (0xFF, b + off2));
197           XPutPixel (image, x, y, (r << 24) | (g << 16) | (b << 8) | a);
198         }
199     }
200
201   for (i = 1; i < 11; i++)
202     {
203       int y = i * image->height / (double) 12;
204       int x;
205       for (x = 0; x < image->width; x++)
206         {
207           unsigned long rgba = XGetPixel (image, x, y);
208           int r = (rgba >> 24) & 0xFF;
209           int g = (rgba >> 16) & 0xFF;
210           int b = (rgba >>  8) & 0xFF;
211           int a = (rgba >>  0) & 0xFF;
212           int off2 = (((r + g + b) / 3) < 0x7F ? off : -off);
213           r = MAX (0, MIN (0xFF, r + off2));
214           g = MAX (0, MIN (0xFF, g + off2));
215           b = MAX (0, MIN (0xFF, b + off2));
216           XPutPixel (image, x, y, (r << 24) | (g << 16) | (b << 8) | a);
217         }
218     }
219 }
220
221
222 static void
223 adjust_brightness (XImage *image, double amount)
224 {
225   uint32_t *in = (uint32_t *) image->data;
226   int i;
227   int end = image->height * image->bytes_per_line / 4;
228   for (i = 0; i < end; i++)
229     {
230       uint32_t p = *in;
231       /* #### Why is this ABGR instead of RGBA? */
232       uint32_t a = (p >> 24) & 0xFF;
233       uint32_t g = (p >> 16) & 0xFF;
234       uint32_t b = (p >>  8) & 0xFF;
235       uint32_t r = (p >>  0) & 0xFF;
236       r = MAX(0, MIN(0xFF, (long) (r * amount)));
237       g = MAX(0, MIN(0xFF, (long) (g * amount)));
238       b = MAX(0, MIN(0xFF, (long) (b * amount)));
239       p = (a << 24) | (g << 16) | (b << 8) | r;
240       *in++ = p;
241     }
242 }
243
244
245 static GLfloat
246 vector_angle (XYZ a, XYZ b)
247 {
248   double La = sqrt (a.x*a.x + a.y*a.y + a.z*a.z);
249   double Lb = sqrt (b.x*b.x + b.y*b.y + b.z*b.z);
250   double cc, angle;
251
252   if (La == 0 || Lb == 0) return 0;
253   if (a.x == b.x && a.y == b.y && a.z == b.z) return 0;
254
255   /* dot product of two vectors is defined as:
256        La * Lb * cos(angle between vectors)
257      and is also defined as:
258        ax*bx + ay*by + az*bz
259      so:
260       La * Lb * cos(angle) = ax*bx + ay*by + az*bz
261       cos(angle)  = (ax*bx + ay*by + az*bz) / (La * Lb)
262       angle = acos ((ax*bx + ay*by + az*bz) / (La * Lb));
263   */
264   cc = (a.x*b.x + a.y*b.y + a.z*b.z) / (La * Lb);
265   if (cc > 1) cc = 1;  /* avoid fp rounding error (1.000001 => sqrt error) */
266   angle = acos (cc);
267
268   return (angle);
269 }
270
271
272 /* Creates a grayscale image encoding the day/night terminator for a random
273    axial tilt.
274  */
275 static XImage *
276 create_daylight_mask (Display *dpy, Visual *v, int w, int h)
277 {
278   XImage *image = XCreateImage (dpy, v, 8, ZPixmap, 0, 0, w, h, 8, 0);
279   int x, y;
280   XYZ sun;
281   double axial_tilt = frand(23.4) / (180/M_PI) * RANDSIGN();
282   double dusk = M_PI * 0.035;
283   sun.x = 0;
284   sun.y = cos (axial_tilt);
285   sun.z = sin (axial_tilt);
286
287   image->data = (char *) malloc (image->height * image->bytes_per_line);
288
289   for (y = 0; y < image->height; y++)
290     {
291       double lat = -M_PI_2 + (M_PI * (y / (double) image->height));
292       double cosL = cos(lat);
293       double sinL = sin(lat);
294       for (x = 0; x < image->width; x++)
295         {
296           double lon = -M_PI_2 + (M_PI * 2 * (x / (double) image->width));
297           XYZ v;
298           double a;
299           unsigned long p;
300           v.x = cos(lon) * cosL;
301           v.y = sin(lon) * cosL;
302           v.z = sinL;
303           a = vector_angle (sun, v);
304           a -= M_PI_2;
305           a = (a < -dusk ? 1 : a >= dusk ? 0 : (dusk - a) / (dusk * 2));
306           p = 0xFF & (unsigned long) (a * 0xFF);
307           XPutPixel (image, x, y, p);
308         }
309     }
310   return image;
311 }
312
313
314 static void
315 load_images (ModeInfo *mi)
316 {
317   planetstruct *gp = &planets[MI_SCREEN(mi)];
318   int i;
319
320   if (which_image && !strcmp (which_image, "BUILTIN_FLAT"))
321     {
322       which_image  = strdup("BUILTIN_FLAT");
323       which_image2 = strdup("BUILTIN_FLAT");
324     }
325   else if (which_image && !strcmp (which_image, "BUILTIN_SAT"))
326     {
327       which_image  = strdup("BUILTIN_DAY");
328       which_image2 = strdup("BUILTIN_NIGHT");
329     }
330
331   if (!which_image)  which_image  = strdup("");
332   if (!which_image2) which_image2 = strdup("");
333
334   for (i = 0; i < 2; i++)
335     {
336       char *s = (i == 0 ? which_image : which_image2);
337       XImage *image;
338       if (!strcmp (s, "BUILTIN_DAY"))
339         image = image_data_to_ximage (MI_DISPLAY (mi), MI_VISUAL (mi),
340                                       earth_png, sizeof(earth_png));
341       else if (!strcmp (s, "BUILTIN_NIGHT"))
342         image = image_data_to_ximage (MI_DISPLAY (mi), MI_VISUAL (mi),
343                                       earth_night_png,sizeof(earth_night_png));
344       else if (!strcmp (s, "BUILTIN") ||
345                !strcmp (s, "BUILTIN_FLAT") ||
346                (i == 0 && !strcmp (s, "")))
347         image = image_data_to_ximage (MI_DISPLAY (mi), MI_VISUAL (mi),
348                                       earth_flat_png, sizeof(earth_flat_png));
349       else if (!strcmp (s, "NONE"))
350         image = 0;
351       else if (*s)
352         image = file_to_ximage (MI_DISPLAY (mi), MI_VISUAL (mi), s);
353       else
354         image = 0;
355
356       /* if (image) fprintf (stderr, "%s: %d: loaded %s\n", progname, i, s); */
357
358       if (i == 0)
359         gp->day = image;
360       else
361         gp->night = image;
362     }
363
364   if (gp->night && !gp->day) 
365     gp->day = gp->night, gp->night = 0;
366
367   gp->nimages = frames;
368   gp->current_frame = random() % gp->nimages;
369
370   if (gp->nimages < 1)
371     gp->nimages = 1;
372
373   if (gp->nimages < 2 && gp->night)
374     {
375       XDestroyImage (gp->night);
376       gp->night = 0;
377       gp->nimages = 1;
378     }
379
380   if (! gp->night)
381     gp->nimages = 1;
382
383   if (do_grid)
384     {
385       if (gp->day)   add_grid_lines (gp->day);
386       if (gp->night) add_grid_lines (gp->night);
387     }
388
389   if (gp->day && gp->night && !gp->dusk)
390     {
391       if (gp->day->width  != gp->night->width ||
392           gp->day->height != gp->night->height)
393         {
394           fprintf (stderr, "%s: day and night images must be the same size"
395                    " (%dx%d vs %dx%d)\n", progname,
396                    gp->day->width, gp->day->height,
397                    gp->night->width, gp->night->height);
398           exit (1);
399         }
400       gp->dusk = create_daylight_mask (MI_DISPLAY (mi), MI_VISUAL (mi),
401                                        gp->day->width, gp->day->height);
402     }
403
404   /* Make the day image brighter, because that's easier than doing it
405      with GL lights. */
406   adjust_brightness (gp->day, 1.4);
407
408   if (!strcmp (which_image, which_image2))
409     /* If day and night are the same image, make night way darker. */
410     adjust_brightness (gp->night, 0.2);
411   else if (gp->night)
412     /* Otherwise make it just a little darker. */
413     adjust_brightness (gp->night, 0.7);
414
415
416   gp->images = (XImage **) calloc (gp->nimages, sizeof(*gp->images));
417
418   /* Create 'cvt', a map that projects each pixel from Equirectangular to
419      Dymaxion.  It is 2x the width/height of the source images. We iterate
420      by half pixel to make sure we hit every pixel in 'out'. It would be
421      cleaner to iterate over 'out' instead of over 'in' but
422      dymaxionmap-coords.c only goes forward. This is... not super fast.
423    */
424   {
425     double W = 5.5;
426     double H = 3 * sqrt(3)/2;
427     int x2, y2;
428     int w = gp->day->width;
429     int h = gp->day->height;
430     uint32_t *out;
431
432     gp->cvt = XCreateImage (MI_DISPLAY(mi), MI_VISUAL(mi), 32, ZPixmap, 0, 0,
433                             gp->day->width*2, gp->day->height*2, 32, 0);
434     gp->cvt->data = (char *)
435       malloc (gp->cvt->height * gp->cvt->bytes_per_line);
436     out = (uint32_t *) gp->cvt->data;
437
438     for (y2 = 0; y2 < h*2; y2++)
439       {
440         double y = (double) y2/2;
441         double lat = -90 + (180 * (y / (double) h));
442         for (x2 = 0; x2 < w*2; x2++)
443           {
444             double x = (double) x2/2;
445             double lon = -180 + (360 * x / w);
446             double ox, oy;
447             dymaxion_convert (lon, lat, &ox, &oy);
448             ox = w - (w * ox / W);
449             oy =     (h * oy / H);
450
451             *out++ = (((((uint32_t) ox) & 0xFFFF) << 16) |
452                       ((((uint32_t) oy) & 0xFFFF)));
453           }
454       }
455   }
456
457   /* A 128 GB iPhone 6s dies at around 540 frames, ~1 GB of XImages.
458      A 16 GB iPad Air 2 dies at around 320 frames, ~640 MB.
459      Caching on mobile doesn't matter much: we can just run at 100% CPU.
460
461      On some systems it would be more efficient to cache the images inside
462      a texture on the GPU instead of moving it from RAM to GPU every few
463      frames; but on other systems, we'd just run out of GPU memory instead. */
464   {
465     unsigned long cache_size = (gp->day->width * gp->day->height * 4 *
466                                 gp->nimages);
467 # ifdef HAVE_MOBILE
468     unsigned long max = 320 * 1024 * 1024L;             /* 320 MB */
469 # else
470     unsigned long max = 2 * 1024 * 1024 * 1024L;        /* 2 GB */
471 # endif
472     gp->cache_p = (cache_size < max);
473   }
474 }
475
476
477 static void
478 cache_current_frame (ModeInfo *mi)
479 {
480   planetstruct *gp = &planets[MI_SCREEN(mi)];
481   XImage *blended, *dymaxion;
482   int i, x, y, w, h, end, xoff;
483   uint32_t *day, *night, *cvt, *out;
484   uint8_t *dusk;
485
486   if (gp->images[(int) gp->current_frame])
487     return;
488
489   xoff = (gp->dusk
490           ? gp->dusk->width * ((double) gp->current_frame / gp->nimages)
491           : 0);
492
493   w = gp->day->width;
494   h = gp->day->height;
495
496   if (!gp->night)
497     blended = gp->day;
498   else
499     {
500       /* Blend the foreground and background images through the dusk map.
501        */
502       blended = XCreateImage (MI_DISPLAY(mi), MI_VISUAL(mi), 
503                               gp->day->depth, ZPixmap, 0, 0, w, h, 32, 0);
504       if (!blended) abort();
505       blended->data = (char *) malloc (h * blended->bytes_per_line);
506       if (!blended->data) abort();
507
508       end = blended->height * blended->bytes_per_line / 4;
509       day   = (uint32_t *) gp->day->data;
510       night = (uint32_t *) gp->night->data;
511       dusk  = (uint8_t  *) gp->dusk->data;
512       out   = (uint32_t *) blended->data;
513
514       for (i = 0; i < end; i++)
515         {
516           uint32_t d = *day++;
517           uint32_t n = *night++;
518           uint32_t x = i % w;
519           uint32_t y = i / w;
520           double r = dusk[y * w + ((x + xoff) % w)] / 256.0;
521           double r2 = 1-r;
522 # define ADD(M) (((unsigned long)             \
523                   ((((d >> M) & 0xFF) * r) +  \
524                    (((n >> M) & 0xFF) * r2))) \
525                  << M)
526           /* #### Why is this ABGR instead of RGBA? */
527           *out++ = (0xFF << 24) | ADD(16) | ADD(8) | ADD(0);
528 # undef ADD
529         }
530     }
531
532   /* Convert blended Equirectangular to Dymaxion through the 'cvt' map.
533    */
534   dymaxion = XCreateImage (MI_DISPLAY(mi), MI_VISUAL(mi), 
535                           gp->day->depth, ZPixmap, 0, 0, w, h, 32, 0);
536   dymaxion->data = (char *) calloc (h, dymaxion->bytes_per_line);
537   
538   day = (uint32_t *) blended->data;
539   out = (uint32_t *) dymaxion->data;
540   cvt = (uint32_t *) gp->cvt->data;
541
542   for (y = 0; y < h*2; y++)
543     for (x = 0; x < w*2; x++)
544       {
545         unsigned long m  = *cvt++;
546         unsigned long dx = (m >> 16) & 0xFFFF;
547         unsigned long dy = m & 0xFFFF;
548         unsigned long p  = day[(y>>1) * w + (x>>1)];
549         unsigned long p2 = out[dy * w + dx];
550         if (p2 & 0xFF000000)
551           /* RGBA nonzero alpha: initialized. Average with existing,
552              otherwise the grid lines look terrible. */
553           p = (((((p>>24) & 0xFF) + ((p2>>24) & 0xFF)) >> 1) << 24 |
554                ((((p>>16) & 0xFF) + ((p2>>16) & 0xFF)) >> 1) << 16 |
555                ((((p>> 8) & 0xFF) + ((p2>> 8) & 0xFF)) >> 1) <<  8 |
556                ((((p>> 0) & 0xFF) + ((p2>> 0) & 0xFF)) >> 1) <<  0);
557         out[dy * w + dx] = p;
558       }
559
560   /* Fill in the triangles that are not a part of The World with the
561      color of the ocean to avoid texture-tearing on the folded edges.
562   */
563   out = (uint32_t *) dymaxion->data;
564   end = dymaxion->height * dymaxion->bytes_per_line / 4;
565   {
566     double lat = -48.44, lon = -123.39;   /* R'Lyeh */
567     int x = (lon + 180) * blended->width  / 360.0;
568     int y = (lat + 90)  * blended->height / 180.0;
569     unsigned long ocean = XGetPixel (gp->day, x, y);
570     for (i = 0; i < end; i++)
571       {
572         uint32_t p = *out;
573         if (! (p & 0xFF000000)) /* AGBR */
574           *out = ocean;
575         out++;
576       }
577   }
578
579   if (blended != gp->day)
580     XDestroyImage (blended);
581
582   gp->images[(int) gp->current_frame] = dymaxion;
583
584   if (!gp->cache_p)  /* Keep only one image around; recompute every time. */
585     {
586       i = ((int) gp->current_frame) - 1;
587       if (i < 0) i = gp->nimages - 1;
588       if (gp->images[i])
589         {
590           XDestroyImage (gp->images[i]);
591           gp->images[i] = 0;
592         }
593     }
594 }
595
596
597 static void
598 setup_texture (ModeInfo * mi)
599 {
600   planetstruct *gp = &planets[MI_SCREEN(mi)];
601   XImage *ground;
602
603   glGenTextures (1, &gp->tex1);
604   glBindTexture (GL_TEXTURE_2D, gp->tex1);
605
606   /* Must be after glBindTexture */
607   glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
608   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
609   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
610   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
611   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
612
613   load_images (mi);
614
615   glGenTextures (1, &gp->tex2);
616   glBindTexture (GL_TEXTURE_2D, gp->tex2);
617
618   /* Must be after glBindTexture */
619   glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
620   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
621   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
622   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
623   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
624
625   /* The underground image can go on flat, without the dymaxion transform. */
626   ground = image_data_to_ximage (MI_DISPLAY (mi), MI_VISUAL (mi),
627                                  ground_png, sizeof(ground_png));
628   clear_gl_error();
629   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
630   /* glPixelStorei(GL_UNPACK_ROW_LENGTH, ground->width); */
631   glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA,
632                 ground->width, ground->height, 0,
633                 GL_RGBA, GL_UNSIGNED_BYTE, ground->data);
634   check_gl_error ("ground texture");
635   XDestroyImage (ground);
636 }
637
638
639 static void
640 init_stars (ModeInfo *mi)
641 {
642   planetstruct *gp = &planets[MI_SCREEN(mi)];
643   int i, j;
644   int width  = MI_WIDTH(mi);
645   int height = MI_HEIGHT(mi);
646   int size = (width > height ? width : height);
647   int nstars = size * size / 80;
648   int max_size = 3;
649   GLfloat inc = 0.5;
650   int steps = max_size / inc;
651   GLfloat scale = 1;
652
653   if (MI_WIDTH(mi) > 2560) {  /* Retina displays */
654     scale *= 2;
655     nstars /= 2;
656   }
657
658   gp->starlist = glGenLists(1);
659   glNewList(gp->starlist, GL_COMPILE);
660   for (j = 1; j <= steps; j++)
661     {
662       glPointSize(inc * j * scale);
663       glBegin (GL_POINTS);
664       for (i = 0; i < nstars / steps; i++)
665         {
666           GLfloat d = 0.1;
667           GLfloat r = 0.15 + frand(0.3);
668           GLfloat g = r + frand(d) - d;
669           GLfloat b = r + frand(d) - d;
670
671           GLfloat x = frand(1)-0.5;
672           GLfloat y = frand(1)-0.5;
673           GLfloat z = ((random() & 1)
674                        ? frand(1)-0.5
675                        : (BELLRAND(1)-0.5)/12);   /* milky way */
676           d = sqrt (x*x + y*y + z*z);
677           x /= d;
678           y /= d;
679           z /= d;
680           glColor3f (r, g, b);
681           glVertex3f (x, y, z);
682           gp->starcount++;
683         }
684       glEnd ();
685     }
686   glEndList ();
687
688   check_gl_error("stars initialization");
689 }
690
691
692 ENTRYPOINT void
693 reshape_planet (ModeInfo *mi, int width, int height)
694 {
695   GLfloat h = (GLfloat) height / (GLfloat) width;
696
697   glViewport(0, 0, (GLint) width, (GLint) height);
698   glMatrixMode(GL_PROJECTION);
699   glLoadIdentity();
700   glFrustum(-1.0, 1.0, -h, h, 5.0, 200.0);
701   glMatrixMode(GL_MODELVIEW);
702   glLoadIdentity();
703   glTranslatef(0.0, 0.0, -40);
704
705 # ifdef HAVE_MOBILE     /* Keep it the same relative size when rotated. */
706   {
707     int o = (int) current_device_rotation();
708     if (o != 0 && o != 180 && o != -180)
709       glScalef (h, h, h);
710   }
711 # endif
712
713   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
714 }
715
716
717 static void
718 do_normal2 (ModeInfo *mi, Bool frontp, XYZ a, XYZ b, XYZ c)
719 {
720   XYZ n = (frontp
721            ? calc_normal (a, b, c)
722            : calc_normal (b, a, c));
723   glNormal3f (n.x, n.y, n.z);
724
725 # if 0
726   if (frontp && MI_IS_WIREFRAME(mi))
727     {
728       glBegin (GL_LINES);
729       glVertex3f ((a.x + b.x + c.x) / 3,
730                   (a.y + b.y + c.y) / 3,
731                   (a.z + b.z + c.z) / 3);
732       glVertex3f ((a.x + b.x + c.x) / 3 + n.x,
733                   (a.y + b.y + c.y) / 3 + n.y,
734                   (a.z + b.z + c.z) / 3 + n.z);
735       glEnd();
736     }
737 # endif
738 }
739
740
741 static void
742 triangle0 (ModeInfo *mi, Bool frontp, GLfloat stel_ratio, int facemask, 
743            XYZ *corners_ret)
744 {
745   /* Render a triangle as six sub-triangles.
746      Facemask bits 0-5 indicate which sub-triangle to draw.
747
748                 A
749                / \
750               / | \
751              /  |  \
752             / 0 | 1 \
753         E  /_   |   _\  F
754           /  \_ | _/  \
755          / 5   \D/   2 \
756         /    /  |  \    \
757        /   / 4  | 3  \   \
758       /  /      |       \ \
759    B ----------------------- C
760                 G
761    */
762
763   Bool wire = MI_IS_WIREFRAME(mi);
764   GLfloat h = sqrt(3) / 2;
765   GLfloat h2 = sqrt(h*h - (h/2)*(h/2)) - 0.5;
766   XYZ  A,  B,  C,  D,  E,  F,  G;
767   XYZ tA, tB, tC, tD, tE, tF, tG;
768   XYZ  a,  b,  c;
769   XYZ ta, tb, tc;
770   A.x =  0;   A.y = h;   A.z = 0;
771   B.x = -0.5, B.y = 0;   B.z = 0;
772   C.x =  0.5, C.y = 0;   C.z = 0;
773   D.x =  0;   D.y = h/3; D.z = 0;
774   E.x = -h2;  E.y = h/2; E.z = 0;
775   F.x =  h2;  F.y = h/2; F.z = 0;
776   G.x =  0;   G.y = 0;   G.z = 0;
777
778   /* When tweaking object XY to stellate, don't change texture coordinates. */
779   tA = A; tB = B; tC = C; tD = D; tE = E; tF = F; tG = G;
780
781   /* Eyeballed this to find the depth of stellation that seems to most
782      approximate a sphere.
783    */
784   D.z = 0.193 * stel_ratio;
785
786   /* We want to raise E, F and G as well but we can't just shift Z:
787      we need to keep them on the same vector from the center of the sphere,
788      which means also changing F and G's X and Y.
789   */
790   E.z = F.z = G.z = 0.132 * stel_ratio;
791   {
792     double magic_x = 0.044;
793     double magic_y = 0.028;
794     /* G.x stays 0 */
795     G.y -= sqrt (magic_x*magic_x + magic_y*magic_y) * stel_ratio;
796     E.x -= magic_x * stel_ratio;
797     E.y += magic_y * stel_ratio;
798     F.x += magic_x * stel_ratio;
799     F.y += magic_y * stel_ratio;
800   }
801
802
803   if (facemask & 1<<0)
804     {
805       a  =  E;  b =  D;  c =  A;
806       ta = tE; tb = tD; tc = tA;
807       do_normal2 (mi, frontp, a, b, c);
808       glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLES);
809       glTexCoord2f (ta.x, ta.y); glVertex3f (a.x, a.y, a.z);
810       glTexCoord2f (tb.x, tb.y); glVertex3f (b.x, b.y, b.z);
811       glTexCoord2f (tc.x, tc.y); glVertex3f (c.x, c.y, c.z);
812       glEnd();
813       mi->polygon_count++;
814     }
815   if (facemask & 1<<1)
816     {
817       a  =  D;  b =  F;  c =  A;
818       ta = tD; tb = tF; tc = tA;
819       do_normal2 (mi, frontp, a, b, c);
820       glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLES);
821       glTexCoord2f (ta.x, ta.y); glVertex3f (a.x, a.y, a.z);
822       glTexCoord2f (tb.x, tb.y); glVertex3f (b.x, b.y, b.z);
823       glTexCoord2f (tc.x, tc.y); glVertex3f (c.x, c.y, c.z);
824       glEnd();
825       mi->polygon_count++;
826     }
827   if (facemask & 1<<2)
828     {
829       a  =  D;  b =  C;  c =  F;
830       ta = tD; tb = tC; tc = tF;
831       do_normal2 (mi, frontp, a, b, c);
832       glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLES);
833       glTexCoord2f (ta.x, ta.y); glVertex3f (a.x, a.y, a.z);
834       glTexCoord2f (tb.x, tb.y); glVertex3f (b.x, b.y, b.z);
835       glTexCoord2f (tc.x, tc.y); glVertex3f (c.x, c.y, c.z);
836       glEnd();
837       mi->polygon_count++;
838     }
839   if (facemask & 1<<3)
840     {
841       a  =  G;  b =  C;  c =  D;
842       ta = tG; tb = tC; tc = tD;
843       do_normal2 (mi, frontp, a, b, c);
844       glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLES);
845       glTexCoord2f (ta.x, ta.y); glVertex3f (a.x, a.y, a.z);
846       glTexCoord2f (tb.x, tb.y); glVertex3f (b.x, b.y, b.z);
847       glTexCoord2f (tc.x, tc.y); glVertex3f (c.x, c.y, c.z);
848       glEnd();
849       mi->polygon_count++;
850     }
851   if (facemask & 1<<4)
852     {
853       a  =  B;  b =  G;  c =  D;
854       ta = tB; tb = tG; tc = tD;
855       do_normal2 (mi, frontp, a, b, c);
856       glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLES);
857       glTexCoord2f (ta.x, ta.y); glVertex3f (a.x, a.y, a.z);
858       glTexCoord2f (tb.x, tb.y); glVertex3f (b.x, b.y, b.z);
859       glTexCoord2f (tc.x, tc.y); glVertex3f (c.x, c.y, c.z);
860       glEnd();
861       mi->polygon_count++;
862     }
863   if (facemask & 1<<5)
864     {
865       a  =  B;  b =  D;  c =  E;
866       ta = tB; tb = tD; tc = tE;
867       do_normal2 (mi, frontp, a, b, c);
868       glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLES);
869       glTexCoord2f (ta.x, ta.y); glVertex3f (a.x, a.y, a.z);
870       glTexCoord2f (tb.x, tb.y); glVertex3f (b.x, b.y, b.z);
871       glTexCoord2f (tc.x, tc.y); glVertex3f (c.x, c.y, c.z);
872       glEnd();
873       mi->polygon_count++;
874     }
875   if (facemask & 1<<6)
876     {
877       a  =  E;  b =  D;  c =  A;
878       ta = tE; tb = tD; tc = tA;
879       do_normal2 (mi, frontp, a, b, c);
880       glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLES);
881       glTexCoord2f (ta.x, ta.y); glVertex3f (a.x, a.y, a.z);
882       glTexCoord2f (tb.x, tb.y); glVertex3f (b.x, b.y, b.z);
883       glTexCoord2f (tc.x, tc.y); glVertex3f (c.x, c.y, c.z);
884       glEnd();
885       mi->polygon_count++;
886     }
887
888   if (corners_ret)
889     {
890       corners_ret[0] = A;
891       corners_ret[1] = B;
892       corners_ret[2] = C;
893       corners_ret[3] = D;
894       corners_ret[4] = E;
895       corners_ret[5] = F;
896       corners_ret[6] = G;
897     }
898 }
899
900
901 /* The segments, numbered arbitrarily from the top left:
902              ________         _      ________
903              \      /\      /\ \    |\      /
904               \ 0  /  \    /  \3>   | \ 5  /
905                \  / 1  \  / 2  \| ..|4 \  /-6-..
906      ___________\/______\/______\/______\/______\
907     |   /\      /\      /\      /\      /\   
908     |7 /  \ 9  /  \ 11 /  \ 13 /  \ 15 /  \  
909     | / 8  \  / 10 \  / 12 \  / 14 \  / 16 \ 
910     |/______\/______\/______\/______\/______\
911      \      /\      /       /\      /\
912       \ 17 /  \ 18 /       /  \ 20 /  \
913        \  /    \  /       / 19 \  / 21 \
914         \/      \/       /______\/______\
915
916    Each triangle can be connected to at most two other triangles.
917    We start from the middle, #12, and work our way to the edges.
918    Its centroid is 0,0.
919
920    (Note that dymaxionmap-coords.c uses a different numbering system.)
921  */
922 static void
923 triangle (ModeInfo *mi, int which, Bool frontp, 
924           GLfloat fold_ratio, GLfloat stel_ratio)
925 {
926   planetstruct *gp = &planets[MI_SCREEN(mi)];
927   const GLfloat fg[] = { 1, 1, 1, 1 };
928   const GLfloat bg[] = { 0.3, 0.3, 0.3, 1 };
929   int a = -1, b = -1;
930   GLfloat max = acos (sqrt(5)/3);
931   GLfloat rot = -max * fold_ratio / (M_PI/180);
932   Bool wire = MI_IS_WIREFRAME(mi);
933   XYZ corners[7];
934
935   glColor3fv (fg);
936   if (!wire)
937     glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, fg);
938
939   switch (which) {
940   case 3:                               /* One third of the face. */
941     triangle0 (mi, frontp, stel_ratio, 1<<3 | 1<<4, corners);
942     break;
943   case 4:                               /* Two thirds of the face: convex. */
944     triangle0 (mi, frontp, stel_ratio, 1<<1 | 1<<2 | 1<<3 | 1<<4, corners);
945     break;
946   case 6:                               /* One half of the face. */
947     triangle0 (mi, frontp, stel_ratio, 1<<1 | 1<<2 | 1<<3, corners);
948     break;
949   case 7:                               /* One half of the face. */
950     triangle0 (mi, frontp, stel_ratio, 1<<2 | 1<<3 | 1<<4, corners);
951     break;
952   default:                              /* Full face. */
953     triangle0 (mi, frontp, stel_ratio, 0x3F, corners);
954     break;
955   }
956
957   if (wire)
958     {
959       char tag[20];
960       glColor3fv (bg);
961       sprintf (tag, "%d", which);
962       glPushMatrix();
963       glTranslatef (-0.1, 0.2, 0);
964       glScalef (0.005, 0.005, 0.005);
965       print_texture_string (gp->font_data, tag);
966       glPopMatrix();
967       mi->polygon_count++;
968     }
969
970
971   /* The connection hierarchy of the faces starting at the middle, #12. */
972   switch (which) {
973   case  0: break;
974   case  1: a =  0; b = -1; break;
975   case  2: a = -1; b =  3; break;
976   case  3: break;
977   case  4: a = -1; b =  5; break;
978   case  5: a = -1; b =  6; break;
979   case  7: break;
980   case  6: break;
981   case  8: a = 17; b =  7; break;
982   case  9: a =  8; b = -1; break;
983   case 10: a = 18; b =  9; break;
984   case 11: a = 10; b =  1; break;
985   case 12: a = 11; b = 13; break;
986   case 13: a =  2; b = 14; break;
987   case 14: a = 15; b = 20; break;
988   case 15: a =  4; b = 16; break;
989   case 16: break;
990   case 17: break;
991   case 18: break;
992   case 19: break;
993   case 20: a = 21; b = 19; break;
994   case 21: break;
995   default: abort(); break;
996   }
997
998   if (a != -1)
999     {
1000       glPushMatrix();
1001       glTranslatef (-0.5, 0, 0);        /* Move model matrix to upper left */
1002       glRotatef (60, 0, 0, 1);
1003       glTranslatef ( 0.5, 0, 0);
1004
1005       glMatrixMode(GL_TEXTURE);
1006       /* glPushMatrix(); */
1007       glTranslatef (-0.5, 0, 0);        /* Move texture matrix the same way */
1008       glRotatef (60, 0, 0, 1);
1009       glTranslatef ( 0.5, 0, 0);
1010
1011       glMatrixMode(GL_MODELVIEW);
1012
1013       glRotatef (rot, 1, 0, 0);
1014       triangle (mi, a, frontp, fold_ratio, stel_ratio);
1015
1016       /* This should just be a PopMatrix on the TEXTURE stack, but
1017          fucking iOS has GL_MAX_TEXTURE_STACK_DEPTH == 4!  WTF!
1018          So we have to undo our rotations and translations manually.
1019        */
1020       glMatrixMode(GL_TEXTURE);
1021       /* glPopMatrix(); */
1022       glTranslatef (-0.5, 0, 0);
1023       glRotatef (-60, 0, 0, 1);
1024       glTranslatef (0.5, 0, 0);
1025
1026       glMatrixMode(GL_MODELVIEW);
1027       glPopMatrix();
1028     }
1029
1030   if (b != -1)
1031     {
1032       glPushMatrix();
1033       glTranslatef (0.5, 0, 0);         /* Move model matrix to upper right */
1034       glRotatef (-60, 0, 0, 1);
1035       glTranslatef (-0.5, 0, 0);
1036
1037       glMatrixMode(GL_TEXTURE);
1038       /* glPushMatrix(); */
1039       glTranslatef (0.5, 0, 0);         /* Move texture matrix the same way */
1040       glRotatef (-60, 0, 0, 1);
1041       glTranslatef (-0.5, 0, 0);
1042
1043       glMatrixMode(GL_MODELVIEW);
1044
1045       glRotatef (rot, 1, 0, 0);
1046       triangle (mi, b, frontp, fold_ratio, stel_ratio);
1047
1048       /* See above. Grr. */
1049       glMatrixMode(GL_TEXTURE);
1050       /* glPopMatrix(); */
1051       glTranslatef (0.5, 0, 0);
1052       glRotatef (60, 0, 0, 1);
1053       glTranslatef (-0.5, 0, 0);
1054
1055       glMatrixMode(GL_MODELVIEW);
1056       glPopMatrix();
1057     }
1058
1059
1060   /* Draw a border around the edge of the world.
1061    */
1062   if (!wire && frontp && stel_ratio == 0 && fold_ratio < 0.95)
1063     {
1064       int edges = 0;
1065       GLfloat c[] = { 0, 0.2, 0.5, 1 };
1066       c[3] = 1-fold_ratio;
1067
1068       switch (which)
1069         {
1070         case  0: edges = 1<<0 | 1<<2; break;
1071         case  1: edges = 1<<2;        break;
1072         case  2: edges = 1<<0;        break;
1073         case  3: edges = 1<<3 | 1<<4; break;
1074         case  4: edges = 1<<3 | 1<<5; break;
1075         case  5: edges = 1<<0 | 1<<6; break;
1076         case  6: edges = 1<<2 | 1<<7; break;
1077         case 16: edges = 1<<0 | 1<<2; break;
1078         case 21: edges = 1<<0 | 1<<2; break;
1079         case 19: edges = 1<<0 | 1<<2; break;
1080         case 12: edges = 1<<1;        break;
1081         case 18: edges = 1<<0 | 1<<2; break;
1082         case 17: edges = 1<<0 | 1<<2; break;
1083         case  7: edges = 1<<8 | 1<<9; break;
1084         case  9: edges = 1<<2;        break;
1085         default: break;
1086         }
1087
1088       glDisable (GL_TEXTURE_2D);
1089       glDisable (GL_LIGHTING);
1090       glLineWidth (2);
1091       glColor4fv (c);
1092       glBegin (GL_LINES);
1093       if (edges & 1<<0)
1094         {
1095           glVertex3f (corners[0].x, corners[0].y, corners[0].z);
1096           glVertex3f (corners[1].x, corners[1].y, corners[1].z);
1097         }
1098       if (edges & 1<<1)
1099         {
1100           glVertex3f (corners[1].x, corners[1].y, corners[1].z);
1101           glVertex3f (corners[2].x, corners[2].y, corners[2].z);
1102         }
1103       if (edges & 1<<2)
1104         {
1105           glVertex3f (corners[2].x, corners[2].y, corners[2].z);
1106           glVertex3f (corners[0].x, corners[0].y, corners[0].z);
1107         }
1108       if (edges & 1<<3)
1109         {
1110           glVertex3f (corners[1].x, corners[1].y, corners[1].z);
1111           glVertex3f (corners[3].x, corners[3].y, corners[3].z);
1112         }
1113       if (edges & 1<<4)
1114         {
1115           glVertex3f (corners[3].x, corners[3].y, corners[3].z);
1116           glVertex3f (corners[2].x, corners[2].y, corners[2].z);
1117         }
1118       if (edges & 1<<5)
1119         {
1120           glVertex3f (corners[3].x, corners[3].y, corners[3].z);
1121           glVertex3f (corners[0].x, corners[0].y, corners[0].z);
1122         }
1123       if (edges & 1<<6)
1124         {
1125           glVertex3f (corners[0].x, corners[0].y, corners[0].z);
1126           glVertex3f (corners[5].x, corners[5].y, corners[5].z);
1127         }
1128       if (edges & 1<<7)
1129         {
1130           glVertex3f (corners[0].x, corners[0].y, corners[0].z);
1131           glVertex3f (corners[6].x, corners[6].y, corners[6].z);
1132         }
1133       if (edges & 1<<8)
1134         {
1135           glVertex3f (corners[1].x, corners[1].y, corners[1].z);
1136           glVertex3f (corners[5].x, corners[5].y, corners[5].z);
1137         }
1138       if (edges & 1<<9)
1139         {
1140           glVertex3f (corners[5].x, corners[5].y, corners[5].z);
1141           glVertex3f (corners[2].x, corners[2].y, corners[2].z);
1142         }
1143       glEnd();
1144       glEnable (GL_TEXTURE_2D);
1145       glEnable (GL_LIGHTING);
1146     }
1147 }
1148
1149
1150 static void
1151 draw_triangles (ModeInfo *mi, GLfloat fold_ratio, GLfloat stel_ratio)
1152 {
1153   planetstruct *gp = &planets[MI_SCREEN(mi)];
1154   Bool wire = MI_IS_WIREFRAME(mi);
1155   GLfloat h = sqrt(3) / 2;
1156   GLfloat c = h / 3;
1157
1158   glTranslatef (0, -h/3, 0);  /* Center on face 12 */
1159
1160   /* When closed, center on midpoint of icosahedron. Eyeballed this. */
1161   glTranslatef (0, 0, fold_ratio * 0.754);
1162
1163   glFrontFace (GL_CCW);
1164
1165   /* Adjust the texture matrix so that it has the same coordinate space
1166      as the model. */
1167
1168   glMatrixMode(GL_TEXTURE);
1169   glPushMatrix();
1170   {
1171     GLfloat texw = 5.5;
1172     GLfloat texh = 3 * h;
1173     GLfloat midx = 2.5;
1174     GLfloat midy = 3 * c;
1175     glScalef (1/texw, -1/texh, 1);
1176     glTranslatef (midx, midy, 0);
1177   }
1178   glMatrixMode(GL_MODELVIEW);
1179
1180
1181
1182   /* Front faces */
1183
1184   if (wire)
1185     glDisable (GL_TEXTURE_2D);
1186   else if (do_texture)
1187     {
1188       glEnable (GL_TEXTURE_2D);
1189       glBindTexture (GL_TEXTURE_2D, gp->tex1);
1190     }
1191   else
1192     glDisable (GL_TEXTURE_2D);
1193
1194   triangle (mi, 12, True, fold_ratio, stel_ratio);
1195
1196   /* Back faces */
1197
1198   if (wire)
1199     glDisable (GL_TEXTURE_2D);
1200   else if (do_texture)
1201     {
1202       glEnable (GL_TEXTURE_2D);
1203       glBindTexture (GL_TEXTURE_2D, gp->tex2);
1204     }
1205   else
1206     glDisable (GL_TEXTURE_2D);
1207
1208   glFrontFace (GL_CW);
1209
1210   triangle (mi, 12, False, fold_ratio, 0);
1211
1212   glMatrixMode(GL_TEXTURE);
1213   glPopMatrix();
1214   glMatrixMode(GL_MODELVIEW);
1215 }
1216
1217
1218 static void
1219 align_axis (ModeInfo *mi, int undo)
1220 {
1221   /* Rotate so that an axis is lined up with the north and south poles
1222      on the map, which are not in the center of their faces, or any
1223      other easily computable spot. */
1224
1225   GLfloat r1 = 20.5;
1226   GLfloat r2 = 28.5;
1227
1228   if (undo)
1229     {
1230       glRotatef (-r2, 0, 1, 0);
1231       glRotatef ( r2, 1, 0, 0);
1232       glRotatef (-r1, 1, 0, 0);
1233     }
1234   else
1235     {
1236       glRotatef (r1, 1, 0, 0);
1237       glRotatef (-r2, 1, 0, 0);
1238       glRotatef ( r2, 0, 1, 0);
1239     }
1240 }
1241
1242
1243 static void
1244 draw_axis (ModeInfo *mi)
1245 {
1246   GLfloat s;
1247   glDisable (GL_TEXTURE_2D);
1248   glDisable (GL_LIGHTING);
1249   glPushMatrix();
1250
1251   align_axis (mi, 0);
1252   glTranslatef (0.34, 0.39, -0.61);
1253
1254   s = 0.96;
1255   glScalef (s, s, s);   /* tighten up the enclosing sphere */
1256
1257   glLineWidth (1);
1258   glColor3f (0.5, 0.5, 0);
1259
1260   glRotatef (90,  1, 0, 0);    /* unit_sphere is off by 90 */
1261   glRotatef (9.5, 0, 1, 0);    /* line up the time zones */
1262   glFrontFace (GL_CCW);
1263   unit_sphere (12, 24, True);
1264   glBegin(GL_LINES);
1265   glVertex3f(0, -2, 0);
1266   glVertex3f(0,  2, 0);
1267   glEnd();
1268
1269   glPopMatrix();
1270 }
1271
1272
1273
1274
1275 ENTRYPOINT Bool
1276 planet_handle_event (ModeInfo *mi, XEvent *event)
1277 {
1278   planetstruct *gp = &planets[MI_SCREEN(mi)];
1279
1280   if (gltrackball_event_handler (event, gp->trackball,
1281                                  MI_WIDTH (mi), MI_HEIGHT (mi),
1282                                  &gp->button_down_p))
1283     return True;
1284   else if (event->xany.type == KeyPress)
1285     {
1286       KeySym keysym;
1287       char c = 0;
1288       XLookupString (&event->xkey, &c, 1, &keysym, 0);
1289       if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
1290         {
1291           int i;
1292           double cf = gp->current_frame;
1293
1294           /* Switch between the satellite and flat map, preserving position. */
1295           if (gp->day)   XDestroyImage (gp->day);
1296           if (gp->night) XDestroyImage (gp->night);
1297           if (gp->cvt)   XDestroyImage (gp->cvt);
1298           gp->day    = 0;
1299           gp->night  = 0;
1300           gp->cvt    = 0;
1301
1302           for (i = 0; i < gp->nimages; i++)
1303             if (gp->images[i]) XDestroyImage (gp->images[i]);
1304           free (gp->images);
1305           gp->images = 0;
1306
1307           which_image  = strdup (!strcmp (which_image, "BUILTIN_DAY")
1308                                  ? "BUILTIN_FLAT" : "BUILTIN_DAY");
1309           which_image2 = strdup (!strcmp (which_image2, "BUILTIN_NIGHT")
1310                                  ? "BUILTIN_FLAT" : "BUILTIN_NIGHT");
1311           load_images (mi);
1312           gp->current_frame = cf;
1313 # if 0
1314           switch (gp->state) {
1315           case FLAT: case ICO: case STEL: case AXIS: case ICO2:
1316             gp->ratio = 1;
1317             break;
1318           default:
1319             break;
1320           }
1321 # endif
1322           return True;
1323         }
1324     }
1325
1326   return False;
1327 }
1328
1329
1330 ENTRYPOINT void
1331 init_planet (ModeInfo * mi)
1332 {
1333   planetstruct *gp;
1334   int screen = MI_SCREEN(mi);
1335   Bool wire = MI_IS_WIREFRAME(mi);
1336
1337   MI_INIT (mi, planets);
1338   gp = &planets[screen];
1339
1340   if ((gp->glx_context = init_GL(mi)) != NULL) {
1341     reshape_planet(mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1342   }
1343
1344   gp->state = STARTUP;
1345   gp->ratio = 0;
1346   gp->font_data = load_texture_font (mi->dpy, "labelFont");
1347   gp->delay = MI_DELAY(mi);
1348
1349   {
1350     double spin_speed   = 0.1;
1351     double wander_speed = 0.002;
1352     gp->rot = make_rotator (do_roll ? spin_speed : 0,
1353                             do_roll ? spin_speed : 0,
1354                             0, 1,
1355                             do_wander ? wander_speed : 0,
1356                             False);
1357     gp->rot2 = make_rotator (0, 0, 0, 0, wander_speed, False);
1358     gp->trackball = gltrackball_init (True);
1359   }
1360
1361   if (wire)
1362     do_texture = False;
1363
1364   if (do_texture)
1365     setup_texture (mi);
1366
1367   if (do_stars)
1368     init_stars (mi);
1369
1370   glEnable (GL_DEPTH_TEST);
1371   glEnable (GL_NORMALIZE);
1372   glEnable (GL_CULL_FACE);
1373
1374   if (!wire)
1375     {
1376       GLfloat pos[4] = {1, 1, 1, 0};
1377       GLfloat amb[4] = {0, 0, 0, 1};
1378       GLfloat dif[4] = {1, 1, 1, 1};
1379       GLfloat spc[4] = {0, 1, 1, 1};
1380       glEnable(GL_LIGHTING);
1381       glEnable(GL_LIGHT0);
1382       glLightfv(GL_LIGHT0, GL_POSITION, pos);
1383       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
1384       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
1385       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1386     }
1387 }
1388
1389
1390 static GLfloat
1391 ease_fn (GLfloat r)
1392 {
1393   return cos ((r/2 + 1) * M_PI) + 1; /* Smooth curve up, end at slope 1. */
1394 }
1395
1396
1397 static GLfloat
1398 ease_ratio (GLfloat r)
1399 {
1400   GLfloat ease = 0.35;
1401   if      (r <= 0)     return 0;
1402   else if (r >= 1)     return 1;
1403   else if (r <= ease)  return     ease * ease_fn (r / ease);
1404   else if (r > 1-ease) return 1 - ease * ease_fn ((1 - r) / ease);
1405   else                 return r;
1406 }
1407
1408
1409 ENTRYPOINT void
1410 draw_planet (ModeInfo * mi)
1411 {
1412   planetstruct *gp = &planets[MI_SCREEN(mi)];
1413   int wire = MI_IS_WIREFRAME(mi);
1414   Display *dpy = MI_DISPLAY(mi);
1415   Window window = MI_WINDOW(mi);
1416   long delay = gp->delay;
1417   double x, y, z;
1418
1419   if (!gp->glx_context)
1420     return;
1421
1422   glDrawBuffer(GL_BACK);
1423   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1424
1425   glXMakeCurrent (dpy, window, *(gp->glx_context));
1426
1427   mi->polygon_count = 0;
1428
1429   if (! gp->button_down_p)
1430     switch (gp->state) {
1431     case STARTUP:  gp->ratio += speed * 0.01;  break;
1432     case FLAT:     gp->ratio += speed * 0.005 *
1433         /* Stay flat longer if animating day and night. */
1434         (gp->nimages <= 1 ? 1 : 0.3);
1435       break;
1436     case FOLD:     gp->ratio += speed * 0.01;  break;
1437     case ICO:      gp->ratio += speed * 0.01;  break;
1438     case STEL_IN:  gp->ratio += speed * 0.05;  break;
1439     case STEL:     gp->ratio += speed * 0.01;  break;
1440     case STEL_OUT: gp->ratio += speed * 0.07;  break;
1441     case ICO2:     gp->ratio += speed * 0.07;  break;
1442     case AXIS:     gp->ratio += speed * 0.02;  break;
1443     case SPIN:     gp->ratio += speed * 0.005; break;
1444     case UNFOLD:   gp->ratio += speed * 0.01;  break;
1445     default:       abort();
1446     }
1447
1448   if (gp->ratio > 1.0)
1449     {
1450       gp->ratio = 0;
1451       switch (gp->state) {
1452       case STARTUP:  gp->state = FLAT;     break;
1453       case FLAT:     gp->state = FOLD;     break;
1454       case FOLD:     gp->state = ICO;      break;
1455       case ICO:      gp->state = STEL_IN;  break;
1456       case STEL_IN:  gp->state = STEL;     break;
1457       case STEL:
1458         {
1459           int i = (random() << 9) % 7;
1460           gp->state = (i < 3 ? STEL_OUT :
1461                        i < 6 ? SPIN : AXIS);
1462         }
1463         break;
1464       case AXIS:     gp->state = STEL_OUT; break;
1465       case SPIN:     gp->state = STEL_OUT; break;
1466       case STEL_OUT: gp->state = ICO2;     break;
1467       case ICO2:     gp->state = UNFOLD;   break;
1468       case UNFOLD:   gp->state = FLAT;     break;
1469       default:       abort();
1470       }
1471     }
1472
1473   glEnable(GL_LINE_SMOOTH);
1474   glEnable(GL_POINT_SMOOTH);
1475   glEnable(GL_DEPTH_TEST);
1476   glEnable(GL_CULL_FACE);
1477   glCullFace(GL_BACK); 
1478
1479   glPushMatrix();
1480
1481   gltrackball_rotate (gp->trackball);
1482   glRotatef (current_device_rotation(), 0, 0, 1);
1483
1484 # ifdef HAVE_MOBILE   /* Fill more of the screen. */
1485     {
1486       int size = MI_WIDTH(mi) < MI_HEIGHT(mi)
1487         ? MI_WIDTH(mi) : MI_HEIGHT(mi);
1488       GLfloat s = (size > 768 ? 1.4 :  /* iPad */
1489                    2);                 /* iPhone */
1490       glScalef (s, s, s);
1491       if (MI_WIDTH(mi) < MI_HEIGHT(mi))
1492         glRotatef (90, 0, 0, 1);
1493     }
1494 # endif
1495
1496   if (gp->state != STARTUP)
1497     {
1498       get_position (gp->rot, &x, &y, &z, !gp->button_down_p);
1499       x = (x - 0.5) * 3;
1500       y = (y - 0.5) * 3;
1501       z = 0;
1502       glTranslatef(x, y, z);
1503     }
1504
1505   if (do_roll && gp->state != STARTUP)
1506     {
1507       double max = 65;
1508       get_position (gp->rot2, &x, &y, 0, !gp->button_down_p);
1509       glRotatef (max/2 - x*max, 1, 0, 0);
1510       glRotatef (max/2 - y*max, 0, 1, 0);
1511     }
1512
1513   if (do_stars)
1514     {
1515       glDisable(GL_TEXTURE_2D);
1516       glDisable(GL_LIGHTING);
1517       glPushMatrix();
1518       glScalef (60, 60, 60);
1519       glRotatef (90, 1, 0, 0);
1520       glRotatef (35, 1, 0, 0);
1521       glCallList (gp->starlist);
1522       mi->polygon_count += gp->starcount;
1523       glPopMatrix();
1524       glClear(GL_DEPTH_BUFFER_BIT);
1525     }
1526
1527   if (! wire)
1528     {
1529       glEnable (GL_LIGHTING);
1530       glEnable (GL_BLEND);
1531       glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1532     }
1533
1534   if (do_texture)
1535     glEnable(GL_TEXTURE_2D);
1536
1537   if (do_texture /* && !gp->button_down_p */)
1538     {
1539       int i;
1540       int prev = gp->current_frame;
1541
1542       /* By default, advance terminator by about an hour every 5 seconds. */
1543       gp->current_frame += 0.1 * speed * (gp->nimages / 360.0);
1544       while (gp->current_frame >= gp->nimages)
1545         gp->current_frame -= gp->nimages;
1546       i = gp->current_frame;
1547
1548       /* Load the current image into the texture.
1549        */
1550       if (i != prev || !gp->images[i])
1551         {
1552           double start = double_time();
1553           cache_current_frame (mi);
1554
1555           glBindTexture (GL_TEXTURE_2D, gp->tex1);
1556
1557           /* Must be after glBindTexture */
1558           glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
1559           glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
1560           glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
1561           glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1562           glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1563
1564           glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA,
1565                         gp->images[i]->width,
1566                         gp->images[i]->height, 0,
1567                         GL_RGBA, GL_UNSIGNED_BYTE,
1568                         gp->images[i]->data);
1569           check_gl_error ("texture");
1570
1571           /* If caching the image took a bunch of time, deduct that from
1572              our per-frame delay to keep the timing a little smoother. */
1573           delay -= 1000000 * (double_time() - start);
1574           if (delay < 0) delay = 0;
1575         }
1576     }
1577
1578   glTranslatef (-0.5, -0.4, 0);
1579   glScalef (2.6, 2.6, 2.6);
1580
1581   {
1582     GLfloat fold_ratio = 0;
1583     GLfloat stel_ratio = 0;
1584     switch (gp->state) {
1585     case FOLD:     fold_ratio =     gp->ratio; break;
1586     case UNFOLD:   fold_ratio = 1 - gp->ratio; break;
1587     case ICO: case ICO2: fold_ratio = 1; break;
1588     case STEL: case AXIS: case SPIN: fold_ratio = 1; stel_ratio = 1; break;
1589     case STEL_IN:  fold_ratio = 1; stel_ratio = gp->ratio; break;
1590     case STEL_OUT: fold_ratio = 1; stel_ratio = 1 - gp->ratio; break;
1591     case STARTUP:      /* Tilt in from flat */
1592       glRotatef (-90 * ease_ratio (1 - gp->ratio), 1, 0, 0);
1593       break;
1594
1595     default: break;
1596     }
1597
1598 # ifdef HAVE_MOBILE  /* Enlarge the icosahedron a bit to make it more visible */
1599     {
1600       GLfloat s = 1 + 1.3 * ease_ratio (fold_ratio);
1601       glScalef (s, s, s);
1602     }
1603 # endif
1604
1605     if (gp->state == SPIN)
1606       {
1607         align_axis (mi, 0);
1608         glRotatef (ease_ratio (gp->ratio) * 360 * 3, 0, 0, 1);
1609         align_axis (mi, 1);
1610       }
1611
1612     draw_triangles (mi, ease_ratio (fold_ratio), ease_ratio (stel_ratio));
1613
1614     if (gp->state == AXIS)
1615       draw_axis(mi);
1616   }
1617
1618   glPopMatrix();
1619
1620   if (mi->fps_p) do_fps (mi);
1621   glFinish();
1622   glXSwapBuffers(dpy, window);
1623
1624   MI_DELAY(mi) = delay;
1625 }
1626
1627
1628 ENTRYPOINT void
1629 free_planet (ModeInfo * mi)
1630 {
1631   planetstruct *gp = &planets[MI_SCREEN(mi)];
1632   int i;
1633
1634   if (gp->day)   XDestroyImage (gp->day);
1635   if (gp->night) XDestroyImage (gp->night);
1636   if (gp->dusk)  XDestroyImage (gp->dusk);
1637   if (gp->cvt)   XDestroyImage (gp->cvt);
1638
1639   for (i = 0; i < gp->nimages; i++)
1640     if (gp->images[i]) XDestroyImage (gp->images[i]);
1641   free (gp->images);
1642
1643   if (gp->glx_context) {
1644     glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(gp->glx_context));
1645
1646     if (glIsList(gp->starlist))
1647       glDeleteLists(gp->starlist, 1);
1648   }
1649 }
1650
1651
1652 XSCREENSAVER_MODULE_2 ("DymaxionMap", dymaxionmap, planet)
1653
1654 #endif