+ if (!which_image) which_image = strdup("");
+ if (!which_image2) which_image2 = strdup("");
+
+ for (i = 0; i < 2; i++)
+ {
+ char *s = (i == 0 ? which_image : which_image2);
+ XImage *image;
+ if (!strcmp (s, "BUILTIN_DAY"))
+ image = image_data_to_ximage (MI_DISPLAY (mi), MI_VISUAL (mi),
+ earth_png, sizeof(earth_png));
+ else if (!strcmp (s, "BUILTIN_NIGHT"))
+ image = image_data_to_ximage (MI_DISPLAY (mi), MI_VISUAL (mi),
+ earth_night_png,sizeof(earth_night_png));
+ else if (!strcmp (s, "BUILTIN") ||
+ !strcmp (s, "BUILTIN_FLAT") ||
+ (i == 0 && !strcmp (s, "")))
+ image = image_data_to_ximage (MI_DISPLAY (mi), MI_VISUAL (mi),
+ earth_flat_png, sizeof(earth_flat_png));
+ else if (!strcmp (s, "NONE"))
+ image = 0;
+ else if (*s)
+ image = file_to_ximage (MI_DISPLAY (mi), MI_VISUAL (mi), s);
+ else
+ image = 0;
+
+ /* if (image) fprintf (stderr, "%s: %d: loaded %s\n", progname, i, s); */
+
+ if (i == 0)
+ gp->day = image;
+ else
+ gp->night = image;
+ }
+
+ if (gp->night && !gp->day)
+ gp->day = gp->night, gp->night = 0;
+
+ gp->nimages = frames;
+ gp->current_frame = random() % gp->nimages;
+
+ if (gp->nimages < 1)
+ gp->nimages = 1;
+
+ if (gp->nimages < 2 && gp->night)
+ {
+ XDestroyImage (gp->night);
+ gp->night = 0;
+ gp->nimages = 1;
+ }
+
+ if (! gp->night)
+ gp->nimages = 1;
+
+ if (do_grid)
+ {
+ if (gp->day) add_grid_lines (gp->day);
+ if (gp->night) add_grid_lines (gp->night);
+ }
+
+ if (gp->day && gp->night && !gp->dusk)
+ {
+ if (gp->day->width != gp->night->width ||
+ gp->day->height != gp->night->height)
+ {
+ fprintf (stderr, "%s: day and night images must be the same size"
+ " (%dx%d vs %dx%d)\n", progname,
+ gp->day->width, gp->day->height,
+ gp->night->width, gp->night->height);
+ exit (1);
+ }
+ gp->dusk = create_daylight_mask (MI_DISPLAY (mi), MI_VISUAL (mi),
+ gp->day->width, gp->day->height);
+ }
+
+ /* Make the day image brighter, because that's easier than doing it
+ with GL lights. */
+ adjust_brightness (gp->day, 1.4);
+
+ if (!strcmp (which_image, which_image2))
+ /* If day and night are the same image, make night way darker. */
+ adjust_brightness (gp->night, 0.2);
+ else if (gp->night)
+ /* Otherwise make it just a little darker. */
+ adjust_brightness (gp->night, 0.7);
+
+
+ gp->images = (XImage **) calloc (gp->nimages, sizeof(*gp->images));
+
+ /* Create 'cvt', a map that projects each pixel from Equirectangular to
+ Dymaxion. It is 2x the width/height of the source images. We iterate
+ by half pixel to make sure we hit every pixel in 'out'. It would be
+ cleaner to iterate over 'out' instead of over 'in' but
+ dymaxionmap-coords.c only goes forward. This is... not super fast.
+ */
+ {
+ double W = 5.5;
+ double H = 3 * sqrt(3)/2;
+ int x2, y2;
+ int w = gp->day->width;
+ int h = gp->day->height;
+ uint32_t *out;
+
+ gp->cvt = XCreateImage (MI_DISPLAY(mi), MI_VISUAL(mi), 32, ZPixmap, 0, 0,
+ gp->day->width*2, gp->day->height*2, 32, 0);
+ gp->cvt->data = (char *)
+ malloc (gp->cvt->height * gp->cvt->bytes_per_line);
+ out = (uint32_t *) gp->cvt->data;
+
+ for (y2 = 0; y2 < h*2; y2++)
+ {
+ double y = (double) y2/2;
+ double lat = -90 + (180 * (y / (double) h));
+ for (x2 = 0; x2 < w*2; x2++)
+ {
+ double x = (double) x2/2;
+ double lon = -180 + (360 * x / w);
+ double ox, oy;
+ dymaxion_convert (lon, lat, &ox, &oy);
+ ox = w - (w * ox / W);
+ oy = (h * oy / H);
+
+ *out++ = (((((uint32_t) ox) & 0xFFFF) << 16) |
+ ((((uint32_t) oy) & 0xFFFF)));
+ }
+ }
+ }
+
+ /* A 128 GB iPhone 6s dies at around 540 frames, ~1 GB of XImages.
+ A 16 GB iPad Air 2 dies at around 320 frames, ~640 MB.
+ Caching on mobile doesn't matter much: we can just run at 100% CPU.
+
+ On some systems it would be more efficient to cache the images inside
+ a texture on the GPU instead of moving it from RAM to GPU every few
+ frames; but on other systems, we'd just run out of GPU memory instead. */
+ {
+ unsigned long cache_size = (gp->day->width * gp->day->height * 4 *
+ gp->nimages);
+# ifdef HAVE_MOBILE
+ unsigned long max = 320 * 1024 * 1024L; /* 320 MB */
+# else
+ unsigned long max = 2 * 1024 * 1024 * 1024L; /* 2 GB */
+# endif
+ gp->cache_p = (cache_size < max);
+ }
+}
+
+
+static void
+cache_current_frame (ModeInfo *mi)
+{
+ planetstruct *gp = &planets[MI_SCREEN(mi)];
+ XImage *blended, *dymaxion;
+ int i, x, y, w, h, end, xoff;
+ uint32_t *day, *night, *cvt, *out;
+ uint8_t *dusk;
+
+ if (gp->images[(int) gp->current_frame])
+ return;
+
+ xoff = (gp->dusk
+ ? gp->dusk->width * ((double) gp->current_frame / gp->nimages)
+ : 0);
+
+ w = gp->day->width;
+ h = gp->day->height;
+
+ if (!gp->night)
+ blended = gp->day;
+ else
+ {
+ /* Blend the foreground and background images through the dusk map.
+ */
+ blended = XCreateImage (MI_DISPLAY(mi), MI_VISUAL(mi),
+ gp->day->depth, ZPixmap, 0, 0, w, h, 32, 0);
+ if (!blended) abort();
+ blended->data = (char *) malloc (h * blended->bytes_per_line);
+ if (!blended->data) abort();
+
+ end = blended->height * blended->bytes_per_line / 4;
+ day = (uint32_t *) gp->day->data;
+ night = (uint32_t *) gp->night->data;
+ dusk = (uint8_t *) gp->dusk->data;
+ out = (uint32_t *) blended->data;
+
+ for (i = 0; i < end; i++)
+ {
+ uint32_t d = *day++;
+ uint32_t n = *night++;
+ uint32_t x = i % w;
+ uint32_t y = i / w;
+ double r = dusk[y * w + ((x + xoff) % w)] / 256.0;
+ double r2 = 1-r;
+# define ADD(M) (((unsigned long) \
+ ((((d >> M) & 0xFF) * r) + \
+ (((n >> M) & 0xFF) * r2))) \
+ << M)
+ /* #### Why is this ABGR instead of RGBA? */
+ *out++ = (0xFF << 24) | ADD(16) | ADD(8) | ADD(0);
+# undef ADD
+ }
+ }
+
+ /* Convert blended Equirectangular to Dymaxion through the 'cvt' map.
+ */
+ dymaxion = XCreateImage (MI_DISPLAY(mi), MI_VISUAL(mi),
+ gp->day->depth, ZPixmap, 0, 0, w, h, 32, 0);
+ dymaxion->data = (char *) calloc (h, dymaxion->bytes_per_line);
+
+ day = (uint32_t *) blended->data;
+ out = (uint32_t *) dymaxion->data;
+ cvt = (uint32_t *) gp->cvt->data;
+
+ for (y = 0; y < h*2; y++)
+ for (x = 0; x < w*2; x++)
+ {
+ unsigned long m = *cvt++;
+ unsigned long dx = (m >> 16) & 0xFFFF;
+ unsigned long dy = m & 0xFFFF;
+ unsigned long p = day[(y>>1) * w + (x>>1)];
+ unsigned long p2 = out[dy * w + dx];
+ if (p2 & 0xFF000000)
+ /* RGBA nonzero alpha: initialized. Average with existing,
+ otherwise the grid lines look terrible. */
+ p = (((((p>>24) & 0xFF) + ((p2>>24) & 0xFF)) >> 1) << 24 |
+ ((((p>>16) & 0xFF) + ((p2>>16) & 0xFF)) >> 1) << 16 |
+ ((((p>> 8) & 0xFF) + ((p2>> 8) & 0xFF)) >> 1) << 8 |
+ ((((p>> 0) & 0xFF) + ((p2>> 0) & 0xFF)) >> 1) << 0);
+ out[dy * w + dx] = p;
+ }
+
+ /* Fill in the triangles that are not a part of The World with the
+ color of the ocean to avoid texture-tearing on the folded edges.
+ */
+ out = (uint32_t *) dymaxion->data;
+ end = dymaxion->height * dymaxion->bytes_per_line / 4;
+ {
+ double lat = -48.44, lon = -123.39; /* R'Lyeh */
+ int x = (lon + 180) * blended->width / 360.0;
+ int y = (lat + 90) * blended->height / 180.0;
+ unsigned long ocean = XGetPixel (gp->day, x, y);
+ for (i = 0; i < end; i++)
+ {
+ uint32_t p = *out;
+ if (! (p & 0xFF000000)) /* AGBR */
+ *out = ocean;
+ out++;
+ }
+ }
+
+ if (blended != gp->day)
+ XDestroyImage (blended);
+
+ gp->images[(int) gp->current_frame] = dymaxion;
+
+ if (!gp->cache_p) /* Keep only one image around; recompute every time. */
+ {
+ i = ((int) gp->current_frame) - 1;
+ if (i < 0) i = gp->nimages - 1;
+ if (gp->images[i])
+ {
+ XDestroyImage (gp->images[i]);
+ gp->images[i] = 0;
+ }
+ }