From http://www.jwz.org/xscreensaver/xscreensaver-5.35.tar.gz
[xscreensaver] / hacks / glx / dymaxionmap.c
diff --git a/hacks/glx/dymaxionmap.c b/hacks/glx/dymaxionmap.c
new file mode 100644 (file)
index 0000000..f82a98d
--- /dev/null
@@ -0,0 +1,1028 @@
+/* dymaxionmap --- Buckminster Fuller's unwrapped icosahedral globe.
+ * Copyright (c) 2016 Jamie Zawinski.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose and without fee is hereby granted,
+ * provided that the above copyright notice appear in all copies and that
+ * both that copyright notice and this permission notice appear in
+ * supporting documentation.
+ *
+ * This file is provided AS IS with no warranties of any kind. The author
+ * shall have no liability with respect to the infringement of copyrights,
+ * trade secrets or any patents by this file or any part thereof.  In no
+ * event will the author be liable for any lost revenue or profits or
+ * other special, indirect and consequential damages.
+ */
+
+#define LABEL_FONT "-*-helvetica-bold-r-normal-*-*-240-*-*-*-*-*-*"
+
+#ifdef STANDALONE
+#define DEFAULTS    "*delay:           20000   \n" \
+                   "*showFPS:          False   \n" \
+                   "*wireframe:        False   \n" \
+                   "*labelFont:  " LABEL_FONT "\n"
+# define refresh_planet 0
+# include "xlockmore.h"                    /* from the xscreensaver distribution */
+#else  /* !STANDALONE */
+# include "xlock.h"                /* from the xlockmore distribution */
+#endif /* !STANDALONE */
+
+#ifdef USE_GL /* whole file */
+
+#include "sphere.h"
+#include "normals.h"
+#include "texfont.h"
+
+#ifdef HAVE_XMU
+# ifndef VMS
+#  include <X11/Xmu/Drawing.h>
+#else  /* VMS */
+#  include <Xmu/Drawing.h>
+# endif /* VMS */
+#endif
+
+#define DEF_ROTATE  "True"
+#define DEF_ROLL    "True"
+#define DEF_WANDER  "True"
+#define DEF_TEXTURE "True"
+#define DEF_STARS   "True"
+#define DEF_SPEED   "1.0"
+#define DEF_IMAGE   "BUILTIN"
+
+#undef countof
+#define countof(x) (sizeof((x))/sizeof((*x)))
+
+#undef BELLRAND
+#define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
+
+static int do_roll;
+static int do_wander;
+static int do_texture;
+static int do_stars;
+static GLfloat speed;
+static char *which_image;
+
+static XrmOptionDescRec opts[] = {
+  {"-speed",   ".dymaxionmap.speed",   XrmoptionSepArg, 0 },
+  {"-roll",    ".dymaxionmap.roll",    XrmoptionNoArg, "true" },
+  {"+roll",    ".dymaxionmap.roll",    XrmoptionNoArg, "false" },
+  {"-wander",  ".dymaxionmap.wander",  XrmoptionNoArg, "true" },
+  {"+wander",  ".dymaxionmap.wander",  XrmoptionNoArg, "false" },
+  {"-texture", ".dymaxionmap.texture", XrmoptionNoArg, "true" },
+  {"+texture", ".dymaxionmap.texture", XrmoptionNoArg, "false" },
+  {"-stars",   ".dymaxionmap.stars",   XrmoptionNoArg, "true" },
+  {"+stars",   ".dymaxionmap.stars",   XrmoptionNoArg, "false" },
+  {"-image",   ".dymaxionmap.image",  XrmoptionSepArg, 0 },
+};
+
+static argtype vars[] = {
+  {&speed,       "speed",   "Speed",   DEF_SPEED,   t_Float},
+  {&do_roll,    "roll",    "Roll",    DEF_ROLL,    t_Bool},
+  {&do_wander,  "wander",  "Wander",  DEF_WANDER,  t_Bool},
+  {&do_texture,         "texture", "Texture", DEF_TEXTURE, t_Bool},
+  {&do_stars,   "stars",   "Stars",   DEF_STARS,   t_Bool},
+  {&which_image, "image",   "Image",   DEF_IMAGE,   t_String},
+};
+
+ENTRYPOINT ModeSpecOpt planet_opts = {countof(opts), opts, countof(vars), vars, NULL};
+
+#ifdef USE_MODULES
+ModStruct   planet_description =
+{"planet", "init_planet", "draw_planet", "release_planet",
+ "draw_planet", "init_planet", NULL, &planet_opts,
+ 1000, 1, 2, 1, 4, 1.0, "",
+ "Buckminster Fuller's unwrapped icosahedral globe", 0, NULL};
+#endif
+
+# ifdef __GNUC__
+  __extension__         /* don't warn about "string length is greater than the length
+                   ISO C89 compilers are required to support" when including
+                   the following XPM file... */
+# endif
+#include "../images/dymaxionmap.xpm"
+#include "../images/ground.xpm"
+
+#include "xpm-ximage.h"
+#include "rotator.h"
+#include "gltrackball.h"
+
+
+typedef struct {
+  GLXContext *glx_context;
+  GLuint starlist;
+  int starcount;
+  rotator *rot;
+  trackball_state *trackball;
+  Bool button_down_p;
+  enum { STARTUP, FLAT, FOLD, 
+         ICO, STEL_IN, AXIS, SPIN, STEL, STEL_OUT,
+         ICO2, UNFOLD } state;
+  GLfloat ratio;
+  GLuint tex1, tex2;
+  texture_font_data *font_data;
+} planetstruct;
+
+
+static planetstruct *planets = NULL;
+
+
+/* Set up and enable texturing on our object */
+static void
+setup_xpm_texture (ModeInfo *mi, char **xpm_data)
+{
+  XImage *image = xpm_to_ximage (MI_DISPLAY (mi), MI_VISUAL (mi),
+                                 MI_COLORMAP (mi), xpm_data);
+  char buf[1024];
+  clear_gl_error();
+  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
+              image->width, image->height, 0,
+              GL_RGBA,
+              /* GL_UNSIGNED_BYTE, */
+              GL_UNSIGNED_INT_8_8_8_8_REV,
+              image->data);
+  sprintf (buf, "builtin texture (%dx%d)", image->width, image->height);
+  check_gl_error(buf);
+
+  /* setup parameters for texturing */
+  glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+  glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
+  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+}
+
+
+static void
+setup_file_texture (ModeInfo *mi, char *filename)
+{
+  Display *dpy = mi->dpy;
+  Visual *visual = mi->xgwa.visual;
+  char buf[1024];
+
+  Colormap cmap = mi->xgwa.colormap;
+  XImage *image = xpm_file_to_ximage (dpy, visual, cmap, filename);
+  clear_gl_error();
+  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
+              image->width, image->height, 0,
+              GL_RGBA,
+              /* GL_UNSIGNED_BYTE, */
+              GL_UNSIGNED_INT_8_8_8_8_REV,
+              image->data);
+  sprintf (buf, "texture: %.100s (%dx%d)",
+          filename, image->width, image->height);
+  check_gl_error(buf);
+
+  /* setup parameters for texturing */
+  glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
+  glPixelStorei(GL_UNPACK_ROW_LENGTH, image->width);
+
+  glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
+  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+}
+
+
+static void
+setup_texture(ModeInfo * mi)
+{
+  planetstruct *gp = &planets[MI_SCREEN(mi)];
+
+  if (!which_image ||
+      !*which_image ||
+      !strcmp(which_image, "BUILTIN"))
+    {
+      glGenTextures (1, &gp->tex1);
+      glBindTexture (GL_TEXTURE_2D, gp->tex1);
+      setup_xpm_texture (mi, dymaxionmap_xpm);
+    }
+  else
+    {
+      glGenTextures (1, &gp->tex1);
+      glBindTexture (GL_TEXTURE_2D, gp->tex1);
+      setup_file_texture (mi, which_image);
+    }
+
+  glGenTextures (1, &gp->tex2);
+  glBindTexture (GL_TEXTURE_2D, gp->tex2);
+  setup_xpm_texture (mi, ground);
+
+  check_gl_error("texture initialization");
+
+  /* Need to flip the texture top for bottom for some reason. */
+  glMatrixMode (GL_TEXTURE);
+  glScalef (1, -1, 1);
+  glMatrixMode (GL_MODELVIEW);
+}
+
+
+static void
+init_stars (ModeInfo *mi)
+{
+  planetstruct *gp = &planets[MI_SCREEN(mi)];
+  int i, j;
+  int width  = MI_WIDTH(mi);
+  int height = MI_HEIGHT(mi);
+  int size = (width > height ? width : height);
+  int nstars = size * size / 80;
+  int max_size = 3;
+  GLfloat inc = 0.5;
+  int steps = max_size / inc;
+
+  gp->starlist = glGenLists(1);
+  glNewList(gp->starlist, GL_COMPILE);
+  for (j = 1; j <= steps; j++)
+    {
+      glPointSize(inc * j);
+      glBegin (GL_POINTS);
+      for (i = 0; i < nstars / steps; i++)
+       {
+         GLfloat d = 0.1;
+         GLfloat r = 0.15 + frand(0.3);
+         GLfloat g = r + frand(d) - d;
+         GLfloat b = r + frand(d) - d;
+
+         GLfloat x = frand(1)-0.5;
+         GLfloat y = frand(1)-0.5;
+         GLfloat z = ((random() & 1)
+                      ? frand(1)-0.5
+                      : (BELLRAND(1)-0.5)/12);   /* milky way */
+         d = sqrt (x*x + y*y + z*z);
+         x /= d;
+         y /= d;
+         z /= d;
+         glColor3f (r, g, b);
+         glVertex3f (x, y, z);
+         gp->starcount++;
+       }
+      glEnd ();
+    }
+  glEndList ();
+
+  check_gl_error("stars initialization");
+}
+
+
+ENTRYPOINT void
+reshape_planet (ModeInfo *mi, int width, int height)
+{
+  GLfloat h = (GLfloat) height / (GLfloat) width;
+
+  glViewport(0, 0, (GLint) width, (GLint) height);
+  glMatrixMode(GL_PROJECTION);
+  glLoadIdentity();
+  glFrustum(-1.0, 1.0, -h, h, 5.0, 100.0);
+  glMatrixMode(GL_MODELVIEW);
+  glLoadIdentity();
+  glTranslatef(0.0, 0.0, -40);
+
+# ifdef HAVE_MOBILE    /* Keep it the same relative size when rotated. */
+  {
+    int o = (int) current_device_rotation();
+    if (o != 0 && o != 180 && o != -180)
+      glScalef (h, h, h);
+  }
+# endif
+
+  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+}
+
+
+static void
+do_normal2 (Bool frontp, XYZ a, XYZ b, XYZ c)
+{
+  if (frontp)
+    do_normal (a.x, a.y, a.z,
+               b.x, b.y, b.z,
+               c.x, c.y, c.z);
+  else
+    do_normal (b.x, b.y, b.z,
+               a.x, a.y, a.z,
+               c.x, c.y, c.z);
+}
+
+
+static void
+triangle0 (ModeInfo *mi, Bool frontp, GLfloat stel_ratio, int bitmask)
+{
+  /* Render a triangle as six sub-triangles:
+
+                A
+               / \
+              / | \
+             /  |  \
+            / 0 | 1 \
+        E  /_   |   _\  F
+          /  \_ | _/  \
+         / 5   \D/   2 \
+        /    /  |  \    \
+       /   / 4  | 3  \   \
+      /  /      |       \ \
+   B ----------------------- C
+                G
+   */
+
+  Bool wire = MI_IS_WIREFRAME(mi);
+  GLfloat h = sqrt(3) / 2;
+  GLfloat h2 = sqrt(h*h - (h/2)*(h/2)) - 0.5;
+  XYZ  A,  B,  C,  D,  E,  F,  G;
+  XYZ tA, tB, tC, tD, tE, tF, tG;
+  XYZ  a,  b,  c;
+  XYZ ta, tb, tc;
+  A.x =  0;   A.y = h;   A.z = 0;
+  B.x = -0.5, B.y = 0;   B.z = 0;
+  C.x =  0.5, C.y = 0;   C.z = 0;
+  D.x =  0;   D.y = h/3; D.z = 0;
+  E.x = -h2;  E.y = h/2; E.z = 0;
+  F.x =  h2;  F.y = h/2; F.z = 0;
+  G.x =  0;   G.y = 0;   G.z = 0;
+
+  /* When tweaking object XY to stellate, don't change texture coordinates. */
+  tA = A; tB = B; tC = C; tD = D; tE = E; tF = F; tG = G;
+
+  /* Eyeballed this to find the depth of stellation that seems to most
+     approximate a sphere.
+   */
+  D.z = 0.193 * stel_ratio;
+
+  /* We want to raise E, F and G as well but we can't just shift Z:
+     we need to keep them on the same vector from the center of the sphere,
+     which means also changing F and G's X and Y.
+  */
+  E.z = F.z = G.z = 0.132 * stel_ratio;
+  {
+    double magic_x = 0.044;
+    double magic_y = 0.028;
+    /* G.x stays 0 */
+    G.y -= sqrt (magic_x*magic_x + magic_y*magic_y) * stel_ratio;
+    E.x -= magic_x * stel_ratio;
+    E.y += magic_y * stel_ratio;
+    F.x += magic_x * stel_ratio;
+    F.y += magic_y * stel_ratio;
+  }
+
+
+  if (bitmask & 1<<0)
+    {
+      glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLES);
+      a  =  E;  b =  D;  c =  A;
+      ta = tE; tb = tD; tc = tA;
+      do_normal2 (frontp, a, b, c);
+      glTexCoord2f (ta.x, ta.y); glVertex3f (a.x, a.y, a.z);
+      glTexCoord2f (tb.x, tb.y); glVertex3f (b.x, b.y, b.z);
+      glTexCoord2f (tc.x, tc.y); glVertex3f (c.x, c.y, c.z);
+      glEnd();
+      mi->polygon_count++;
+    }
+  if (bitmask & 1<<1)
+    {
+      glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLES);
+      a  =  D;  b =  F;  c =  A;
+      ta = tD; tb = tF; tc = tA;
+      do_normal2 (frontp, a, b, c);
+      glTexCoord2f (ta.x, ta.y); glVertex3f (a.x, a.y, a.z);
+      glTexCoord2f (tb.x, tb.y); glVertex3f (b.x, b.y, b.z);
+      glTexCoord2f (tc.x, tc.y); glVertex3f (c.x, c.y, c.z);
+      glEnd();
+      mi->polygon_count++;
+    }
+  if (bitmask & 1<<2)
+    {
+      glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLES);
+      a  =  D;  b =  C;  c =  F;
+      ta = tD; tb = tC; tc = tF;
+      do_normal2 (frontp, a, b, c);
+      glTexCoord2f (ta.x, ta.y); glVertex3f (a.x, a.y, a.z);
+      glTexCoord2f (tb.x, tb.y); glVertex3f (b.x, b.y, b.z);
+      glTexCoord2f (tc.x, tc.y); glVertex3f (c.x, c.y, c.z);
+      glEnd();
+      mi->polygon_count++;
+    }
+  if (bitmask & 1<<3)
+    {
+      glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLES);
+      a  =  G;  b =  C;  c =  D;
+      ta = tG; tb = tC; tc = tD;
+      do_normal2 (frontp, a, b, c);
+      glTexCoord2f (ta.x, ta.y); glVertex3f (a.x, a.y, a.z);
+      glTexCoord2f (tb.x, tb.y); glVertex3f (b.x, b.y, b.z);
+      glTexCoord2f (tc.x, tc.y); glVertex3f (c.x, c.y, c.z);
+      glEnd();
+      mi->polygon_count++;
+    }
+  if (bitmask & 1<<4)
+    {
+      glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLES);
+      a  =  B;  b =  G;  c =  D;
+      ta = tB; tb = tG; tc = tD;
+      do_normal2 (frontp, a, b, c);
+      glTexCoord2f (ta.x, ta.y); glVertex3f (a.x, a.y, a.z);
+      glTexCoord2f (tb.x, tb.y); glVertex3f (b.x, b.y, b.z);
+      glTexCoord2f (tc.x, tc.y); glVertex3f (c.x, c.y, c.z);
+      glEnd();
+      mi->polygon_count++;
+    }
+  if (bitmask & 1<<5)
+    {
+      glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLES);
+      a  =  B;  b =  D;  c =  E;
+      ta = tB; tb = tD; tc = tE;
+      do_normal2 (frontp, a, b, c);
+      glTexCoord2f (ta.x, ta.y); glVertex3f (a.x, a.y, a.z);
+      glTexCoord2f (tb.x, tb.y); glVertex3f (b.x, b.y, b.z);
+      glTexCoord2f (tc.x, tc.y); glVertex3f (c.x, c.y, c.z);
+      glEnd();
+      mi->polygon_count++;
+    }
+  if (bitmask & 1<<6)
+    {
+      glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLES);
+      a  =  E;  b =  D;  c =  A;
+      ta = tE; tb = tD; tc = tA;
+      do_normal2 (frontp, a, b, c);
+      glTexCoord2f (ta.x, ta.y); glVertex3f (a.x, a.y, a.z);
+      glTexCoord2f (tb.x, tb.y); glVertex3f (b.x, b.y, b.z);
+      glTexCoord2f (tc.x, tc.y); glVertex3f (c.x, c.y, c.z);
+      glEnd();
+      mi->polygon_count++;
+    }
+}
+
+
+/* The segments, numbered arbitrarily from the top left:
+             ________         _      ________
+             \      /\      /\ \    |\      /
+              \ 0  /  \    /  \3>   | \ 5  /
+               \  / 1  \  / 2  \| ..|4 \  /-6-..
+     ___________\/______\/______\/______\/______\
+    |   /\      /\      /\      /\      /\   
+    |7 /  \ 9  /  \ 11 /  \ 13 /  \ 15 /  \  
+    | / 8  \  / 10 \  / 12 \  / 14 \  / 16 \ 
+    |/______\/______\/______\/______\/______\
+     \      /\      /       /\      /\
+      \ 17 /  \ 18 /       /  \ 20 /  \
+       \  /    \  /       / 19 \  / 21 \
+        \/      \/       /______\/______\
+
+   Each triangle can be connected to at most two other triangles.
+   We start from the middle, #12, and work our way to the edges.
+   Its centroid is 0,0.
+ */
+static void
+triangle (ModeInfo *mi, int which, Bool frontp, 
+          GLfloat fold_ratio, GLfloat stel_ratio)
+{
+  planetstruct *gp = &planets[MI_SCREEN(mi)];
+  const GLfloat fg[3] = { 1, 1, 1 };
+  const GLfloat bg[3] = { 0.3, 0.3, 0.3 };
+  int a = -1, b = -1;
+  GLfloat max = acos (sqrt(5)/3);
+  GLfloat rot = -max * fold_ratio / (M_PI/180);
+  Bool wire = MI_IS_WIREFRAME(mi);
+
+  if (wire)
+    glColor3fv (fg);
+
+  switch (which) {
+  case 3:                              /* One third of the face. */
+    triangle0 (mi, frontp, stel_ratio, 1<<3 | 1<<4);
+    break;
+  case 4:                              /* Two thirds of the face: convex. */
+    triangle0 (mi, frontp, stel_ratio, 1<<1 | 1<<2 | 1<<3 | 1<<4);
+    break;
+  case 6:                              /* One half of the face. */
+    triangle0 (mi, frontp, stel_ratio, 1<<1 | 1<<2 | 1<<3);
+    break;
+  case 7:                              /* One half of the face. */
+    triangle0 (mi, frontp, stel_ratio, 1<<2 | 1<<3 | 1<<4);
+    break;
+  default:                             /* Full face. */
+    triangle0 (mi, frontp, stel_ratio, 0x3F);
+    break;
+  }
+
+  if (wire)
+    {
+      char tag[20];
+      glColor3fv (bg);
+      sprintf (tag, "%d", which);
+      glPushMatrix();
+      glTranslatef (-0.1, 0.2, 0);
+      glScalef (0.005, 0.005, 0.005);
+      print_texture_string (gp->font_data, tag);
+      glPopMatrix();
+      mi->polygon_count++;
+    }
+
+
+  /* The connection hierarchy of the faces starting at the middle, #12. */
+  switch (which) {
+  case  0: break;
+  case  1: a =  0; b = -1; break;
+  case  2: a = -1; b =  3; break;
+  case  3: break;
+  case  4: a = -1; b =  5; break;
+  case  5: a = -1; b =  6; break;
+  case  7: break;
+  case  6: break;
+  case  8: a = 17; b =  7; break;
+  case  9: a =  8; b = -1; break;
+  case 10: a = 18; b =  9; break;
+  case 11: a = 10; b =  1; break;
+  case 12: a = 11; b = 13; break;
+  case 13: a =  2; b = 14; break;
+  case 14: a = 15; b = 20; break;
+  case 15: a =  4; b = 16; break;
+  case 16: break;
+  case 17: break;
+  case 18: break;
+  case 19: break;
+  case 20: a = 21; b = 19; break;
+  case 21: break;
+  default: abort(); break;
+  }
+
+  if (a != -1)
+    {
+      glPushMatrix();
+      glTranslatef (-0.5, 0, 0);       /* Move model matrix to upper left */
+      glRotatef (60, 0, 0, 1);
+      glTranslatef ( 0.5, 0, 0);
+
+      glMatrixMode(GL_TEXTURE);
+      /* glPushMatrix(); */
+      glTranslatef (-0.5, 0, 0);       /* Move texture matrix the same way */
+      glRotatef (60, 0, 0, 1);
+      glTranslatef ( 0.5, 0, 0);
+
+      glMatrixMode(GL_MODELVIEW);
+
+      glRotatef (rot, 1, 0, 0);
+      triangle (mi, a, frontp, fold_ratio, stel_ratio);
+
+      /* This should just be a PopMatrix on the TEXTURE stack, but
+         fucking iOS has GL_MAX_TEXTURE_STACK_DEPTH == 4!  WTF!
+         So we have to undo our rotations and translations manually.
+       */
+      glMatrixMode(GL_TEXTURE);
+      /* glPopMatrix(); */
+      glTranslatef (-0.5, 0, 0);
+      glRotatef (-60, 0, 0, 1);
+      glTranslatef (0.5, 0, 0);
+
+      glMatrixMode(GL_MODELVIEW);
+      glPopMatrix();
+    }
+
+  if (b != -1)
+    {
+      glPushMatrix();
+      glTranslatef (0.5, 0, 0);                /* Move model matrix to upper right */
+      glRotatef (-60, 0, 0, 1);
+      glTranslatef (-0.5, 0, 0);
+
+      glMatrixMode(GL_TEXTURE);
+      /* glPushMatrix(); */
+      glTranslatef (0.5, 0, 0);                /* Move texture matrix the same way */
+      glRotatef (-60, 0, 0, 1);
+      glTranslatef (-0.5, 0, 0);
+
+      glMatrixMode(GL_MODELVIEW);
+
+      glRotatef (rot, 1, 0, 0);
+      triangle (mi, b, frontp, fold_ratio, stel_ratio);
+
+      /* See above. Grr. */
+      glMatrixMode(GL_TEXTURE);
+      /* glPopMatrix(); */
+      glTranslatef (0.5, 0, 0);
+      glRotatef (60, 0, 0, 1);
+      glTranslatef (-0.5, 0, 0);
+
+      glMatrixMode(GL_MODELVIEW);
+      glPopMatrix();
+    }
+}
+
+
+static void
+draw_triangles (ModeInfo *mi, GLfloat fold_ratio, GLfloat stel_ratio)
+{
+  planetstruct *gp = &planets[MI_SCREEN(mi)];
+  Bool wire = MI_IS_WIREFRAME(mi);
+  GLfloat h = sqrt(3) / 2;
+  GLfloat c = h / 3;
+
+  glTranslatef (0, -h/3, 0);  /* Center on face 12 */
+
+  /* When closed, center on midpoint of icosahedron. Eyeballed this. */
+  glTranslatef (0, 0, fold_ratio * 0.754);
+
+  glFrontFace (GL_CCW);
+
+  /* Adjust the texture matrix so that it has the same coordinate space
+     as the model. */
+
+  glMatrixMode(GL_TEXTURE);
+  glPushMatrix();
+  {
+    GLfloat texw = 5.5;
+    GLfloat texh = 3 * h;
+    GLfloat midx = 2.5;
+    GLfloat midy = 3 * c;
+    glScalef (1/texw, -1/texh, 1);
+    glTranslatef (midx, midy, 0);
+  }
+  glMatrixMode(GL_MODELVIEW);
+
+
+
+  /* Front faces */
+
+  if (wire)
+    glDisable (GL_TEXTURE_2D);
+  else if (do_texture)
+    {
+      glEnable (GL_TEXTURE_2D);
+      glBindTexture (GL_TEXTURE_2D, gp->tex1);
+    }
+  else
+    glDisable (GL_TEXTURE_2D);
+
+  triangle (mi, 12, True, fold_ratio, stel_ratio);
+
+  /* Back faces */
+
+  if (wire)
+    glDisable (GL_TEXTURE_2D);
+  else if (do_texture)
+    {
+      glEnable (GL_TEXTURE_2D);
+      glBindTexture (GL_TEXTURE_2D, gp->tex2);
+    }
+  else
+    glDisable (GL_TEXTURE_2D);
+
+  glFrontFace (GL_CW);
+
+  triangle (mi, 12, False, fold_ratio, 0);
+
+  glMatrixMode(GL_TEXTURE);
+  glPopMatrix();
+  glMatrixMode(GL_MODELVIEW);
+}
+
+
+static void
+align_axis (ModeInfo *mi, int undo)
+{
+  /* Rotate so that an axis is lined up with the north and south poles
+     on the map, which are not in the center of their faces, or any
+     other easily computable spot. */
+
+  GLfloat r1 = 20.5;
+  GLfloat r2 = 28.5;
+
+  if (undo)
+    {
+      glRotatef (-r2, 0, 1, 0);
+      glRotatef ( r2, 1, 0, 0);
+      glRotatef (-r1, 1, 0, 0);
+    }
+  else
+    {
+      glRotatef (r1, 1, 0, 0);
+      glRotatef (-r2, 1, 0, 0);
+      glRotatef ( r2, 0, 1, 0);
+    }
+}
+
+
+static void
+draw_axis (ModeInfo *mi)
+{
+  GLfloat s;
+  glDisable (GL_TEXTURE_2D);
+  glDisable (GL_LIGHTING);
+  glPushMatrix();
+
+  align_axis (mi, 0);
+  glTranslatef (0.34, 0.39, -0.61);
+
+  s = 0.96;
+  glScalef (s, s, s);   /* tighten up the enclosing sphere */
+
+  glColor3f (0.5, 0.5, 0);
+
+  glRotatef (90,  1, 0, 0);    /* unit_sphere is off by 90 */
+  glRotatef (9.5, 0, 1, 0);    /* line up the time zones */
+  glFrontFace (GL_CCW);
+  unit_sphere (12, 24, True);
+  glBegin(GL_LINES);
+  glVertex3f(0, -2, 0);
+  glVertex3f(0,  2, 0);
+  glEnd();
+
+  glPopMatrix();
+}
+
+
+
+
+ENTRYPOINT Bool
+planet_handle_event (ModeInfo *mi, XEvent *event)
+{
+  planetstruct *gp = &planets[MI_SCREEN(mi)];
+
+  if (gltrackball_event_handler (event, gp->trackball,
+                                MI_WIDTH (mi), MI_HEIGHT (mi),
+                                &gp->button_down_p))
+    return True;
+  else if (event->xany.type == KeyPress)
+    {
+      KeySym keysym;
+      char c = 0;
+      XLookupString (&event->xkey, &c, 1, &keysym, 0);
+      if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
+        {
+          switch (gp->state) {
+          case FLAT: case ICO: case STEL: case AXIS: case ICO2:
+            gp->ratio = 1;
+            break;
+          default:
+            break;
+          }
+          return True;
+        }
+    }
+
+  return False;
+}
+
+
+ENTRYPOINT void
+init_planet (ModeInfo * mi)
+{
+  planetstruct *gp;
+  int screen = MI_SCREEN(mi);
+  Bool wire = MI_IS_WIREFRAME(mi);
+
+  if (planets == NULL) {
+    if ((planets = (planetstruct *) calloc(MI_NUM_SCREENS(mi),
+                                         sizeof (planetstruct))) == NULL)
+      return;
+  }
+  gp = &planets[screen];
+
+  if ((gp->glx_context = init_GL(mi)) != NULL) {
+    reshape_planet(mi, MI_WIDTH(mi), MI_HEIGHT(mi));
+  }
+
+  gp->state = STARTUP;
+  gp->ratio = 0;
+  gp->font_data = load_texture_font (mi->dpy, "labelFont");
+
+  {
+    double spin_speed  = 0.1;
+    double wander_speed = 0.002;
+    gp->rot = make_rotator (do_roll ? spin_speed : 0,
+                           do_roll ? spin_speed : 0,
+                           0, 1,
+                           do_wander ? wander_speed : 0,
+                           False);
+    gp->trackball = gltrackball_init (True);
+  }
+
+  if (wire)
+    do_texture = False;
+
+  if (do_texture)
+    setup_texture (mi);
+
+  if (do_stars)
+    init_stars (mi);
+
+  glEnable (GL_DEPTH_TEST);
+  glEnable (GL_NORMALIZE);
+  glEnable (GL_CULL_FACE);
+
+  if (!wire)
+    {
+      GLfloat pos[4] = {0.4, 0.2, 0.4, 0.0};
+      GLfloat amb[4] = {0.4, 0.4, 0.4, 1.0};
+      GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
+      GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
+
+      glEnable(GL_LIGHTING);
+      glEnable(GL_LIGHT0);
+      glLightfv(GL_LIGHT0, GL_POSITION, pos);
+      glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
+      glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
+      glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
+    }
+}
+
+
+static GLfloat
+ease_fn (GLfloat r)
+{
+  return cos ((r/2 + 1) * M_PI) + 1; /* Smooth curve up, end at slope 1. */
+}
+
+
+static GLfloat
+ease_ratio (GLfloat r)
+{
+  GLfloat ease = 0.35;
+  if      (r <= 0)     return 0;
+  else if (r >= 1)     return 1;
+  else if (r <= ease)  return     ease * ease_fn (r / ease);
+  else if (r > 1-ease) return 1 - ease * ease_fn ((1 - r) / ease);
+  else                 return r;
+}
+
+
+ENTRYPOINT void
+draw_planet (ModeInfo * mi)
+{
+  planetstruct *gp = &planets[MI_SCREEN(mi)];
+  int wire = MI_IS_WIREFRAME(mi);
+  Display *dpy = MI_DISPLAY(mi);
+  Window window = MI_WINDOW(mi);
+  double x, y, z;
+
+  if (!gp->glx_context)
+    return;
+
+  glDrawBuffer(GL_BACK);
+  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+  glXMakeCurrent (dpy, window, *(gp->glx_context));
+
+  mi->polygon_count = 0;
+
+  if (! gp->button_down_p)
+    switch (gp->state) {
+    case STARTUP:  gp->ratio += speed * 0.01;  break;
+    case FLAT:     gp->ratio += speed * 0.005; break;
+    case FOLD:     gp->ratio += speed * 0.01;  break;
+    case ICO:      gp->ratio += speed * 0.01;  break;
+    case STEL_IN:  gp->ratio += speed * 0.05;  break;
+    case STEL:     gp->ratio += speed * 0.01;  break;
+    case STEL_OUT: gp->ratio += speed * 0.07;  break;
+    case ICO2:     gp->ratio += speed * 0.07;  break;
+    case AXIS:     gp->ratio += speed * 0.02;  break;
+    case SPIN:     gp->ratio += speed * 0.005; break;
+    case UNFOLD:   gp->ratio += speed * 0.01;  break;
+    default:       abort();
+    }
+
+  if (gp->ratio > 1.0)
+    {
+      gp->ratio = 0;
+      switch (gp->state) {
+      case STARTUP:  gp->state = FLAT;     break;
+      case FLAT:     gp->state = FOLD;     break;
+      case FOLD:     gp->state = ICO;      break;
+      case ICO:      gp->state = STEL_IN;  break;
+      case STEL_IN:  gp->state = STEL;     break;
+      case STEL:
+        {
+          int i = (random() << 9) % 7;
+          gp->state = (i < 3 ? STEL_OUT :
+                       i < 6 ? SPIN : AXIS);
+        }
+        break;
+      case AXIS:     gp->state = STEL_OUT; break;
+      case SPIN:     gp->state = STEL_OUT; break;
+      case STEL_OUT: gp->state = ICO2;     break;
+      case ICO2:     gp->state = UNFOLD;   break;
+      case UNFOLD:   gp->state = FLAT;     break;
+      default:       abort();
+      }
+    }
+
+  glEnable(GL_LINE_SMOOTH);
+  glEnable(GL_POINT_SMOOTH);
+  glEnable(GL_DEPTH_TEST);
+  glEnable(GL_CULL_FACE);
+  glCullFace(GL_BACK); 
+
+  glPushMatrix();
+
+  gltrackball_rotate (gp->trackball);
+  glRotatef (current_device_rotation(), 0, 0, 1);
+
+  if (gp->state != STARTUP)
+    {
+      get_position (gp->rot, &x, &y, &z, !gp->button_down_p);
+      x = (x - 0.5) * 3;
+      y = (y - 0.5) * 3;
+      z = 0;
+      glTranslatef(x, y, z);
+    }
+
+  if (do_roll && gp->state != STARTUP)
+    {
+      get_rotation (gp->rot, &x, &y, 0, !gp->button_down_p);
+      glRotatef (x * 360, 1.0, 0.0, 0.0);
+      glRotatef (y * 360, 0.0, 1.0, 0.0);
+    }
+
+  if (do_stars)
+    {
+      glDisable(GL_TEXTURE_2D);
+      glDisable(GL_LIGHTING);
+      glPushMatrix();
+      glScalef (60, 60, 60);
+      glRotatef (90, 1, 0, 0);
+      glRotatef (35, 1, 0, 0);
+      glCallList (gp->starlist);
+      mi->polygon_count += gp->starcount;
+      glPopMatrix();
+      glClear(GL_DEPTH_BUFFER_BIT);
+    }
+
+  if (! wire)
+    glEnable (GL_LIGHTING);
+
+  if (do_texture)
+    glEnable(GL_TEXTURE_2D);
+
+  glScalef (2.6, 2.6, 2.6);
+
+  {
+    GLfloat fold_ratio = 0;
+    GLfloat stel_ratio = 0;
+    switch (gp->state) {
+    case FOLD:     fold_ratio =     gp->ratio; break;
+    case UNFOLD:   fold_ratio = 1 - gp->ratio; break;
+    case ICO: case ICO2: fold_ratio = 1; break;
+    case STEL: case AXIS: case SPIN: fold_ratio = 1; stel_ratio = 1; break;
+    case STEL_IN:  fold_ratio = 1; stel_ratio = gp->ratio; break;
+    case STEL_OUT: fold_ratio = 1; stel_ratio = 1 - gp->ratio; break;
+    case STARTUP:      /* Tilt in from flat */
+      glRotatef (-90 * ease_ratio (1 - gp->ratio), 1, 0, 0);
+      break;
+
+    default: break;
+    }
+
+# ifdef HAVE_MOBILE  /* Enlarge the icosahedron a bit to make it more visible */
+    {
+      GLfloat s = 1 + 1.3 * ease_ratio (fold_ratio);
+      glScalef (s, s, s);
+    }
+# endif
+
+    if (gp->state == SPIN)
+      {
+        align_axis (mi, 0);
+        glRotatef (ease_ratio (gp->ratio) * 360 * 3, 0, 0, 1);
+        align_axis (mi, 1);
+      }
+
+    draw_triangles (mi, ease_ratio (fold_ratio), ease_ratio (stel_ratio));
+
+    if (gp->state == AXIS)
+      draw_axis(mi);
+  }
+
+  glPopMatrix();
+
+  if (mi->fps_p) do_fps (mi);
+  glFinish();
+  glXSwapBuffers(dpy, window);
+}
+
+
+ENTRYPOINT void
+release_planet (ModeInfo * mi)
+{
+  if (planets != NULL) {
+    int screen;
+
+    for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++) {
+      planetstruct *gp = &planets[screen];
+
+      if (gp->glx_context) {
+       /* Display lists MUST be freed while their glXContext is current. */
+       /* but this gets a BadMatch error. -jwz */
+       /*glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(gp->glx_context));*/
+
+       if (glIsList(gp->starlist))
+         glDeleteLists(gp->starlist, 1);
+      }
+    }
+    (void) free((void *) planets);
+    planets = NULL;
+  }
+  FreeAllGL(mi);
+}
+
+
+XSCREENSAVER_MODULE_2 ("DymaxionMap", dymaxionmap, planet)
+
+#endif